11# -*- coding: utf-8 -*-
2+ import logging
23from collections import OrderedDict , defaultdict
34
45from django .contrib .contenttypes .fields import GenericRelation
56from django .contrib .contenttypes .models import ContentType
67from django .db import transaction
78from django .db .models import ProtectedError , FieldDoesNotExist , OneToOneRel
8- from django .db .models .fields .related import ForeignObjectRel , OneToOneField , ManyToManyField
9+ from django .db .models .fields .related import ForeignObjectRel , ManyToManyField
910from django .utils .translation import ugettext_lazy as _
1011from rest_framework import serializers
1112from rest_framework .exceptions import ValidationError
@@ -429,6 +430,10 @@ def update(self, instance, validated_data):
429430
430431class FieldLookupMixin (serializers .Serializer ):
431432
433+ def __init__ (self , * args , ** kwargs ):
434+ self .logger = logging .getLogger (self .__class__ .__name__ )
435+ super (FieldLookupMixin , self ).__init__ (* args , ** kwargs )
436+
432437 def _get_model_field (self , source ):
433438 """Returns the field on the model"""
434439 # for serializers like ModelSerializer, the Meta.model can be used to classify fields
@@ -472,6 +477,8 @@ def _populate_field_types(self):
472477 self ._cache_field_types = {}
473478 self ._cache_field_sources = {}
474479 for field_name , field in self .fields .items ():
480+ if isinstance (self ._get_model_field (field .source ), GenericRelation ):
481+ raise TypeError ("GenericRelation not currently supported" )
475482 if field .read_only :
476483 self ._cache_field_types [field_name ] = self .TYPE_READ_ONLY
477484 self ._cache_field_sources [field .source ] = self .TYPE_READ_ONLY
@@ -506,7 +513,9 @@ class RelatedSaveMixin(FieldLookupMixin):
506513
507514 def run_validation (self , data = empty ):
508515 """Cache nested call to `to_representation` on _validate_data for use when saving."""
516+ self .logger .debug ("{} validating: {}" .format (self .__class__ .__name__ , data ))
509517 self ._validated_data = super (RelatedSaveMixin , self ).run_validation (data )
518+ self .logger .debug ("{} validated: {}" .format (self .__class__ .__name__ , self ._validated_data ))
510519 self ._errors = {}
511520 return self ._validated_data
512521
@@ -538,9 +547,9 @@ def validated_data(self):
538547 if k not in self .field_sources or self .field_sources [k ] != self .TYPE_REVERSE }
539548
540549 def save (self , ** kwargs ):
541- """We already converted the inputs into a model so we need to save that model """
550+ """Convert validated data into related objects and save. """
542551 # Create or update direct relations (foreign key, one-to-one)
543- print ("RelatedSaveMixin.save for {}" .format (self .__class__ .__name__ ))
552+ print ("RelatedSaveMixin.save for {} with data, kwargs: {}, {} " .format (self .__class__ .__name__ , self . _validated_data , kwargs ))
544553 self ._save_direct_relations (kwargs = kwargs )
545554 instance = super (RelatedSaveMixin , self ).save (** kwargs )
546555 self ._save_reverse_relations (instance = instance , kwargs = kwargs )
@@ -551,14 +560,14 @@ def _save_direct_relations(self, kwargs):
551560 for field_name , field in self .fields .items ():
552561 if self .field_types [field_name ] != self .TYPE_DIRECT :
553562 continue
554- print ("{} direct save {}" .format (self .__class__ .__name__ , field_name ))
555- if self ._validated_data .get (field_name , empty ) == empty and kwargs .get (field_name , empty ) == empty :
563+ print ("{} direct save {} with data, kwargs; {}, {} " .format (self .__class__ .__name__ , field_name , self . _validated_data . get ( field . source , empty ), kwargs . get ( field_name , {}) ))
564+ if self ._validated_data .get (field . source , empty ) == empty and kwargs .get (field_name , empty ) == empty :
556565 continue # nothing to save
557566 #if self._validated_data.get(field_name) is None or kwargs.get(field_name) is None:
558567 # continue # delete existing objects
559568 # we need to pop from kwargs so the value doesn't "overwrite" the value generated by save
560- self ._validated_data [field_name ] = field .save (** kwargs .pop (field_name , {}))
561- print ("{}._validated_data[{}] to direct {}" .format (self .__class__ .__name__ , field_name , self ._validated_data [field_name ]))
569+ self ._validated_data [field . source ] = field .save (** kwargs .pop (field_name , {}))
570+ print ("{}._validated_data[{}] set to direct {}" .format (self .__class__ .__name__ , field_name , self ._validated_data [field . source ]))
562571
563572 def _format_generic_lookup (self , instance , related_field ):
564573 return {
@@ -571,20 +580,23 @@ def _save_reverse_relations(self, instance, kwargs):
571580 for field_name , field in self .fields .items ():
572581 if self .field_types [field_name ] != self .TYPE_REVERSE :
573582 continue
574- if self ._validated_data .get (field_name , empty ) == empty and kwargs .get (field_name , empty ) == empty :
583+ if self ._validated_data .get (field . source , empty ) == empty and kwargs .get (field_name , empty ) == empty :
575584 continue # nothing to save
576585 # inject the instance into reverse relations so the <parent>_id ForeignKey field is valid when saved
577586 related_field = self ._get_model_field (field .source ).field
587+ print ("{} populating reverse field {}" .format (self .__class__ .__name__ , related_field .name ))
578588 if isinstance (field , serializers .ListSerializer ):
579589 for obj in field ._validated_data :
580590 obj [related_field .name ] = instance
581591 elif isinstance (field , serializers .ModelSerializer ):
592+ if field ._validated_data is None :
593+ field ._validated_data = {} # delete situation, but need a place to put FK
582594 field ._validated_data [related_field .name ] = instance
583595 else :
584596 raise Exception ("unexpected serializer type" )
585597 # no tests fail if we do not cache this value in _validated_data, but it's consistent with forward relations
586- self ._validated_data [field_name ] = field .save (** kwargs .get (field_name , {}))
587- print ("{}._validated_data[{}] to reverse {}" .format (self .__class__ .__name__ , field_name , self ._validated_data [field_name ]))
598+ self ._validated_data [field . source ] = field .save (** kwargs .get (field_name , {}))
599+ print ("{}._validated_data[{}] to reverse {}" .format (self .__class__ .__name__ , field_name , self ._validated_data [field . source ]))
588600
589601
590602class FocalSaveMixin (FieldLookupMixin ):
@@ -594,12 +606,25 @@ class FocalSaveMixin(FieldLookupMixin):
594606 'incorrect_type' : _ ('Nested field received an incorrect data type ({data_type}): {exception_message}' ),
595607 }
596608
609+ def to_internal_value (self , data ):
610+ """Injects the PK of this field into reverse relations so they validate when created in to_internal_value."""
611+ # to_internal_value only preserves writable fields and match_on may need read-only like PK
612+ self ._validated_data = super (FocalSaveMixin , self ).to_internal_value (data )
613+ # patch read-only fields for match_on
614+ for field_name , field in self .fields .items ():
615+ if not field .read_only :
616+ continue
617+ if field_name not in data :
618+ continue
619+ self ._validated_data [field .source ] = field .to_internal_value (data [field_name ])
620+ return self ._validated_data
621+
597622 def build_match_on (self , kwargs ):
598623 match_on = {}
599624 for field_name , field in self .fields .items ():
600625 if self .match_on == '__all__' or field_name in self .match_on :
601626 # build match_on dict
602- match_on [field .source or field_name ] = kwargs .get (field_name , self ._validated_data .get (field_name ))
627+ match_on [field .source ] = kwargs .get (field_name , self ._validated_data .get (field . source ))
603628 # a parent serializer may inject a value that isn't among the fields, but is in `match_on`
604629 for key in self .match_on :
605630 if key not in self .fields .keys ():
@@ -613,20 +638,22 @@ def build_direct_values(self, kwargs):
613638 continue # m2m fields
614639 elif self .field_types [field_name ] == self .TYPE_LOCAL :
615640 # need to check kwargs dict since there's no pre-processing
616- values [field_name ] = kwargs .get (field_name , self ._validated_data .get (field_name ))
641+ values [field . source ] = kwargs .get (field_name , self ._validated_data .get (field . source ))
617642 elif self .field_types [field_name ] == self .TYPE_DIRECT :
618643 # kwargs should have been injected into _validated_data when direct relations were saved
619- values [field_name ] = self ._validated_data .get (field_name )
644+ values [field . source ] = self ._validated_data .get (field . source )
620645 # reverse relations aren't sent to a create
621646 return values
622647
623648 def match (self , kwargs ):
624- print ("FocalSaveMixin.match with no super" )
649+ print ("FocalSaveMixin.match with no super and kwargs {}" . format ( kwargs ) )
625650 return self .instance
626651
627652 @transaction .atomic
628653 def save (self , ** kwargs ):
629- print ("FocalSaveMixin.save for {}" .format (self .__class__ .__name__ ))
654+ print ("FocalSaveMixin.save for {} with data, kwargs {}" .format (self .__class__ .__name__ , self ._validated_data , kwargs ))
655+ if self ._validated_data is None and kwargs == {}:
656+ return None # deleted
630657 match = self .match (kwargs )
631658 print ("Match: {}" .format (match ))
632659 self .do_update (match , kwargs )
@@ -648,6 +675,10 @@ def do_m2m_update(self, match, kwargs):
648675class NestedSaveListSerializer (serializers .ListSerializer ):
649676 """Need a special save() method that cascades to the list of child instances"""
650677
678+ def __init__ (self , * args , ** kwargs ):
679+ self .logger = logging .getLogger (self .__class__ .__name__ )
680+ super (NestedSaveListSerializer , self ).__init__ (* args , ** kwargs )
681+
651682 def save (self , ** kwargs ):
652683 """
653684 Save and return a list of object instances.
@@ -661,24 +692,27 @@ def save(self, **kwargs):
661692 "need to set extra attributes on the saved model instance. "
662693 "For example: 'serializer.save(owner=request.user)'.'"
663694 )
664- print ("List-{} save: {}, {}" .format (self .child .__class__ .__name__ , self ._validated_data , kwargs ))
695+ print ("List-{} save with data, kwargs : {}, {}" .format (self .child .__class__ .__name__ , self ._validated_data , kwargs ))
665696
666697 new_values = []
667698
668699 for item in self ._validated_data :
669700 # integrate save kwargs
670701 self .child ._validated_data = item
671- # since we reuse the serializer, we need to re-inject the new _validated_data using save kwargs
672- new_values .append (self .child .save (** kwargs ))
702+ new_value = self .child .save (** kwargs )
703+ # None indicates a deleted item
704+ if new_value is not None :
705+ new_values .append (new_value )
673706
674707 print ("List-{} saved: {}" .format (self .child .__class__ .__name__ , new_values ))
675708 return new_values
676709
677710 def run_validation (self , data = empty ):
678711 """Since a nested serializer is treated like a Field, `is_valid` will not be called so we need to set
679712 _validated_data in the mixin."""
713+ self .logger .debug ("List-{} validating: {}" .format (self .child .__class__ .__name__ , data ))
680714 self ._validated_data = super (NestedSaveListSerializer , self ).run_validation (data )
681- print ("List-{} validated: {}" .format (self .child .__class__ .__name__ , self ._validated_data ))
715+ self . logger . debug ("List-{} validated: {}" .format (self .child .__class__ .__name__ , self ._validated_data ))
682716 return self ._validated_data
683717
684718
@@ -707,6 +741,8 @@ class Meta:
707741 return super (NestedSaveSerializer , cls ).many_init (* args , ** kwargs )
708742
709743 def __init__ (self , * args , ** kwargs ):
744+ if kwargs .get ('partial' , False ) is True :
745+ raise ValueError ("Partial Updates not currently supported by NestedSaveSerializer" )
710746 self .queryset = kwargs .pop ('queryset' , self .queryset )
711747 if self .queryset is None and hasattr (self , 'Meta' ) and hasattr (self .Meta , 'model' ):
712748 self .queryset = self .Meta .model .objects .all ()
@@ -722,9 +758,11 @@ def __init__(self, *args, **kwargs):
722758
723759 def run_validation (self , data = empty ):
724760 """A nested serializer is treated like a Field so `is_valid` will not be called and `_validated_data` not set."""
761+ self .logger .debug ("{} validating: {}" .format (self .__class__ .__name__ , data ))
725762 # ensure Unique and UniqueTogether don't collide with a DB match
726763 validators = self .remove_validation_unique ()
727764 self ._validated_data = super (NestedSaveSerializer , self ).run_validation (data )
765+ self .logger .debug ("{} validated: {}" .format (self .__class__ .__name__ , self ._validated_data ))
728766 # restore Unique or UniqueTogether
729767 self .restore_validation_unique (validators )
730768 return self ._validated_data
@@ -763,6 +801,10 @@ def restore_validation_unique(self, unique_validators):
763801 for validator in validators :
764802 fields [name ].validators .append (validator )
765803
804+ def save (self , ** kwargs ):
805+ print ("NestedSaveSerializer.save with data, kwargs: {}, {}" .format (self ._validated_data , kwargs ))
806+ return super ().save (** kwargs )
807+
766808 def update (self , instance , validated_data ):
767809 raise KeyError (
768810 "Update should never be called on a NestedSerializerBase. Make sure parent object uses NestedSaveMixin" )
@@ -773,6 +815,7 @@ def create(self, validated_data):
773815
774816
775817class UpdateDoSaveMixin (NestedSaveSerializer ):
818+ """Adds behavior to update the focal object, including M2M relations"""
776819
777820 def do_update (self , match , kwargs ):
778821 update_values = self .build_direct_values (kwargs )
@@ -787,17 +830,21 @@ def do_m2m_update(self, match, kwargs):
787830 # we don't care whether it's forward or reverse
788831 # if we provide a custom serializer, it may not inherit from ManyRelatedField
789832 if isinstance (field , ManyRelatedField ) or isinstance (self ._get_model_field (field .source ), ManyToManyField ):
790- value = kwargs .get (field_name , self ._validated_data .get (field_name ))
791- print ("{}: m2m set to {}, {}" .format (self .__class__ .__name__ , field_name , value ))
792- getattr (match , field_name ).set (value )
833+ value = kwargs .get (field_name , self ._validated_data .get (field .source , empty ))
834+ if value is empty :
835+ continue # no information
836+ if value is None :
837+ value = [] # explicitly clear
838+ print ("{}: m2m set to {}, {}" .format (self .__class__ .__name__ , field .source , value ))
839+ getattr (match , field .source ).set (value )
793840
794841
795842class GetOnlyNestedSerializerMixin (NestedSaveSerializer ):
796843 """Gets (without updating) requetsed object or fails."""
797844
798845 def match (self , kwargs ):
799846 match = super (GetOnlyNestedSerializerMixin , self ).match (kwargs )
800- print ("GetOnlyNestedSerializerMixin.match with super: {}" .format (match ))
847+ print ("GetOnlyNestedSerializerMixin.match with super {} and kwargs {} " .format (match , kwargs ))
801848 if match is not None :
802849 return match
803850 try :
@@ -817,7 +864,7 @@ class GetOrCreateNestedSerializerMixin(GetOnlyNestedSerializerMixin):
817864
818865 def match (self , kwargs ):
819866 match = super (GetOrCreateNestedSerializerMixin , self ).match (kwargs )
820- print ("GetOrCreateNestedSerializerMixin.match with super: {}" .format (match ))
867+ print ("GetOrCreateNestedSerializerMixin.match with super {} and kwargs {} " .format (match , kwargs ))
821868 if match is not None :
822869 return match
823870 create_values = self .build_direct_values (kwargs )
@@ -832,7 +879,7 @@ class CreateOnlyNestedSerializerMixin(NestedSaveSerializer):
832879 """Creates requested object or fails."""
833880
834881 def match (self , kwargs ):
835- print ("CreateOnlyNestedSerializerMixin .match with no super" )
882+ print ("GetOrCreateNestedSerializerMixin .match with no super and kwargs {}" . format ( kwargs ) )
836883 create_values = self .build_direct_values (kwargs )
837884 return self .queryset .model (** create_values )
838885
0 commit comments