Note that there are some explanatory texts on larger screens.

plurals
  1. POdjango-rest-framework - trying to set required=False flag on nested 1-to-M?
    primarykey
    data
    text
    <p>I'm having some issue with django-rest-framework, and nested objects.</p> <p>I have a Cart object, as well as CartItem, which links back to a Cart:</p> <pre><code>class Cart(models.Model): customer = models.ForeignKey(Customer) date_created = models.DateTimeField(auto_now_add=True) date_modified = models.DateTimeField(auto_now=True) class CartItem(models.Model): cart = models.ForeignKey(Cart, related_name='cartitems') product = models.ForeignKey(Product, help_text='Product in a cart') quantity = models.PositiveIntegerField(default=1, help_text='Quantity of this product.') date_added = models.DateTimeField(auto_now_add=True, help_text='Date that this product was added to the cart.') </code></pre> <p>I've created serializers for both:</p> <pre><code>class CartItemSerializer(serializers.ModelSerializer): product = serializers.HyperlinkedRelatedField(view_name='product-detail') class Meta: model = CartItem class CartSerializer(serializers.ModelSerializer): customer = serializers.HyperlinkedRelatedField(view_name='customer-detail') cartitems = CartItemSerializer(required=False) total_price = serializers.CharField(source='total_price', read_only=True) shipping_cost = serializers.CharField(source='shipping_cost', read_only=True) class Meta: model = Cart fields = ('id', 'customer', 'date_created', 'date_modified', 'cartitems', 'total_price', 'shipping_cost') </code></pre> <p>However, whenever I try to POST to create a new cart, I get an error, assumedly when it tries to set the non-existent CartItem:</p> <pre><code>TypeError at /api/v1/carts/ add() argument after * must be a sequence, not NoneType </code></pre> <p>However, a Cart isn't required to actually have CartItems.</p> <p>Is there any way to get DRF to respect the <code>required=False</code> flag I get on Cart.cartitems?</p> <p>Cheers, Victor</p> <p>EDIT:</p> <p>I took a stab at tracing it through again:</p> <p>It's calling BaseSerializer.save() in rest_framework/serializers.py with a CartSerializer object.</p> <pre><code>def save(self, **kwargs): """ Save the deserialized object and return it. """ if isinstance(self.object, list): [self.save_object(item, **kwargs) for item in self.object] if self.object._deleted: [self.delete_object(item) for item in self.object._deleted] else: self.save_object(self.object, **kwargs) return self.object </code></pre> <p>It then calls save_object() on the same class:</p> <pre><code>def save_object(self, obj, **kwargs): """ Save the deserialized object and return it. """ if getattr(obj, '_nested_forward_relations', None): # Nested relationships need to be saved before we can save the # parent instance. for field_name, sub_object in obj._nested_forward_relations.items(): if sub_object: self.save_object(sub_object) setattr(obj, field_name, sub_object) obj.save(**kwargs) if getattr(obj, '_m2m_data', None): for accessor_name, object_list in obj._m2m_data.items(): setattr(obj, accessor_name, object_list) del(obj._m2m_data) if getattr(obj, '_related_data', None): for accessor_name, related in obj._related_data.items(): if isinstance(related, RelationsList): # Nested reverse fk relationship for related_item in related: fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name setattr(related_item, fk_field, obj) self.save_object(related_item) # Delete any removed objects if related._deleted: [self.delete_object(item) for item in related._deleted] elif isinstance(related, models.Model): # Nested reverse one-one relationship fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name setattr(related, fk_field, obj) self.save_object(related) else: # Reverse FK or reverse one-one setattr(obj, accessor_name, related) del(obj._related_data) </code></pre> <p>The Cart object has a <code>_related_data</code> field that is set to a dict:</p> <pre><code>{'cartitems': None} </code></pre> <p>Hence, on the second-last line, it calls setattr in django/db/models/fields/related.py:</p> <pre><code>def __set__(self, instance, value): if instance is None: raise AttributeError("Manager must be accessed via instance") manager = self.__get__(instance) # If the foreign key can support nulls, then completely clear the related set. # Otherwise, just move the named objects into the set. if self.related.field.null: manager.clear() manager.add(*value) </code></pre> <p>It's this last liner (manager.add(*value)) that causes the:</p> <pre><code>TypeError: add() argument after * must be a sequence, not NoneType </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload