When I ask the model manager to get an object, it raises DoesNotExist开发者_如何学C
when there is no matching object.
go = Content.objects.get(name="baby")
Instead of DoesNotExist
, how can I have go
be None
instead?
There is no 'built in' way to do this. Django will raise the DoesNotExist exception every time. The idiomatic way to handle this in python is to wrap it in a try catch:
try:
go = SomeModel.objects.get(foo='bar')
except SomeModel.DoesNotExist:
go = None
What I did do, is to subclass models.Manager, create a safe_get
like the code above and use that manager for my models. That way you can write: SomeModel.objects.safe_get(foo='bar')
.
Since django 1.6 you can use first() method like so:
Content.objects.filter(name="baby").first()
You can create a generic function for this.
def get_or_none(classmodel, **kwargs):
try:
return classmodel.objects.get(**kwargs)
except classmodel.DoesNotExist:
return None
Use this like below:
go = get_or_none(Content,name="baby")
go
will be None
if no entry matches else will return the Content entry.
Note:It will raises exception MultipleObjectsReturned
if more than one entry returned for name="baby"
.
You should handle it on the data model to avoid this kind of error but you may prefer to log it at run time like this:
def get_or_none(classmodel, **kwargs):
try:
return classmodel.objects.get(**kwargs)
except classmodel.MultipleObjectsReturned as e:
print('ERR====>', e)
except classmodel.DoesNotExist:
return None
From django docs
get()
raises aDoesNotExist
exception if an object is not found for the given parameters. This exception is also an attribute of the model class. TheDoesNotExist
exception inherits fromdjango.core.exceptions.ObjectDoesNotExist
You can catch the exception and assign None
to go.
from django.core.exceptions import ObjectDoesNotExist
try:
go = Content.objects.get(name="baby")
except ObjectDoesNotExist:
go = None
You can do it this way:
go = Content.objects.filter(name="baby").first()
Now go variable could be either the object you want or None
Ref: https://docs.djangoproject.com/en/1.8/ref/models/querysets/#django.db.models.query.QuerySet.first
To make things easier, here is a snippet of the code I wrote, based on inputs from the wonderful replies here:
class MyManager(models.Manager):
def get_or_none(self, **kwargs):
try:
return self.get(**kwargs)
except ObjectDoesNotExist:
return None
And then in your model:
class MyModel(models.Model):
objects = MyManager()
That's it. Now you have MyModel.objects.get() as well as MyModel.objetcs.get_or_none()
you could use exists
with a filter:
Content.objects.filter(name="baby").exists()
#returns False or True depending on if there is anything in the QS
just an alternative for if you only want to know if it exists
It's one of those annoying functions that you might not want to re-implement:
from annoying.functions import get_object_or_None
#...
user = get_object_or_None(Content, name="baby")
Maybe is better you use:
User.objects.filter(username=admin_username).exists()
Handling exceptions at different points in your views could really be cumbersome..What about defining a custom Model Manager, in the models.py file, like
class ContentManager(model.Manager):
def get_nicely(self, **kwargs):
try:
return self.get(kwargs)
except(KeyError, Content.DoesNotExist):
return None
and then including it in the content Model class
class Content(model.Model):
...
objects = ContentManager()
In this way it can be easily dealt in the views i.e.
post = Content.objects.get_nicely(pk = 1)
if post:
# Do something
else:
# This post doesn't exist
If you want a simple one-line solution that doesn't involve exception handling, conditional statements or a requirement of Django 1.6+, do this instead:
x = next(iter(SomeModel.objects.filter(foo='bar')), None)
I think it isn't bad idea to use get_object_or_404()
from django.shortcuts import get_object_or_404
def my_view(request):
my_object = get_object_or_404(MyModel, pk=1)
This example is equivalent to:
from django.http import Http404
def my_view(request):
try:
my_object = MyModel.objects.get(pk=1)
except MyModel.DoesNotExist:
raise Http404("No MyModel matches the given query.")
You can read more about get_object_or_404() in django online documentation.
From django 1.7 onwards you can do like:
class MyQuerySet(models.QuerySet):
def get_or_none(self, **kwargs):
try:
return self.get(**kwargs)
except self.model.DoesNotExist:
return None
class MyBaseModel(models.Model):
objects = MyQuerySet.as_manager()
class MyModel(MyBaseModel):
...
class AnotherMyModel(MyBaseModel):
...
The advantage of "MyQuerySet.as_manager()" is that both of the following will work:
MyModel.objects.filter(...).get_or_none()
MyModel.objects.get_or_none()
This is a copycat from Django's get_object_or_404 except that the method returns None. This is extremely useful when we have to use only()
query to retreive certain fields only. This method can accept a model or a queryset.
from django.shortcuts import _get_queryset
def get_object_or_none(klass, *args, **kwargs):
"""
Use get() to return an object, or return None if object
does not exist.
klass may be a Model, Manager, or QuerySet object. All other passed
arguments and keyword arguments are used in the get() query.
Like with QuerySet.get(), MultipleObjectsReturned is raised if more than
one object is found.
"""
queryset = _get_queryset(klass)
if not hasattr(queryset, 'get'):
klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
raise ValueError(
"First argument to get_object_or_none() must be a Model, Manager, "
"or QuerySet, not '%s'." % klass__name
)
try:
return queryset.get(*args, **kwargs)
except queryset.model.DoesNotExist:
return None
I use Django 2.2.16. And this is how I solve this problem:
from typing import Any
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.db.models.base import ModelBase
from django.db.models.manager import Manager
class SManager(Manager):
def get_if_exist(self, *args: Any, **kwargs: Any):
try:
return self.get(*args, **kwargs)
except ObjectDoesNotExist:
return None
class SModelBase(ModelBase):
def _prepare(cls):
manager = SManager()
manager.auto_created = True
cls.add_to_class("objects", manager)
super()._prepare()
class Meta:
abstract = True
class SModel(models.Model, metaclass=SModelBase):
managers = False
class Meta:
abstract = True
And after that, in every models, you just need to import in:
from custom.models import SModel
class SUser(SModel):
pass
And in views
, you can call like this:
SUser.objects.get_if_exist(id=1)
Here's a variation on the helper function that allows you to optionally pass in a QuerySet
instance, in case you want to get the unique object (if present) from a queryset other than the model's all
objects queryset (e.g. from a subset of child items belonging to a parent instance):
def get_unique_or_none(model, queryset=None, **kwargs):
"""
Performs the query on the specified `queryset`
(defaulting to the `all` queryset of the `model`'s default manager)
and returns the unique object matching the given
keyword arguments. Returns `None` if no match is found.
Throws a `model.MultipleObjectsReturned` exception
if more than one match is found.
"""
if queryset is None:
queryset = model.objects.all()
try:
return queryset.get(**kwargs)
except model.DoesNotExist:
return None
This can be used in two ways, e.g.:
obj = get_unique_or_none(Model, **kwargs)
as previosuly discussedobj = get_unique_or_none(Model, parent.children, **kwargs)
Without exception:
if SomeModel.objects.filter(foo='bar').exists():
x = SomeModel.objects.get(foo='bar')
else:
x = None
Using an exception:
try:
x = SomeModel.objects.get(foo='bar')
except SomeModel.DoesNotExist:
x = None
There is a bit of an argument about when one should use an exception in python. On the one hand, "it is easier to ask for forgiveness than for permission". While I agree with this, I believe that an exception should remain, well, the exception, and the "ideal case" should run without hitting one.
We can use Django builtin exception which attached to the models named as .DoesNotExist
. So, we don't have to import ObjectDoesNotExist
exception.
Instead doing:
from django.core.exceptions import ObjectDoesNotExist
try:
content = Content.objects.get(name="baby")
except ObjectDoesNotExist:
content = None
We can do this:
try:
content = Content.objects.get(name="baby")
except Content.DoesNotExist:
content = None
I was facing with the same problem too. It's hard to write and read try-except
for each time when you want to get an element from your model as in @Arthur Debert's answer. So, my solution is to create an Getter
class which is inherited by the models:
class Getter:
@classmethod
def try_to_get(cls, *args, **kwargs):
try:
return cls.objects.get(**kwargs)
except Exception as e:
return None
class MyActualModel(models.Model, Getter):
pk_id = models.AutoField(primary_key=True)
...
In this way, I can get the actual element of MyActualModel
or None
:
MyActualModel.try_to_get(pk_id=1)
I prefer this method without using exceptions. It also handles multiple objects as well as no objects.
go_list = Content.objects.filter(name="baby")
if (len(go_list) == 1):
go = go_list[0]
else:
go = None # optionally do other things if there are multiple objects / no objects.
How about a slice? It will parse to a limit 1.
go = Content.objects.filter(name="baby")[0]
精彩评论