Merge branch 'feature/filter-content-per-user'

This commit is contained in:
Daniel Egger 2018-10-06 16:52:02 +02:00
commit a305d35f5f
54 changed files with 252 additions and 370 deletions

View File

@ -6,8 +6,6 @@
{{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>
@ -16,14 +14,12 @@
<script>
import ContentBlock from '@/components/ContentBlock';
import AddContentBlockButton from '@/components/AddContentBlockButton';
export default {
props: ['chapter', 'index'],
components: {
ContentBlock,
AddContentBlockButton
},
computed: {
@ -33,13 +29,13 @@
: [];
},
currentFilter() {
return this.$store.state.filterForGroup;
}
return this.$store.state.filterForSchoolClass;
},
},
methods: {
visibleFor(contentBlock, userGroup) {
return !contentBlock.hiddenFor.map(entry => entry.id).includes(userGroup);
visibleFor(contentBlock, schoolClassId) {
return !contentBlock.hiddenFor.map(entry => entry.id).includes(schoolClassId);
}
}
}

View File

@ -1,7 +1,7 @@
<template>
<div class="content-block__container">
<div class="content-block" :class="specialClass">
<div class="content-block__actions">
<div v-if="canChangeContentBlock" class="content-block__actions">
<a @click="toggleVisibility()" class="content-block__visibility-button">
<eye-icon class="content-block__action-icon"></eye-icon>
</a>
@ -25,7 +25,7 @@
</div>
<add-content-block-button :after="contentBlock.id"></add-content-block-button>
<add-content-block-button v-if="canChangeContentBlock" :after="contentBlock.id"></add-content-block-button>
</div>
@ -45,6 +45,7 @@
import VisibilityPopover from '@/components/VisibilityPopover';
import EyeIcon from '@/components/icons/EyeIcon';
import PenIcon from '@/components/icons/PenIcon';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
export default {
props: ['contentBlock'],
@ -68,6 +69,9 @@
computed: {
specialClass() {
return `content-block--${this.contentBlock.type.toLowerCase()}`
},
canChangeContentBlock() {
return this.me.permissions.includes('user.can_edit_modules');
}
},
@ -77,12 +81,21 @@
},
editContentBlock() {
this.$store.dispatch('editContentBlock', this.contentBlock.id);
}
},
},
apollo: {
me: {
query: ME_QUERY,
},
},
data() {
return {
showVisibility: false
showVisibility: false,
me: {
permissions: []
}
}
}
}

View File

@ -1,47 +1,63 @@
<template>
<div class="filter-bar">
<radiobutton label="Alles" :checked="!currentFilter" v-on:input="updateFilter(false)"></radiobutton>
<radiobutton v-if="showEverythingRadioButton" label="Alles" :checked="!currentFilter" v-on:input="updateFilter(false)"></radiobutton>
<radiobutton
v-for="group in userGroups"
:key="group.id"
:label="group.name"
:item="group"
:checked="group.id === currentFilter"
v-on:input="updateFilter(group.id)"
v-for="schoolClass in schoolClasses"
:key="schoolClass.id"
:label="schoolClass.name"
:item="schoolClass"
:checked="schoolClass.id === currentFilter"
v-on:input="updateFilter(schoolClass.id)"
></radiobutton>
</div>
</template>
<script>
import {userGroupsQuery} from '@/helpers/user-groups'
import {mapActions} from 'vuex';
import Radiobutton from '@/components/Radiobutton';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
export default {
components: {
Radiobutton
},
beforeUpdate() {
if (!this.showEverythingRadioButton && !this.currentFilter) {
this.updateFilter(this.schoolClasses && this.schoolClasses.length > 0 ? this.schoolClasses[0].id : null);
}
},
apollo: {
userGroupsQuery: userGroupsQuery
me: {
query: ME_QUERY
}
},
data() {
return {
userGroups: []
me: {
permissions: []
}
}
},
computed: {
currentFilter() {
return this.$store.state.filterForGroup;
}
return this.$store.state.filterForSchoolClass;
},
schoolClasses() {
return this.$getRidOfEdges(this.me.schoolclassSet);
},
showEverythingRadioButton() {
return this.me.permissions.includes('user.can_edit_modules');
},
},
methods: {
...mapActions({
updateFilter: 'setFilterForGroup'
updateFilter: 'setfilterForSchoolClass'
})
}
}

