A.objects.getdefault

471 views
Skip to first unread message

Ole Laursen

unread,
Oct 9, 2012, 10:05:17 AM10/9/12
to django-d...@googlegroups.com
Hi!

What do people think of

  A.objects.getdefault(slug="hello")  # returns None if slug doesn't exist
  A.objects.getdefault(slug="hello", default=A())  # returns empty object if slug doesn't exist

I find that in practice, most of the time it would be better to get None back instead of the DoesNotExist exception, but changing .get() is of course impossible without breaking the API.

I do think that

  A.objects.get(slug="hello", default=None)

is more elegant, but it might break some code. Although it does seem really unlikely anyone would add a field named "default" to their model and then use it to locate a unique object.

The only related info I found in the issue tracker and on this list was a thread from 2006.

Ole

Anssi Kääriäinen

unread,
Oct 9, 2012, 10:29:53 AM10/9/12
to Django developers
My approach would be to define .nget() method - just a shorthand for:
try:
obj = qs.get(somecondition)
except DoesNotExist:
obj = None

This would be really simple to implement, and I know I would use it.
It would also be simple to document:
"works exacltly like .get(), but when .get() raises
DoesNotExist .nget() returns None".

If the .getdefault() is wanted, then we could always
have .get(somecondition, __default=None). Using '__' as prefix will
guarantee there will be no name clashes.

- Anssi

Carlos Goldsmith

unread,
Oct 9, 2012, 10:43:01 AM10/9/12
to django-d...@googlegroups.com


Hi -
Personally I would prefer to override the get method intentionally on a per model basis. Or add a customized nget type method as is suggested by Ansii.
I could see new coders getting in the habit of using something like getdefault without putting enough thought into it.

As an engineer I tend to think that beauty is achieved not by what you add but by what you take away so I would personally vote to keep this
out of Django.  But I wouldn't be distraught if it made it in.

-CJ-


--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To post to this group, send email to django-d...@googlegroups.com.
To unsubscribe from this group, send email to django-develop...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.


ptone

unread,
Oct 9, 2012, 12:53:05 PM10/9/12
to django-d...@googlegroups.com
So basically you want:


but without the create part - just an empty(), unsaved, object?

This existing method is so close to what you want - that I'd be inclined to explore whether it could be modified to do more.

In forms we already have the .save(commit=False) pattern - so it is not without reason that we could adopt that for the notion of 'create' on that method.

Because get_or_create takes an open list of kwargs, we can't just use 'commit' - but we could use '__commit'

So it would look like:

obj, created = A.objects.get_or_create(slug="hello", __commit=False)

what the value of created should be in this case is tricky - arguments could be made either way.

so I'm far from certain this sort of overloading of get_or_create is workable, but if a method were added, but if  method were to be added, I'd argue it should follow the get_or_* naming pattern.

This is so easy to do in a custom manager or queryset, the question is whether this is a common enough need to bake into the default.

-Preston

ptone

unread,
Oct 9, 2012, 1:15:55 PM10/9/12
to django-d...@googlegroups.com

Wim Feijen

unread,
Oct 9, 2012, 2:48:35 PM10/9/12
to django-d...@googlegroups.com
For me, get_or_none would prevent a lot of boilerplate code in my views, so I am in favor of adding the method.

Wim

Op dinsdag 9 oktober 2012 19:15:55 UTC+2 schreef ptone het volgende:

Tino de Bruijn

unread,
Oct 9, 2012, 5:04:47 PM10/9/12
to django-d...@googlegroups.com
+1 on a method that returns non upon not finding. We don't need to do:

try:
   val = some_dict.get('key')
except KeyError:
   val = None

either. A nget or get_or_none would save me a lot of boilerplate code in my views as well.

Tino

--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To view this discussion on the web visit https://groups.google.com/d/msg/django-developers/-/_nHUoZZICcQJ.

Anssi Kääriäinen

unread,
Oct 9, 2012, 5:29:50 PM10/9/12
to Django developers
On 9 loka, 20:15, ptone <pres...@ptone.com> wrote:
> Unsurprisingly - this has come up before:
>
> Earlier discussionhttps://groups.google.com/forum/?fromgroups=#!topic/django-developers...
>
> tickets:https://code.djangoproject.com/ticket/17546https://code.djangoproject.com/ticket/2659https://code.djangoproject.com/ticket/11352
>
> All the ticket's were wontfixed

