Add highlight model to server
This commit is contained in:
parent
4584cb860e
commit
f3dc0e9b09
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,)
|
||||||
|
|
|
||||||
|
|
@ -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!
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue