Merged in feature/news (pull request #65)

Feature/news

Approved-by: Ramon Wenger
This commit is contained in:
Christian Cueni 2020-06-17 11:53:45 +00:00
commit 1b4a0da2e1
27 changed files with 408 additions and 32 deletions

View File

@ -0,0 +1,102 @@
<template>
<div class="news-teasers">
<div v-for="teaser in newsTeasers" :key="teaser.id" class="news-teasers__teaser teaser">
<a :href="teaser.newsArticleUrl">
<img :src="teaser.imageUrl" class="teaser__image" />
<p class="teaser__image-source"><a class="tiny-text" :href="teaser.imageSource">Quelle {{teaser.imageSource}}</a></p>
<h4 class="teaser__title">{{teaser.title}}</h4>
<p class="teaser__description">{{teaser.description}}</p>
<p class="teaser__date">{{teaser.displayDate}}</p>
</a>
</div>
</div>
</template>
<script>
import NEWS_TEASER_QUERY from '@/graphql/gql/newsTeasersQuery.gql';
export default {
components: {},
computed: {
},
apollo: {
$client: 'publicClient',
newsTeasers: {
query: NEWS_TEASER_QUERY,
update(data) {
return this.$getRidOfEdges(data).newsTeasers
},
},
},
data() {
return {
newsTeasers: []
}
},
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_functions.scss";
@import "@/styles/_mixins.scss";
$news_width: 550px;
$image_height: 254px;
.teasers {
display: block;
@include desktop {
@supports (display: grid) {
display: grid;
display: -ms-grid;
}
grid-template-columns: repeat( auto-fit, minmax(320px, $news_width) );
grid-gap: 40px;
grid-auto-rows: minmax(400px, auto);
grid-template-rows: auto auto;
-ms-grid-columns: $news_width $news_width;
}
}
.teaser {
margin-bottom: $large-spacing;
position: relative;
&__image {
display: block;
max-width: 100%;
height: auto;
@include desktop {
max-width: $news_width;
}
}
&__image-source {
line-height: 25px;
}
&__description {
margin-bottom: $large-spacing;
}
&__date {
font-family: $sans-serif-font-family;
font-weight: $font-weight-regular;
color: $color-silver-dark;
position: absolute;
bottom: 0;
left: 0;
}
}
</style>

View File

@ -0,0 +1,15 @@
query NewsTeasers {
newsTeasers {
edges {
node {
id
description
title
imageUrl
newsArticleUrl
displayDate
imageSource
}
}
}
}

32
client/src/pages/news.vue Normal file
View File

@ -0,0 +1,32 @@
<template>
<div class="news">
<h1 class="news__heading">News</h1>
<NewsTeasers />
</div>
</template>
<script>
import NewsTeasers from '@/components/NewsTeasers';
export default {
components: {NewsTeasers},
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.news {
padding: 0 $large-spacing;
max-width: 800px;
margin: 0 auto;
@include desktop {
max-width: unset;
margin: 0;
}
}
</style>

View File

@ -38,6 +38,7 @@ import joinClass from '@/pages/joinClass'
import oldClasses from '@/pages/oldClasses';
import createClass from '@/pages/createClass';
import showCode from '@/pages/showCode';
import news from '@/pages/news';
import store from '@/store/index';
@ -187,6 +188,11 @@ const routes = [
public: true
}
},
{
path: '/news',
component: news,
name: 'news'
},
{path: '/styleguide', component: styleGuidePage},
{path: '*', component: p404}
];

View File

@ -80,3 +80,10 @@ input, textarea, select, button {
font-family: $sans-serif-font-family;
color: $color-brand-dark;
}
.tiny-text {
font-size: toRem(11px);
font-family: $sans-serif-font-family;
font-weight: $font-weight-regular;
color: $color-silver-dark;
}

View File

@ -2,6 +2,7 @@ import graphene
from django.conf import settings
from graphene_django.debug import DjangoDebug
from news.schema_public import AllNewsTeasersQuery
from users.mutations_public import UserMutations
from registration.mutations_public import RegistrationMutations
@ -12,16 +13,11 @@ class Mutation(UserMutations, RegistrationMutations, graphene.ObjectType):
debug = graphene.Field(DjangoDebug, name='__debug')
# graphene neets some kind of schema in order to create a schema
class DummyQuery(object):
meaning_of_life = graphene.Int()
class Query(AllNewsTeasersQuery, graphene.ObjectType):
node = graphene.relay.Node.Field()
def resolve_meaning_of_life(self, info, **kwargs):
return 42
class Query(DummyQuery, graphene.ObjectType):
pass
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='_debug')
schema = graphene.Schema(mutation=Mutation, query=Query)

