Add visibility actions to objective group

This commit is contained in:
Ramon Wenger 2021-02-22 17:04:45 +01:00
parent 6bab4320ec
commit e10481ce49
16 changed files with 134 additions and 94 deletions

View File

@ -47,14 +47,11 @@
import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue';
import Chapter from '@/components/Chapter.vue';
import UPDATE_OBJECTIVE_PROGRESS_MUTATION from '@/graphql/gql/mutations/updateObjectiveProgress.gql';
import UPDATE_LAST_MODULE_MUTATION from '@/graphql/gql/mutations/updateLastModule.gql';
import UPDATE_MODULE_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateModuleBookmark.gql';
import OBJECTIVE_QUERY from '@/graphql/gql/objectiveQuery.gql';
import ME_QUERY from '@/graphql/gql/meQuery.gql';
import MODULE_FRAGMENT from '@/graphql/gql/fragments/moduleParts.gql';
import {withoutOwnerFirst} from '@/helpers/sorting';
import BookmarkActions from '@/components/notes/BookmarkActions';
import meMixin from '@/mixins/me';
@ -82,18 +79,15 @@
computed: {
languageCommunicationObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups
.filter(group => group.title === 'LANGUAGE_COMMUNICATION')
.sort(withoutOwnerFirst) : [];
.filter(group => group.title === 'LANGUAGE_COMMUNICATION') : [];
},
societyObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups
.filter(group => group.title === 'SOCIETY')
.sort(withoutOwnerFirst) : [];
.filter(group => group.title === 'SOCIETY') : [];
},
interdisciplinaryObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups
.filter(group => group.title === 'INTERDISCIPLINARY')
.sort(withoutOwnerFirst) : [];
.filter(group => group.title === 'INTERDISCIPLINARY') : [];
},
isStudent() {
return !this.me.permissions.includes('users.can_manage_school_class_content');
@ -145,28 +139,6 @@
}
});
},
updateObjectiveProgress(done, objectiveId) {
this.$apollo.mutate({
mutation: UPDATE_OBJECTIVE_PROGRESS_MUTATION,
variables: {
input: {
id: objectiveId,
done: done
}
},
update(store, {data: {updateObjectiveProgress: {objective}}}) {
if (objective) {
const variables = {id: objectiveId};
const query = OBJECTIVE_QUERY;
const data = store.readQuery({query, variables});
if (data && data.objective.objectiveProgress.edges.length > 0) {
data.objective.objectiveProgress.edges[0].node.done = done;
}
store.writeQuery({query: OBJECTIVE_QUERY, data, variables});
}
}
});
},
bookmark(bookmarked) {
const slug = this.module.slug;
this.$apollo.mutate({

View File

@ -5,6 +5,7 @@
v-if="editModule || !hidden">
<visibility-action
:block="objective"
:type="type"
v-if="editModule"/>
<div
class="block-actions"
@ -28,16 +29,19 @@
import UserWidget from '@/components/UserWidget';
import MoreOptionsWidget from '@/components/MoreOptionsWidget';
import {mapState} from 'vuex';
import {meQuery} from '@/graphql/queries';
import DELETE_OBJECTIVE_MUTATION from '@/graphql/gql/mutations/deleteObjective.gql';
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
import {hidden} from '@/helpers/visibility';
import {OBJECTIVE_TYPE} from '@/consts/types';
import editModule from '@/mixins/edit-module';
import me from '@/mixins/me';
export default {
props: ['objective', 'schoolClass'],
mixins: [me, editModule],
components: {
MoreOptionsWidget,
VisibilityAction,
@ -46,17 +50,16 @@
data() {
return {
me: {}
type: OBJECTIVE_TYPE
};
},
computed: {
...mapState(['editModule']),
hidden() {
return hidden({
block: this.objective,
schoolClass: this.schoolClass,
type: OBJECTIVE_TYPE
type: this.type
});
},
canEdit() {
@ -94,17 +97,14 @@
// }
});
}
},
apollo: {
me: meQuery
},
}
};
</script>
<style scoped lang="scss">
.objective {
min-height: 50px;
&__user-widget {
margin-right: 0;
}

View File

@ -1,5 +1,12 @@
<template>
<div class="objective-group">
<div
:class="{'hideable-element--greyed-out': hidden}"
class="objective-group hideable-element"
v-if="editModule || !hidden">
<visibility-action
:block="group"
:type="type"
v-if="editModule"/>
<h4>{{ group.displayTitle }}</h4>
@ -7,7 +14,7 @@
<objective
:key="objective.id"
:objective="objective"
:school-class="currentFilter"
:school-class="schoolClass"
class="objective-group__objective"
v-for="objective in group.objectives"/>
</ul>
@ -20,12 +27,14 @@
<script>
import Objective from '@/components/objective-groups/Objective';
import VisibilityAction from '@/components/visibility/VisibilityAction';
import AddContentButton from '@/components/AddContentButton';
import {mapState} from 'vuex';
import me from '@/mixins/me';
import editModule from '@/mixins/edit-module';
import {OBJECTIVE_GROUP_TYPE} from '@/consts/types';
import {hidden} from '@/helpers/visibility';
export default {
props: {
@ -35,17 +44,27 @@
}
},
mixins: [me],
mixins: [me, editModule],
components: {
AddContentButton,
Objective
Objective,
VisibilityAction
},
data() {
return {
type: OBJECTIVE_GROUP_TYPE
};
},
computed: {
...mapState(['editModule']),
currentFilter() {
return this.me.selectedClass;
hidden() {
return hidden({
block: this.group,
schoolClass: this.schoolClass,
type: this.type
});
},
},
};

View File

@ -10,7 +10,6 @@
<script>
import ObjectiveGroup from '@/components/objective-groups/ObjectiveGroup';
import {meQuery} from '@/graphql/queries';
import {withoutOwnerFirst} from '@/helpers/sorting';
export default {
props: {
@ -47,19 +46,13 @@
return this.groups;
} else {
// todo: maybe this can be done a bit more elegantly
const groups = [...this.groups].sort(withoutOwnerFirst);
let groups = this.groups;
const objectives = groups.map(g => g.objectives).flat(); // get all objectives in one array
const firstGroup = Object.assign({}, groups.shift(), {objectives});
return [firstGroup];
}
}
},
methods: {
updateObjectiveProgress(checked, id) {
this.$emit('updateObjectiveProgress', checked, id);
}
}
};
</script>

