Add original creator to custom content blocks

This commit is contained in:
Ramon Wenger 2021-06-03 17:52:29 +02:00
parent c8edbf9b13
commit 6646b328b7
11 changed files with 98 additions and 30 deletions

View File

@ -1,2 +1,2 @@
nodejs 12.22.1 nodejs 12.22.1
python 3.8.5 python 3.8.10

View File

@ -0,0 +1,26 @@
# Generated by Django 2.2.23 on 2021-06-03 13:05
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('books', '0029_auto_20210511_1301'),
]
operations = [
migrations.AddField(
model_name='contentblock',
name='original_creator',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='snapshot',
name='objective_groups',
field=models.ManyToManyField(related_name='_snapshot_objective_groups_+', through='books.ObjectiveGroupSnapshot', to='objectives.ObjectiveGroup'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 2.2.23 on 2021-06-03 13:06
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('books', '0030_auto_20210603_1305'),
]
operations = [
migrations.AlterField(
model_name='contentblock',
name='original_creator',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -42,6 +42,7 @@ class ContentBlock(StrictHierarchyPage):
# blocks with owner are hidden by default, need to be shown for each class # blocks with owner are hidden by default, need to be shown for each class
visible_for = models.ManyToManyField(SchoolClass, related_name='visible_content_blocks') visible_for = models.ManyToManyField(SchoolClass, related_name='visible_content_blocks')
user_created = models.BooleanField(default=False) user_created = models.BooleanField(default=False)
original_creator = models.ForeignKey(User, null=True, blank=True, default=None, on_delete=models.SET_NULL)
bookmarks = models.ManyToManyField(User, through=ContentBlockBookmark, related_name='bookmarked_content_blocks') bookmarks = models.ManyToManyField(User, through=ContentBlockBookmark, related_name='bookmarked_content_blocks')
@ -133,7 +134,8 @@ class ContentBlockSnapshot(ContentBlock):
contents=self.contents, contents=self.contents,
type=self.type, type=self.type,
title=self.title, title=self.title,
owner=owner owner=owner,
original_creator=self.original_creator
) )
self.add_sibling(instance=cb, pos='right') self.add_sibling(instance=cb, pos='right')
# some wagtail magic # some wagtail magic

View File

@ -59,7 +59,8 @@ class SnapshotManager(models.Manager):
snapshot=snapshot, snapshot=snapshot,
contents=content_block.contents, contents=content_block.contents,
type=content_block.type, type=content_block.type,
title=content_block.title title=content_block.title,
original_creator=content_block.owner
) )
content_block.add_sibling(instance=new_content_block, pos='right') content_block.add_sibling(instance=new_content_block, pos='right')
revision = new_content_block.save_revision() revision = new_content_block.save_revision()

View File

@ -39,6 +39,7 @@ def is_solution_and_hidden_for_user(type, user, module):
class ContentBlockNode(DjangoObjectType, HiddenAndVisibleForMixin): class ContentBlockNode(DjangoObjectType, HiddenAndVisibleForMixin):
mine = graphene.Boolean() mine = graphene.Boolean()
bookmarks = graphene.List(ContentBlockBookmarkNode) bookmarks = graphene.List(ContentBlockBookmarkNode)
original_creator = graphene.Field('users.schema.PublicUserNode')
class Meta: class Meta:
model = ContentBlock model = ContentBlock

View File

