Add admin slug model

This commit is contained in:
Christian Cueni 2019-08-07 14:42:20 +02:00
parent 8c42daca7f
commit 083a8b03a8
12 changed files with 277 additions and 8 deletions

View File

@ -18,12 +18,12 @@ from portfolio.schema import PortfolioQuery
from surveys.schema import SurveysQuery
from surveys.mutations import SurveysMutations
from rooms.mutations import RoomMutations
from rooms.schema import RoomsQuery
from rooms.schema import RoomsQuery, AdminRoomsQuery
from users.schema import UsersQuery
from users.mutations import ProfileMutations
class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery,
class Query(UsersQuery, AdminRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery,
BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, SurveysQuery, graphene.ObjectType):
node = relay.Node.Field()

View File

@ -98,6 +98,14 @@ class InstrumentTextBlock(blocks.StructBlock):
text = blocks.RichTextBlock(features=INSTRUMENTS_RICH_TEXT_FEATURES)
class AdminRoomSlugBlock(blocks.StructBlock):
class Meta:
icon = 'link'
slug = blocks.TextBlock()
title = blocks.TextBlock()
# 'text_block' 'task' 'basic_knowledge' 'student_entry' 'image_block'
#
# url = blocks.URLBlock()

View File

@ -0,0 +1,24 @@
# Generated by Django 2.0.6 on 2019-08-07 12:20
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', '0012_auto_20190722_0932'),
]
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())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('admin_room_slug', wagtail.core.blocks.StructBlock([('slug', wagtail.core.blocks.TextBlock()), ('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())])), ('subtitle', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock())])), ('admin_room_slug', wagtail.core.blocks.StructBlock([('slug', wagtail.core.blocks.TextBlock()), ('title', wagtail.core.blocks.TextBlock())]))]))], blank=True, null=True),
),
]

View File

@ -7,7 +7,7 @@ from wagtail.core.fields import StreamField
from wagtail.images.blocks import ImageChooserBlock
from books.blocks import TextBlock, BasicKnowledgeBlock, LinkBlock, VideoBlock, DocumentBlock, \
ImageUrlBlock, AssignmentBlock, InfogramBlock, GeniallyBlock, SubtitleBlock, SurveyBlock
ImageUrlBlock, AssignmentBlock, InfogramBlock, GeniallyBlock, SubtitleBlock, SurveyBlock, AdminRoomSlugBlock
from core.wagtail_utils import StrictHierarchyPage
from users.models import SchoolClass
@ -48,7 +48,8 @@ class ContentBlock(StrictHierarchyPage):
('document_block', DocumentBlock()),
('infogram_block', InfogramBlock()),
('genially_block', GeniallyBlock()),
('subtitle', SubtitleBlock())
('subtitle', SubtitleBlock()),
('admin_room_slug', AdminRoomSlugBlock())
]
content_list_item = StreamBlock(content_blocks)

View File

@ -1,6 +1,6 @@
from django.contrib import admin
from rooms.models import Room, RoomEntry
from rooms.models import Room, RoomEntry, AdminGeneratedRoomSlug
@admin.register(Room)
@ -13,3 +13,9 @@ class RoomAdmin(admin.ModelAdmin):
class RoomEntryAdmin(admin.ModelAdmin):
list_display = ('id', 'slug', 'title', 'room', 'author')
list_filter = ('room', 'author')
@admin.register(AdminGeneratedRoomSlug)
class AdminGeneratedRoomSlugAdmin(admin.ModelAdmin):
list_display = ('id', 'slug', 'title')
list_filter = ('slug', 'title')

View File

@ -8,7 +8,7 @@ from wagtail.core.rich_text import RichText
from books.factories import TextBlockFactory, ImageUrlBlockFactory, LinkBlockFactory
from core.factories import fake, fake_paragraph
from rooms.models import Room, RoomEntry
from rooms.models import Room, RoomEntry, AdminGeneratedRoomSlug
from users.models import SchoolClass
@ -77,3 +77,12 @@ class RoomEntryFactory(factory.django.DjangoModelFactory):
def create(cls, **kwargs):
cls.stream_field_magic(kwargs, 'contents')
return cls._generate(CREATE_STRATEGY, kwargs)
class AdminGeneratedRoomSlugFactory(factory.django.DjangoModelFactory):
class Meta:
model = AdminGeneratedRoomSlug
slug = factory.Sequence(lambda n: u'slug-{:d}'.format(n))
title = factory.Sequence(lambda n: u'Title {:d}'.format(n))

View File

@ -0,0 +1,26 @@
# Generated by Django 2.0.6 on 2019-08-07 12:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rooms', '0006_auto_20190722_0932'),
]
operations = [
migrations.CreateModel(
name='AdminGeneratedRoomSlug',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.CharField(max_length=255)),
('title', models.CharField(max_length=255)),
],
),
migrations.AddField(
model_name='room',
name='user_created',
field=models.BooleanField(default=True),
),
]

