Login

Update only selected model fields

Author:
jcrocholl
Posted:
November 22, 2007
Language:
Python
Version:
.96
Tags:
field model save performance consistency update fields integrity
Score:
4 (after 4 ratings)

Watch out! Previous versions of this snippet (without the values list) were vulnerable to SQL injection attacks. The "correct" solution is probably to wait until ticket 4102 hits the trunk. But here's my temporary fix while we wait for that happy day.

Django's model.save() method is a PITA:

  1. It does a SELECT first to see if the instance is already in the database.
  2. It has additional database performance overhead because it writes all fields, not just the ones that have been changed.
  3. It overwrites other model fields with data that may be out of date. This is a real problem in concurrent applications, like almost all web apps.

If you just want to update a field or two on a model instance which is already in the database, try this:

update_fields(user,
    email='user@example.com',
    is_staff=True,
    last_login=datetime.now())

Or you can add it to your models (see below) and then do this:

user.update_fields(
    email='user@example.com',
    is_staff=True,
    last_login=datetime.now())

To add it to your model, put it in a module called granular_update, then write this in your models.py:

import granular_update                                                      
class User(models.Model):                                                
    email = models.EmailField()                          
    is_staff = models.BooleanField()                                          
    last_login = models.DateTimeField()
    update_fields = granular_update.update_fields
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from django.db import models, connection, transaction

def update_fields(self, **kwargs):
    """                                                                         
    Update selected model fields in the database, but leave the other           
    fields alone. Use this rather than model.save() for performance             
    and data consistency.                                                       
    """
    sql = ['UPDATE', connection.ops.quote_name(self._meta.db_table), 'SET']
    values = []
    for field_name in kwargs:
        setattr(self, field_name, kwargs[field_name])
        field = self._meta.get_field(field_name)
        value = field.get_db_prep_save(kwargs[field_name])
        if isinstance(value, models.Model):
            value = value.id
        sql.extend((connection.ops.quote_name(field.column), '=', '%s', ','))
        values.append(value)
    sql.pop(-1) # Remove the last comma                                         
    sql.extend(['WHERE', 'id', '=', '%s'])
    values.append(self.id)
    sql = ' '.join(sql)
    connection.cursor().execute(sql, values)
    transaction.commit_unless_managed()

update_fields.alters_data = True

More like this

  1. Improved Pickled Object Field by taavi223 5 years, 7 months ago
  2. PatchModelForm - A ModelForm subclass with the semantics of the PATCH HTTP method by gnrfan 2 years, 10 months ago
  3. Display values from a bound (submitted) form by masida 1 year ago
  4. Mobilize your Django site by stevena0 5 years, 11 months ago
  5. DKIM Email Backend by DrMeers 4 years, 11 months ago

Comments

dotsbb (on May 25, 2010):

Too lengthy.

The same functionality in one line:

def update_fields(self, **kwargs):
    self.objects.filter(pk=self.pk).update(**kwargs)
update_fields.alters_data = True

#

dotsbb (on May 31, 2010):

def update_fields(self, **kwargs):
    self.__class__.objects.filter(pk=self.pk).update(**kwargs)

#

Please login first before commenting.