View File

@ -1,10 +1,10 @@
<template>
<div class="visibility-menu" v-if="show">
<h3 class="visibility-menu__title">Sichtbarkeit</h3>
<div v-for="group in userGroupsWithVisibilityInfo" :key="group.id" class="visibility-menu__item">
<checkbox :checked="!group.hidden"
:item="group"
:label="group.name"
<div v-for="schoolClass in schoolClassVisibility" :key="schoolClass.id" class="visibility-menu__item">
<checkbox :checked="!schoolClass.hidden"
:item="schoolClass"
:label="schoolClass.name"
v-on:input="updateVisibility"
></checkbox>
</div>
@ -13,10 +13,9 @@
<script>
import CHANGE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/mutateContentBlock.gql';
// import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
import Checkbox from '@/components/Checkbox';
import {userGroupsQuery} from '@/helpers/user-groups'
import ME_QUERY from '@/graphql/gql/meQuery.gql'
// import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
// import store from '@/store/index';
@ -28,12 +27,14 @@
},
apollo: {
userGroupsQuery: userGroupsQuery
me: {
query: ME_QUERY,
},
},
data() {
return {
userGroups: []
me: {}
}
},
@ -47,34 +48,29 @@
input: {
id: this.contentBlock.id,
contentBlock: {
visibility: this.userGroupsWithVisibilityInfo.map(g => {
visibility: this.schoolClassVisibility.map(g => {
return {
userGroupId: g.id,
schoolClassId: g.id,
hidden: g.hidden || false
}
})
}
}
}
// refetchQueries: [{
// query: MODULE_DETAILS_QUERY,
// variables: {
// slug: store.state.moduleSlug
// }
// }]
// update: (store, {data: {mutateContentBlock: {contentBlock}}}) => {
// this.$store.dispatch('updateContentBlocks');
// }
});
}
},
computed: {
userGroupsWithVisibilityInfo() {
return this.userGroups.map(userGroup => {
schoolClasses() {
return this.$getRidOfEdges(this.me.schoolclassSet);
},
schoolClassVisibility() {
return this.schoolClasses.map(schoolClass => {
return {
...userGroup,
hidden: !!this.contentBlock.hiddenFor.find(el => el.id === userGroup.id)
...schoolClass,
hidden: !!this.contentBlock.hiddenFor.find(el => el.id === schoolClass.id)
}
});
}

View File

@ -28,7 +28,7 @@
title: room.title,
appearance: room.appearance,
description: room.description,
userGroup: room.userGroup
schoolClass: room.schoolClass,
}
}
}

View File

@ -24,7 +24,7 @@
appearance: defaultColor,
title: '',
description: '',
userGroup: {}
schoolClass: {}
}
}
},

View File