View File

@ -15,6 +15,7 @@ class Room(TitleSlugDescriptionModel):
school_class = models.ForeignKey(SchoolClass, blank=False, null=False, on_delete=models.CASCADE, related_name='rooms')
appearance = models.CharField(blank=True, null=False, max_length=255)
user_created = models.BooleanField(blank=False, null=False, default=True)
def __str__(self):
return 'Room {}-{}-{}'.format(self.id, self.title, self.school_class)
@ -41,3 +42,9 @@ class RoomEntry(TitleSlugDescriptionModel):
def can_user_see_entry(self, user):
return user.is_superuser or self.room.school_class.is_user_in_schoolclass(user)
class AdminGeneratedRoomSlug(models.Model):
slug = models.CharField(blank=False, null=False, max_length=255)
title = models.CharField(blank=False, null=False, max_length=255)

View File

@ -6,7 +6,8 @@ from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from api.utils import get_object, get_by_id_or_slug
from rooms.models import Room, RoomEntry
from rooms.models import Room, RoomEntry, AdminGeneratedRoomSlug
from users.models import SchoolClass
from users.schema import UserNode
logger = logging.getLogger(__name__)
@ -53,7 +54,7 @@ class RoomsQuery(object):
user = info.context.user
if user.is_superuser:
return Room.objects.all()
return Room.objects.filter(school_class__in=user.school_classes.all())
return Room.objects.filter(school_class__in=user.school_classes.all()).exclude(user_created=False)
def resolve_room(self, info, **kwargs):
room = get_by_id_or_slug(Room, **kwargs)
@ -82,3 +83,34 @@ class RoomsQuery(object):
return RoomEntry.objects.none()
else:
return RoomEntry.objects.all()
class AdminRoomsQuery(object):
admin_room = graphene.Field(RoomNode, slug=graphene.String(), class_id=graphene.ID())
def resolve_admin_room(self, info, **kwargs):
slug = kwargs.get('slug')
schoolclass = get_object(SchoolClass, kwargs.get('class_id'))
try:
slug = AdminGeneratedRoomSlug.objects.get(slug=slug)
except AdminGeneratedRoomSlug.DoesNotExist:
return None
if schoolclass is None or not schoolclass.is_user_in_schoolclass(info.context.user):
return None
room, created = Room.objects.get_or_create(school_class=schoolclass, user_created=False, slug=slug,
title=slug.title)
if not room.user_created and room.school_class.is_user_in_schoolclass(info.context.user):
return room
else:
return None
# def resolve_all_room_entries(self, info, **kwargs):
# if not info.context.user.is_superuser:
# return RoomEntry.objects.none()
# else:
# return RoomEntry.objects.all()