View File

@ -0,0 +1,21 @@
# Generated by Django 2.1.15 on 2020-05-20 09:54
from django.db import migrations
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.images.blocks
class Migration(migrations.Migration):
dependencies = [
('basicknowledge', '0005_auto_20200408_0834'),
]
operations = [
migrations.AlterField(
model_name='basicknowledge',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'ul', 'brand', 'secondary']))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('section_title', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())]))], blank=True, null=True),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 2.1.15 on 2020-05-20 09:54
import assignments.models
from django.db import migrations
import surveys.models
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.images.blocks
import wagtail.snippets.blocks
class Migration(migrations.Migration):
dependencies = [
('books', '0020_topic_instructions'),
]
operations = [
migrations.AlterField(
model_name='contentblock',
name='contents',
field=wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('instruction', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock()), ('text', wagtail.core.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())])), ('content_list_item', wagtail.core.blocks.StreamBlock([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock(required=False)), ('basic_knowledge', wagtail.core.blocks.PageChooserBlock(required=True, target_model=['basicknowledge.BasicKnowledge']))])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.snippets.blocks.SnippetChooserBlock(assignments.models.Assignment))])), ('survey', wagtail.core.blocks.StructBlock([('survey_id', wagtail.snippets.blocks.SnippetChooserBlock(surveys.models.Survey))])), ('image_block', wagtail.images.blocks.ImageChooserBlock()), ('image_url_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('link_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('solution', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock(features=['ul']))], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('infogram_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())])), ('genially_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('thinglink_block', wagtail.core.blocks.StructBlock([('id', wagtail.core.blocks.TextBlock())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('instruction', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock()), ('text', wagtail.core.blocks.TextBlock(required=False))])), ('module_room_slug', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.TextBlock())]))]))], blank=True, null=True),
),
]

View File

@ -112,4 +112,5 @@ class Command(BaseCommand):
# now create all and rooms
management.call_command('dummy_rooms', verbosity=0)
management.call_command('dummy_news', verbosity=0)

View File

@ -3,13 +3,10 @@ import random
import shutil
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.management import BaseCommand
from wagtail.core.models import Site
from rooms.factories import RoomFactory, RoomEntryFactory
from rooms.models import Room
from users.factories import SchoolClassFactory
data = [
{

View File

@ -57,6 +57,7 @@ INSTALLED_APPS = [
'surveys',
'notes',
'registration',
'news',
'wagtail.contrib.forms',
'wagtail.contrib.redirects',
@ -399,4 +400,3 @@ TASKBASE_BASEURL = os.environ.get("TASKBASE_BASEURL")
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
TEST_OUTPUT_DIR = './test-reports/'
TEST_OUTPUT_VERBOSE = 1

0
server/news/__init__.py Normal file
View File

9
server/news/admin.py Normal file
View File

@ -0,0 +1,9 @@
from django.contrib import admin
from news.models import NewsTeaser
@admin.register(NewsTeaser)
class NewsTeaserAdmin(admin.ModelAdmin):
list_display = ('title', 'date', 'order_id')
list_filter = ('title',)

5
server/news/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class UserConfig(AppConfig):
name = 'news'

View File

@ -0,0 +1,26 @@
def month_to_german_string(month):
if month == 1:
month = 'Januar'
elif month == 2:
month = 'Februar'
elif month == 3:
month = 'März'
elif month == 4:
month = 'April'
elif month == 5:
month = 'Mai'
elif month == 6:
month = 'Juni'
elif month == 7:
month = 'Juli'
elif month == 8:
month = 'August'
elif month == 9:
month = 'September'
elif month == 10:
month = 'Oktober'
elif month == 11:
month = 'November'
elif month == 12:
month = 'Dezember'
return month

21
server/news/factories.py Normal file
View File

@ -0,0 +1,21 @@
import datetime
import random
import factory
from factory.fuzzy import FuzzyDateTime
from pytz import UTC
from core.factories import fake, fake_title
from news.models import NewsTeaser
class NewsTeaserFactory(factory.django.DjangoModelFactory):
class Meta:
model = NewsTeaser
title = factory.LazyAttribute(lambda x: fake_title(max_words=2))
description = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(4, 8)))
image_url = factory.LazyAttribute(lambda x: 'https://picsum.photos/550/257/?random')
news_article_url = factory.LazyAttribute(lambda x: 'https://myskillbox-abu-news.webflow.io/brexit')
date = FuzzyDateTime(datetime.datetime(2020, 1, 1, tzinfo=UTC))
image_source = factory.LazyAttribute(lambda x: 'https://picsum.photos/550/257/?random')

View File

View File

@ -0,0 +1,14 @@
from django.core.management import BaseCommand
from news.factories import NewsTeaserFactory
from news.models import NewsTeaser
class Command(BaseCommand):
def handle(self, *args, **options):
NewsTeaser.objects.all().delete()
for index in range(9):
NewsTeaserFactory()

View File

@ -0,0 +1,27 @@
# Generated by Django 2.1.15 on 2020-05-20 13:39
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='NewsTeaser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image_url', models.URLField(null=True, verbose_name='Image URL')),
('title', models.CharField(max_length=300, verbose_name='Title')),
('description', models.TextField(null=True, verbose_name='Description')),
('date', models.DateField(null=True)),
('order_id', models.IntegerField(default=-1)),
('news_article_url', models.URLField(null=True, verbose_name='News Article URL')),
('image_source', models.CharField(max_length=100, verbose_name='Image Source')),
],
),
]