@ -12,14 +12,14 @@
<select
class="skillbox-input room-form__input"
id="room-class"
v-model="localRoom.userGroup"
v-model="localRoom.schoolClass"
>
<option disabled value="">-</option>
<option
v-for="userGroup in userGroups"
:key="userGroup.id"
v-bind:value="userGroup"
>{{userGroup.name}}
v-for="schoolClass in schoolClasses"
:key="schoolClass.id"
v-bind:value="schoolClass"
>{{schoolClass.name}}
</option>
</select>
<h2 class="room-form__property-heading">Farbe</h2>
@ -42,7 +42,7 @@
<script>
import RoomColors from '@/components/rooms/RoomColors';
import {userGroupsQuery} from '@/helpers/user-groups'
import ME_QUERY from '@/graphql/gql/meQuery.gql';
export default {
props: ['room'],
@ -54,7 +54,13 @@
data() {
return {
localRoom: Object.assign({}, this.room),
userGroups: []
me: {}
}
},
computed: {
schoolClasses() {
return this.$getRidOfEdges(this.me.schoolclassSet);
}
},
@ -66,7 +72,9 @@
},
apollo: {
userGroupsQuery: userGroupsQuery
me: {
query: ME_QUERY,
}
},
created() {

View File

@ -2,7 +2,7 @@
<div class="room-widget" :class="roomClass">
<router-link :to="{name: 'room', params: {slug: slug}}" tag="div" class="room-widget__content">
<h2 class="room-widget__title">{{title}}</h2>
<room-group-widget v-bind="userGroup"></room-group-widget>
<room-group-widget v-bind="schoolClass"></room-group-widget>
<room-entry-count-widget :entryCount="entryCount"></room-entry-count-widget>
</router-link>
<div class="room-widget__footer">
@ -26,7 +26,7 @@
import RoomPopover from '@/components/rooms/RoomPopover';
export default {
props: ['slug', 'title', 'entryCount', 'appearance', 'userGroup', 'id'],
props: ['slug', 'title', 'entryCount', 'appearance', 'schoolClass', 'id'],
components: {
RoomEntryCountWidget,

View File

@ -1,4 +1,4 @@
#import "./userGroupParts.gql"
#import "./SchoolClassParts.gql"
fragment RoomParts on RoomNode {
id
slug
@ -6,7 +6,7 @@ fragment RoomParts on RoomNode {
entryCount
appearance
description
userGroup {
...UserGroupParts
schoolClass {
...SchoolClassParts
}
}

View File

@ -0,0 +1,5 @@
fragment SchoolClassParts on SchoolClassNode {
id
name
year
}

View File

@ -1,5 +0,0 @@
fragment UserGroupParts on UserGroupNode {
id
name
year
}

View File

@ -1,3 +1,4 @@
#import "./SchoolClassParts.gql"
fragment UserParts on UserNode {
id
pk
@ -6,4 +7,11 @@ fragment UserParts on UserNode {
firstName
lastName
avatar
schoolclassSet {
edges {
node {
...SchoolClassParts
}
}
}
}

View File

@ -1,3 +1,4 @@
#import "./fragments/SchoolClassParts.gql"
#import "./fragments/userParts.gql"
query MeQuery {
me {

View File

@ -1,10 +0,0 @@
#import "./fragments/userGroupParts.gql"
query UserGroupsQuery {
userGroups {
edges {
node {
...UserGroupParts
}
}
}
}

View File

@ -1,12 +0,0 @@
import USER_GROUPS_QUERY from '@/graphql/gql/userGroupsQuery.gql';
export const userGroupsQuery = {
query: USER_GROUPS_QUERY,
manual: true,
result({data, loading, networkStatus}) {
if (!loading) {
const cleanedData = this.$getRidOfEdges(data)
this.userGroups = cleanedData.userGroups || {};
}
}
};

View File

@ -6,7 +6,7 @@
{{room.description}}
</p>
<div class="room__meta">
<room-group-widget v-bind="room.userGroup"></room-group-widget>
<room-group-widget v-bind="room.schoolClass"></room-group-widget>
<room-entry-count-widget :entry-count="roomEntryCount"></room-entry-count-widget>
</div>
</div>

View File

@ -22,13 +22,13 @@
return this.rooms.filter(room => this.visibleFor(room, this.currentFilter));
},
currentFilter() {
return this.$store.state.filterForGroup;
return this.$store.state.filterForSchoolClass;
}
},
methods: {
visibleFor(room, userGroup) {
return !userGroup || room.userGroup.id === userGroup;
visibleFor(room, schoolClass) {
return !schoolClass || room.schoolClass.id === schoolClass;
}
},

View File

@ -12,7 +12,7 @@ export default new Vuex.Store({
contentBlockPosition: {},
scrollPosition: 0,
moduleSlug: 'geld',
filterForGroup: false,
filterForSchoolClass: false,
currentContentBlock: '',
parentRoom: null
},
@ -49,8 +49,8 @@ export default new Vuex.Store({
document.body.classList.add('no-scroll'); // won't get at the body any other way
commit('setModal', payload);
},
setFilterForGroup({commit}, payload) {
commit('setFilterForGroup', payload);
setfilterForSchoolClass({commit}, payload) {
commit('setfilterForSchoolClass', payload);
}
},
@ -73,8 +73,8 @@ export default new Vuex.Store({
setCurrentContentBlock(state, payload) {
state.currentContentBlock = payload;
},
setFilterForGroup(state, payload) {
state.filterForGroup = payload;
setfilterForSchoolClass(state, payload) {
state.filterForSchoolClass = payload;
},
setParentRoom(state, payload) {
state.parentRoom = payload;

View File

@ -1,6 +1,5 @@
# Generated by Django 2.0.6 on 2018-10-04 12:28
# Generated by Django 2.0.6 on 2018-10-05 09:24
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django_extensions.db.fields
@ -11,8 +10,6 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('books', '0002_contentblock_hidden_for'),
]
operations = [
@ -65,19 +62,4 @@ class Migration(migrations.Migration):
name='assignment',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to='assignments.Assignment'),
),
migrations.AddField(
model_name='studentsubmission',
name='student',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='assignment',
name='module',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='assignments', to='books.Module'),
),
migrations.AddField(
model_name='assignment',
name='owner',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -0,0 +1,34 @@
# Generated by Django 2.0.6 on 2018-10-05 09:24
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('books', '0001_initial'),
('assignments', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='studentsubmission',
name='student',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='assignment',
name='module',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='assignments', to='books.Module'),
),
migrations.AddField(
model_name='assignment',
name='owner',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-04 07:39
# Generated by Django 2.0.6 on 2018-10-05 09:24
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-04 07:39
# Generated by Django 2.0.6 on 2018-10-05 09:24
from django.db import migrations, models
@ -8,14 +8,14 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('user', '0001_initial'),
('books', '0001_initial'),
('user', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='contentblock',
name='hidden_for',
field=models.ManyToManyField(to='user.UserGroup'),
field=models.ManyToManyField(to='user.SchoolClass'),
),
]

View File

@ -8,7 +8,7 @@ from wagtail.images.blocks import ImageChooserBlock
from books.blocks import TextBlock, BasicKnowledgeBlock, LinkBlock, VideoBlock, DocumentBlock, \
ImageUrlBlock, AssignmentBlock
from core.wagtail_utils import StrictHierarchyPage
from user.models import UserGroup
from user.models import SchoolClass
logger = logging.getLogger(__name__)
@ -30,7 +30,7 @@ class ContentBlock(StrictHierarchyPage):
(BLUE, 'Blau'),
)
hidden_for = models.ManyToManyField(UserGroup)
hidden_for = models.ManyToManyField(SchoolClass)
contents = StreamField([
('text_block', TextBlock()),

View File

@ -30,7 +30,7 @@ class ContentElementInput(InputObjectType):
class UserGroupContentBlockVisibility(InputObjectType):
user_group_id = graphene.ID(required=True)
school_class_id = graphene.ID(required=True)
hidden = graphene.Boolean(required=True)
@ -38,4 +38,4 @@ class ContentBlockInput(InputObjectType):
title = graphene.String()
type = graphene.String()
contents = graphene.List(ContentElementInput)
visibility = graphene.List(UserGroupContentBlockVisibility)
visibility = graphene.List(UserGroupContentBlockVisibility)

View File

@ -5,10 +5,9 @@ from django.core.exceptions import ValidationError
from graphene import relay
from api.utils import get_object, get_errors
from books.models import ContentBlock, Chapter, UserGroup
from books.models import ContentBlock, Chapter, SchoolClass
from books.schema.inputs import ContentBlockInput
from books.schema.queries import ContentBlockNode
from .utils import handle_content_block
@ -34,11 +33,11 @@ class MutateContentBlock(relay.ClientIDMutation):
if visibility_list is not None:
for v in visibility_list:
user_group = get_object(UserGroup, v.user_group_id)
school_class = get_object(SchoolClass, v.school_class_id)
if v.hidden:
content_block.hidden_for.add(user_group)
content_block.hidden_for.add(school_class)
else:
content_block.hidden_for.remove(user_group)
content_block.hidden_for.remove(school_class)
if title is not None:
content_block.title = title

View File

@ -10,7 +10,7 @@ from faker import Faker
from wagtail.documents.models import get_document_model
from wagtail.images import get_image_model
fake = Faker('de_DE')
fake = Faker('de_CH')
def fake_title(x=None, min_words=2, max_words=4):

View File

@ -743,5 +743,5 @@ class Command(BaseCommand):
# ContentBlockFactory.create(parent=chapter, **self.filter_data(content_block_data, 'contents'))
ContentBlockFactory.create(parent=chapter, module=module, **content_block_data)
# now create all usergroups and rooms
# now create all and rooms
management.call_command('dummy_rooms', verbosity=0)

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 UserGroupFactory
from user.factories import SchoolClassFactory
data = [
{

View File

@ -1,9 +0,0 @@
from django.contrib import admin
from filteredbook.models import Visibility
@admin.register(Visibility)
class VisibilityAdmin(admin.ModelAdmin):
list_display = ('user_group', 'content_block')
list_filter = ('user_group', 'content_block')

View File

@ -1,27 +0,0 @@
# Generated by Django 2.0.6 on 2018-10-04 07:39
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('books', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Visibility',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content_block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='visible_to', to='books.ContentBlock')),
],
options={
'verbose_name': 'Visibility',
'verbose_name_plural': 'Visibilities',
},
),
]

View File

@ -1,22 +0,0 @@
# Generated by Django 2.0.6 on 2018-10-04 07:39
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('user', '0001_initial'),
('filteredbook', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='visibility',
name='user_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.UserGroup'),
),
]

View File

@ -1,16 +1,2 @@
from django.db import models
from books.models import ContentBlock
from user.models import UserGroup
class Visibility(models.Model):
class Meta:
verbose_name = 'Visibility'
verbose_name_plural = 'Visibilities'
user_group = models.ForeignKey(UserGroup, blank=False, null=False, on_delete=models.CASCADE)
content_block = models.ForeignKey(ContentBlock, blank=False, null=False, on_delete=models.CASCADE, related_name='visible_to')
def __str__(self):
return 'Visibility {}-{}'.format(self.user_group, self.content_block)

View File

@ -1,7 +1,4 @@
from itertools import chain
import graphene
from django.db.models import Q
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
@ -12,7 +9,7 @@ from books.schema.queries import BookNode, TopicNode, ModuleNode, ContentBlockNo
class FilteredChapterNode(DjangoObjectType):
content_blocks = DjangoFilterConnectionField(ContentBlockNode, user_groups=graphene.List(graphene.String))
content_blocks = DjangoFilterConnectionField(ContentBlockNode)
class Meta:
model = Chapter
@ -25,26 +22,7 @@ class FilteredChapterNode(DjangoObjectType):
interfaces = (relay.Node,)
def resolve_content_blocks(self, *args, **kwargs):
user_groups = kwargs.get('user_groups')
if user_groups:
reduced = []
for user_group in user_groups:
content_blocks = ContentBlock.get_by_parent(self).exclude(
Q(visible_to__user_group__name=user_group)
)
reduced = chain(reduced, content_blocks)
filtered = list(set(reduced))
return filtered
else:
return ContentBlock.get_by_parent(self)
class VisibilityNode(DjangoObjectType):
class Meta:
model = Chapter
filter_fields = ['user_group', 'content_block']
interfaces = (relay.Node,)
return ContentBlock.get_by_parent(self)
class BookQuery(object):

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-04 07:39
# Generated by Django 2.0.6 on 2018-10-05 09:24
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-04 07:39
# Generated by Django 2.0.6 on 2018-10-05 09:24
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),
]
operations = [

View File

@ -5,8 +5,8 @@ from rooms.models import Room, RoomEntry
@admin.register(Room)
class RoomAdmin(admin.ModelAdmin):
list_display = ('id', 'slug', 'title', 'user_group', 'appearance')
list_filter = ('user_group', 'appearance')
list_display = ('id', 'slug', 'title', 'school_class', 'appearance')
list_filter = ('school_class', 'appearance')
@admin.register(RoomEntry)

View File

@ -6,11 +6,10 @@ from django.contrib.auth import get_user_model
from factory import CREATE_STRATEGY
from wagtail.core.rich_text import RichText
from books.blocks import ImageUrlBlock
from books.factories import TextBlockFactory, BasicKnowledgeBlockFactory, ImageUrlBlockFactory, LinkBlockFactory
from books.factories import TextBlockFactory, ImageUrlBlockFactory, LinkBlockFactory
from core.factories import fake, fake_paragraph
from rooms.models import Room, RoomEntry
from user.models import UserGroup
from user.models import SchoolClass
class RoomFactory(factory.django.DjangoModelFactory):
@ -18,7 +17,7 @@ class RoomFactory(factory.django.DjangoModelFactory):
model = Room
title = factory.LazyAttribute(lambda x: fake.sentence(nb_words=random.randint(4, 8)))
user_group = factory.Iterator(UserGroup.objects.all())
school_class = factory.Iterator(SchoolClass.objects.all())
appearance = factory.LazyAttribute(lambda x: random.choice(['red', 'green', 'brown']))

View File

@ -2,13 +2,13 @@ import graphene
from graphene import InputObjectType
from books.schema.inputs import ContentElementInput
from user.inputs import UserGroupInput
from user.inputs import SchoolClassInput
class RoomInput(InputObjectType):
title = graphene.String()
description = graphene.String()
user_group = UserGroupInput()
school_class = SchoolClassInput()
appearance = graphene.String()

View File

@ -1,4 +1,4 @@
# Generated by Django 2.0.6 on 2018-10-04 07:39
# Generated by Django 2.0.6 on 2018-10-05 09:24
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-04 07:39
# Generated by Django 2.0.6 on 2018-10-05 09:24
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),
('rooms', '0001_initial'),
('user', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@ -28,7 +28,7 @@ class Migration(migrations.Migration):
),
migrations.AddField(
model_name='room',
name='user_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.UserGroup'),
name='school_class',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.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 UserGroup
from user.models import SchoolClass
class Room(TitleSlugDescriptionModel):
@ -13,11 +13,11 @@ class Room(TitleSlugDescriptionModel):
verbose_name = 'Raum'
verbose_name_plural = 'Räume'
user_group = models.ForeignKey(UserGroup, blank=False, null=False, on_delete=models.CASCADE)
school_class = models.ForeignKey(SchoolClass, blank=False, null=False, on_delete=models.CASCADE)
appearance = models.CharField(blank=True, null=False, max_length=255)
def __str__(self):
return 'Room {}-{}-{}'.format(self.id, self.title, self.user_group)
return 'Room {}-{}-{}'.format(self.id, self.title, self.school_class)
class RoomEntry(TitleSlugDescriptionModel):

View File

@ -3,10 +3,10 @@ from graphene import relay
from api.utils import get_object
from rooms.inputs import UpdateRoomArgument, AddRoomArgument, AddRoomEntryArgument
from rooms.models import Room, RoomEntry
from rooms.models import Room
from rooms.schema import RoomNode, RoomEntryNode
from rooms.serializers import RoomSerializer, RoomEntrySerializer
from user.models import UserGroup
from user.models import SchoolClass
class MutateRoom(relay.ClientIDMutation):
@ -16,8 +16,8 @@ class MutateRoom(relay.ClientIDMutation):
@classmethod
def mutate_and_get_payload(cls, *args, **kwargs):
room_data = kwargs.get('room')
user_group = get_object(UserGroup, room_data.get('user_group').get('id'))
room_data['user_group'] = user_group.id
school_class = get_object(SchoolClass, room_data.get('school_class').get('id'))
room_data['school_class'] = school_class.id
if room_data.get('id') is not None:
room = get_object(Room, room_data['id'])
serializer = RoomSerializer(room, data=room_data)

View File

@ -14,7 +14,7 @@ class ContentsSerializer(serializers.Field):
class RoomSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = ('id', 'title', 'description', 'slug', 'user_group', 'appearance',)
fields = ('id', 'title', 'description', 'slug', 'school_class', 'appearance',)
read_only_fields = ('id', 'slug',)

View File

@ -1,13 +1,13 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User, UserGroup, School, SchoolRole, UserSchoolRole
from .models import User, SchoolClass, School, SchoolRole, UserSchoolRole
admin.site.register(User, UserAdmin)
@admin.register(UserGroup)
class UserGroupAdmin(admin.ModelAdmin):
@admin.register(SchoolClass)
class SchoolClassAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'year')
list_filter = ('year',)

View File

@ -2,7 +2,7 @@ import random
import factory
from user.models import UserGroup
from user.models import SchoolClass
class_types = ['DA', 'KV', 'INF', 'EE']
class_suffix = ['A', 'B', 'C', 'D', 'E']
@ -12,9 +12,9 @@ class_suffix = ['A', 'B', 'C', 'D', 'E']
# TODO: refactor to have non-overlapping user groups?
class UserGroupFactory(factory.django.DjangoModelFactory):
class SchoolClassFactory(factory.django.DjangoModelFactory):
class Meta:
model = UserGroup
model = SchoolClass
name = factory.Sequence(lambda n: '{}{}{}'.format(random.choice(class_types), '18', class_suffix[n % len(class_suffix)]))
year = factory.LazyAttribute(lambda x: random.choice([2017, 2018, 2019]))

View File

@ -2,7 +2,7 @@ import graphene
from graphene import InputObjectType
class UserGroupInput(InputObjectType):
class SchoolClassInput(InputObjectType):
id = graphene.ID()
name = graphene.String()
year = graphene.Int()

View File

@ -1,11 +1,13 @@
# Generated by Django 2.0.6 on 2018-10-04 07:39
# Generated by Django 2.0.6 on 2018-10-05 09:24
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import user.models
class Migration(migrations.Migration):
@ -44,13 +46,49 @@ class Migration(migrations.Migration):
],
),
migrations.CreateModel(
name='UserGroup',
name='School',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='Name')),
],
),
migrations.CreateModel(
name='SchoolClass',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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)),
],
),
migrations.CreateModel(
name='SchoolRole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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')),
],
options={
'permissions': (('can_edit_modules', 'Can create new contentblocks'),),
},
managers=[
('objects', user.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')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AlterUniqueTogether(
name='schoolrole',
unique_together={('key', 'school')},
),
]

