Add highlight model to server

This commit is contained in:
Ramon Wenger 2024-01-11 09:48:56 +01:00
parent 4584cb860e
commit f3dc0e9b09
5 changed files with 162 additions and 33 deletions

View File

@ -10,7 +10,7 @@ from books.utils import are_solutions_enabled_for
from core.logger import get_logger from core.logger import get_logger
from core.mixins import HiddenAndVisibleForMixin from core.mixins import HiddenAndVisibleForMixin
from notes.models import ContentBlockBookmark from notes.models import ContentBlockBookmark
from notes.schema import ContentBlockBookmarkNode from notes.schema import ContentBlockBookmarkNode, HighlightNode
from rooms.models import ModuleRoomSlug from rooms.models import ModuleRoomSlug
logger = get_logger(__name__) logger = get_logger(__name__)
@ -20,7 +20,7 @@ class TextBlockNode(graphene.ObjectType):
text = graphene.String() text = graphene.String()
def resolve_text(root, info, **kwargs): def resolve_text(root, info, **kwargs):
return root['value']['text'] return root["value"]["text"]
class ContentNode(graphene.Union): class ContentNode(graphene.Union):
@ -30,30 +30,43 @@ class ContentNode(graphene.Union):
@classmethod @classmethod
def resolve_type(cls, instance, info): def resolve_type(cls, instance, info):
logger.info(instance) logger.info(instance)
if instance['type'] == 'text_block': if instance["type"] == "text_block":
return TextBlockNode return TextBlockNode
def is_solution_and_hidden_for_user(type, user, module): def is_solution_and_hidden_for_user(type, user, module):
return type == 'solution' and not (are_solutions_enabled_for(user, module) or user.is_teacher()) return type == "solution" and not (
are_solutions_enabled_for(user, module) or user.is_teacher()
)
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') original_creator = graphene.Field("users.schema.PublicUserNode")
instrument_category = graphene.Field(InstrumentCategoryNode) instrument_category = graphene.Field(InstrumentCategoryNode)
path = graphene.String() path = graphene.String()
highlights = graphene.List(HighlightNode)
class Meta: class Meta:
model = ContentBlock model = ContentBlock
only_fields = [ only_fields = [
'slug', 'title', 'type', 'contents', 'hidden_for', 'visible_for', 'user_created' "slug",
"title",
"type",
"contents",
"hidden_for",
"visible_for",
"user_created",
] ]
filter_fields = [ filter_fields = [
'slug', 'title', "slug",
"title",
] ]
interfaces = (relay.Node, ContentBlockInterface,) interfaces = (
relay.Node,
ContentBlockInterface,
)
convert_choices_to_enum = False convert_choices_to_enum = False
def resolve_mine(parent, info, **kwargs): def resolve_mine(parent, info, **kwargs):
@ -64,18 +77,22 @@ class ContentBlockNode(DjangoObjectType, HiddenAndVisibleForMixin):
updated_raw_data = [] updated_raw_data = []
for content in self.contents.raw_data: for content in self.contents.raw_data:
# only show solutions to teachers and students for whom their teachers have them enabled # only show solutions to teachers and students for whom their teachers have them enabled
if is_solution_and_hidden_for_user(content['type'], info.context.user, self.module): if is_solution_and_hidden_for_user(
logger.debug('Solution is hidden for this user') content["type"], info.context.user, self.module
):
logger.debug("Solution is hidden for this user")
continue continue
if content['type'] == 'content_list_item': if content["type"] == "content_list_item":
_values = [] _values = []
for index, list_block in enumerate(content['value']): for index, list_block in enumerate(content["value"]):
if is_solution_and_hidden_for_user(list_block['type'], info.context.user, self.module): if is_solution_and_hidden_for_user(
logger.debug('Solution is hidden for this user') list_block["type"], info.context.user, self.module
):
logger.debug("Solution is hidden for this user")
continue continue
_values.append(process_module_room_slug_block(list_block)) _values.append(process_module_room_slug_block(list_block))
content['value'] = _values content["value"] = _values
content = process_module_room_slug_block(content) content = process_module_room_slug_block(content)
updated_raw_data.append(content) updated_raw_data.append(content)
@ -85,16 +102,18 @@ class ContentBlockNode(DjangoObjectType, HiddenAndVisibleForMixin):
def resolve_bookmarks(self, info, **kwargs): def resolve_bookmarks(self, info, **kwargs):
return ContentBlockBookmark.objects.filter( return ContentBlockBookmark.objects.filter(
user=info.context.user, user=info.context.user, content_block=self
content_block=self
) )
@staticmethod @staticmethod
def resolve_instrument_category(root: ContentBlock, info, **kwargs): def resolve_instrument_category(root: ContentBlock, info, **kwargs):
if root.type == ContentBlock.INSTRUMENT: if root.type == ContentBlock.INSTRUMENT:
for content in root.contents.raw_data: for content in root.contents.raw_data:
if content['type'] == 'instrument' or content['type'] == 'basic_knowledge': if (
_id = content['value']['basic_knowledge'] content["type"] == "instrument"
or content["type"] == "basic_knowledge"
):
_id = content["value"]["basic_knowledge"]
instrument = BasicKnowledge.objects.get(id=_id) instrument = BasicKnowledge.objects.get(id=_id)
category = instrument.new_type.category category = instrument.new_type.category
return category return category
@ -103,17 +122,22 @@ class ContentBlockNode(DjangoObjectType, HiddenAndVisibleForMixin):
@staticmethod @staticmethod
def resolve_path(root: ContentBlock, info, **kwargs): def resolve_path(root: ContentBlock, info, **kwargs):
module = root.get_parent().get_parent() module = root.get_parent().get_parent()
return f'module/{module.slug}#{root.graphql_id}' return f"module/{module.slug}#{root.graphql_id}"
@staticmethod
def resolve_highlights(root: ContentBlock, info, **kwargs):
return root.highlights.filter(user=info.context.user)
def process_module_room_slug_block(content): def process_module_room_slug_block(content):
if content['type'] == 'module_room_slug': if content["type"] == "module_room_slug":
try: try:
module_room_slug = ModuleRoomSlug.objects.get( module_room_slug = ModuleRoomSlug.objects.get(
title=content['value']['title']) title=content["value"]["title"]
content['value'] = { )
'title': content['value']['title'], content["value"] = {
'slug': module_room_slug.slug "title": content["value"]["title"],
"slug": module_room_slug.slug,
} }
except ModuleRoomSlug.DoesNotExist: except ModuleRoomSlug.DoesNotExist:
pass pass

