Add "add room entry" mutation with serializer

This commit is contained in:
Ramon Wenger 2018-09-25 17:01:16 +02:00
parent 1ef4c6d8e1
commit cb0f96f81e
10 changed files with 191 additions and 97 deletions

View File

@ -3,8 +3,8 @@ from django.conf import settings
from graphene import relay
from graphene_django.debug import DjangoDebug
# Keep this import exactly here, it's necessary for StreamField conversion
from api import graphene_wagtail
# noinspection PyUnresolvedReferences
from api import graphene_wagtail # Keep this import exactly here, it's necessary for StreamField conversion
from book.schema.mutations import BookMutations
from filteredbook.schema import BookQuery
@ -22,7 +22,6 @@ class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, graphene.ObjectT
class Mutation(BookMutations, RoomMutations, graphene.ObjectType):
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='__debug')

View File

@ -5,39 +5,60 @@ DEFAULT_RICH_TEXT_FEATURES = ['bold', 'italic', 'link', 'ol', 'ul']
# link_block
class LinkBlock(blocks.StructBlock):
class Meta:
icon = 'link'
text = blocks.TextBlock()
url = blocks.URLBlock()
# 'text_block' 'task'
class TextBlock(blocks.StructBlock):
class Meta:
icon = 'doc-full'
text = blocks.RichTextBlock()
# 'basic_knowledge'
class BasicKnowledgeBlock(blocks.StructBlock):
class Meta:
icon = 'placeholder'
description = blocks.RichTextBlock()
url = blocks.URLBlock()
# 'image_url'
class ImageUrlBlock(blocks.StructBlock):
class Meta:
icon = 'image'
title = blocks.RichTextBlock()
url = blocks.URLBlock()
# 'student_entry'
class StudentEntryBlock(blocks.StructBlock):
class Meta:
icon = 'download'
task_text = blocks.RichTextBlock()
# 'video_block'
class VideoBlock(blocks.StructBlock):
class Meta:
icon = 'media'
url = blocks.URLBlock()
# 'document_block'
class DocumentBlock(blocks.StructBlock):
class Meta:
icon = 'doc-full'
url = blocks.URLBlock()
# 'text_block' 'task' 'basic_knowledge' 'student_entry' 'image_block'

View File