View File

@ -1,5 +1,12 @@
export const CONTENT_TYPE = 'content';
export const OBJECTIVE_TYPE = 'objective';
export const OBJECTIVE_GROUP_TYPE = 'objective-group';
export const CHAPTER_TITLE_TYPE = 'chapter-title';
export const CHAPTER_DESCRIPTION_TYPE = 'chapter-description';
export const TYPES = [CONTENT_TYPE, OBJECTIVE_TYPE, CHAPTER_TITLE_TYPE, CHAPTER_DESCRIPTION_TYPE];
export const TYPES = [
CONTENT_TYPE,
OBJECTIVE_TYPE,
OBJECTIVE_GROUP_TYPE,
CHAPTER_TITLE_TYPE,
CHAPTER_DESCRIPTION_TYPE
];

View File

@ -2,10 +2,6 @@ fragment ObjectiveGroupParts on ObjectiveGroupNode {
id
title
displayTitle
mine
owner {
id
}
hiddenFor {
edges {
node {
@ -14,12 +10,4 @@ fragment ObjectiveGroupParts on ObjectiveGroupNode {
}
}
}
visibleFor {
edges {
node {
id
name
}
}
}
}

View File

@ -19,12 +19,4 @@ fragment ObjectiveParts on ObjectiveNode {
}
}
}
objectiveProgress {
edges {
node {
id
done
}
}
}
}

View File

@ -0,0 +1,9 @@
#import "../fragments/objectiveGroupParts.gql"
mutation UpdateObjectiveGroupVisibility($input: UpdateObjectiveGroupVisibilityInput!) {
updateObjectiveGroupVisibility(input: $input) {
objectiveGroup {
...ObjectiveGroupParts
}
}
}

View File