OK, it seems .get_or_none() method is out. I can see the point of that
decision, there are a lot of similar methods that could be added using
the convenience argument (like .first() in conjunction
with .latest()).

But, how about .get() with __default kwarg? No new method, and this is
obviously Pythonic - see dict.get() and dict.pop(). The default
argument for pop() is there only to avoid code like:
try:
val = somedict.pop('somekey')
except KeyError:
val = None

The __default kwarg is easy to implement:
https://github.com/akaariai/django/commit/3ea711544364723b26fcef83dc700f0b359655c9

On the other hand, this is still API bloat... So, I will not push this
issue further without some support for this idea.

It would maybe be time to investigate again the "chainable" manager
methods. Having them available would solve a lot of the convenience
method problems. And a lot of other problems, too...

- Anssi

Michael Hudson-Doyle

unread,
Oct 9, 2012, 5:49:44 PM10/9/12
to django-d...@googlegroups.com
On 10 October 2012 10:29, Anssi Kääriäinen <anssi.ka...@thl.fi> wrote:
> On 9 loka, 20:15, ptone <pres...@ptone.com> wrote:
>> Unsurprisingly - this has come up before:
>>
>> Earlier discussionhttps://groups.google.com/forum/?fromgroups=#!topic/django-developers...
>>
>> tickets:https://code.djangoproject.com/ticket/17546https://code.djangoproject.com/ticket/2659https://code.djangoproject.com/ticket/11352
>>
>> All the ticket's were wontfixed
>
> OK, it seems .get_or_none() method is out. I can see the point of that
> decision, there are a lot of similar methods that could be added using
> the convenience argument (like .first() in conjunction
> with .latest()).
>
> But, how about .get() with __default kwarg?

My experience is that the _only_ default you will ever use in this
situation is None.

I think this is because QuerySet.get() returns a full blown object. A
default object would have to be some other full blown object, and it's
unlikely that you'll be in the situation where you want to find an
object but some other object is an acceptable alternative. You might
want to find an object and create it if it does not exist -- this is
why get_or_create is a useful API :-)

Dictionaries by contrast can contain 'primitive' types as values and
they have acceptable and varying 'default' values -- 0, '', [] etc.
Many uses of get-with-default could be replaced with a defaultdict.

FWIW, get_or_none() seems useful to me but I can live without it.

Cheers,
mwh

Anssi Kääriäinen

unread,
Oct 9, 2012, 6:11:59 PM10/9/12
to Django developers
On 10 loka, 00:49, Michael Hudson-Doyle <mica...@gmail.com> wrote:
> > OK, it seems .get_or_none() method is out. I can see the point of that
> > decision, there are a lot of similar methods that could be added using
> > the convenience argument (like .first() in conjunction
> > with .latest()).
>
> > But, how about .get() with __default kwarg?
>
> My experience is that the _only_ default you will ever use in this
> situation is None.

Good point - it seems all usages of __default with non-None values
would be abuse of the API. So, maybe the kwarg could be named
'__allow_none' or something along those lines?

> FWIW, get_or_none() seems useful to me but I can live without it.

For the record: that is my take on this, too.

- Anssi

Ole Laursen

unread,
Oct 11, 2012, 10:45:46 AM10/11/12
to django-d...@googlegroups.com
On Tuesday, October 9, 2012 7:15:55 PM UTC+2, ptone wrote:

This was the thread I referred to. If was from 2006 and ended up being about something else.
 
tickets:

Closed as dupe with no further analysis.
 

This is about a different API which is a bit more cumbersome. And it was closed 6 years ago with a - "this could be feature creep".
 

This is a different proposal.


Just to expand slightly on my proposal - I don't actually want to get anything else than None out of get. I just want something that makes intuitive sense to a Python programmer and saves me from the annoying try-except pattern or the related:

    objs = SomeClass.objects.filter(slug="xyz")
    obj = None
    if objs:
        obj = objs[0]

which I often end up shortening as

    obj = SomeClass.objects.filter(slug="xyz")
    if obj:
        obj = obj[0]

You can't get a zero or one objects out of the ORM elegantly, something I find I have to do relatively often in some code bases. I don't consider try-except intuitive for a situation where the exception case isn't exceptional.

default=None makes sense when you are used to dicts, .first() or .get_or_none() or .getnone() or .single() would be fine too.


Ole

Marijonas Petrauskas

unread,
Oct 11, 2012, 1:00:54 PM10/11/12
to django-d...@googlegroups.com
You can use:

