Add "add room entry" mutation with serializer
This commit is contained in:
parent
1ef4c6d8e1
commit
cb0f96f81e
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue