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 @@
+
+
+
News
+
+
+
+
+
+
+
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') }}
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