Merge branch 'develop' of bitbucket.org:iterativ/skillbox into develop

This commit is contained in:
Christian Cueni 2020-06-17 14:22:31 +02:00
commit e07e0a199a
31 changed files with 433 additions and 47 deletions

View File

@ -8,6 +8,7 @@
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": ". ../server/.env && npm run dev", "start": ". ../server/.env && npm run dev",
"lint": "eslint --ext .js,.vue src", "lint": "eslint --ext .js,.vue src",
"fix-lint": "eslint --ext .js,.vue --fix src",
"build": "node build/build.js", "build": "node build/build.js",
"open:cypress": "cypress open", "open:cypress": "cypress open",
"test:cypress": "cypress run", "test:cypress": "cypress run",

View File

@ -6,7 +6,7 @@
:type="'checkbox'" :type="'checkbox'"
@input="passOn" @input="passOn"
> >
<slot></slot> <slot/>
</base-input> </base-input>
</template> </template>

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

@ -12,9 +12,12 @@
<tick v-if="type === 'checkbox'"/> <tick v-if="type === 'checkbox'"/>
<circle-icon v-if="type === 'radiobutton'"/> <circle-icon v-if="type === 'radiobutton'"/>
</span> </span>
<span v-if="label" class="base-input-container__label">{{ label }}</span> <span
<slot v-if="!label" class="base-input-container__label"> class="base-input-container__label"
</slot> v-if="label">{{ label }}</span>
<slot
class="base-input-container__label"
v-if="!label"/>
</label> </label>
</template> </template>

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

@ -199,41 +199,47 @@
for="password2" for="password2"
class="skillboxform-input__label">Passwort wiederholen</label> class="skillboxform-input__label">Passwort wiederholen</label>
<input <input
id="password-confirmation"
name="password-confirmation"
type="text"
v-model="passwordConfirmation" v-model="passwordConfirmation"
v-validate="'required|confirmed:password'" v-validate="'required|confirmed:password'"
data-vv-as="Passwort wiederholen"
:class="{ 'skillboxform-input__input--error': errors.has('password-confirmation') && submitted }" :class="{ 'skillboxform-input__input--error': errors.has('password-confirmation') && submitted }"
name="password-confirmation"
type="text"
data-vv-as="Passwort wiederholen"
class="change-form__new skillbox-input skillboxform-input__input" class="change-form__new skillbox-input skillboxform-input__input"
autocomplete="off" autocomplete="off"
data-cy="passwordConfirmation-input" data-cy="passwordConfirmation-input"
id="password-confirmation"
> >
<small <small
v-if="errors.has('password-confirmation') && submitted"
class="skillboxform-input__error" class="skillboxform-input__error"
data-cy="passwordConfirmation-local-errors" data-cy="passwordConfirmation-local-errors"
v-if="errors.has('password-confirmation') && submitted"
>{{ errors.first('password-confirmation') }}</small> >{{ errors.first('password-confirmation') }}</small>
</div> </div>
<div class="change-form__field skillboxform-input"> <div class="change-form__field skillboxform-input">
<checkbox <checkbox
id="accepted-terms"
name="accepted-terms"
v-model="acceptedTerms" v-model="acceptedTerms"
class="skillboxform-input__checkbox"
:checked="acceptedTerms" :checked="acceptedTerms"
v-validate="'required:true'" v-validate="'required:true'"
:class="{ 'skillboxform-input__input--error': errors.has('accepted-terms') && submitted}" :class="{ 'skillboxform-input__input--error': errors.has('accepted-terms') && submitted}"
name="accepted-terms"
class="skillboxform-input__checkbox"
data-cy="acceptedTerms-input" data-cy="acceptedTerms-input"
id="accepted-terms"
> >
<span>Hiermit akzeptiere ich die <a href="https://www.hep-verlag.ch/agb" target="_blank" class="hep-link">AGB</a> und <span>Hiermit akzeptiere ich die <a
<a href="https://www.hep-verlag.ch/datenschutz" target="_blank" class="hep-link">Datenschutzbestimmungen</a></span> href="https://www.hep-verlag.ch/agb"
target="_blank"
class="hep-link">AGB</a> und
<a
href="https://www.hep-verlag.ch/datenschutz"
target="_blank"
class="hep-link">Datenschutzbestimmungen</a></span>
</checkbox> </checkbox>
<small <small
v-if="errors.has('accepted-terms') && submitted"
class="skillboxform-input__error" class="skillboxform-input__error"
data-cy="acceptedTerms-local-errors" data-cy="acceptedTerms-local-errors"
v-if="errors.has('accepted-terms') && submitted"
>Sie müssen hier zustimmen, damit Sie sich registrieren können.</small> >Sie müssen hier zustimmen, damit Sie sich registrieren können.</small>
</div> </div>
<div class="skillboxform-input"> <div class="skillboxform-input">

View File

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

View File

@ -80,3 +80,10 @@ input, textarea, select, button {
font-family: $sans-serif-font-family; font-family: $sans-serif-font-family;
color: $color-brand-dark; 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 django.conf import settings
from graphene_django.debug import DjangoDebug from graphene_django.debug import DjangoDebug
from news.schema_public import AllNewsTeasersQuery
from users.mutations_public import UserMutations from users.mutations_public import UserMutations
from registration.mutations_public import RegistrationMutations from registration.mutations_public import RegistrationMutations
@ -12,16 +13,11 @@ class Mutation(UserMutations, RegistrationMutations, graphene.ObjectType):
debug = graphene.Field(DjangoDebug, name='__debug') debug = graphene.Field(DjangoDebug, name='__debug')
# graphene neets some kind of schema in order to create a schema class Query(AllNewsTeasersQuery, graphene.ObjectType):
class DummyQuery(object): node = graphene.relay.Node.Field()
meaning_of_life = graphene.Int()
def resolve_meaning_of_life(self, info, **kwargs): if settings.DEBUG:
return 42 debug = graphene.Field(DjangoDebug, name='_debug')
class Query(DummyQuery, graphene.ObjectType):
pass
schema = graphene.Schema(mutation=Mutation, query=Query) 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 # now create all and rooms
management.call_command('dummy_rooms', verbosity=0) management.call_command('dummy_rooms', verbosity=0)
management.call_command('dummy_news', verbosity=0)

View File

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

View File

@ -57,6 +57,7 @@ INSTALLED_APPS = [
'surveys', 'surveys',
'notes', 'notes',
'registration', 'registration',
'news',
'wagtail.contrib.forms', 'wagtail.contrib.forms',
'wagtail.contrib.redirects', 'wagtail.contrib.redirects',
@ -399,4 +400,3 @@ TASKBASE_BASEURL = os.environ.get("TASKBASE_BASEURL")
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner' TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
TEST_OUTPUT_DIR = './test-reports/' TEST_OUTPUT_DIR = './test-reports/'
TEST_OUTPUT_VERBOSE = 1 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 import graphene
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate, login 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.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase, RequestFactory from django.test import TestCase, RequestFactory
from graphene.test import Client from graphene.test import Client