View File

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
#
# Created on 2019-08-07
# @author: chrigu <christian.cueni@iterativ.ch>
from django.test import TestCase, RequestFactory
from graphene.test import Client
from graphql_relay import to_global_id
from api.schema import schema
from core.factories import UserFactory
from rooms.factories import RoomFactory, AdminGeneratedRoomSlugFactory
from users.factories import SchoolClassFactory
class AdminRoomQueryPermission(TestCase):
def setUp(self):
self.user = UserFactory(username='aschi')
self.another_user = UserFactory(username='pesche')
self.sc1 = SchoolClassFactory(users=[self.user])
sc2 = SchoolClassFactory(users=[self.another_user])
self.room1 = RoomFactory(school_class=self.sc1)
self.room2 = RoomFactory(school_class=sc2)
self.admin_slug = AdminGeneratedRoomSlugFactory(slug='test-slug', title='title')
self.sc1_id = to_global_id('SchoolClass', self.sc1.pk)
self.sc2_id = to_global_id('SchoolClass', sc2.pk)
request = RequestFactory().get('/')
request.user = self.user
self.client = Client(schema=schema, context_value=request)
self.query = '''
query AdminRoomQuery($slug: String, $classId: ID!) {
adminRoom(slug: $slug, classId: $classId) {
title
}
}
'''
def test_should_return_none_if_slug_does_not_exist(self):
result = self.client.execute(self.query, variables={
'slug': 'no-slug',
'classId': 'norealId'
})
self.assertIsNone(result.get('errors'))
self.assertIsNone(result.get('data').get('adminRoom'))
def test_should_return_none_if_class_id_does_not_exist(self):
result = self.client.execute(self.query, variables={
'slug': 'no-slug',
'classId': 'norealId'
})
self.assertIsNone(result.get('errors'))
self.assertIsNone(result.get('data').get('adminRoom'))
def test_user_should_not_be_able_to_create_room_for_other_class(self):
result = self.client.execute(self.query, variables={
'slug': self.admin_slug.slug,
'classId': self.sc2_id
})
self.assertIsNone(result.get('errors'))
self.assertIsNone(result.get('data').get('adminRoom'))
def test_should_create_room_if_none_exists(self):
result = self.client.execute(self.query, variables={
'slug': self.admin_slug.slug,
'classId': self.sc1_id
})
self.assertIsNone(result.get('errors'))
self.assertEqual(result.get('data').get('adminRoom').get('title'), self.admin_slug.title)
def test_should_return_room_if_one_exists(self):
existing_room = RoomFactory(school_class=self.sc1, user_created=False)
admin_slug = AdminGeneratedRoomSlugFactory(slug=existing_room.slug, title=existing_room.title)
result = self.client.execute(self.query, variables={
'slug': admin_slug.slug,
'classId': self.sc1_id
})
self.assertIsNone(result.get('errors'))
self.assertEqual(result.get('data').get('adminRoom').get('title'), existing_room.title)

View File

@ -61,6 +61,27 @@ class RoomQueryPermission(TestCase):
self.assertIsNone(result.get('errors'))
self.assertEqual(result.get('data').get('room'), None)
def test_student_should_only_user_created_rooms(self):
admin_room = RoomFactory(school_class=self.room1.school_class, user_created=False)
query = '''
query {
rooms {
edges {
node {
title
}
}
}
}
'''
result = self.client.execute(query)
self.assertIsNone(result.get('errors'))
self.assertEqual(len(result.get('data').get('rooms').get('edges')), 1)
self.assertNotEqual(result.get('data').get('rooms').get('edges')[0].get('node').get('title'), admin_room.title)
class RoomEntryQueryPermissions(TestCase):

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
#
# Created on 2019-08-05
# @author: chrigu <christian.cueni@iterativ.ch>
from wagtail.core import hooks
from rooms.models import AdminGeneratedRoomSlug
@hooks.register('after_edit_page')
@hooks.register('after_create_page')
def do_after_page_edit(request, page):
blocks = get_room_blocks(page)
for block in blocks:
AdminGeneratedRoomSlug.objects.get_or_create(slug=block[1]['slug'])
def get_room_blocks(page):
top_level_admin_slug_blocks = get_block_from_stream_data(page.contents.stream_data, 'admin_room_slug')
content_list_admin_slug_blocks = get_admin_slugs_from_content_list(page.contents.stream_data)
return top_level_admin_slug_blocks + content_list_admin_slug_blocks
def get_block_from_stream_data(stream_data, block_name):
return [block for block in stream_data if block[0] in [block_name]]
def get_admin_slugs_from_content_list(stream_data):
admin_slug_blocks = []
content_list_items = get_block_from_stream_data(stream_data, 'content_list_item')
for content_list_item in content_list_items:
admin_slug_blocks = admin_slug_blocks + get_block_from_stream_data(content_list_item[1].stream_data,
'admin_room_slug')
return admin_slug_blocks