Django no(o)b tips #1: try to use .update() instead of .save() in your views

in django •  5 years ago  (edited)

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 marketIdwas 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!

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!