proposal: get_or_none QuerySet method

2,650 views
Skip to first unread message

Gary Wilson

unread,
Dec 18, 2006, 4:40:15 PM12/18/06
to Django developers
I often find myself writing code like:

try:
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
user = None

What do you think about adding a get_or_none QuerySet method?

def get_or_none(self, **kwargs):
try:
return self.get(**kwargs)
except self.model.DoesNotExist:
return None

Waylan Limberg

unread,
Dec 18, 2006, 5:08:54 PM12/18/06
to django-d...@googlegroups.com

Or how about using something similar to QuearyDict's get(key,
default)? That way the fallback is not restricted to `None`. So
something like this:

user = User.objects.get(pk=user_id, default=None)

Of course, I think that would probably be significantly more work than
your little wrapper function.
--
----
Waylan Limberg
way...@gmail.com

Honza Král

unread,
Dec 18, 2006, 5:14:31 PM12/18/06
to django-d...@googlegroups.com

note neccessarily (beware, quick&ugly proof of concept):
Index: django/db/models/query.py
===================================================================
--- django/db/models/query.py (revision 4227)
+++ django/db/models/query.py (working copy)
@@ -12,6 +12,8 @@

# The string constant used to separate query parts
LOOKUP_SEPARATOR = '__'
+# for get(default)
+NOT_SET='WHATEVER'

