Ticket #3182: 3182.update_or_create-only.3.diff

File 3182.update_or_create-only.3.diff, 8.4 KB (added by Antti Kaihola, 12 years ago)

Adds .update_or_create() to QuerySet and Manager, without adding .update() to model instances. I attempted to make this atomic. Patch against git HEAD as of Jun 8 2012.

  • django/db/models/manager.py

    diff --git a/django/db/models/manager.py b/django/db/models/manager.py
    index e1bbf6e..c32b45e 100644
    a b class Manager(object):  
    133133    def get_or_create(self, **kwargs):
    134134        return self.get_query_set().get_or_create(**kwargs)
    135135
     136    def update_or_create(self, **kwargs):
     137        return self.get_query_set().update_or_create(**kwargs)
     138
    136139    def create(self, **kwargs):
    137140        return self.get_query_set().create(**kwargs)
    138141
  • django/db/models/query.py

    diff --git a/django/db/models/query.py b/django/db/models/query.py
    index 755820c..1c2dad5 100644
    a b class QuerySet(object):  
    469469                    # Re-raise the IntegrityError with its original traceback.
    470470                    raise exc_info[1], None, exc_info[2]
    471471
     472    def update_or_create(self, **kwargs):
     473        """
     474        Looks up an object with the given kwargs, creating one if necessary.
     475        If the object already exists, then its fields are updated with the
     476        values passed in the defaults dictionary.
     477        Returns a tuple of (object, created), where created is a boolean
     478        specifying whether an object was created.
     479
     480        TODO: opportunity for refactoring common code with get_or_create()
     481        """
     482        assert kwargs, \
     483            'update_or_create() must be passed at least one keyword argument'
     484        defaults = kwargs.pop('defaults', {})
     485        lookup = kwargs.copy()
     486        for f in self.model._meta.fields:
     487            if f.attname in lookup:
     488                lookup[f.name] = lookup.pop(f.attname)
     489        self._for_write = True
     490        sid = transaction.savepoint(using=self.db)
     491        try:
     492            obj = self.get(**lookup)
     493            create = False
     494        except self.model.DoesNotExist:
     495            params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
     496            obj = self.model(**params)
     497            create = True
     498        for attname, value in defaults.items():
     499            setattr(obj, attname, value)
     500        try:
     501            obj.save(force_insert=create, using=self.db)
     502            transaction.savepoint_commit(sid, using=self.db)
     503            return obj, create
     504        except IntegrityError:
     505            transaction.savepoint_rollback(sid, using=self.db)
     506            exc_info = sys.exc_info()
     507            raise exc_info[1], None, exc_info[2]
     508
    472509    def latest(self, field_name=None):
    473510        """
    474511        Returns the latest object, according to the model's 'get_latest_by'
  • docs/ref/models/querysets.txt

    diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
    index eef2272..dfe04a0 100644
    a b has a side effect on your data. For more, see `Safe methods`_ in the HTTP spec.  
    13421342
    13431343.. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
    13441344
     1345update_or_create
     1346~~~~~~~~~~~~~~~~
     1347
     1348.. method:: update_or_create(**kwargs)
     1349
     1350.. versionadded:: 1.5
     1351
     1352A convenience method for looking up an object with the given kwargs, and then
     1353either updating the values of the object if one is found or creating an
     1354object if one was not found.
     1355
     1356Like ``get_or_create()``, this method returns a tuple of ``(object, created)``,
     1357where ``object`` is the updated or created object and ``created`` is a boolean
     1358specifying whether a new object was created.
     1359
     1360This is meant as a shortcut to the following type of code::
     1361
     1362    obj, created = Person.objects.get_or_create(first_name='John', last_name='Lennon',
     1363                       defaults={'birthday': date(1940, 10, 9)})
     1364    if not created:
     1365        obj.update(birthday=date(1940, 10, 9))
     1366
     1367This pattern gets quite unwieldy as the number of fields in a model goes up.
     1368The above example can be rewritten using ``update_or_create()`` like so::
     1369
     1370    obj, created = Person.objects.update_or_create(first_name='John', last_name='Lennon',
     1371                       defaults={'birthday': date(1940, 10, 9)})
     1372
     1373Any keyword arguments passed to ``update_or_create()`` — *except* an optional
     1374one called ``defaults`` — will be used in a :meth:`get()` call. If an object is
     1375found, ``update_or_create()`` updates values of its fields according to
     1376``defaults`` and returns a tuple of that object and ``False``. If an object is
     1377*not* found, ``update_or_create()`` will instantiate and save a new object,
     1378returning a tuple of the new object and ``True``. The new object will be created
     1379in a similar way to ``get_or_create()``.
     1380
     1381The ``defaults`` parameter should be a dict of attribute-value pairs that
     1382you want to update. If ``defaults`` is empty or not specified, then
     1383``update_or_create()`` will act exactly like ``get_or_create()`` since there
     1384would be nothing to update.
     1385
     1386As with ``get_or_create()``, if you need to use ``update_or_create()`` in a
     1387view, please make sure to use it only in ``POST`` requests unless you have a
     1388good reason not to. ``GET`` requests shouldn't have any effect on data; use
     1389``POST`` whenever a request to a page has a side effect on your data. For
     1390more, see `Safe methods`_ in the HTTP spec.
     1391
     1392.. _Safe methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
     1393
    13451394bulk_create
    13461395~~~~~~~~~~~
    13471396
  • new file tests/modeltests/update_or_create/models.py

    diff --git a/tests/modeltests/update_or_create/__init__.py b/tests/modeltests/update_or_create/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/modeltests/update_or_create/models.py b/tests/modeltests/update_or_create/models.py
    new file mode 100644
    index 0000000..32ac33f
    - +  
     1"""
     2XX. update_or_create()
     3
     4``get_or_create()`` does what it says: it tries to look up an object with the
     5given parameters. If an object isn't found, it creates one with the given
     6parameters. Otherwise it updates the existing object with the given default
     7values.
     8"""
     9
     10from django.db import models
     11
     12class Person(models.Model):
     13    first_name = models.CharField(max_length=100)
     14    last_name = models.CharField(max_length=100)
     15    birthday = models.DateField()
     16
     17    def __str__(self):
     18        return '%s %s, Birthday: %s' % (self.first_name, self.last_name,
     19                                        self.birthday)
     20
     21    class Meta:
     22        ordering = ('last_name',)
  • new file tests/modeltests/update_or_create/tests.py

    diff --git a/tests/modeltests/update_or_create/tests.py b/tests/modeltests/update_or_create/tests.py
    new file mode 100644
    index 0000000..53c3d33
    - +  
     1from __future__ import absolute_import
     2
     3from datetime import date
     4
     5from django.test import TestCase
     6
     7from .models import Person
     8
     9
     10class UpdateOrCreateTests(TestCase):
     11    def test_update_or_create(self):
     12        Person.objects.create(
     13            first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)
     14        )
     15
     16        p, created = Person.objects.update_or_create(
     17            first_name='John', last_name='Lennon', defaults={
     18                'birthday': date(1970, 10, 9)
     19            }
     20        )
     21        self.assertFalse(created)
     22        self.assertEqual(Person.objects.count(), 1)
     23
     24        p, created = Person.objects.update_or_create(
     25            first_name='George', last_name='Harrison', defaults={
     26                'birthday': date(1943, 2, 25)
     27            }
     28        )
     29        self.assertTrue(created)
     30        self.assertEqual(Person.objects.count(), 2)
     31
     32        # If we execute the exact same statement, it won't create a Person.
     33        p, created = Person.objects.update_or_create(
     34            first_name='George', last_name='Harrison', defaults={
     35                'birthday': date(1943, 2, 25)
     36            }
     37        )
     38        self.assertFalse(created)
     39        self.assertEqual(Person.objects.count(), 2)
     40
     41        # update_or_create() can take an empty 'defaults' parameter, but in
     42        # this situation behaves exactly like get_or_create().  This is useful
     43        # if you are building the 'defaults' dictionary dynamically.
     44        p, created = Person.objects.update_or_create(
     45            first_name='George', last_name='Harrison', defaults={}
     46        )
     47        self.assertFalse(created)
     48
     49        # A different name with an empty 'defaults'.
     50        p, created = Person.objects.update_or_create(
     51            first_name='John', last_name='Smith', birthday=date(1950, 2, 10),
     52            defaults={}
     53        )
     54        self.assertTrue(created)
     55        self.assertEqual(Person.objects.count(), 3)
Back to Top