View File

@ -1,53 +0,0 @@
# Generated by Django 2.0.6 on 2018-10-04 12:46
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import user.models
class Migration(migrations.Migration):
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
('user', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='School',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='Name')),
('old_id', models.IntegerField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='SchoolRole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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')),
],
options={
'permissions': (('can_create_contentblocks', 'Can create new contentblocks'),),
},
managers=[
('objects', user.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')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AlterUniqueTogether(
name='schoolrole',
unique_together={('key', 'school')},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 2.0.6 on 2018-10-04 13:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('user', '0002_auto_20181004_1246'),
]
operations = [
migrations.RemoveField(
model_name='school',
name='old_id',
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 2.0.6 on 2018-10-04 13:47
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0003_remove_school_old_id'),
]
operations = [
migrations.AddField(
model_name='usergroup',
name='school',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='user.School'),
preserve_default=False,
),
]

View File

@ -43,7 +43,7 @@ class School(models.Model):
return self.name
class UserGroup(models.Model):
class SchoolClass(models.Model):
name = models.CharField(max_length=100, blank=False, null=False)
year = models.PositiveIntegerField(blank=False, null=False, validators=[MinValueValidator(1900), MaxValueValidator(2200)])
is_deleted = models.BooleanField(blank=False, null=False, default=False)
@ -51,7 +51,7 @@ class UserGroup(models.Model):
school = models.ForeignKey('School', null=False, on_delete=models.CASCADE)
def __str__(self):
return 'UserGroup {}-{}-{}'.format(self.id, self.name, self.year)
return 'SchoolClass {}-{}-{}'.format(self.id, self.name, self.year)
class SchoolRoleManager(models.Manager):
@ -87,10 +87,10 @@ class SchoolRoleManager(models.Manager):
role = self.create(name=value, school=school, key=key)
role.save()
can_create_contentblocks, = self._create_default_permissions()
can_edit_modules, = self._create_default_permissions()
if key == "teacher":
role.role_permission.add(can_create_contentblocks.id)
role.role_permission.add(can_edit_modules.id)
# elif key == "school_admin":
# role.role_permission.add()
@ -122,9 +122,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_create_contentblocks = Permission.objects.get(content_type=content_type, codename='can_create_contentblocks')
can_edit_modules = Permission.objects.get(content_type=content_type, codename='can_edit_modules')
return can_create_contentblocks,
return can_edit_modules,
class SchoolRole(models.Model):
@ -159,7 +159,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_create_contentblocks', 'Can create new contentblocks'),
('can_edit_modules', 'Can create new contentblocks'),
# ("can_admin_school", "Can admin school"),
)
@ -202,7 +202,7 @@ class UserSchoolRole(models.Model):
@property
def groups(self):
return UserGroup.objects.filter(Q(school_id=self.school_role.school.id) & Q(users=self.user.id))
return SchoolClass.objects.filter(Q(school_id=self.school_role.school.id) & Q(users=self.user.id))
@property
def group_ids(self):

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
from core.factories import UserFactory
from user.factories import UserGroupFactory
from user.factories import SchoolClassFactory
from user.models import School, SchoolRole, UserSchoolRole, DEFAULT_SCHOOL_ID
@ -14,4 +14,4 @@ def create_school_with_users(school_name):
for i in range(1, 7):
student = UserFactory(username='student{}'.format(i))
UserSchoolRole.objects.create(user=student, school_role=student_role)
UserGroupFactory(users=[teacher, student], school=school)
SchoolClassFactory(users=[teacher, student], school=school)