@ -20,6 +20,11 @@ query ModulesQuery($slug: String, $id: ID) {
contentBlocks { contentBlocks {
id id
title title
originalCreator {
id
fullName
avatarUrl
}
visibleFor { visibleFor {
name name
} }

View File

@ -121,6 +121,8 @@ class CreateSnapshotTestCase(SkillboxTestCase):
school_class_name in [school_class['name'] for school_class in school_class_name in [school_class['name'] for school_class in
custom_objective.get('visibleFor')]) custom_objective.get('visibleFor')])
return module
def _compare_content_blocks(self, content_blocks): def _compare_content_blocks(self, content_blocks):
self.assertEqual(len(content_blocks), 4) self.assertEqual(len(content_blocks), 4)
first, second, third, fourth = content_blocks first, second, third, fourth = content_blocks
@ -203,7 +205,10 @@ class CreateSnapshotTestCase(SkillboxTestCase):
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
self._test_module_visibility(client, school_class_name) module = self._test_module_visibility(client, school_class_name)
original_creator = module['chapters'][0]['contentBlocks'][2].get('originalCreator')
self.assertIsNotNone(original_creator)
self.assertEqual(original_creator.get('id'), to_global_id('PublicUserNode', self.teacher.pk))
def test_display_snapshot_module(self): def test_display_snapshot_module(self):
self.snapshot = Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class, self.snapshot = Snapshot.objects.create_snapshot(module=self.module, school_class=self.skillbox_class,

View File

@ -8,14 +8,13 @@ from graphene_django.filter import DjangoFilterConnectionField
from api.utils import get_object, get_by_id_or_slug from api.utils import get_object, get_by_id_or_slug
from rooms.models import Room, RoomEntry, ModuleRoomSlug from rooms.models import Room, RoomEntry, ModuleRoomSlug
from users.models import SchoolClass from users.models import SchoolClass
from users.schema import UserNode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class RoomEntryNode(DjangoObjectType): class RoomEntryNode(DjangoObjectType):
pk = graphene.Int() pk = graphene.Int()
author = UserNode() author = graphene.Field('users.schema.PublicUserNode')
class Meta: class Meta:
model = RoomEntry model = RoomEntry

View File

@ -47,7 +47,7 @@ class TeamNode(DjangoObjectType):
interfaces = (relay.Node,) interfaces = (relay.Node,)
pk = graphene.Int() pk = graphene.Int()
members = graphene.List('users.schema.UserNode') members = graphene.List('users.schema.PublicUserNode')
def resolve_pk(self, *args, **kwargs): def resolve_pk(self, *args, **kwargs):
return self.id return self.id
@ -68,7 +68,28 @@ class RecentModuleFilter(FilterSet):
) )
class UserNode(DjangoObjectType): class PublicUserNode(DjangoObjectType):
is_me = graphene.Boolean()
full_name = graphene.String(required=True)
class Meta:
model = User
only_fields = ['full_name', 'first_name', 'last_name', 'avatar_url']
interfaces = (relay.Node,)
@staticmethod
def resolve_is_me(parent: User, info, **kwargs):
return info.context.user.pk == parent.pk
class PrivateUserNode(DjangoObjectType):
class Meta:
model = User
filter_fields = ['username', 'email']
only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module',
'last_topic', 'avatar_url',
'selected_class', 'expiry_date', 'onboarding_visited', 'team']
interfaces = (relay.Node,)
pk = graphene.Int() pk = graphene.Int()
permissions = graphene.List(graphene.String) permissions = graphene.List(graphene.String)
selected_class = graphene.Field(SchoolClassNode) selected_class = graphene.Field(SchoolClassNode)
@ -77,15 +98,6 @@ class UserNode(DjangoObjectType):
old_classes = DjangoFilterConnectionField(SchoolClassNode) old_classes = DjangoFilterConnectionField(SchoolClassNode)
recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter) recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
team = graphene.Field(TeamNode) team = graphene.Field(TeamNode)
is_me = graphene.Boolean()
class Meta:
model = User
filter_fields = ['username', 'email']
only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module',
'last_topic', 'avatar_url',
'selected_class', 'expiry_date', 'onboarding_visited', 'team']
interfaces = (relay.Node,)
def resolve_pk(self, info, **kwargs): def resolve_pk(self, info, **kwargs):
return self.id return self.id
@ -121,16 +133,13 @@ class UserNode(DjangoObjectType):
def resolve_team(self, info, **kwargs): def resolve_team(self, info, **kwargs):
return self.team return self.team
def resolve_is_me(self, info, **kwargs):
return info.context.user.pk == self.pk
class ClassMemberNode(ObjectType): class ClassMemberNode(ObjectType):
""" """
We need to build this ourselves, because we want the active property on the node, because providing it on the We need to build this ourselves, because we want the active property on the node, because providing it on the
Connection or Edge for a UserNodeConnection is difficult. Connection or Edge for a UserNodeConnection is difficult.
""" """
user = graphene.Field(UserNode) user = graphene.Field('users.schema.PublicUserNode')
active = graphene.Boolean() active = graphene.Boolean()
first_name = graphene.String() first_name = graphene.String()
last_name = graphene.String() last_name = graphene.String()
@ -138,7 +147,7 @@ class ClassMemberNode(ObjectType):
id = graphene.ID() id = graphene.ID()
def resolve_id(self, *args): def resolve_id(self, *args):
return to_global_id('UserNode', self.user.pk) return to_global_id('PublicUserNode', self.user.pk)
def resolve_active(self, *args): def resolve_active(self, *args):
return self.active return self.active
@ -157,8 +166,8 @@ class ClassMemberNode(ObjectType):
class UsersQuery(object): class UsersQuery(object):
me = graphene.Field(UserNode) me = graphene.Field(PrivateUserNode)
all_users = DjangoFilterConnectionField(UserNode) all_users = DjangoFilterConnectionField(PrivateUserNode)
my_activity = DjangoFilterConnectionField(ModuleNode) my_activity = DjangoFilterConnectionField(ModuleNode)
my_instrument_activity = DjangoFilterConnectionField(InstrumentNode) my_instrument_activity = DjangoFilterConnectionField(InstrumentNode)
@ -179,8 +188,8 @@ class UsersQuery(object):
class AllUsersQuery(object): class AllUsersQuery(object):
me = graphene.Field(UserNode) me = graphene.Field(PrivateUserNode)
all_users = DjangoFilterConnectionField(UserNode) all_users = DjangoFilterConnectionField(PrivateUserNode)
def resolve_all_users(self, info, **kwargs): def resolve_all_users(self, info, **kwargs):
if not info.context.user.is_superuser: if not info.context.user.is_superuser:

