It sounds great.

any chance of getting a insert_or_replace function as well?

to do something similar to mysql 5's 

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;


(which is the other common pattern I'm seeing in my code)

regards
Ian

On 06/06/2006, at 3:39 PM, Adrian Holovaty wrote:


Time and time again I have the following Django code:

    try:
        obj = Person.objects.get(first_name='John', last_name='Lennon')
    except Person.DoesNotExist:
        obj = Person(first_name='John', last_name='Lennon',
birthday=date(1940, 10, 9))
        obj.save()

This pattern gets quite unwieldy as the number of fields in a model goes up.

So I'm proposing a Manager.get_or_create() method, which I believe was
Jacob's idea a few months ago. You would pass it keyword arguments to
use in get(), and you'd pass an optional "defaults" keyword argument
which would specify the kwargs to use in creating a new object if the
object matching the get parameters doesn't already exist.

Thus, the above example would be rewritten as:

    obj = Person.objects.get_or_create(first_name='John', last_name='Lennon',
        defaults={'birthday': date(1940, 10, 9)})

Note that I didn't specify first_name and last_name in the defaults
parameter. This is a small time- and keystroke-saving optimization:
Any fields not included in the "defaults" dict will be assumed to be
taken from the lookup arguments. That means, in some very basic
examples, you wouldn't even have to specify the defaults at all.
Example:

    try:
        obj = Country.objects.get(name='Poland')
    except Country.DoesNotExist:
        obj = Country(name='Poland')
        obj.save()

Using get_or_create(), this could be rewritten like so:

    obj = Country.objects.get_or_create(name='Poland')

The catch is that the lookup arguments would have to be "exact"
lookups, rather than "__contains" or "__lt", in order for the
arguments to be valid parameters for "defaults".

Here's my implementation, which I'm using in a personal project as a
custom Manager. I'd like this to be rolled in as a default Manager
method.

class GetOrCreateManager(models.Manager):
    def get_or_create(self, **kwargs):
        assert len(kwargs), 'get_or_create() must be passed at least
one keyword argument'
        defaults = kwargs.pop('defaults', {})
        try:
            return (False, self.get(**kwargs))
        except self.model.DoesNotExist:
            params = dict([(k, v) for k, v in kwargs.items() if '__' not in k])
            params.update(defaults)
            obj = self.model(**params)
            obj.save()
            return (True, obj)

A couple things to note:

* I needed to get both the new object *and* a boolean specifying
whether a new object was created, so get_or_create() returns a tuple
of (new_object_created, object). This is a bit inelegant, but I can't
think of any more elegant ways of returning both of those values.

* Currently this assumes the model has no field named "defaults". This
is a bit of a smell. Better solutions?

Adrian

-- 
Adrian Holovaty
holovaty.com | djangoproject.com


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "Django developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at http://groups.google.com/group/django-developers
-~----------~----~----~----~------~----~------~--~---

Reply via email to