@ -33,15 +33,15 @@ class ContentBlock(StrictHierarchyPage):
hidden_for = models.ManyToManyField(UserGroup)
contents = StreamField([
('text_block', TextBlock(icon='doc-full')),
('basic_knowledge', BasicKnowledgeBlock(icon='placeholder')),
('student_entry', StudentEntryBlock(icon='download')),
('image_block', ImageChooserBlock(icon='image')),
('image_url_block', ImageUrlBlock(icon='image')),
('link_block', LinkBlock(icon='link')),
('text_block', TextBlock()),
('basic_knowledge', BasicKnowledgeBlock()),
('student_entry', StudentEntryBlock()),
('image_block', ImageChooserBlock()),
('image_url_block', ImageUrlBlock()),
('link_block', LinkBlock()),
('task', TextBlock(icon='tick')),
('video_block', VideoBlock(icon='media')),
('document_block', DocumentBlock(icon='doc-full')),
('video_block', VideoBlock()),
('document_block', DocumentBlock()),
], null=True, blank=True)
type = models.CharField(

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
#
# Iterativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2018 Iterativ GmbH. All rights reserved.
#
# Created on 25.09.18
# @author: Ramon Wenger <ramon.wenger@iterativ.ch>
from .contentblock import AddContentBlock, MutateContentBlock
class BookMutations(object):
mutate_content_block = MutateContentBlock.Field()
add_content_block = AddContentBlock.Field()

View File

@ -1,8 +1,6 @@
import json
import bleach
import graphene
import re
from django.core.exceptions import ValidationError
from graphene import relay
@ -11,70 +9,7 @@ from book.models import ContentBlock, Chapter, UserGroup
from book.schema.inputs import ContentBlockInput
from book.schema.queries import ContentBlockNode
def newlines_to_paragraphs(text):
parts = re.split(r'[\r\n]+', text)
paragraphs = ['<p>{}</p>'.format(p.strip()) for p in parts]
return '\n'.join(paragraphs)
ALLOWED_BLOCKS = (
'text_block',
'student_entry',
'image_url_block',
'link_block',
'video_block',
'document_block',
)
def handle_content_blocks(content_data, allowed_blocks=ALLOWED_BLOCKS):
new_contents = []
for content in content_data:
# todo: add all the content blocks
# todo: sanitize user inputs!
if content['type'] not in allowed_blocks:
continue
if content['type'] == 'text_block':
new_contents.append({
'type': 'text_block',
'value': {
'text': newlines_to_paragraphs(bleach.clean(content['value']['text'], strip=True))
}})
elif content['type'] == 'student_entry':
pass
elif content['type'] == 'image_url_block':
new_contents.append({
'type': 'image_url_block',
'value': {
'url': bleach.clean(content['value']['url'])
}})
elif content['type'] == 'link_block':
new_contents.append({
'type': 'link_block',
'value': {
'text': bleach.clean(content['value']['text']),
'url': bleach.clean(content['value']['url'])
}
})
elif content['type'] == 'task':
pass
elif content['type'] == 'video_block':
new_contents.append({
'type': 'video_block',
'value': {
'url': bleach.clean(content['value']['url'])
}})
elif content['type'] == 'document_block':
new_contents.append({
'type': 'document_block',
'value': {
'url': bleach.clean(content['value']['url'])
}})
return new_contents
from .utils import handle_content_block
class MutateContentBlock(relay.ClientIDMutation):
@ -109,7 +44,7 @@ class MutateContentBlock(relay.ClientIDMutation):
content_block.title = title
if contents is not None:
content_block.contents = json.dumps(handle_content_blocks(contents))
content_block.contents = json.dumps(list(map(handle_content_block, contents)))
content_block.save()
@ -157,8 +92,8 @@ class AddContentBlock(relay.ClientIDMutation):
revision.publish()
new_content_block.save()
new_contents = handle_content_blocks(contents) # can only do this after the content block has been saved
new_content_block.contents = json.dumps(new_contents)
new_content_block.contents = json.dumps(
list(map(handle_content_block, contents))) # can only do this after the content block has been saved
new_content_block.save()
return new_content_block
@ -180,8 +115,3 @@ class AddContentBlock(relay.ClientIDMutation):
errors = ['Error: {}'.format(e)]
return cls(new_content_block=None, errors=errors)
class BookMutations(object):
mutate_content_block = MutateContentBlock.Field()
add_content_block = AddContentBlock.Field()

View File

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
#
# Iterativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2018 Iterativ GmbH. All rights reserved.
#
# Created on 25.09.18
# @author: Ramon Wenger <ramon.wenger@iterativ.ch>
import bleach
import re
def newlines_to_paragraphs(text):
parts = re.split(r'[\r\n]+', text)
paragraphs = ['<p>{}</p>'.format(p.strip()) for p in parts]
return '\n'.join(paragraphs)
ALLOWED_BLOCKS = (
'text_block',
'student_entry',
'image_url_block',
'link_block',
'video_block',
'document_block',
)
def handle_content_block(content, allowed_blocks=ALLOWED_BLOCKS):
# todo: add all the content blocks
# todo: sanitize user inputs!
if content['type'] not in allowed_blocks:
return
if content['type'] == 'text_block':
return {
'type': 'text_block',
'value': {
'text': newlines_to_paragraphs(bleach.clean(content['value']['text'], strip=True))
}}
elif content['type'] == 'student_entry':
return
elif content['type'] == 'image_url_block':
return {
'type': 'image_url_block',
'value': {
'url': bleach.clean(content['value']['url'])
}}
elif content['type'] == 'link_block':
return {
'type': 'link_block',
'value': {
'text': bleach.clean(content['value']['text']),
'url': bleach.clean(content['value']['url'])
}
}
elif content['type'] == 'video_block':
return {
'type': 'video_block',
'value': {
'url': bleach.clean(content['value']['url'])
}}
elif content['type'] == 'document_block':
return {
'type': 'document_block',
'value': {
'url': bleach.clean(content['value']['url'])
}}
return None

View File

@ -1,6 +1,7 @@
import graphene
from graphene import InputObjectType
from book.schema.inputs import ContentElementInput
from user.inputs import UserGroupInput
@ -17,3 +18,10 @@ class AddRoomArgument(RoomInput):
class UpdateRoomArgument(RoomInput):
id = graphene.ID(required=True)
class AddRoomEntryArgument(InputObjectType):
title = graphene.String(required=True)
subtitle = graphene.String()
contents = graphene.List(ContentElementInput)
room = graphene.ID(required=True)

View File

@ -3,7 +3,7 @@ from django.db import models
from django_extensions.db.models import TitleDescriptionModel, TitleSlugDescriptionModel
from wagtail.core.fields import StreamField
from book.blocks import ImageUrlBlock, LinkBlock
from book.blocks import ImageUrlBlock, LinkBlock, VideoBlock
from book.models import ContentBlock, TextBlock
from user.models import UserGroup
@ -30,9 +30,10 @@ class RoomEntry(TitleSlugDescriptionModel):
subtitle = models.CharField(blank=True, null=False, max_length=255)
contents = StreamField([
('text_block', TextBlock(icon='doc-full')),
('image_url', ImageUrlBlock(icon='image')),
('link_block', LinkBlock(icon='link'))
('text_block', TextBlock()),
('image_url', ImageUrlBlock()),
('link_block', LinkBlock()),
('video_block', VideoBlock())
], null=True, blank=True)
def __str__(self):

View File

@ -1,12 +1,11 @@
import graphene
from graphene import relay
from graphql_relay import from_global_id
from api.utils import get_object
from rooms.inputs import UpdateRoomArgument, AddRoomArgument
from rooms.models import Room
from rooms.schema import RoomNode
from rooms.serializers import RoomSerializer
from rooms.inputs import UpdateRoomArgument, AddRoomArgument, AddRoomEntryArgument
from rooms.models import Room, RoomEntry
from rooms.schema import RoomNode, RoomEntryNode
from rooms.serializers import RoomSerializer, RoomEntrySerializer
from user.models import UserGroup
@ -56,7 +55,31 @@ class DeleteRoom(relay.ClientIDMutation):
return cls(success=True)
class AddRoomEntry(relay.ClientIDMutation):
class Input:
room_entry = graphene.Argument(AddRoomEntryArgument)
room_entry = graphene.Field(RoomEntryNode)
errors = graphene.List(graphene.String)
@classmethod
def mutate_and_get_payload(cls, *args, **kwargs):
room_entry_data = kwargs.get('room_entry')
room_entry_data['room'] = get_object(Room, room_entry_data.get('room')).id
serializer = RoomEntrySerializer(data=room_entry_data)
if serializer.is_valid():
serializer.save()
return cls(room_entry=serializer.instance)
return cls(room_entry=None, errors=['{}: {}'.format(key, value) for key, value in serializer.errors.items()])
class RoomMutations:
update_room = UpdateRoom.Field()
add_room = AddRoom.Field()
delete_room = DeleteRoom.Field()
add_room_entry = AddRoomEntry.Field()

View File

@ -1,10 +1,35 @@
import json
from rest_framework import serializers
from rooms.models import Room
from book.schema.mutations.utils import handle_content_block
from rooms.models import Room, RoomEntry
class ContentsSerializer(serializers.Field):
def to_internal_value(self, data):
return json.dumps(list(map(handle_content_block, data)))
class RoomSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = ('id', 'title', 'description', 'slug', 'user_group', 'appearance', )
read_only_fields = ('id', 'slug', )
fields = ('id', 'title', 'description', 'slug', 'user_group', 'appearance',)
read_only_fields = ('id', 'slug',)
class RoomEntrySerializer(serializers.ModelSerializer):
contents = ContentsSerializer()
class Meta:
model = RoomEntry
fields = ('room', 'author', 'subtitle', 'title', 'contents')
read_only_fields = ('id', 'slug',)
def validate_contents(self, value):
return value
def create(self, validated_data):
room_entry = RoomEntry(**validated_data)
room_entry.save()
return room_entry