obj = next(iter(SomeModel.objects.filter(foo='bar')), None)

The 'iter' part is not particularly elegant, but it's the only one-liner known to me.


-- Marijonas

--
You received this message because you are subscribed to the Google Groups "Django developers" group.
To view this discussion on the web visit https://groups.google.com/d/msg/django-developers/-/r8vZ2A9tIkgJ.

Daniel Moisset

unread,
Oct 11, 2012, 1:55:45 PM10/11/12
to django-d...@googlegroups.com
On Thu, Oct 11, 2012 at 2:00 PM, Marijonas Petrauskas <mar...@gmail.com> wrote:
> You can use:
>
> obj = next(iter(SomeModel.objects.filter(foo='bar')), None)
>
> The 'iter' part is not particularly elegant, but it's the only one-liner
> known to me.
>

obj, = SomeModel.objects.filter(foo='bar') or [None]

but we're getting deep into django-users land.

D.

Chris Wilson

unread,
Oct 12, 2012, 9:35:29 AM10/12/12
to django-d...@googlegroups.com
Hi all,

On Thu, 11 Oct 2012, Daniel Moisset wrote:

> obj, = SomeModel.objects.filter(foo='bar') or [None]

Daniel's solution is elegant, but far from clear or clean.

I'm strongly in favour of a simple, obvious way to do the common thing,
which is to return None if the object doesn't exist, instead of throwing
an exception. My preferred method names would be .nget(), .get_or_none()
or .first().

May I invoke the Zen of Python to add more than +1 to votes for this
change? (not that my vote counts anyway, of course :)

+1 Beautiful is better than ugly.
+1 Explicit is better than implicit.
+1 Simple is better than complex.
+1 Flat is better than nested.
+1 Readability counts.
-1 Special cases aren't special enough to break the rules.
+1 Although practicality beats purity.
+1 There should be one-- and preferably only one --obvious way to do
it.
+1 Now is better than never. :)
+1 If the implementation is hard to explain, it's a bad idea. (the
alternative one-liner proposed by Daniel is hard to explain)
+1 If the implementation is easy to explain, it may be a good idea.

+9 Total

Cheers, Chris.
--
Aptivate | http://www.aptivate.org | Phone: +44 1223 967 838
Future Business, Cam City FC, Milton Rd, Cambridge, CB4 1UY, UK

Aptivate is a not-for-profit company registered in England and Wales
with company number 04980791.

Ole Laursen

unread,
Oct 15, 2012, 8:13:12 AM10/15/12
to django-d...@googlegroups.com
On Friday, October 12, 2012 3:35:53 PM UTC+2, Chris Wilson wrote:
I'm strongly in favour of a simple, obvious way to do the common thing,
which is to return None if the object doesn't exist, instead of throwing
an exception. My preferred method names would be .nget(), .get_or_none()
or .first().

IMHO the fact that we have all these inventive solutions is ample proof that people do come across this problem and find it annoying. I just found out today that there's a somewhat related .latest().

Anyway, without a sponsor with commit access I guess this is dead, so I'll shut up now. Thank you for your time.


Ole

Anssi Kääriäinen

unread,
Oct 15, 2012, 8:42:39 AM10/15/12
to django-d...@googlegroups.com, Ole Laursen
For the record, I still do like this idea, specifically .get_or_none().

It seems there is significant support for this idea. I do think this method is Pythonic. There are cases where no match from get() isn't exceptional, and in such cases try-except just feels wrong.

The counter argument is that this is API bloat, and this will set an example for more API bloat.

In the end this is a decision with almost no technical considerations and a lot of "good taste" considerations. So, this seems like BDFL area.

If there are no signs from BDFL that .get_or_none() is acceptable, then lets bury this one. If it is acceptable, then I am willing to do all the work to get this committed.

 - Anssi

Tom Evans

unread,
Oct 15, 2012, 10:03:04 AM10/15/12
to django-d...@googlegroups.com
Where would this method live? On the queryset/manager? I'm not
convinced it should be there, since it's a utility method.

My personal ideal would be a new django.shortcuts.get_object_or_none
shortcut function, as I can see using this as an analogue to
get_object_or_404, but I have a feeling shortcuts is not intended to
grow.

Cheers

Tom

Jacob Kaplan-Moss

