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
>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.
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
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
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.
> * 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
+1 from my side, with Simon's note in mind.
--
Jeroen Ruigrok van der Werven