# The list of valid query types
QUERY_TERMS = (
@@ -202,7 +204,8 @@
cursor.execute("SELECT COUNT(*)" + sql, params)
return cursor.fetchone()[0]

- def get(self, *args, **kwargs):
+
+ def get(self, default=NOT_SET, *args, **kwargs):
"Performs the SELECT and returns a single object matching the
given keyword arguments."
clone = self.filter(*args, **kwargs)
# clean up SQL by removing unneeded ORDER BY
@@ -210,6 +213,8 @@
clone._order_by = ()
obj_list = list(clone)
if len(obj_list) < 1:
+ if default != NOT_SET:
+ return default
raise self.model.DoesNotExist, "%s matching query does
not exist." % self.model._meta.object_name
assert len(obj_list) == 1, "get() returned more than one %s
-- it returned %s! Lookup parameters were %s" %
(self.model._meta.object_name, len(obj_list), kwargs)
return obj_list[0]


> --
> ----
> Waylan Limberg
> way...@gmail.com
>
> >
>


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

SmileyChris

unread,
Dec 18, 2006, 7:04:21 PM12/18/06
to Django developers
On Dec 19, 11:08 am, "Waylan Limberg" <way...@gmail.com> wrote:
> something like this:
>
> user = User.objects.get(pk=user_id, default=None)

Except this would break (or at least limit the functionality of)
objects which use "default" as a field name.

Waylan Limberg

unread,
Dec 18, 2006, 9:35:05 PM12/18/06
to django-d...@googlegroups.com
Nice job Honza. I was about to suggest just about the same, but
hesitated because of the *args and **kwargs. I just wasn't sure why
that made me hesitate.

Now I know why. Good call SmileyChris. Unfortunately I have no ideas
for a workaround.

Gary Wilson

unread,
Dec 20, 2006, 11:00:46 AM12/20/06
to Django developers

Of course, this didn't stop get_or_create(), which uses "defaults" as a
parameter. If you had an object with an attribute named "default" then
you could still do something like:

obj = MyModel.objects.get(default__exact=42, default=None)

But get_or_create() was new functionality and this would be changing
existing functionality. If that is unacceptable, then maybe a
get_or_default() that optionally takes the "default" parameter, using
None if no "default" specified.

So...
MyModel.objects.get_or_default(pk=1)
would be equivalent to
MyModel.objects.get_or_default(pk=1, default=None)

Joseph Perla

unread,
Dec 20, 2006, 11:14:38 AM12/20/06
to django-d...@googlegroups.com
There should also be an update_or_create() function.  I often find myself get_or_creating, then updating the object's dict if it wasnt just created, then saving.  This can easily be expressed more clearly in one line with this function.
j

On 12/20/06, Gary Wilson <gary....@gmail.com> wrote:

SmileyChris wrote:
> On Dec 19, 11:08 am, "Waylan Limberg" <way...@gmail.com> wrote:
> > something like this:
> >
> > user = User.objects.get (pk=user_id, default=None)

Brantley Harris

unread,
Dec 20, 2006, 2:19:27 PM12/20/06
to django-d...@googlegroups.com
On 12/18/06, Gary Wilson <gary....@gmail.com> wrote:
> What do you think about adding a get_or_none QuerySet method?

+1

On 12/20/06, Joseph Perla <josep...@gmail.com> wrote:
> There should also be an update_or_create() function.

+1

Joseph Perla

unread,
Dec 20, 2006, 11:42:00 PM12/20/06
to django-d...@googlegroups.com
Also, here's the pseudo-ish code for it:

def update_or_create( --same arguments as get_or_create()-- ):
     object, created = Class.objects.get_or_create( --args above-- )
     if not created:
          object.__dict__.update(defaults)
          object.save()
     return object

Well, Class is self or this or whatever it is in python/djangomanagers code.  I'm also not sure how to do the crazy **kwargs stuff.  The rest is clear: if you didn't create it, just update it, save it, and return.
j


On 12/20/06, Joseph Perla <josep...@gmail.com> wrote:

Gary Wilson

unread,
Dec 22, 2006, 12:58:31 AM12/22/06
to Django developers
Joseph Perla wrote:
> There should also be an update_or_create() function. I often find myself
> get_or_creating, then updating the object's dict if it wasnt just created,
> then saving. This can easily be expressed more clearly in one line with
> this function.

This gave me the idea of an update() method for model instances. I
have created a ticket for this with code, documentation, and tests.

http://code.djangoproject.com/ticket/3180

Gary Wilson

unread,
Dec 22, 2006, 2:07:14 AM12/22/06
to Django developers
Joseph Perla wrote:
> Also, here's the pseudo-ish code for it:
>
> def update_or_create( --same arguments as get_or_create()-- ):
> object, created = Class.objects.get_or_create( --args above-- )
> if not created:
> object.__dict__.update(defaults)
> object.save()
> return object
>
> Well, Class is self or this or whatever it is in python/djangomanagers
> code. I'm also not sure how to do the crazy **kwargs stuff. The rest is
> clear: if you didn't create it, just update it, save it, and return.

I have created a ticket for this too and have attached some code:

http://code.djangoproject.com/ticket/3182

Gary Wilson

unread,
Dec 24, 2006, 5:50:10 PM12/24/06
to Django developers
Gary Wilson wrote:
> I have created a ticket for this too and have attached some code:
>
> http://code.djangoproject.com/ticket/3182

Documentation and tests attached too now.

Jacob Kaplan-Moss

unread,
Dec 24, 2006, 10:35:33 PM12/24/06
to django-d...@googlegroups.com

Hm - I get a whole bunch of failures when I apply the patch. Looking closer,
you say it depends on an ``update()`` patch in #3181, but I don't see any such
patch attached to #3181.

Can you update #3182 to include this other patch (wherever it lives), and
close the other ticket as a dup of #3182? Once that's done I'll go ahead and
check this in.

Jacob

Gary Wilson

unread,
Dec 25, 2006, 12:15:21 AM12/25/06
to Django developers
Jacob Kaplan-Moss wrote:
> Hm - I get a whole bunch of failures when I apply the patch. Looking closer,
> you say it depends on an ``update()`` patch in #3181, but I don't see any such
> patch attached to #3181.

Sorry about that, it should be #3180.

> Can you update #3182 to include this other patch (wherever it lives), and
> close the other ticket as a dup of #3182? Once that's done I'll go ahead and
> check this in.

Well, the two tickets are different functionality. I could create a
single patch for both, the only thing was that in the patch for #3180 I
added an "Updating objects" to the db api documentation, but then
realized that it should probably be integrated into the "Saving changes
to objects" section. I haven't spent the time to do that yet.

Jacob Kaplan-Moss

unread,
Dec 25, 2006, 1:05:37 AM12/25/06
to django-d...@googlegroups.com
On 12/24/06 11:15 PM, Gary Wilson wrote:
> Well, the two tickets are different functionality. I could create a
> single patch for both, the only thing was that in the patch for #3180 I
> added an "Updating objects" to the db api documentation, but then
> realized that it should probably be integrated into the "Saving changes
> to objects" section. I haven't spent the time to do that yet.

Yeah, they're different... but I think they make the most sense if they go in
as a unit. I'd prefer a single patch (and tests, docs, etc.) if you're up to it.

Jacob

Gary Wilson

unread,
Dec 25, 2006, 1:18:53 AM12/25/06
to Django developers

Sure, I'll work on the documentation fixes and let you know when done.

Gary Wilson

unread,
Dec 26, 2006, 1:10:05 AM12/26/06
to Django developers

Ok. I reworked the documentation, rolled both patches into one, and
attached it to #3182.

Gary Wilson

unread,
Jan 3, 2007, 7:46:19 PM1/3/07
to Django developers
Ok, after getting sidetracked by update() and update_or_create(), back
to the topic of the OP. Here are some options:

1) Add a get_or_none() method that returns None if object does not
exist.

