drf-writable-nested icon indicating copy to clipboard operation
drf-writable-nested copied to clipboard

Create on Primary and Update on Nested: Attribute Error on update_or_create_reverse_relations

Open beautifulDrifter opened this issue 8 years ago • 3 comments

So I am successfully creating objects, but now I need to be able to update them from a response. The scenario is that I have a multi-level nested serialize where I need to create the first two levels and then update the bottom level. I've added a mixin to populate the pk and instance on the bottom level, but I am getting AttributeError: 'category' object has no attribute 'items' which is spawning from update_or_create_reverse_relations. The mixin uses additional attributes to identify the existing records. See below example.

Models:

class category(models.Model):
    name = models.CharField(max_length=255, help_text="",blank=True)
    short_name = models.CharField(max_length=255, help_text="",blank=True)
    external_id = models.IntegerField(help_text="",blank=True,null=True)
    sales_channel = models.ForeignKey('sales_channel',null=True,blank=True)
    purchase_channel = models.ForeignKey('purchase_channel',null=True,blank=True)
    def __str__(self):
        return self.name

class response_log_detail(models.Model):
    category = models.ManyToManyField(category)
    response_log = models.OneToOneField('response_log', null = True, blank = True)

class response_log(models.Model):
    timestamp = models.DateTimeField()
    status = models.CharField(max_length = 25, null=True, blank=True)
    category_id = models.ManyToManyField(category)

Mixin:

class AddPkInstanceMixin(s.ModelSerializer):
    
    def __init__(self, **kwargs):
        object_identifier = kwargs.pop('object_identifier', None)
        object_identifier_source = kwargs.pop('object_identifier_source', None)
        super(AddPkInstanceMixin, self ).__init__(**kwargs)
       
        
    def to_internal_value(self,data):    
        model = self.Meta.model
        id_value = data[self.object_identifier]
        if model is not None and id_value is not None:
            try:
                pk = model.objects.values('pk').get(**{self.object_identifier_source : id_value})
                if pk is not None:
                    data['pk']=pk['pk']
                    instance = model.objects.get(pk = pk['pk'])
                    return model.objects.get(pk = pk['pk'])
            except:
                pass
        return super().to_internal_value(data)

Serializers:

class categoryArray(AddPkInstanceMixin, s.ModelSerializer):

    object_identifier = 'categoryId'
    object_identifier_source = 'external_id'
    
    # Fields #
    categoryId = s.IntegerField(source='external_id')
    categoryName = s.CharField(source='name')
    categoryBriefName = s.CharField(source='short_name')

    class Meta:
        model = category
        fields = (
                'pk',
                'categoryId',
                'categoryName',
                'categoryBriefName',
                )

class getCategoriesResponseSerializer(WritableNestedModelSerializer):
    # Fields #
    categoryArray = categoryArray(many=True, source='category', partial=True)
    # errorMessage = bonanzaErrorMessage()
    # warnings = bonanzaWarnings()
    
    class Meta:
        model = response_log_detail
        fields = (
                #'pk',
                'categoryArray',
                # 'errorMessage',
                # 'warnings',
                )

class responseSerializer(WritableNestedModelSerializer):
    # Fields #
    getCategoriesResponse = getCategoriesResponseSerializer(source='response_log_detail' )
    ack = s.CharField(source='status')
    timestamp = s.DateTimeField()
    
    # errorMessage = bonanzaErrorMessage()
    # warnings = bonanzaWarnings()

    class Meta:
        model = response_log
        fields = (
        #'pk',
        
        'getCategoriesResponse',
        'ack',
        'timestamp',
        
        )

Run these to create the categories:

response = {'ack': 'Success',
 'getCategoriesResponse': {'categoryArray': [{'categoryBriefName': 'Collectibles',
    'categoryId': 1,
    'categoryLevel': 1,
    'categoryName': 'Collectibles',
    'leafCategory': 'false',
    'traitCount': 1},
   {'categoryBriefName': 'Everything Else',
    'categoryId': 99,
    'categoryLevel': 1,
    'categoryName': 'Everything Else',
    'leafCategory': 'false',
    'traitCount': 5}]},
 'timestamp': '2017-10-12T12:00:40.000Z',
 'version': '1.0'}


serializer = responseSerializer(data = response)
serializer.is_valid(raise_exception=True)
response_log = serializer.save()

Run these to try and update the categories: Changed name on Collectibles.

response = {'ack': 'Success',
 'getCategoriesResponse': {'categoryArray': [{'categoryBriefName': 'Collectibles - Name Change',
    'categoryId': 1,
    'categoryLevel': 1,
    'categoryName': 'Collectibles - Name Change',
    'leafCategory': 'false',
    'traitCount': 1},
   {'categoryBriefName': 'Everything Else',
    'categoryId': 99,
    'categoryLevel': 1,
    'categoryName': 'Everything Else',
    'leafCategory': 'false',
    'traitCount': 5}]},
 'timestamp': '2017-10-12T12:00:40.000Z',
 'version': '1.0'}


serializer = responseSerializer(data = response)
serializer.is_valid(raise_exception=True)
response_log = serializer.save()

beautifulDrifter avatar Oct 19 '17 16:10 beautifulDrifter

Same as https://github.com/beda-software/drf-writable-nested/issues/28

cjroth avatar Nov 13 '17 22:11 cjroth

I believe this fixes it: https://github.com/beda-software/drf-writable-nested/pull/29

cjroth avatar Nov 16 '17 22:11 cjroth

Hello @beautifulDrifter! The latest version 4.0.2 contains the fix of the problem with OneToOne updating without specifying id. I will appreciate if you check your code with the latest version of the package and leave feedback if everything is OK.

ruscoder avatar Apr 20 '18 17:04 ruscoder