1
- from lxml import etree
1
+ from lxml import etree , html
2
2
3
3
from rest_framework import serializers
4
4
5
5
from editorsnotes .main .models import (Note , TextNS , CitationNS , NoteReferenceNS ,
6
- Document )
6
+ Document , NoteSection )
7
7
from editorsnotes .main .models .notes import NOTE_STATUS_CHOICES
8
8
9
9
from .base import (RelatedTopicSerializerMixin , CurrentProjectDefault ,
@@ -17,18 +17,17 @@ class TextNSSerializer(serializers.ModelSerializer):
17
17
section_type = serializers .ReadOnlyField (source = 'section_type_label' )
18
18
class Meta :
19
19
model = TextNS
20
- fields = ('section_id' , 'section_type' , 'ordering' , ' content' ,)
20
+ fields = ('section_id' , 'section_type' , 'content' ,)
21
21
22
22
class CitationNSSerializer (serializers .ModelSerializer ):
23
23
section_id = serializers .ReadOnlyField (source = 'note_section_id' )
24
- #note_id = serializers.ReadOnlyField(source='note_id')
25
24
section_type = serializers .ReadOnlyField (source = 'section_type_label' )
26
25
document = HyperlinkedProjectItemField (view_name = 'api:api-documents-detail' ,
27
26
queryset = Document .objects .all ())
28
27
document_description = serializers .SerializerMethodField ()
29
28
class Meta :
30
29
model = CitationNS
31
- fields = ('section_id' , 'section_type' , 'ordering' ,
30
+ fields = ('section_id' , 'section_type' ,
32
31
'document' , 'document_description' , 'content' ,)
33
32
def get_document_description (self , obj ):
34
33
return etree .tostring (obj .document .description )
@@ -41,7 +40,7 @@ class NoteReferenceNSSerializer(serializers.ModelSerializer):
41
40
note_reference_title = serializers .SerializerMethodField ()
42
41
class Meta :
43
42
model = NoteReferenceNS
44
- fields = ('section_id' , 'section_type' , 'ordering' ,
43
+ fields = ('section_id' , 'section_type' ,
45
44
'note_reference' , 'note_reference_title' , 'content' ,)
46
45
def get_note_reference_title (self , obj ):
47
46
return obj .note_reference .title
@@ -59,17 +58,24 @@ def _serializer_from_section_type(section_type):
59
58
return serializer
60
59
61
60
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 )
71
66
serializer = serializer_class (section , context = self .context )
72
67
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
73
79
74
80
class NoteStatusField (serializers .ReadOnlyField ):
75
81
def get_attribute (self , obj ):
@@ -89,25 +95,87 @@ class NoteSerializer(RelatedTopicSerializerMixin,
89
95
updaters = UpdatersField ()
90
96
status = NoteStatusField ()
91
97
related_topics = TopicAssignmentField ()
92
- sections = NoteSectionField (read_only = True )
98
+ sections = NoteSectionField (many = True , source = 'get_sections_with_subclasses' )
93
99
class Meta :
94
100
model = Note
95
101
fields = ('id' , 'title' , 'url' , 'project' , 'is_private' , 'last_updated' ,
96
102
'updaters' , 'related_topics' , 'content' , 'status' , 'sections' ,)
97
103
validators = [
98
104
UniqueToProjectValidator ('title' )
99
105
]
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 )
100
126
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