View File

@ -0,0 +1,52 @@
# Generated by Django 4.2.8 on 2024-01-09 15:07
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("books", "0045_alter_snapshot_objective_groups"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("notes", "0004_auto_20210322_1514"),
]
operations = [
migrations.CreateModel(
name="Highlight",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("content_index", models.IntegerField()),
("content_uuid", models.UUIDField()),
("paragraph_index", models.IntegerField()),
("start_position", models.IntegerField()),
("selection_length", models.IntegerField()),
("text", models.TextField()),
("color", models.CharField(max_length=50)),
(
"content_block",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="highlights",
to="books.contentblock",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@ -19,26 +19,50 @@ class Bookmark(models.Model):
class ContentBlockBookmark(Bookmark): class ContentBlockBookmark(Bookmark):
uuid = models.UUIDField(unique=False) uuid = models.UUIDField(unique=False)
content_block = models.ForeignKey('books.ContentBlock', on_delete=models.CASCADE) content_block = models.ForeignKey("books.ContentBlock", on_delete=models.CASCADE)
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint(fields=['uuid', 'content_block', 'user'], name='unique_content_bookmark_per_user') models.UniqueConstraint(
fields=["uuid", "content_block", "user"],
name="unique_content_bookmark_per_user",
)
] ]
class ModuleBookmark(Bookmark): class ModuleBookmark(Bookmark):
module = models.ForeignKey('books.Module', on_delete=models.CASCADE) module = models.ForeignKey("books.Module", on_delete=models.CASCADE)
class ChapterBookmark(Bookmark): class ChapterBookmark(Bookmark):
chapter = models.ForeignKey('books.Chapter', on_delete=models.CASCADE) chapter = models.ForeignKey("books.Chapter", on_delete=models.CASCADE)
class InstrumentBookmark(Bookmark): class InstrumentBookmark(Bookmark):
uuid = models.UUIDField(unique=False) uuid = models.UUIDField(unique=False)
instrument = models.ForeignKey('basicknowledge.BasicKnowledge', on_delete=models.CASCADE) instrument = models.ForeignKey(
"basicknowledge.BasicKnowledge", on_delete=models.CASCADE
)
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint(fields=['uuid', 'instrument', 'user'], name='unique_instrument_bookmark_per_user') models.UniqueConstraint(
fields=["uuid", "instrument", "user"],
name="unique_instrument_bookmark_per_user",
)
] ]
class Highlight(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
content_block = models.ForeignKey(
"books.ContentBlock", on_delete=models.CASCADE, related_name="highlights"
)
# see highlight.ts for comments
content_index = models.IntegerField()
content_uuid = models.UUIDField()
paragraph_index = models.IntegerField()
start_position = models.IntegerField()
selection_length = models.IntegerField()
text = models.TextField()
color = models.CharField(max_length=50)

View File

@ -2,7 +2,14 @@ import graphene
from graphene import relay from graphene import relay
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from notes.models import Note, ContentBlockBookmark, ModuleBookmark, ChapterBookmark, InstrumentBookmark from notes.models import (
Highlight,
Note,
ContentBlockBookmark,
ModuleBookmark,
ChapterBookmark,
InstrumentBookmark,
)
class NoteNode(DjangoObjectType): class NoteNode(DjangoObjectType):
@ -36,7 +43,6 @@ class ModuleBookmarkNode(DjangoObjectType):
fields = "__all__" fields = "__all__"
class ChapterBookmarkNode(DjangoObjectType): class ChapterBookmarkNode(DjangoObjectType):
note = graphene.Field(NoteNode) note = graphene.Field(NoteNode)
@ -56,3 +62,11 @@ class InstrumentBookmarkNode(DjangoObjectType):
fields = "__all__" fields = "__all__"
filter_fields = [] filter_fields = []
interfaces = (relay.Node,) interfaces = (relay.Node,)
class HighlightNode(DjangoObjectType):
class Meta:
model = Highlight
fields = "__all__"
filter_fields = []
interfaces = (relay.Node,)

View File

@ -511,6 +511,7 @@ type ContentBlockNode implements Node & ContentBlockInterface {
originalCreator: PublicUserNode originalCreator: PublicUserNode
instrumentCategory: InstrumentCategoryNode instrumentCategory: InstrumentCategoryNode
path: String path: String
highlights: [HighlightNode]
} }
type ContentBlockBookmarkNode implements Node { type ContentBlockBookmarkNode implements Node {
@ -616,6 +617,20 @@ type InstrumentCategoryNode implements Node {
types: [InstrumentTypeNode] types: [InstrumentTypeNode]
} }
type HighlightNode implements Node {
"""The ID of the object"""
id: ID!
user: PrivateUserNode!
contentBlock: ContentBlockNode!
contentIndex: Int!
contentUuid: UUID!
paragraphIndex: Int!
startPosition: Int!
selectionLength: Int!
text: String!
color: String!
}
type SnapshotObjectiveGroupNode implements Node { type SnapshotObjectiveGroupNode implements Node {
"""The ID of the object""" """The ID of the object"""
id: ID! id: ID!