Skip to content
This repository was archived by the owner on Jun 1, 2022. It is now read-only.

Commit 127b2dc

Browse files
committed
Nested section serialization for notes
Instead of treating notes as a container resource which can be POSTed to create new sections, just treat the 'sections' field on a note resource as multiple nested sections.
1 parent 546a7ea commit 127b2dc

File tree

6 files changed

+143
-268
lines changed

6 files changed

+143
-268
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
master
22
========
33
* Upgrade to Django REST framework 3
4+
* `sections` field in Note API resource is now a writeable multi-valued
5+
field. This is in contrast to the previous approach, where note sections
6+
were treated like separate, individual resources independent of the note.
47
* `related_topics` fields in API resources should now be links, not names
58

69
v0.9.0

editorsnotes/api/serializers/notes.py

Lines changed: 97 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from lxml import etree
1+
from lxml import etree, html
22

33
from rest_framework import serializers
44

55
from editorsnotes.main.models import (Note, TextNS, CitationNS, NoteReferenceNS,
6-
Document)
6+
Document, NoteSection)
77
from editorsnotes.main.models.notes import NOTE_STATUS_CHOICES
88

99
from .base import (RelatedTopicSerializerMixin, CurrentProjectDefault,
@@ -17,18 +17,17 @@ class TextNSSerializer(serializers.ModelSerializer):
1717
section_type = serializers.ReadOnlyField(source='section_type_label')
1818
class Meta:
1919
model = TextNS
20-
fields = ('section_id', 'section_type', 'ordering', 'content',)
20+
fields = ('section_id', 'section_type', 'content',)
2121

2222
class CitationNSSerializer(serializers.ModelSerializer):
2323
section_id = serializers.ReadOnlyField(source='note_section_id')
24-
#note_id = serializers.ReadOnlyField(source='note_id')
2524
section_type = serializers.ReadOnlyField(source='section_type_label')
2625
document = HyperlinkedProjectItemField(view_name='api:api-documents-detail',
2726
queryset=Document.objects.all())
2827
document_description = serializers.SerializerMethodField()
2928
class Meta:
3029
model = CitationNS
31-
fields = ('section_id', 'section_type', 'ordering',
30+
fields = ('section_id', 'section_type',
3231
'document', 'document_description', 'content',)
3332
def get_document_description(self, obj):
3433
return etree.tostring(obj.document.description)
@@ -41,7 +40,7 @@ class NoteReferenceNSSerializer(serializers.ModelSerializer):
4140
note_reference_title = serializers.SerializerMethodField()
4241
class Meta:
4342
model = NoteReferenceNS
44-
fields = ('section_id', 'section_type', 'ordering',
43+
fields = ('section_id', 'section_type',
4544
'note_reference', 'note_reference_title', 'content',)
4645
def get_note_reference_title(self, obj):
4746
return obj.note_reference.title
@@ -59,17 +58,24 @@ def _serializer_from_section_type(section_type):
5958
return serializer
6059

6160
class NoteSectionField(serializers.RelatedField):
62-
def get_attribute(self, obj):
63-
return obj.sections.all().select_subclasses()\
64-
.select_related('citationns__document__project',
65-
'notereferencens__note__project')
66-
def to_representation(self, value):
67-
return [self._serialize_section(section) for section in value]
68-
def _serialize_section(self, section):
69-
serializer_class = _serializer_from_section_type(
70-
section.section_type_label)
61+
def __init__(self, *args, **kwargs):
62+
kwargs['queryset'] = NoteSection.objects.all()
63+
super(NoteSectionField, self).__init__(*args, **kwargs)
64+
def to_representation(self, section):
65+
serializer_class = _serializer_from_section_type(section.section_type_label)
7166
serializer = serializer_class(section, context=self.context)
7267
return serializer.data
68+
def to_internal_value(self, data):
69+
section_type = data['section_type']
70+
serializer_class = _serializer_from_section_type(section_type)
71+
serializer = serializer_class(data=data, context={
72+
'request': self.context['request']
73+
})
74+
if serializer.is_valid():
75+
if 'section_id' in data:
76+
serializer.validated_data['section_id'] = data['section_id']
77+
serializer.validated_data['section_type'] = section_type
78+
return serializer.validated_data
7379

7480
class NoteStatusField(serializers.ReadOnlyField):
7581
def get_attribute(self, obj):
@@ -89,25 +95,87 @@ class NoteSerializer(RelatedTopicSerializerMixin,
8995
updaters = UpdatersField()
9096
status = NoteStatusField()
9197
related_topics = TopicAssignmentField()
92-
sections = NoteSectionField(read_only=True)
98+
sections = NoteSectionField(many=True, source='get_sections_with_subclasses')
9399
class Meta:
94100
model = Note
95101
fields = ('id', 'title', 'url', 'project', 'is_private', 'last_updated',
96102
'updaters', 'related_topics', 'content', 'status', 'sections',)
97103
validators = [
98104
UniqueToProjectValidator('title')
99105
]
106+
# TODO Make sure all section IDs are valid?
107+
def _create_note_section(self, note, data):
108+
section_type = data.pop('section_type')
109+
section_klass = _serializer_from_section_type(section_type).Meta.model
110+
section = section_klass.objects.create(
111+
note=note,
112+
creator=self.context['request'].user,
113+
last_updater=self.context['request'].user,
114+
**data)
115+
return section
116+
def create(self, validated_data):
117+
sections_data = validated_data.pop('get_sections_with_subclasses')
118+
note = super(NoteSerializer, self).create(validated_data)
119+
for idx, section_data in enumerate(sections_data, 1):
120+
section_data['ordering'] = idx
121+
self._create_note_section(note, section_data)
122+
return note
123+
def update(self, instance, validated_data):
124+
sections_data = validated_data.pop('get_sections_with_subclasses')
125+
note = super(NoteSerializer, self).update(instance, validated_data)
100126

101-
class MinimalNoteSerializer(RelatedTopicSerializerMixin,
102-
serializers.ModelSerializer):
103-
status = NoteStatusField()
104-
url = URLField()
105-
project = ProjectSlugField(default=CurrentProjectDefault())
106-
related_topics = TopicAssignmentField()
107-
class Meta:
108-
model = Note
109-
fields = ('id', 'title', 'url', 'project', 'related_topics', 'content',
110-
'status', 'is_private',)
111-
validators = [
112-
UniqueToProjectValidator('title')
113-
]
127+
# Maybe do this over? It's not perty.
128+
# Go through every section in the update and save an instance if
129+
# necessary.
130+
existing_sections = note.get_sections_with_subclasses()
131+
existing_sections_by_id = {
132+
section.note_section_id: section
133+
for section in existing_sections
134+
}
135+
136+
existing_order = tuple(ns.id for ns in existing_sections)
137+
new_order = []
138+
in_update = []
139+
140+
for section in sections_data:
141+
142+
section_id = section.pop('section_id', None)
143+
if section_id is None:
144+
# New section; create it and add it to the note
145+
new_section = self._create_note_section(note, section)
146+
new_order.append(new_section.id)
147+
continue
148+
149+
del section['section_type']
150+
151+
# TODO: Make sure no changing of section types
152+
existing_section = existing_sections_by_id[section_id]
153+
in_update.append(section_id)
154+
new_order.append(existing_section.id)
155+
changed = False
156+
157+
for field, value in section.items():
158+
old_value = getattr(existing_section, field)
159+
setattr(existing_section, field, value)
160+
if changed: continue
161+
162+
if isinstance(value, html.HtmlElement):
163+
changed = etree.tostring(value) != etree.tostring(old_value)
164+
else:
165+
changed = value != old_value
166+
167+
if changed:
168+
existing_section.last_updater = self.context['request'].user
169+
existing_section.save()
170+
171+
# Delete sections no longer in the note
172+
to_delete = (section for section in existing_sections
173+
if section.note_section_id not in in_update)
174+
for section in to_delete:
175+
section.delete()
176+
177+
if len(new_order) and existing_order != tuple(new_order):
178+
positions_dict = {v: k for k, v in enumerate(new_order)}
179+
note.sections.bulk_update_order('ordering', positions_dict)
180+
181+
return note

0 commit comments

Comments
 (0)