Merge branch 'feature/user-created-content-blocks'

This commit is contained in:
Ramon Wenger 2018-10-10 15:48:08 +02:00
commit 4d1e156f9f
34 changed files with 127 additions and 74 deletions

View File

@ -6,6 +6,8 @@
{{chapter.description}}
</p>
<add-content-block-button :parent="chapter.id"></add-content-block-button>
<content-block :contentBlock="contentBlock"
:key="contentBlock.id" v-for="contentBlock in filteredContentBlocks">
</content-block>
@ -14,12 +16,14 @@
<script>
import ContentBlock from '@/components/ContentBlock';
import AddContentBlockButton from '@/components/AddContentBlockButton';
export default {
props: ['chapter', 'index'],
components: {
ContentBlock,
AddContentBlockButton
},
computed: {

View File

@ -1,8 +1,8 @@
<template>
<div class="content-block__container">
<div class="content-block" :class="specialClass">
<div v-if="canChangeContentBlock" class="content-block__actions">
<a @click="toggleVisibility()" class="content-block__visibility-button">
<div class="content-block__actions">
<a @click="toggleVisibility()" v-if="canManageContent" class="content-block__visibility-button">
<eye-icon class="content-block__action-icon"></eye-icon>
</a>
<visibility-popover
@ -10,12 +10,12 @@
:content-block="contentBlock"
class="content-block__visibility-menu"
></visibility-popover>
<a @click="editContentBlock()">
<a @click="editContentBlock()" v-if="contentBlock.userCreated">
<pen-icon class="content-block__action-icon"></pen-icon>
</a>
</div>
<h4 class="content-block__title">{{contentBlock.title}}</h4>
<h4 class="content-block__title">{{contentBlock.title}} {{contentBlock.userCreated}}</h4>
<component v-for="component in contentBlock.contents"
:key="component.id"
@ -25,7 +25,7 @@
</div>
<add-content-block-button v-if="canChangeContentBlock" :after="contentBlock.id"></add-content-block-button>
<add-content-block-button :after="contentBlock.id"></add-content-block-button>
</div>
@ -70,8 +70,8 @@
specialClass() {
return `content-block--${this.contentBlock.type.toLowerCase()}`
},
canChangeContentBlock() {
return this.me.permissions.includes('user.can_edit_modules');
canManageContent() {
return this.me.permissions.includes('users.can_manage_school_class_content');
}
},

View File

@ -4,6 +4,7 @@ fragment ContentBlockParts on ContentBlockNode {
title
type
contents
userCreated
hiddenFor {
edges {
node {
@ -12,4 +13,12 @@ fragment ContentBlockParts on ContentBlockNode {
}
}
}
visibleFor {
edges {
node {
id
name
}
}
}
}

View File

@ -12,7 +12,7 @@ from filteredbook.schema import BookQuery
from objectives.schema import ObjectivesQuery
from rooms.mutations import RoomMutations
from rooms.schema import RoomsQuery
from user.schema import UsersQuery
from users.schema import UsersQuery
class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, graphene.ObjectType):

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-05 09:24
# Generated by Django 2.0.6 on 2018-10-10 08:25
from django.db import migrations, models
import django.db.models.deletion

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-05 09:24
# Generated by Django 2.0.6 on 2018-10-10 08:25
from django.conf import settings
from django.db import migrations, models

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-05 09:24
# Generated by Django 2.0.6 on 2018-10-10 08:25
from django.db import migrations, models
import django.db.models.deletion
@ -12,8 +12,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('wagtailcore', '0040_page_draft_title'),
('wagtailimages', '0021_image_file_hash'),
('wagtailcore', '0040_page_draft_title'),
]
operations = [
@ -44,6 +44,7 @@ class Migration(migrations.Migration):
name='ContentBlock',
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')),
('user_created', models.BooleanField(default=False)),
('contents', wagtail.core.fields.StreamField([('text_block', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())])), ('basic_knowledge', wagtail.core.blocks.StructBlock([('description', wagtail.core.blocks.RichTextBlock()), ('url', wagtail.core.blocks.URLBlock())])), ('assignment', wagtail.core.blocks.StructBlock([('assignment_id', wagtail.core.blocks.IntegerBlock())])), ('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())])), ('task', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.RichTextBlock())], icon='tick')), ('video_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())])), ('document_block', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True)),
('type', models.CharField(choices=[('plain', 'Normal'), ('yellow', 'Gelb'), ('green', 'Grün'), ('blue', 'Blau')], default='plain', max_length=100)),
],

View File

@ -0,0 +1,26 @@
# Generated by Django 2.0.6 on 2018-10-10 08:25
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('users', '0001_initial'),
('books', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='contentblock',
name='hidden_for',
field=models.ManyToManyField(related_name='hidden_content_blocks', to='users.SchoolClass'),
),
migrations.AddField(
model_name='contentblock',
name='visible_for',
field=models.ManyToManyField(related_name='visible_content_blocks', to='users.SchoolClass'),
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 2.0.6 on 2018-10-05 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('books', '0001_initial'),
('user', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='contentblock',
name='hidden_for',
field=models.ManyToManyField(to='user.SchoolClass'),
),
]

View File

@ -4,11 +4,12 @@ from django.db import models
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList, StreamFieldPanel
from wagtail.core.fields import StreamField
from wagtail.images.blocks import ImageChooserBlock
from wagtail.core import hooks
from books.blocks import TextBlock, BasicKnowledgeBlock, LinkBlock, VideoBlock, DocumentBlock, \
ImageUrlBlock, AssignmentBlock
from core.wagtail_utils import StrictHierarchyPage
from user.models import SchoolClass
from users.models import SchoolClass
logger = logging.getLogger(__name__)
@ -30,7 +31,9 @@ class ContentBlock(StrictHierarchyPage):
(BLUE, 'Blau'),
)
hidden_for = models.ManyToManyField(SchoolClass)
hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_content_blocks')
visible_for = models.ManyToManyField(SchoolClass, related_name='visible_content_blocks')
user_created = models.BooleanField(default=False)
contents = StreamField([
('text_block', TextBlock()),
@ -69,3 +72,10 @@ class ContentBlock(StrictHierarchyPage):
parent_page_types = ['books.Chapter']
subpage_types = []
@hooks.register('construct_explorer_page_queryset')
def remove_user_created_content_blocks_from_menu(parent_page, pages, request):
if parent_page.content_type.model == 'chapter':
return ContentBlock.get_by_parent(parent_page.specific).exclude(user_created=True)
return pages

View File

@ -74,7 +74,7 @@ class AddContentBlock(relay.ClientIDMutation):
title = content_block_data.get('title')
contents = content_block_data.get('contents')
new_content_block = ContentBlock(title=title)
new_content_block = ContentBlock(title=title, user_created=True, owner=context.user)
if parent is not None:
parent_chapter = get_object(Chapter, parent).specific

View File

@ -10,7 +10,7 @@ class ContentBlockNode(DjangoObjectType):
class Meta:
model = ContentBlock
only_fields = [
'slug', 'title', 'type', 'contents', 'hidden_for'
'slug', 'title', 'type', 'contents', 'hidden_for', 'visible_for', 'user_created'
]
filter_fields = [
'slug', 'title',
@ -31,8 +31,29 @@ class ChapterNode(DjangoObjectType):
]
interfaces = (relay.Node,)
def resolve_content_blocks(self, *args, **kwargs):
return ContentBlock.get_by_parent(self)
def resolve_content_blocks(self, info, **kwargs):
user = info.context.user
school_classes = user.school_classes.values_list('pk')
if user.has_perm('users.can_manage_school_class_content'): # teacher
publisher_content_blocks = ContentBlock.get_by_parent(self).filter(user_created=False)
user_created_content_blocks = ContentBlock.get_by_parent(self).filter(user_created=True, owner=user)
else: # student
publisher_content_blocks = ContentBlock.get_by_parent(self).filter(user_created=False).exclude(
hidden_for__in=school_classes)
self_created_content_blocks = ContentBlock.get_by_parent(self).filter(user_created=True, owner=user)
user_created_content_blocks = ContentBlock.get_by_parent(self).filter(user_created=True,
visible_for__in=school_classes).union(
self_created_content_blocks)
return publisher_content_blocks.union(user_created_content_blocks)
# if user.has_perm('users.can_manage_school_class_content'):
# return ContentBlock.get_by_parent(self)
# else:
# return ContentBlock.get_by_parent(self)
class ModuleNode(DjangoObjectType):

View File

@ -12,7 +12,7 @@ from wagtail.core.models import Page
from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory
from core.factories import UserFactory
from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory
from user.services import create_school_with_users
from users.services import create_school_with_users
data = [
{

View File

@ -9,7 +9,7 @@ from wagtail.core.models import Site
from rooms.factories import RoomFactory, RoomEntryFactory
from rooms.models import Room
from user.factories import SchoolClassFactory
from users.factories import SchoolClassFactory
data = [
{

View File

@ -45,7 +45,7 @@ if not DEBUG:
INSTALLED_APPS = [
'core',
'api',
'user',
'users',
'books',
'objectives',
'rooms',
@ -144,7 +144,7 @@ DATABASES = {
}
# Django custom user
AUTH_USER_MODEL = 'user.User'
AUTH_USER_MODEL = 'users.User'
# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-05 09:24
# Generated by Django 2.0.6 on 2018-10-10 08:25
from django.db import migrations, models
import django.db.models.deletion

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-05 09:24
# Generated by Django 2.0.6 on 2018-10-10 08:25
from django.conf import settings
from django.db import migrations, models
@ -10,9 +10,9 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('books', '0002_contentblock_hidden_for'),
('objectives', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('books', '0002_auto_20181010_0825'),
]
operations = [

View File

@ -9,7 +9,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 user.models import SchoolClass
from users.models import SchoolClass
class RoomFactory(factory.django.DjangoModelFactory):

View File

@ -2,7 +2,7 @@ import graphene
from graphene import InputObjectType
from books.schema.inputs import ContentElementInput
from user.inputs import SchoolClassInput
from users.inputs import SchoolClassInput
class RoomInput(InputObjectType):

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-05 09:24
# Generated by Django 2.0.6 on 2018-10-10 08:25
from django.db import migrations, models
import django_extensions.db.fields

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-05 09:24
# Generated by Django 2.0.6 on 2018-10-10 08:25
from django.conf import settings
from django.db import migrations, models
@ -11,8 +11,8 @@ class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('users', '0001_initial'),
('rooms', '0001_initial'),
('user', '0001_initial'),
]
operations = [
@ -29,6 +29,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='room',
name='school_class',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.SchoolClass'),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.SchoolClass'),
),
]

View File

@ -5,7 +5,7 @@ from wagtail.core.fields import StreamField
from books.blocks import ImageUrlBlock, LinkBlock, VideoBlock
from books.models import ContentBlock, TextBlock
from user.models import SchoolClass
from users.models import SchoolClass
class Room(TitleSlugDescriptionModel):

View File

@ -6,7 +6,7 @@ from rooms.inputs import UpdateRoomArgument, AddRoomArgument, AddRoomEntryArgume
from rooms.models import Room
from rooms.schema import RoomNode, RoomEntryNode
from rooms.serializers import RoomSerializer, RoomEntrySerializer
from user.models import SchoolClass
from users.models import SchoolClass
class MutateRoom(relay.ClientIDMutation):

View File

@ -7,7 +7,7 @@ from graphene_django.rest_framework.mutation import SerializerMutation
from api.utils import get_object
from rooms.models import Room, RoomEntry
from rooms.serializers import RoomSerializer
from user.schema import UserNode
from users.schema import UserNode
class RoomEntryNode(DjangoObjectType):

View File

@ -2,4 +2,4 @@ from django.apps import AppConfig
class UserConfig(AppConfig):
name = 'user'
name = 'users'

View File

@ -2,7 +2,7 @@ import random
import factory
from user.models import SchoolClass
from users.models import SchoolClass
class_types = ['DA', 'KV', 'INF', 'EE']
class_suffix = ['A', 'B', 'C', 'D', 'E']

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-05 09:24
# Generated by Django 2.0.6 on 2018-10-10 08:25
from django.conf import settings
import django.contrib.auth.models
@ -7,7 +7,7 @@ import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import user.models
import users.models
class Migration(migrations.Migration):
@ -59,8 +59,8 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=100)),
('year', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2200)])),
('is_deleted', models.BooleanField(default=False)),
('school', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.School')),
('users', models.ManyToManyField(to=settings.AUTH_USER_MODEL)),
('school', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.School')),
('users', models.ManyToManyField(related_name='school_classes', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
@ -70,20 +70,20 @@ class Migration(migrations.Migration):
('key', models.CharField(max_length=100, verbose_name='Key')),
('name', models.CharField(max_length=100, verbose_name='Name')),
('role_permission', models.ManyToManyField(blank=True, related_name='role_set', related_query_name='role', to='auth.Permission', verbose_name='Role permission')),
('school', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.School')),
('school', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.School')),
],
options={
'permissions': (('can_edit_modules', 'Can create new contentblocks'),),
'permissions': (('can_manage_school_class_content', 'Can manage contents for assigned school clases'),),
},
managers=[
('objects', user.models.SchoolRoleManager()),
('objects', users.models.SchoolRoleManager()),
],
),
migrations.CreateModel(
name='UserSchoolRole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('school_role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.SchoolRole')),
('school_role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.SchoolRole')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),

View File

@ -27,6 +27,9 @@ class User(AbstractUser):
django_permissions = super().get_all_permissions(obj)
return django_permissions.union(self.get_school_permissions(School.objects.get(pk=DEFAULT_SCHOOL_ID)))
def has_perm(self, perm, obj=None):
return super(User, self).has_perm(perm, obj) or perm in self.get_all_permissions(obj)
class School(models.Model):
name = models.CharField(_(u'Name'), max_length=100, null=False, blank=False)
@ -87,10 +90,10 @@ class SchoolRoleManager(models.Manager):
role = self.create(name=value, school=school, key=key)
role.save()
can_edit_modules, = self._create_default_permissions()
can_manage_school_class_content, = self._create_default_permissions()
if key == "teacher":
role.role_permission.add(can_edit_modules.id)
role.role_permission.add(can_manage_school_class_content.id)
# elif key == "school_admin":
# role.role_permission.add()
@ -122,9 +125,9 @@ class SchoolRoleManager(models.Manager):
#edit_own_comments = Permission.objects.get(content_type=content_type, codename="can_edit_own_comments")
#delete_comments = Permission.objects.get(content_type=content_type, codename="can_delete_comments")
#admin_school = Permission.objects.get(content_type=content_type, codename="can_admin_school")
can_edit_modules = Permission.objects.get(content_type=content_type, codename='can_edit_modules')
can_manage_school_class_content = Permission.objects.get(content_type=content_type, codename='can_manage_school_class_content')
return can_edit_modules,
return can_manage_school_class_content,
class SchoolRole(models.Model):
@ -159,7 +162,7 @@ class SchoolRole(models.Model):
# ("can_edit_events", "Can edit events"),
# ("can_edit_own_comments", "Can edit own comments"),
# ("can_delete_comments", "Can delete comments"),
('can_edit_modules', 'Can create new contentblocks'),
('can_manage_school_class_content', 'Can manage contents for assigned school clases'),
# ("can_admin_school", "Can admin school"),
)

View File

@ -3,7 +3,7 @@ from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from user.models import SchoolClass, User
from users.models import SchoolClass, User
class UserNode(DjangoObjectType):

View File

@ -1,6 +1,6 @@
from core.factories import UserFactory
from user.factories import SchoolClassFactory
from user.models import School, SchoolRole, UserSchoolRole, DEFAULT_SCHOOL_ID
from users.factories import SchoolClassFactory
from users.models import School, SchoolRole, UserSchoolRole, DEFAULT_SCHOOL_ID
def create_school_with_users(school_name):