View File

@ -39,11 +39,11 @@ class JoinSchoolClassTest(TestCase):
] ]
create_users(user_data) create_users(user_data)
teacher = User.objects.get(username='emily.sands') teacher = User.objects.get(username='emily.sands')
self.teacher_id = to_global_id('UserNode', teacher.pk) self.teacher_id = to_global_id('PublicUserNode', teacher.pk)
student = User.objects.get(username='adam.groff') student = User.objects.get(username='adam.groff')
self.student_id = to_global_id('UserNode', student.pk) self.student_id = to_global_id('PublicUserNode', student.pk)
other_student = User.objects.get(username='eric.effiong') other_student = User.objects.get(username='eric.effiong')
self.other_student_id = to_global_id('UserNode', other_student.pk) self.other_student_id = to_global_id('PublicUserNode', other_student.pk)
school_class = SchoolClass.objects.get(name=self.school_class_name) school_class = SchoolClass.objects.get(name=self.school_class_name)
self.school_class_id = to_global_id('SchoolClassNode', school_class.pk) self.school_class_id = to_global_id('SchoolClassNode', school_class.pk)
@ -115,7 +115,7 @@ class JoinSchoolClassTest(TestCase):
result = self.client.execute(self.mutation, variables={ result = self.client.execute(self.mutation, variables={
'input': { 'input': {
'schoolClass': to_global_id('SchoolClassNode', school_class.id), 'schoolClass': to_global_id('SchoolClassNode', school_class.id),
'member': to_global_id('UserNode', student.id), 'member': to_global_id('PublicUserNode', student.id),
'active': False 'active': False
} }
}, context=self.teacher_context) }, context=self.teacher_context)