1- from lxml import etree
1+ from lxml import etree , html
22
33from rest_framework import serializers
44
55from editorsnotes .main .models import (Note , TextNS , CitationNS , NoteReferenceNS ,
6- Document )
6+ Document , NoteSection )
77from editorsnotes .main .models .notes import NOTE_STATUS_CHOICES
88
99from .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
2222class 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
6160class 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
7480class 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