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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#import "./userGroupParts.gql" #import "./SchoolClassParts.gql"
fragment RoomParts on RoomNode { fragment RoomParts on RoomNode {
id id
slug slug
@ -6,7 +6,7 @@ fragment RoomParts on RoomNode {
entryCount entryCount
appearance appearance
description description
userGroup { schoolClass {
...UserGroupParts ...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 { fragment UserParts on UserNode {
id id
pk pk
@ -6,4 +7,11 @@ fragment UserParts on UserNode {
firstName firstName
lastName lastName
avatar avatar
schoolclassSet {
edges {
node {
...SchoolClassParts
}
}
}
} }

View File

@ -1,3 +1,4 @@
#import "./fragments/SchoolClassParts.gql"
#import "./fragments/userParts.gql" #import "./fragments/userParts.gql"
query MeQuery { query MeQuery {
me { 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}} {{room.description}}
</p> </p>
<div class="room__meta"> <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> <room-entry-count-widget :entry-count="roomEntryCount"></room-entry-count-widget>
</div> </div>
</div> </div>

View File

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

View File

@ -12,7 +12,7 @@ export default new Vuex.Store({
contentBlockPosition: {}, contentBlockPosition: {},
scrollPosition: 0, scrollPosition: 0,
moduleSlug: 'geld', moduleSlug: 'geld',
filterForGroup: false, filterForSchoolClass: false,
currentContentBlock: '', currentContentBlock: '',
parentRoom: null 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 document.body.classList.add('no-scroll'); // won't get at the body any other way
commit('setModal', payload); commit('setModal', payload);
}, },
setFilterForGroup({commit}, payload) { setfilterForSchoolClass({commit}, payload) {
commit('setFilterForGroup', payload); commit('setfilterForSchoolClass', payload);
} }
}, },
@ -73,8 +73,8 @@ export default new Vuex.Store({
setCurrentContentBlock(state, payload) { setCurrentContentBlock(state, payload) {
state.currentContentBlock = payload; state.currentContentBlock = payload;
}, },
setFilterForGroup(state, payload) { setfilterForSchoolClass(state, payload) {
state.filterForGroup = payload; state.filterForSchoolClass = payload;
}, },
setParentRoom(state, payload) { setParentRoom(state, payload) {
state.parentRoom = 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 from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django_extensions.db.fields import django_extensions.db.fields
@ -11,8 +10,6 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('books', '0002_contentblock_hidden_for'),
] ]
operations = [ operations = [
@ -65,19 +62,4 @@ class Migration(migrations.Migration):
name='assignment', name='assignment',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to='assignments.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 from django.db import migrations, models
import django.db.models.deletion 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 from django.db import migrations, models
@ -8,14 +8,14 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('user', '0001_initial'),
('books', '0001_initial'), ('books', '0001_initial'),
('user', '0001_initial'),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='contentblock', model_name='contentblock',
name='hidden_for', 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, \ from books.blocks import TextBlock, BasicKnowledgeBlock, LinkBlock, VideoBlock, DocumentBlock, \
ImageUrlBlock, AssignmentBlock ImageUrlBlock, AssignmentBlock
from core.wagtail_utils import StrictHierarchyPage from core.wagtail_utils import StrictHierarchyPage
from user.models import UserGroup from user.models import SchoolClass
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -30,7 +30,7 @@ class ContentBlock(StrictHierarchyPage):
(BLUE, 'Blau'), (BLUE, 'Blau'),
) )
hidden_for = models.ManyToManyField(UserGroup) hidden_for = models.ManyToManyField(SchoolClass)
contents = StreamField([ contents = StreamField([
('text_block', TextBlock()), ('text_block', TextBlock()),

View File

@ -30,7 +30,7 @@ class ContentElementInput(InputObjectType):
class UserGroupContentBlockVisibility(InputObjectType): class UserGroupContentBlockVisibility(InputObjectType):
user_group_id = graphene.ID(required=True) school_class_id = graphene.ID(required=True)
hidden = graphene.Boolean(required=True) hidden = graphene.Boolean(required=True)
@ -38,4 +38,4 @@ class ContentBlockInput(InputObjectType):
title = graphene.String() title = graphene.String()
type = graphene.String() type = graphene.String()
contents = graphene.List(ContentElementInput) 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 graphene import relay
from api.utils import get_object, get_errors 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.inputs import ContentBlockInput
from books.schema.queries import ContentBlockNode from books.schema.queries import ContentBlockNode
from .utils import handle_content_block from .utils import handle_content_block
@ -34,11 +33,11 @@ class MutateContentBlock(relay.ClientIDMutation):
if visibility_list is not None: if visibility_list is not None:
for v in visibility_list: 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: if v.hidden:
content_block.hidden_for.add(user_group) content_block.hidden_for.add(school_class)
else: else:
content_block.hidden_for.remove(user_group) content_block.hidden_for.remove(school_class)
if title is not None: if title is not None:
content_block.title = title content_block.title = title

View File

@ -10,7 +10,7 @@ from faker import Faker
from wagtail.documents.models import get_document_model from wagtail.documents.models import get_document_model
from wagtail.images import get_image_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): 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, **self.filter_data(content_block_data, 'contents'))
ContentBlockFactory.create(parent=chapter, module=module, **content_block_data) 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) 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.factories import RoomFactory, RoomEntryFactory
from rooms.models import Room from rooms.models import Room
from user.factories import UserGroupFactory from user.factories import SchoolClassFactory
data = [ 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 import graphene
from django.db.models import Q
from graphene import relay from graphene import relay
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
@ -12,7 +9,7 @@ from books.schema.queries import BookNode, TopicNode, ModuleNode, ContentBlockNo
class FilteredChapterNode(DjangoObjectType): class FilteredChapterNode(DjangoObjectType):
content_blocks = DjangoFilterConnectionField(ContentBlockNode, user_groups=graphene.List(graphene.String)) content_blocks = DjangoFilterConnectionField(ContentBlockNode)
class Meta: class Meta:
model = Chapter model = Chapter
@ -25,26 +22,7 @@ class FilteredChapterNode(DjangoObjectType):
interfaces = (relay.Node,) interfaces = (relay.Node,)
def resolve_content_blocks(self, *args, **kwargs): def resolve_content_blocks(self, *args, **kwargs):
user_groups = kwargs.get('user_groups') return ContentBlock.get_by_parent(self)
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,)
class BookQuery(object): 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 from django.db import migrations, models
import django.db.models.deletion 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.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -10,9 +10,9 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('books', '0002_contentblock_hidden_for'), ('books', '0002_contentblock_hidden_for'),
('objectives', '0001_initial'), ('objectives', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [

View File

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

View File

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

View File

@ -2,13 +2,13 @@ import graphene
from graphene import InputObjectType from graphene import InputObjectType
from books.schema.inputs import ContentElementInput from books.schema.inputs import ContentElementInput
from user.inputs import UserGroupInput from user.inputs import SchoolClassInput
class RoomInput(InputObjectType): class RoomInput(InputObjectType):
title = graphene.String() title = graphene.String()
description = graphene.String() description = graphene.String()
user_group = UserGroupInput() school_class = SchoolClassInput()
appearance = graphene.String() 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 from django.db import migrations, models
import django_extensions.db.fields 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.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -10,9 +10,9 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('rooms', '0001_initial'), ('rooms', '0001_initial'),
('user', '0001_initial'), ('user', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
@ -28,7 +28,7 @@ class Migration(migrations.Migration):
), ),
migrations.AddField( migrations.AddField(
model_name='room', model_name='room',
name='user_group', name='school_class',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.UserGroup'), 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.blocks import ImageUrlBlock, LinkBlock, VideoBlock
from books.models import ContentBlock, TextBlock from books.models import ContentBlock, TextBlock
from user.models import UserGroup from user.models import SchoolClass
class Room(TitleSlugDescriptionModel): class Room(TitleSlugDescriptionModel):
@ -13,11 +13,11 @@ class Room(TitleSlugDescriptionModel):
verbose_name = 'Raum' verbose_name = 'Raum'
verbose_name_plural = 'Räume' 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) appearance = models.CharField(blank=True, null=False, max_length=255)
def __str__(self): 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): class RoomEntry(TitleSlugDescriptionModel):

View File

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

View File

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

View File

@ -1,13 +1,13 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin 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.site.register(User, UserAdmin)
@admin.register(UserGroup) @admin.register(SchoolClass)
class UserGroupAdmin(admin.ModelAdmin): class SchoolClassAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'year') list_display = ('id', 'name', 'year')
list_filter = ('year',) list_filter = ('year',)

View File

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

View File

@ -2,7 +2,7 @@ import graphene
from graphene import InputObjectType from graphene import InputObjectType
class UserGroupInput(InputObjectType): class SchoolClassInput(InputObjectType):
id = graphene.ID() id = graphene.ID()
name = graphene.String() name = graphene.String()
year = graphene.Int() 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 from django.conf import settings
import django.contrib.auth.models import django.contrib.auth.models
import django.contrib.auth.validators import django.contrib.auth.validators
import django.core.validators import django.core.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import user.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -44,13 +46,49 @@ class Migration(migrations.Migration):
], ],
), ),
migrations.CreateModel( 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=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)), ('name', models.CharField(max_length=100)),
('year', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2200)])), ('year', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2200)])),
('is_deleted', models.BooleanField(default=False)), ('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)), ('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 return self.name
class UserGroup(models.Model): class SchoolClass(models.Model):
name = models.CharField(max_length=100, blank=False, null=False) name = models.CharField(max_length=100, blank=False, null=False)
year = models.PositiveIntegerField(blank=False, null=False, validators=[MinValueValidator(1900), MaxValueValidator(2200)]) year = models.PositiveIntegerField(blank=False, null=False, validators=[MinValueValidator(1900), MaxValueValidator(2200)])
is_deleted = models.BooleanField(blank=False, null=False, default=False) 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) school = models.ForeignKey('School', null=False, on_delete=models.CASCADE)
def __str__(self): 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): class SchoolRoleManager(models.Manager):
@ -87,10 +87,10 @@ class SchoolRoleManager(models.Manager):
role = self.create(name=value, school=school, key=key) role = self.create(name=value, school=school, key=key)
role.save() role.save()
can_create_contentblocks, = self._create_default_permissions() can_edit_modules, = self._create_default_permissions()
if key == "teacher": if key == "teacher":
role.role_permission.add(can_create_contentblocks.id) role.role_permission.add(can_edit_modules.id)
# elif key == "school_admin": # elif key == "school_admin":
# role.role_permission.add() # 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") #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") #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") #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): class SchoolRole(models.Model):
@ -159,7 +159,7 @@ class SchoolRole(models.Model):
# ("can_edit_events", "Can edit events"), # ("can_edit_events", "Can edit events"),
# ("can_edit_own_comments", "Can edit own comments"), # ("can_edit_own_comments", "Can edit own comments"),
# ("can_delete_comments", "Can delete 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"), # ("can_admin_school", "Can admin school"),
) )
@ -202,7 +202,7 @@ class UserSchoolRole(models.Model):
@property @property
def groups(self): 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 @property
def group_ids(self): 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 core.factories import UserFactory
from user.factories import UserGroupFactory from user.factories import SchoolClassFactory
from user.models import School, SchoolRole, UserSchoolRole, DEFAULT_SCHOOL_ID 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): for i in range(1, 7):
student = UserFactory(username='student{}'.format(i)) student = UserFactory(username='student{}'.format(i))
UserSchoolRole.objects.create(user=student, school_role=student_role) UserSchoolRole.objects.create(user=student, school_role=student_role)
UserGroupFactory(users=[teacher, student], school=school) SchoolClassFactory(users=[teacher, student], school=school)