Let's consider this basic model of a product in a shopping list web application:
from django.db import models as m
class Product(m.Model):
name = m.CharField(max_length=100)
market = m.ForeignKey(Market, m.SET_NULL)
category = m.ForeignKey(Category, m.SET_DEFAULT, default=2)
info = m.TextField(blank=True, null=True)
you want to update the product with id
"1" using this dictionary as a reference:
product_kwargs = {
'id' : 1,
'categoryId': 2,
'info': 'Don't forget to take the small one!',
}
Using the .save() method
if you've read any django tutorial you would probably do something like this:
product = Product.objects.get(id=product_kwargs['id'])
category = Category.objects.get(id=product_kwargs['categoryId'])
product.category = category
product.info = product_kwargs['info']
product.save()
So, first we need to query the DB for the instance of product with the id
found in product_kwargs
. Then, since product.category
is a foreign key, we can't update it simply by using the category id. In other words, we need first to instantiate category
, and only then we can update it.
Finally remember to call .save(), if we don't every change will be lost!
But now.. what if the categoryId
key was missing from our dictionary? What if a new marketId
was present? Should we use a convoluted series of nested if
for every field in our model? And what about those inconvenient foreign key fields?
Let's try to use .update() instead!
First of all, the update
method is callable only on a queryset, differently from save
, which was called from an instance, but that's easy: simply filter your model based on its id
to create a single element queryset.
product = Product.objects.filter(id=product_kwargs['id'])
Now we want to update it, and we'll use a little trick called field lookup to solve our little foreign keys problem. With a field lookup we can look for a field value of another model without the need to instantiate the model itself, by using a double underscore. To make things clearer:
product.update(
category__id=product_kwargs['categoryId']
)
That's better, but if we are able to change the way our dictionary is formatted, our code will be much more versatile:
product_dict = {
'id': xxx,
'kwargs': {
'info': xxx,
'category__id': xxx,
'market__id': xxx,
}
}
that is, simply separate the id from every other kwargs, now a nested dictionary, and use as key name what will be the actual parameter name to pass on our update
.
The result, exploiting kwargs unpacking, is now this:
Product.objects.filter(
id=product_dict['id']).update(**product_dict['kwargs'])
that's it! You can remove or add any key from kwargs
and, as long as those keys are present in the model and their value corresponds to the field type, the final code won't change a single bit.
Enjoy!