2) Add a get_or_default() method that accepts a "default" keyword
argument, the value of which gets returned if object does not exist.
If a default is not specified then None gets returned by default.

3) Modify the current get() method to accept a "default" keyword
argument as in option 2), except that instead of returning None if
"default" is not specified, a DoesNotExist exception is raised like
normal. To return None, you would have to explicitly say
"default=None".

Note that this would also be backwards incompatible in that models with
a field named "default" would have to use "default__exact=..." instead
of "default=..." as with get_or_create(). Looking further, it looks
like functions that use get() might also have to change slightly, i.e.
get_or_create().

Comments/votes/suggestions?

Waylan Limberg

unread,
Jan 3, 2007, 8:56:04 PM1/3/07
to django-d...@googlegroups.com
I like 3, but considering the potential complications 2 may make more
sense. Either way, I don't see the value in 1 as in either 2 or 3, the
default can still be set to None.

Gary Wilson

unread,
Jan 14, 2007, 9:48:57 PM1/14/07
to Django developers
On Dec 24 2006, 9:35 pm, Jacob Kaplan-Moss <j...@jacobian.org> wrote:
> On 12/24/06 4:50 PM, Gary Wilson wrote:
>
> >> I have created a ticket for this too and have attached some code:
>
> >>http://code.djangoproject.com/ticket/3182
>
> > Documentation and tests attached too now.Hm - I get a whole bunch of failures when I apply the patch. Looking closer,

> you say it depends on an ``update()`` patch in #3181, but I don't see any such
> patch attached to #3181.
>
> Can youupdate#3182 to include this other patch (wherever it lives), and

> close the other ticket as a dup of #3182? Once that's done I'll go ahead and
> check this in.
>
> Jacob

Any other requests for the new, combined patch?

whiteinge

unread,
Mar 10, 2007, 10:54:10 AM3/10/07
to Django developers
If someone has a moment I would appreciate hearing a status update for
this ticket--it reports ready for checkin, will it still be included
in Django?

I tested the patch and it works as advertised, also the docs are well
written. This is functionality I'm looking forward to.

<http://code.djangoproject.org/ticket/3182>

Thanks!
- whiteinge

Malcolm Tredinnick

unread,
Mar 10, 2007, 2:27:55 PM3/10/07
to django-d...@googlegroups.com

It will probably be applied at some point. It is one of a number of open
tickets. Sorry, but there are lots of things we want to work on all at
the same time.

Malcolm


Reply all
Reply to author
Forward
0 new messages