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

Commit

Permalink
Merge pull request #255 from editorsnotes/sections-api
Browse files Browse the repository at this point in the history
Sections API
  • Loading branch information
ptgolden committed Feb 4, 2015
2 parents c83e3a3 + ff5a47a commit 09d2d73
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 268 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
master
========

v0.10.0
========
* Upgrade to Django REST framework 3
* `sections` field in Note API resource is now a writeable multi-valued
field. This is in contrast to the previous approach, where note sections
were treated like separate, individual resources independent of the note.
* `related_topics` fields in API resources should now be links, not names

v0.9.0
=======
* Upgrade to Django 1.7
Expand Down
126 changes: 97 additions & 29 deletions editorsnotes/api/serializers/notes.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from lxml import etree
from lxml import etree, html

from rest_framework import serializers

from editorsnotes.main.models import (Note, TextNS, CitationNS, NoteReferenceNS,
Document)
Document, NoteSection)
from editorsnotes.main.models.notes import NOTE_STATUS_CHOICES

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

class CitationNSSerializer(serializers.ModelSerializer):
section_id = serializers.ReadOnlyField(source='note_section_id')
#note_id = serializers.ReadOnlyField(source='note_id')
section_type = serializers.ReadOnlyField(source='section_type_label')
document = HyperlinkedProjectItemField(view_name='api:api-documents-detail',
queryset=Document.objects.all())
document_description = serializers.SerializerMethodField()
class Meta:
model = CitationNS
fields = ('section_id', 'section_type', 'ordering',
fields = ('section_id', 'section_type',
'document', 'document_description', 'content',)
def get_document_description(self, obj):
return etree.tostring(obj.document.description)
Expand All @@ -41,7 +40,7 @@ class NoteReferenceNSSerializer(serializers.ModelSerializer):
note_reference_title = serializers.SerializerMethodField()
class Meta:
model = NoteReferenceNS
fields = ('section_id', 'section_type', 'ordering',
fields = ('section_id', 'section_type',
'note_reference', 'note_reference_title', 'content',)
def get_note_reference_title(self, obj):
return obj.note_reference.title
Expand All @@ -59,17 +58,24 @@ def _serializer_from_section_type(section_type):
return serializer

class NoteSectionField(serializers.RelatedField):
def get_attribute(self, obj):
return obj.sections.all().select_subclasses()\
.select_related('citationns__document__project',
'notereferencens__note__project')
def to_representation(self, value):
return [self._serialize_section(section) for section in value]
def _serialize_section(self, section):
serializer_class = _serializer_from_section_type(
section.section_type_label)
def __init__(self, *args, **kwargs):
kwargs['queryset'] = NoteSection.objects.all()
super(NoteSectionField, self).__init__(*args, **kwargs)
def to_representation(self, section):
serializer_class = _serializer_from_section_type(section.section_type_label)
serializer = serializer_class(section, context=self.context)
return serializer.data
def to_internal_value(self, data):
section_type = data['section_type']
serializer_class = _serializer_from_section_type(section_type)
serializer = serializer_class(data=data, context={
'request': self.context['request']
})
if serializer.is_valid():
if 'section_id' in data:
serializer.validated_data['section_id'] = data['section_id']
serializer.validated_data['section_type'] = section_type
return serializer.validated_data

class NoteStatusField(serializers.ReadOnlyField):
def get_attribute(self, obj):
Expand All @@ -89,25 +95,87 @@ class NoteSerializer(RelatedTopicSerializerMixin,
updaters = UpdatersField()
status = NoteStatusField()
related_topics = TopicAssignmentField()
sections = NoteSectionField(read_only=True)
sections = NoteSectionField(many=True, source='get_sections_with_subclasses')
class Meta:
model = Note
fields = ('id', 'title', 'url', 'project', 'is_private', 'last_updated',
'updaters', 'related_topics', 'content', 'status', 'sections',)
validators = [
UniqueToProjectValidator('title')
]
# TODO Make sure all section IDs are valid?
def _create_note_section(self, note, data):
section_type = data.pop('section_type')
section_klass = _serializer_from_section_type(section_type).Meta.model
section = section_klass.objects.create(
note=note,
creator=self.context['request'].user,
last_updater=self.context['request'].user,
**data)
return section
def create(self, validated_data):
sections_data = validated_data.pop('get_sections_with_subclasses')
note = super(NoteSerializer, self).create(validated_data)
for idx, section_data in enumerate(sections_data, 1):
section_data['ordering'] = idx
self._create_note_section(note, section_data)
return note
def update(self, instance, validated_data):
sections_data = validated_data.pop('get_sections_with_subclasses')
note = super(NoteSerializer, self).update(instance, validated_data)

class MinimalNoteSerializer(RelatedTopicSerializerMixin,
serializers.ModelSerializer):
status = NoteStatusField()
url = URLField()
project = ProjectSlugField(default=CurrentProjectDefault())
related_topics = TopicAssignmentField()
class Meta:
model = Note
fields = ('id', 'title', 'url', 'project', 'related_topics', 'content',
'status', 'is_private',)
validators = [
UniqueToProjectValidator('title')
]
# Maybe do this over? It's not perty.
# Go through every section in the update and save an instance if
# necessary.
existing_sections = note.get_sections_with_subclasses()
existing_sections_by_id = {
section.note_section_id: section
for section in existing_sections
}

existing_order = tuple(ns.id for ns in existing_sections)
new_order = []
in_update = []

for section in sections_data:

section_id = section.pop('section_id', None)
if section_id is None:
# New section; create it and add it to the note
new_section = self._create_note_section(note, section)
new_order.append(new_section.id)
continue

del section['section_type']

# TODO: Make sure no changing of section types
existing_section = existing_sections_by_id[section_id]
in_update.append(section_id)
new_order.append(existing_section.id)
changed = False

for field, value in section.items():
old_value = getattr(existing_section, field)
setattr(existing_section, field, value)
if changed: continue

if isinstance(value, html.HtmlElement):
changed = etree.tostring(value) != etree.tostring(old_value)
else:
changed = value != old_value

if changed:
existing_section.last_updater = self.context['request'].user
existing_section.save()

# Delete sections no longer in the note
to_delete = (section for section in existing_sections
if section.note_section_id not in in_update)
for section in to_delete:
section.delete()

if len(new_order) and existing_order != tuple(new_order):
positions_dict = {v: k for k, v in enumerate(new_order)}
note.sections.bulk_update_order('ordering', positions_dict)

return note
Loading

0 comments on commit 09d2d73

Please sign in to comment.