diff --git a/client/package.json b/client/package.json index 58164b51..060c321e 100644 --- a/client/package.json +++ b/client/package.json @@ -8,6 +8,7 @@ "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": ". ../server/.env && npm run dev", "lint": "eslint --ext .js,.vue src", + "fix-lint": "eslint --ext .js,.vue --fix src", "build": "node build/build.js", "open:cypress": "cypress open", "test:cypress": "cypress run", diff --git a/client/src/components/Checkbox.vue b/client/src/components/Checkbox.vue index 112af7d6..21dad7ec 100644 --- a/client/src/components/Checkbox.vue +++ b/client/src/components/Checkbox.vue @@ -6,7 +6,7 @@ :type="'checkbox'" @input="passOn" > - + diff --git a/client/src/components/NewsTeasers.vue b/client/src/components/NewsTeasers.vue new file mode 100644 index 00000000..62a47182 --- /dev/null +++ b/client/src/components/NewsTeasers.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/client/src/components/inputs/BaseInput.vue b/client/src/components/inputs/BaseInput.vue index 0c9ffad0..f6142b1b 100644 --- a/client/src/components/inputs/BaseInput.vue +++ b/client/src/components/inputs/BaseInput.vue @@ -12,9 +12,12 @@ - {{ label }} - - + {{ label }} + diff --git a/client/src/graphql/gql/newsTeasersQuery.gql b/client/src/graphql/gql/newsTeasersQuery.gql new file mode 100644 index 00000000..b6036c34 --- /dev/null +++ b/client/src/graphql/gql/newsTeasersQuery.gql @@ -0,0 +1,15 @@ +query NewsTeasers { + newsTeasers { + edges { + node { + id + description + title + imageUrl + newsArticleUrl + displayDate + imageSource + } + } + } +} diff --git a/client/src/pages/news.vue b/client/src/pages/news.vue new file mode 100644 index 00000000..47d0358d --- /dev/null +++ b/client/src/pages/news.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/client/src/pages/registration.vue b/client/src/pages/registration.vue index cfcd7cf1..b179c6e8 100644 --- a/client/src/pages/registration.vue +++ b/client/src/pages/registration.vue @@ -199,41 +199,47 @@ for="password2" class="skillboxform-input__label">Passwort wiederholen {{ errors.first('password-confirmation') }}
- Hiermit akzeptiere ich die AGB und - Datenschutzbestimmungen + Hiermit akzeptiere ich die AGB und + Datenschutzbestimmungen Sie müssen hier zustimmen, damit Sie sich registrieren können.
diff --git a/client/src/router/index.js b/client/src/router/index.js index 48808a4b..acdd5d2f 100644 --- a/client/src/router/index.js +++ b/client/src/router/index.js @@ -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} ]; diff --git a/client/src/styles/_typography.scss b/client/src/styles/_typography.scss index e6a43a5a..d30f94b4 100644 --- a/client/src/styles/_typography.scss +++ b/client/src/styles/_typography.scss @@ -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; +} diff --git a/server/api/schema_public.py b/server/api/schema_public.py index 1f643dae..3f344510 100644 --- a/server/api/schema_public.py +++ b/server/api/schema_public.py @@ -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) diff --git a/server/basicknowledge/migrations/0006_auto_20200520_0954.py b/server/basicknowledge/migrations/0006_auto_20200520_0954.py new file mode 100644 index 00000000..2e19d313 --- /dev/null +++ b/server/basicknowledge/migrations/0006_auto_20200520_0954.py @@ -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), + ), + ] diff --git a/server/books/migrations/0021_auto_20200520_0954.py b/server/books/migrations/0021_auto_20200520_0954.py new file mode 100644 index 00000000..157e632a --- /dev/null +++ b/server/books/migrations/0021_auto_20200520_0954.py @@ -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), + ), + ] diff --git a/server/core/management/commands/dummy_data.py b/server/core/management/commands/dummy_data.py index fee625f8..60513856 100644 --- a/server/core/management/commands/dummy_data.py +++ b/server/core/management/commands/dummy_data.py @@ -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) diff --git a/server/core/management/commands/dummy_rooms.py b/server/core/management/commands/dummy_rooms.py index da5bf02f..2c2a7175 100644 --- a/server/core/management/commands/dummy_rooms.py +++ b/server/core/management/commands/dummy_rooms.py @@ -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 = [ { diff --git a/server/core/settings.py b/server/core/settings.py index 55b420f2..df525f22 100644 --- a/server/core/settings.py +++ b/server/core/settings.py @@ -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 - diff --git a/server/news/__init__.py b/server/news/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/news/admin.py b/server/news/admin.py new file mode 100644 index 00000000..d52dda6f --- /dev/null +++ b/server/news/admin.py @@ -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',) + diff --git a/server/news/apps.py b/server/news/apps.py new file mode 100644 index 00000000..78e39c93 --- /dev/null +++ b/server/news/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + name = 'news' diff --git a/server/news/date_helper.py b/server/news/date_helper.py new file mode 100644 index 00000000..62f199a8 --- /dev/null +++ b/server/news/date_helper.py @@ -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 diff --git a/server/news/factories.py b/server/news/factories.py new file mode 100644 index 00000000..bb2701f1 --- /dev/null +++ b/server/news/factories.py @@ -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') diff --git a/server/news/management/__init__.py b/server/news/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/news/management/commands/__init__.py b/server/news/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/news/management/commands/dummy_news.py b/server/news/management/commands/dummy_news.py new file mode 100644 index 00000000..6fcb2f02 --- /dev/null +++ b/server/news/management/commands/dummy_news.py @@ -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() + diff --git a/server/news/migrations/0001_initial.py b/server/news/migrations/0001_initial.py new file mode 100644 index 00000000..f24f0031 --- /dev/null +++ b/server/news/migrations/0001_initial.py @@ -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')), + ], + ), + ] diff --git a/server/news/migrations/__init__.py b/server/news/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/news/models.py b/server/news/models.py new file mode 100644 index 00000000..13511e6b --- /dev/null +++ b/server/news/models.py @@ -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) diff --git a/server/news/schema_public.py b/server/news/schema_public.py new file mode 100644 index 00000000..34f1b307 --- /dev/null +++ b/server/news/schema_public.py @@ -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') + diff --git a/server/news/tests/__init__.py b/server/news/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/news/tests/test_newsteaser.py b/server/news/tests/test_newsteaser.py new file mode 100644 index 00000000..4d86cb47 --- /dev/null +++ b/server/news/tests/test_newsteaser.py @@ -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) + diff --git a/server/users/mutations_public.py b/server/users/mutations_public.py index 4ed9a3ae..0e262b7d 100644 --- a/server/users/mutations_public.py +++ b/server/users/mutations_public.py @@ -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 - import graphene from django.conf import settings from django.contrib.auth import authenticate, login diff --git a/server/users/tests/test_usersettings.py b/server/users/tests/test_usersettings.py index 147bd223..6b5be5c3 100644 --- a/server/users/tests/test_usersettings.py +++ b/server/users/tests/test_usersettings.py @@ -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 from django.contrib.sessions.middleware import SessionMiddleware from django.test import TestCase, RequestFactory from graphene.test import Client