From e21ba0a2323c20ba1da5e4b3e76845dc5e38729f Mon Sep 17 00:00:00 2001 From: Pawel Kowalski Date: Wed, 8 Aug 2018 11:41:39 +0200 Subject: [PATCH] implement base model, factories, nodes --- server/api/schema.py | 4 +- server/book/__init__.py | 0 server/book/admin.py | 18 +++++ server/book/apps.py | 5 ++ server/book/blocks.py | 14 ++++ server/book/factories.py | 30 +++++++ server/book/migrations/0001_initial.py | 57 +++++++++++++ server/book/migrations/__init__.py | 0 server/book/models/__init__.py | 3 + server/book/models/book.py | 29 +++++++ server/book/models/module.py | 54 +++++++++++++ server/book/models/topic.py | 40 +++++++++ server/book/schema.py | 81 +++++++++++++++++++ server/core/management/commands/dummy_data.py | 8 ++ server/core/settings.py | 1 + 15 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 server/book/__init__.py create mode 100644 server/book/admin.py create mode 100644 server/book/apps.py create mode 100644 server/book/blocks.py create mode 100644 server/book/factories.py create mode 100644 server/book/migrations/0001_initial.py create mode 100644 server/book/migrations/__init__.py create mode 100644 server/book/models/__init__.py create mode 100644 server/book/models/book.py create mode 100644 server/book/models/module.py create mode 100644 server/book/models/topic.py create mode 100644 server/book/schema.py diff --git a/server/api/schema.py b/server/api/schema.py index 3169c7e0..c631966e 100644 --- a/server/api/schema.py +++ b/server/api/schema.py @@ -2,8 +2,10 @@ import graphene from django.conf import settings from graphene_django.debug import DjangoDebug +from book.schema import ModulesQuery -class Query(graphene.ObjectType): + +class Query(ModulesQuery, graphene.ObjectType): # This class will inherit from multiple Queries if settings.DEBUG: diff --git a/server/book/__init__.py b/server/book/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/book/admin.py b/server/book/admin.py new file mode 100644 index 00000000..2bb3ae83 --- /dev/null +++ b/server/book/admin.py @@ -0,0 +1,18 @@ +from django.contrib import admin + +from book.models import Book, Topic, Module + + +@admin.register(Book) +class BookAdmin(admin.ModelAdmin): + list_display = ('title', 'slug') + + +@admin.register(Topic) +class TopicAdmin(admin.ModelAdmin): + list_display = ('title', 'slug', 'teaser', 'description') + + +@admin.register(Module) +class ModuleAdmin(admin.ModelAdmin): + list_display = ('title', 'slug', 'meta_title', 'teaser') diff --git a/server/book/apps.py b/server/book/apps.py new file mode 100644 index 00000000..af5749dc --- /dev/null +++ b/server/book/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BookConfig(AppConfig): + name = 'book' diff --git a/server/book/blocks.py b/server/book/blocks.py new file mode 100644 index 00000000..b69f8694 --- /dev/null +++ b/server/book/blocks.py @@ -0,0 +1,14 @@ +from wagtail.core import blocks +from wagtail.documents.blocks import DocumentChooserBlock + +DEFAULT_RICH_TEXT_FEATURES = ['bold', 'italic', 'link', 'ol', 'ul'] + + +class LinkBlock(blocks.StructBlock): + url = blocks.URLBlock() + description = blocks.CharBlock() + + +class DocumentBlock(blocks.StructBlock): + document = DocumentChooserBlock() + description = blocks.CharBlock() diff --git a/server/book/factories.py b/server/book/factories.py new file mode 100644 index 00000000..392c8d77 --- /dev/null +++ b/server/book/factories.py @@ -0,0 +1,30 @@ +import random + +import factory + +from book.models import Book, Topic, Module +from core.factories import BasePageFactory, fake, DummyImageFactory + + +class BookFactory(BasePageFactory): + class Meta: + model = Book + + +class TopicFactory(BasePageFactory): + class Meta: + model = Topic + + teaser = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(8, 12))) + description = factory.LazyAttribute(lambda x: fake.text(max_nb_chars=200)) + + +class ModuleFactory(BasePageFactory): + class Meta: + model = Module + + meta_title = factory.LazyAttribute(lambda x: fake.text(max_nb_chars=20)) + teaser = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(8, 12))) + description = factory.LazyAttribute(lambda x: fake.text(max_nb_chars=200)) + + hero_image = factory.SubFactory(DummyImageFactory) diff --git a/server/book/migrations/0001_initial.py b/server/book/migrations/0001_initial.py new file mode 100644 index 00000000..78cc0c7d --- /dev/null +++ b/server/book/migrations/0001_initial.py @@ -0,0 +1,57 @@ +# Generated by Django 2.0.6 on 2018-08-08 09:26 + +from django.db import migrations, models +import django.db.models.deletion +import wagtail.core.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('wagtailimages', '0020_add-verbose-name'), + ('wagtailcore', '0040_page_draft_title'), + ] + + operations = [ + migrations.CreateModel( + name='Book', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ], + options={ + 'verbose_name': 'Buch', + 'verbose_name_plural': 'Bücher', + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='Module', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ('meta_title', models.CharField(help_text="e.g. 'Intro' or 'Modul 1'", max_length=255)), + ('teaser', models.TextField()), + ('description', wagtail.core.fields.RichTextField()), + ('hero_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')), + ], + options={ + 'verbose_name': 'Modul', + 'verbose_name_plural': 'Module', + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='Topic', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ('teaser', models.TextField()), + ('description', wagtail.core.fields.RichTextField()), + ], + options={ + 'verbose_name': 'Thema', + 'verbose_name_plural': 'Themen', + }, + bases=('wagtailcore.page',), + ), + ] diff --git a/server/book/migrations/__init__.py b/server/book/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/book/models/__init__.py b/server/book/models/__init__.py new file mode 100644 index 00000000..11488f63 --- /dev/null +++ b/server/book/models/__init__.py @@ -0,0 +1,3 @@ +from .module import * +from .topic import * +from .book import * diff --git a/server/book/models/book.py b/server/book/models/book.py new file mode 100644 index 00000000..e0ec923c --- /dev/null +++ b/server/book/models/book.py @@ -0,0 +1,29 @@ +import logging + +from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList +from wagtail.core.models import Page + +logger = logging.getLogger(__name__) + + +class Book(Page): + class Meta: + verbose_name = 'Buch' + verbose_name_plural = 'Bücher' + + content_panels = [ + FieldPanel('title', classname="full title") + ] + + settings_panels = [ + FieldPanel('slug') + ] + + edit_handler = TabbedInterface([ + ObjectList(content_panels, heading='Content'), + ObjectList(settings_panels, heading='Settings'), + ]) + + template = 'generic_page.html' + + subpage_types = ['book.Topic'] diff --git a/server/book/models/module.py b/server/book/models/module.py new file mode 100644 index 00000000..b1fde365 --- /dev/null +++ b/server/book/models/module.py @@ -0,0 +1,54 @@ +import logging + +from django.db import models +from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, \ + ObjectList +from wagtail.core.fields import RichTextField +from wagtail.core.models import Page +from wagtail.images.edit_handlers import ImageChooserPanel + +from book.blocks import DEFAULT_RICH_TEXT_FEATURES + +logger = logging.getLogger(__name__) + + +class Module(Page): + class Meta: + verbose_name = 'Modul' + verbose_name_plural = 'Module' + + meta_title = models.CharField( + max_length=255, + help_text='e.g. \'Intro\' or \'Modul 1\'' + ) + teaser = models.TextField() + description = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES) + + hero_image = models.ForeignKey( + 'wagtailimages.Image', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='+' + ) + + content_panels = [ + FieldPanel('title', classname="full title"), + FieldPanel('meta_title', classname="full title"), + ImageChooserPanel('hero_image'), + FieldPanel('teaser'), + FieldPanel('description'), + ] + + settings_panels = [ + FieldPanel('slug') + ] + + edit_handler = TabbedInterface([ + ObjectList(content_panels, heading='Content'), + ObjectList(settings_panels, heading='Settings'), + ]) + + template = 'generic_page.html' + + parent_page_types = ['book.Topic'] diff --git a/server/book/models/topic.py b/server/book/models/topic.py new file mode 100644 index 00000000..9da2ca92 --- /dev/null +++ b/server/book/models/topic.py @@ -0,0 +1,40 @@ +import logging + +from django.db import models +from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, \ + ObjectList +from wagtail.core.fields import RichTextField +from wagtail.core.models import Page + +from book.blocks import DEFAULT_RICH_TEXT_FEATURES + +logger = logging.getLogger(__name__) + + +class Topic(Page): + class Meta: + verbose_name = 'Thema' + verbose_name_plural = 'Themen' + + teaser = models.TextField() + description = RichTextField(features=DEFAULT_RICH_TEXT_FEATURES) + + content_panels = [ + FieldPanel('title', classname="full title"), + FieldPanel('teaser'), + FieldPanel('description'), + ] + + settings_panels = [ + FieldPanel('slug') + ] + + edit_handler = TabbedInterface([ + ObjectList(content_panels, heading='Content'), + ObjectList(settings_panels, heading='Settings'), + ]) + + template = 'generic_page.html' + + parent_page_types = ['book.Book'] + subpage_types = ['book.Module'] diff --git a/server/book/schema.py b/server/book/schema.py new file mode 100644 index 00000000..c5d39288 --- /dev/null +++ b/server/book/schema.py @@ -0,0 +1,81 @@ +from graphene import relay +from graphene_django import DjangoObjectType +from graphene_django.filter import DjangoFilterConnectionField +import graphene + +from .models import Book, Topic, Module + + +class ModuleNode(DjangoObjectType): + pk = graphene.Int() + hero_image = graphene.String() + + class Meta: + model = Module + only_fields = [ + 'slug', 'title', 'meta_title', 'teaser', 'description', + ] + filter_fields = { + 'slug': ['exact', 'icontains', 'in'], + 'title': ['exact', 'icontains', 'in'], + } + interfaces = (relay.Node,) + + def resolve_pk(self, *args, **kwargs): + return self.id + + def resolve_hero_image(self, *args, **kwargs): + if self.hero_image: + return self.hero_image.file.url + + +class TopicNode(DjangoObjectType): + pk = graphene.Int() + + class Meta: + model = Topic + only_fields = [ + 'slug', 'title', 'meta_title', 'teaser', 'description', + ] + filter_fields = { + 'slug': ['exact', 'icontains', 'in'], + 'title': ['exact', 'icontains', 'in'], + } + interfaces = (relay.Node,) + + def resolve_pk(self, *args, **kwargs): + return self.id + + +class BookNode(DjangoObjectType): + pk = graphene.Int() + + class Meta: + model = Book + only_fields = [ + 'slug', 'title', + ] + filter_fields = { + 'slug': ['exact', 'icontains', 'in'], + 'title': ['exact', 'icontains', 'in'], + } + interfaces = (relay.Node,) + + def resolve_pk(self, *args, **kwargs): + return self.id + + +class ModulesQuery(object): + books = DjangoFilterConnectionField(BookNode) + topics = DjangoFilterConnectionField(TopicNode) + modules = DjangoFilterConnectionField(ModuleNode) + + def resolve_books(self, *args, **kwargs): + return Book.objects.filter(**kwargs).live() + + def resolve_topics(self, *args, **kwargs): + return Topic.objects.filter(**kwargs).live() + + def resolve_modules(self, *args, **kwargs): + return Module.objects.filter(**kwargs).live() + diff --git a/server/core/management/commands/dummy_data.py b/server/core/management/commands/dummy_data.py index d3e51e02..b0f765ae 100644 --- a/server/core/management/commands/dummy_data.py +++ b/server/core/management/commands/dummy_data.py @@ -8,6 +8,7 @@ from django.core.management import BaseCommand from django.db import connection from wagtail.core.models import Page +from book.factories import BookFactory, TopicFactory, ModuleFactory from core.factories import UserFactory data = [ @@ -46,3 +47,10 @@ class Command(BaseCommand): for i in range(0, 4): UserFactory(username='user{}'.format(i)) + + book = BookFactory.create(parent=site.root_page) + for idx_topic in range(0, 11): + topic = TopicFactory.create(parent=book) + + for idc_module in range(0, 5): + module = ModuleFactory.create(parent=topic) diff --git a/server/core/settings.py b/server/core/settings.py index e857cd9c..d349dc47 100644 --- a/server/core/settings.py +++ b/server/core/settings.py @@ -46,6 +46,7 @@ INSTALLED_APPS = [ 'core', 'api', 'user', + 'book', 'wagtail.contrib.forms', 'wagtail.contrib.redirects',