PROPOSAL: Manager.get_or_create()

26 views
Skip to first unread message

Adrian Holovaty

unread,
Jun 6, 2006, 1:39:14 AM6/6/06
to django-d...@googlegroups.com
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

Ian Holsman

unread,
Jun 6, 2006, 1:58:52 AM6/6/06
to django-d...@googlegroups.com
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

Ivan Sagalaev

unread,
Jun 6, 2006, 5:49:43 AM6/6/06
to django-d...@googlegroups.com
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()
>
>

"Me to!" :-)

So +1 on the proposal.

>* 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).
>

My first reaction is that it should be reversed: the "primary" returned
value is an object itself and a flag is often not important so it feels
like "secondary" value.

Honza Král

unread,
Jun 6, 2006, 8:10:04 AM6/6/06
to django-d...@googlegroups.com
+1 as well, I like the idea

with the returning of the boolean - wouldn't it be possible to create
a simple fla/function on a model that would tell you that so that the
function will only have one return parameter?
something like

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

if obj.just_created():
.....

I see this as much more useful, because in situations I imagine I will
want to use this construct, I usually don't care whether its created
or not, so getting a tuple would only complicate (slightly, but still)
things for me.

just to be clear - +1 even with the tuple thing ;)
great idea


--
Honza Král
E-Mail: Honza...@gmail.com
ICQ#: 107471613
Phone: +420 606 678585

Bill de hÓra

unread,
Jun 6, 2006, 11:46:24 AM6/6/06
to django-d...@googlegroups.com
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.
return (True, obj)

I understand why you might need this, but at the HTTP level, when the
example for this goes into the docs, can it be wrapped inside a
request.POST? That will stop someone somewhere from using get_or_create
via a GET view.

cheers
Bill

Adrian Holovaty

unread,
Jun 6, 2006, 11:48:41 AM6/6/06
to django-d...@googlegroups.com
On 6/6/06, Bill de hÓra <bi...@dehora.net> wrote:
> I understand why you might need this, but at the HTTP level, when the
> example for this goes into the docs, can it be wrapped inside a
> request.POST? That will stop someone somewhere from using get_or_create
> via a GET view.

Sure, Bill -- good call. I think get_or_create() is more useful out of
Web-request contexts (namely, in scripts that import data and do a lot
of object creation), but I'll explicitly point the POST preference in
the docs.

Simon Willison

unread,
Jun 6, 2006, 11:52:11 AM6/6/06
to django-d...@googlegroups.com

On 6 Jun 2006, at 06:39, Adrian Holovaty wrote:

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

It could take two dictionaries instead of using keyword arguments:

obj = Person.objects.get_or_create(
{'first_name': 'John', 'last_name': 'Lennon',


{'birthday': date(1940, 10, 9)}
)

Cheers,

Simon

Jeroen Ruigrok van der Werven

unread,
Jun 6, 2006, 3:29:19 PM6/6/06
to django-d...@googlegroups.com
On 6/6/06, Adrian Holovaty <holo...@gmail.com> wrote:
> So I'm proposing a Manager.get_or_create() method, which I believe was
> Jacob's idea a few months ago.

+1 from my side, with Simon's note in mind.


--
Jeroen Ruigrok van der Werven

Reply all
Reply to author
Forward
0 new messages