@ -1,6 +1,13 @@
import {CHAPTER_DESCRIPTION_TYPE, CHAPTER_TITLE_TYPE, CONTENT_TYPE, OBJECTIVE_TYPE} from '@/consts/types';
import {
CHAPTER_DESCRIPTION_TYPE,
CHAPTER_TITLE_TYPE,
CONTENT_TYPE,
OBJECTIVE_GROUP_TYPE,
OBJECTIVE_TYPE
} from '@/consts/types';
import CHANGE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/mutateContentBlock.gql';
import UPDATE_OBJECTIVE_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveVisibility.gql';
import UPDATE_OBJECTIVE_GROUP_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateObjectiveGroupVisibility.gql';
import UPDATE_CHAPTER_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateChapterVisibility.gql';
export const createVisibilityMutation = (type, id, visibility) => {
@ -26,6 +33,15 @@ export const createVisibilityMutation = (type, id, visibility) => {
}
};
break;
case OBJECTIVE_GROUP_TYPE:
mutation = UPDATE_OBJECTIVE_GROUP_VISIBILITY_MUTATION;
variables = {
input: {
id,
visibility
}
};
break;
case CHAPTER_TITLE_TYPE:
case CHAPTER_DESCRIPTION_TYPE:
mutation = UPDATE_CHAPTER_VISIBILITY_MUTATION;
@ -67,6 +83,8 @@ export const hidden = ({
? !containsClass(visibleFor, schoolClass)
// otherwise, is it explicitly hidden for this school class?
: containsClass(hiddenFor, schoolClass);
case OBJECTIVE_GROUP_TYPE:
return containsClass(hiddenFor, schoolClass);
case CHAPTER_TITLE_TYPE:
return containsClass(titleHiddenFor, schoolClass);
case CHAPTER_DESCRIPTION_TYPE:

View File

@ -0,0 +1,7 @@
import {mapState} from 'vuex';
export default {
computed: {
...mapState(['editModule']),
}
};

View File

@ -183,9 +183,7 @@ class ModuleNode(DjangoObjectType):
def resolve_objective_groups(self, root, **kwargs):
return self.objective_groups.all() \
.prefetch_related('hidden_for') \
.prefetch_related('visible_for') \
.prefetch_related('objectives__objective_progress')
.prefetch_related('hidden_for')
class RecentModuleNode(DjangoObjectType):

View File

@ -4,15 +4,16 @@ from wagtail.core.models import Page
from objectives.models import ObjectiveGroup, Objective, ObjectiveProgressStatus
from books.models import Topic
@admin.register(ObjectiveGroup)
class ObjectiveGroupAdmin(admin.ModelAdmin):
list_display = ('title', 'module', 'owner')
list_filter = ('title', 'module', 'owner')
list_display = ('title', 'module')
list_filter = ('title', 'module')
@admin.register(Objective)
class ObjectiveAdmin(admin.ModelAdmin):
list_display = ('text', 'get_topic', 'group', 'order', 'owner')
list_display = ('text', 'get_topic', 'group', 'order', 'owner')
list_filter = ('group', 'owner')
def get_topic(self, obj):

View File

@ -0,0 +1,21 @@
# Generated by Django 2.2.18 on 2021-02-22 15:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('objectives', '0012_auto_20210210_1109'),
]
operations = [
migrations.RemoveField(
model_name='objectivegroup',
name='owner',
),
migrations.RemoveField(
model_name='objectivegroup',
name='visible_for',
),
]

View File

@ -23,11 +23,8 @@ class ObjectiveGroup(models.Model):
title = models.CharField('title', blank=True, null=False, max_length=255, choices=TITLE_CHOICES, default=LANGUAGE_COMMUNICATION)
module = models.ForeignKey(Module, blank=False, null=False, on_delete=models.CASCADE, related_name='objective_groups')
# a user can define her own objectives, hence this optional param
owner = models.ForeignKey(get_user_model(), blank=True, null=True, on_delete=models.PROTECT)
hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_objective_groups', blank=True)
visible_for = models.ManyToManyField(SchoolClass, related_name='visible_objective_groups', blank=True)
def __str__(self):
return '{} - {}'.format(self.module, self.title)

View File

@ -58,6 +58,26 @@ class UpdateObjectiveVisibility(relay.ClientIDMutation):
return cls(objective=objective)
class UpdateObjectiveGroupVisibility(relay.ClientIDMutation):
class Input:
id = graphene.ID(required=True, description='The ID of the objective group')
visibility = graphene.List(UserGroupBlockVisibility)
objective_group = graphene.Field(ObjectiveGroupNode)
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
objective_group_id = kwargs.get('id')
visibility_list = kwargs.get('visibility')
objective_group = get_object(ObjectiveGroup, objective_group_id) # info.context.user = django user
if visibility_list is not None:
set_hidden_for(objective_group, visibility_list)
objective_group.save()
return cls(objective_group=objective_group)
class AddObjective(relay.ClientIDMutation):
class Input:
objective = graphene.Argument(AddObjectiveArgument)
@ -104,7 +124,9 @@ class DeleteObjective(relay.ClientIDMutation):
class ObjectiveMutations:
# todo: remove?
update_objective_progress = UpdateObjectiveProgress.Field()
update_objective_visibility = UpdateObjectiveVisibility.Field()
update_objective_group_visibility = UpdateObjectiveGroupVisibility.Field()
add_objective = AddObjective.Field()
delete_objective = DeleteObjective.Field()

View File

@ -10,7 +10,6 @@ from objectives.models import ObjectiveGroup, Objective, ObjectiveProgressStatus
class ObjectiveGroupNode(DjangoObjectType):
pk = graphene.Int()
display_title = graphene.String()
mine = graphene.Boolean()
class Meta:
model = ObjectiveGroup
@ -23,9 +22,6 @@ class ObjectiveGroupNode(DjangoObjectType):
def resolve_display_title(self, *args, **kwargs):
return self.get_title_display()
def resolve_mine(self, info, **kwargs):
return self.owner is not None and self.owner.pk == info.context.user.pk
def resolve_objectives(self, info, **kwargs):
user = info.context.user
school_classes = user.school_classes.values_list('pk')