unread,
Oct 15, 2012, 2:19:31 PM10/15/12
to django-d...@googlegroups.com
On Mon, Oct 15, 2012 at 7:42 AM, Anssi Kääriäinen
<anssi.ka...@thl.fi> wrote:
> In the end this is a decision with almost no technical considerations and a
> lot of "good taste" considerations. So, this seems like BDFL area.

Hi!

After thinking a bit, I'm +1 on the idea, and I think
`Queryset.first()` is the right name (which implies we should probably
have a `last()` for completion).

This mirrors the name used by a few other ORM/collection APIs that I
think have really nice APIS:

* SQLalchemy (http://docs.sqlalchemy.org/en/rel_0_7/orm/query.html#sqlalchemy.orm.query.Query.first)
* Storm (http://people.canonical.com/~therve/storm/storm.store.ResultSet.html#first)
* Backbone (http://backbonejs.org/#Collection-Underscore-Methods /
http://documentcloud.github.com/underscore/#first)

If someone produces a patch I'll review it and commit it.

Jacob

Michael Hudson-Doyle

unread,
Oct 15, 2012, 6:33:28 PM10/15/12
to django-d...@googlegroups.com
To me, first() is something else -- it implies an order (Storm will
raise an exception if you call first() on an result set without an
order) and it implies that it will not raise if the query matches more
than one object. I thought the API proposed here more closely
resembled Storm's "one()" or SQLalchemy's "scalar()" (or maybe its
just how "get()" works already in SQLAchemy).

Not saying it wouldn't be useful, just different...

Cheers,
mwh

Wim Feijen

unread,
Nov 19, 2012, 4:48:36 PM11/19/12
to django-d...@googlegroups.com
Hi,

I do like the first() method and went ahead and *tried* to implement it.

Ticket:

Patch, including tests and doc changes:

Unfortunately, tests fail. Probably ordering is wrong, or I am making a stupid mistake.

Can somebody experienced at db/models/query.py have a look at my patch? 

Thanks!

Wim

Wim Feijen

unread,
Nov 28, 2012, 6:13:54 PM11/28/12
to django-d...@googlegroups.com
Hi, the patch has been updated and now works. 

Still, feedback would be appreciated. So, Anssi, Jacob?

- Wim

Op maandag 19 november 2012 22:48:36 UTC+1 schreef Wim Feijen het volgende:

Anssi Kääriäinen

unread,
Nov 29, 2012, 2:12:28 AM11/29/12
to Django developers
On 29 marras, 01:13, Wim Feijen <wimfei...@gmail.com> wrote:
> Hi, the patch has been updated and now works.
>
> Still, feedback would be appreciated. So, Anssi, Jacob?

Apart of some whitespace errors the patch looks good to me.

There isn't last() method in the patch. Implementing one is going to
be a little more challenging as one needs to change the direction of
all the ordering clauses. Do we want one in the same patch?

A bigger problem might be that we already have .latest() which does
something a bit different. I wonder if having both .last(filter_args)
and .latest(by_field) is going to be confusing.

Another API issue is that should .first() check for some ordering?
This could add some protection. In testing conditions things might
work, but when updates are done to the rows the expected ordering
suddenly changes. One option is to do automatic ordering on PK if
there isn't any other ordering present.

I still like the idea of .get_default() mainly for the added "if
multiple objects returned, then throw an error" protection it gives.
From implementation/maintenance perspective these are really easy
additions, from API bloat perspective maybe not...

- Anssi

Wim Feijen

unread,
Nov 29, 2012, 5:45:38 PM11/29/12
to django-d...@googlegroups.com
Hi Anssi,

When I thought about it, the most prominent usecase seemed to me of getting one of a set of many and expecting just one result. In that case, get_or_none would suffice. However, as a method name, I prefer first() for being concise. 

For me it is ok to just have the first() method without last(). For example, sqlalchemy defines first() but not last(). Although jquery does define both.

I agree that ordering should be applied and in case no ordering is specified on the model, I really would like to use ordering by pk. Doesn't filter provide results by default in that order? Unfortunately, I was not able to comprehend the workings of clone so I am not sure.

Regards, Wim

Op donderdag 29 november 2012 08:12:28 UTC+1 schreef Anssi Kääriäinen het volgende:

Wim Feijen

unread,
Jan 10, 2013, 2:27:55 AM1/10/13
to django-d...@googlegroups.com
Hi,

Ticket 19326 has been marked as ready for check-in for some time. Can some-one have a look at it?


Thanks,

Wim


Op donderdag 29 november 2012 23:45:38 UTC+1 schreef Wim Feijen het volgende:

Anssi Kääriäinen

unread,
Jan 20, 2013, 1:51:27 AM1/20/13
to Django developers
On 10 tammi, 09:27, Wim Feijen <wimfei...@gmail.com> wrote:
> Hi,
>
> Ticket 19326 has been marked as ready for check-in for some time. Can
> some-one have a look at it?
>
>  https://code.djangoproject.com/ticket/19326
>
> Thanks,
>
> Wim

I did some more polish to the patch. There is now also .last() method,
and if there is no ordering in the queryset, then it will be
automatically ordered by the primary key.

I didn't commit the patch yet, as I wonder if there will be confusion
about .latest(by_field), .last(filter_args). earliest(by_field)
and .first(filter_args)?

Currently, the usage is this:
a =
Article.objects.order_by('headline').first(pub_date__year=2005)
which will return first article by headline if any found or None if no
match. .last() will just change the ordering by first
calling .reverse() on the qs.

The patch is 100% ready for commit as far as I am concerned (cursory
check of the changes doesn't hurt, of course). So, if one of the BDFLs
sees the API as fine just go and commit the patch.

Patch available from https://github.com/akaariai/django/compare/ticket_19326.

- Anssi

Wim Feijen

unread,
Jan 22, 2013, 6:03:22 PM1/22/13
to django-d...@googlegroups.com
Hi Selwin and Anssi,

Anssi, thanks for improving the patch and getting it ready for commit! 

Selwin, you are right that .filter() and .first() are very similar. Rationale for .first() is to get rid of the pattern 
try: 
    instance = ModelX.objects.get 
except ObjectDoesNotExist:
    instance = None
, which is a very common pattern so an exception seems out of place. Using filter to get the first result or none would still require three lines. Other alternative patterns are in this thread.

For me, example use cases would be Address.objects.first() which would return the first address in a list, when browsing through all addresses in detail view, or page.music_files.first() where we just have one music file per page or none. MusicFile.objects.first(page=page) should work too.

Indeed, .earliest('timestamp') could be expressed as .order_by('timestamp').first() , and latest similarly. In my opinion these are consistent with .filter() and .get().

Default ordering by id if no order is specified is the same behaviour as in the admin, so I would argue not to raise an exception when no ordering is specified, but to keep it like it is and order by id. Thanks for adding that Anssi!

Looking forward to other input,

Best regards,

Wim


On Sunday, 20 January 2013 12:18:22 UTC+1, Selwin Ong wrote:
Hi Anssi,

Shouldn't first() and last() raise an exception if no ordering is specified? This keeps it consistent with latest() which requires the user to explicitly specify what field it wants to use as ordering.

Also, like you mentioned, IMHO these APIs are too similar (first, earliest, last and latest) in name, yet the usage is completely different, with latest() expecting a field name, the other filter args. What I also don't like is that the filter_args expected by last() and first() are:
1. Doesn't allow exclude arguments
2. We already have .filter() method which does the same thing (filters a queryset with passed kwargs)

So if I may suggest, I think a better option would be to change the methods first() and last() to behave more like latest(), but they should return None when the query returns no result. Example usage:

User.objects.exclude(a=b).filter(c=d).first('id') # Returns None if there's no match
 
user = User.objects.exclude(a=b).filter(c=d).last('id')
if user:
   # do things...

If last() and first() are introduced, perhaps we can also deprecate latest() in the future because they're very similar.

What do you guys think?

Best,
Selwin

Mike Fogel

unread,
Jan 26, 2013, 7:43:49 PM1/26/13
to django-d...@googlegroups.com
+1 to replacing earliest() and latest() with order_by('field').first() and .last(), respectively. I'm not a fan of the implied semantics of 'earliest' and 'latest' - as if they only worked with time-based fields.

Mike

Selwin Ong

unread,
Jan 26, 2013, 8:12:28 PM1/26/13
to django-d...@googlegroups.com
Hi Wim,

I think there's a slight misunderstanding here. I completely agree with you that .first() and .last() should return None if there's no matching row :). The syntax I proposed:

    User.objects.exclude(a=b).filter(c=d).first('id')  # Returns None if there's no matching row
    User.objects.order_by('id').exclude(a=b).filter(c=d).first()  # Does the same thing as above

Best,
Selwin
Reply all
Reply to author
Forward
0 new messages