View File

15
server/news/models.py Normal file
View File

@ -0,0 +1,15 @@
from django.utils.translation import ugettext_lazy as _
from django.db import models
class NewsTeaser(models.Model):
image_url = models.URLField(_('Image URL'), blank=False, null=True)
title = models.CharField(_('Title'), max_length=300, blank=False, null=False)
description = models.TextField(_('Description'), blank=False, null=True)
date = models.DateField(blank=False, null=True)
order_id = models.IntegerField(blank=False, null=False, default=-1)
news_article_url = models.URLField(_('News Article URL'), blank=False, null=True)
image_source = models.CharField(_('Image Source'), max_length=100, blank=False, null=False)
def __str__(self):
return '{}'.format(self.title)

View File

@ -0,0 +1,35 @@
import locale
import graphene
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from news.date_helper import month_to_german_string
from news.models import NewsTeaser
class NewsTeaserNode(DjangoObjectType):
display_date = graphene.String()
class Meta:
model = NewsTeaser
filter_fields = ['date',]
interfaces = (relay.Node,)
def resolve_display_date(self, *args, **kwargs):
# play it safe, locale might not be installed on platform
try:
locale.setlocale(locale.LC_TIME, "de_DE")
return self.date.strftime("%-d. %B %Y")
except:
month = month_to_german_string(self.date.month)
return f'{self.date.day}. {month}. {self.date.year}'
class AllNewsTeasersQuery(object):
news_teasers = DjangoFilterConnectionField(NewsTeaserNode)
def resolve_news_teasers(self, info, **kwargs):
return NewsTeaser.objects.all().order_by('order_id')

View File

View File

@ -0,0 +1,42 @@
from unittest import TestCase
from graphene.test import Client
from api.schema_public import schema
from news.factories import NewsTeaserFactory
class NewsTeaserTests(TestCase):
def setUp(self):
self.news_teaser1 = NewsTeaserFactory(order_id=1)
self.news_teaser2 = NewsTeaserFactory(order_id=2)
self.client = Client(schema=schema)
def make_query(self):
query = '''
query NewsTeasers {
newsTeasers {
edges {
node {
id
description
title
imageUrl
newsArticleUrl
displayDate
imageSource
}
}
}
}
'''
return self.client.execute(query)
def test_public_can_get_news_teaser(self):
result = self.make_query()
self.assertIsNone(result.get('errors'))
news_teasers = result.get('data').get('newsTeasers').get('edges')
self.assertEqual(news_teasers[0].get('node').get('title'), self.news_teaser1.title)
self.assertEqual(news_teasers[1].get('node').get('title'), self.news_teaser2.title)

View File

@ -1,13 +1,3 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
#
# Created on 2019-10-01
# @author: chrigu <christian.cueni@iterativ.ch>
import graphene
from django.conf import settings
from django.contrib.auth import authenticate, login

View File

@ -1,12 +1,3 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
#
# Created on 2019-07-24
# @author: chrigu <christian.cueni@iterativ.ch>
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase, RequestFactory
from graphene.test import Client