I am using a custom Django model Manager to create an instance. The fields of the instance are derived from data fetched from a given URL. One of the fields is a filename containing a JSON report that I need to open. My question is - if I determine an error has occurred (file not found, content not valid, etc.), is it reasonable to throw an exception in a create() method and is there a preferred exception type to throw?
The model needs the parsed data to create a valid instance so I would already know the model is not valid before executing the create() method.
class IndexingUpdateRunManager(models.Manager):
def create_from_run(self,run_history_url):
run_info_dict = self.extract_fields_from_url(run_history_url)
run_config_file = run_info_dict["run_config_file"]
report_filename = run_info_dict["status_report_file"]
try:
out_fh = open(report_filename,'r')
report_data = json.loads(out_fh)
status_code=report_data["status"]
except Exception, e:
# throw an exception?
this_run=self.create(run_config_file_used=run_config_file,
report_filename = report_filename,
run_status_code=status_code)
return this_run
class MyUpdateRun(models.Model):
run_config_file_used = models.FilePathField(max_length=1024,
help_text="config file for run")
report_filename = models.FilePathField(max_length=1024,
help_text="status report file for run")
run_status_code = models.IntegerField(help_text="status code for overall run execution")
objects = MyUpdateRunManager()
>>MyUpdateRun.objects.create_from_run("https://server/job_status/builds/200/")
You can raise one ObjectDoesNotExist ( from django.core.exceptions ) and as well you can create your own exception call like:
class MyException(ObjectDoesNotExist):
pass
and raise it in some specific situation that Django doesn't provide a satisfying exception.
Ps.: Your exception can still inherit from the base exception:
class MyException(Exception):
pass
Related
I want to customize Meta class constraints default message. Here is my model that I have been working out.
class BusRoute(BaseModel):
route = models.ForeignKey(Route, on_delete=models.PROTECT)
shift = models.ForeignKey(
Shift,
null=True,
on_delete=models.PROTECT
)
journey_length = models.TimeField(null=True)
class Meta:
default_permissions = ()
verbose_name = 'Bus Route'
verbose_name_plural = 'Bus Routes'
constraints = [
models.UniqueConstraint(
fields=['route', 'shift'],
name='unique_bus_company_route'
)
]
The default constraint error message is as following
Bus Route with this Shift already exists.
Is there any way out I can customize this constraint message to something like,
Bus Route and shift already in use please validate again
I just figured out how to create a custom UniqueConstraint error while using the CBV CreateView and the form ModelForm.
On the ModelForm I added this:
def validate_unique(self):
"""
Call the instance's validate_unique() method and update the form's
validation errors if any were raised.
"""
exclude = self._get_validation_exclusions()
try:
self.instance.validate_unique(exclude=exclude)
except ValidationError as e:
error = {'__all__': "Custom error message."}
self._update_errors(error)
This is mostly the class's default code, except I edited the contents in the except block.
You could use a try and except statement to catch the UniqueConstraint error, and raise your own instead. As far as I know, django doesn't have any provisions to override the unique constraint error directly, so this may be considered as best practice.
Implementation of the idea:
bus_route = BusRoute(**field_data)
try:
bus_route.save()
except IntegrityError as err:
raise CustomException('CUSTOM ERROR MESSAGE')
I have a Post class for my Django app that has several subclasses TextPost, AudioPost, etc, each with their own render_html() method.
class Post(models.Model):
author = models.ForeinKey(User,...)
title = models.CharField(...)
pub_date = models.DateTimeField(...)
...
def render_html(self):
return "rendered title, author date"
class AudioPost(Post):
audioFile = FileField(...)
def render_html(self):
return "Correct audio html"
...
each of these child models has an ModelForm with rules for uploading, validation, and saving.
In a home page view, I'd like to take all posts, arrange them by date, and render them. To me this should be as simple as
## in view
context = { 'posts' : Post.objects.order_by('-pub_date')[:5] }
and
## in template
{{ post.render_html() | safe }}
I remember things working this way for abstract classes in Java. But when I do it this way in Python, the render_html method gets called as if they are each members of the parent class. I've looked up how Django does multi-table inheritence, it seems like I either need to check the generated OneToOneFields one by one until I've found one that doesn't raise an exception, or use the InheritanceManager utility manager. Is one of those two ways the best way to do this or should I do something else?
I solved this via the following method inside Post, which allows me to do
{{ post.get_subclass().render_html() }}
inside the template. Assuming 4 subclasses, AudioPost, VideoPost, TextPost, and RichTextPost:
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
class Post(models.Model):
...
...
def get_subclass(self):
try:
textpost = self.textpost
return textpost
except ObjectDoesNotExist:
pass
try:
audiopost = self.audiopost
return audiopost
except ObjectDoesNotExist:
pass
try:
videopost = self.videopost
return videopost
except ObjectDoesNotExist:
pass
try:
richtextpost = self.richtextpost
return richtextpost
except ObjectDoesNotExist:
pass
raise ObjectDoesNotExist
I would suggest another method for your problem which you can use to get all the subclasses of the base class. It will be a bit of handy as you don't need to get query set for every child class manually
querysets_child = [child_cls.objects.all() for child_cls in vars()['BaseClass'].__subclasses__()]
The method you refer to which works on java but I don't think it can work here. Either you use child classes manually or get all child classes with subclass function mentioned above.
I'm trying to override a custom Django model property via factory_boy for testing purposes. But it seems like it is simply taking the default behavior of the model. is factory boy not able to change the default behaviour of custom attributes?
Here is a basic test I wrote:
models.py:
class Session(models.Model):
name = models.CharField(max_length=100)
#property
def foo(self):
return method_not_callable_in_testing()
def method_not_callable_in_testing():
return 42
SessionFactory.py:
class SessionFactory(factory.django.DjangoModelFactory):
class Meta:
model = Session
name = "session"
foo = 1337
tests.py:
class TestSession(TestCase):
def test_custom_attribute_overwritten_by_factoryboy(self):
session = SessionFactory.create()
self.assertEquals(session.foo, 1337)
When running the tests I get the following error:
F
======================================================================
FAIL: test_custom_attribute_overwritten_by_factoryboy (bar.tests.TestSession)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/sh4ke/repos/foo/bar/tests.py", line 10, in test_custom_attribute_overwritten_by_factoryboy
self.assertEquals(session.foo, 1337)
AssertionError: 42 != 1337
tl;dr
factory_boy can change "custom attributes", (which I assume you mean ones that are not django Fields), but it can't change read-only attributes, which is what you are trying to do.
More info
Your problem is that you are trying to use the factory to set an attribute for a read-only property. Roughly, the combination of factory_boy and django means that the call to SessionFactory() is doing:
session = Session()
session.foo = 1337
But I wouldn't expect this to work because unless you declare the #foo.setter method #property assumes you wanted a read-only property, and so raises attribute error when you attempt to set it.
The weird bit is why you don't see that error, and it turns out that django quietly suppresses it! It sees that your model has the foo attribute by doing getattr(session, 'foo'), but it has wrapped the whole thing including the setattr inside a try: except AttributeError:, so while it detects and raises an attempt to set an attribute that doesn't exist at all, it also catches and quietly ignores the failed case of an attempt to set a read-only property.
I suspect this is a bug... so will probably try and raise it with the django community.
This code is in django/db/models/fields.py It creates/defines an exception?
class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjectDescriptorMethods)):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
# a single "remote" value, on the class that defines the related field.
# In the example "choice.poll", the poll attribute is a
# ReverseSingleRelatedObjectDescriptor instance.
def __init__(self, field_with_rel):
self.field = field_with_rel
self.cache_name = self.field.get_cache_name()
#cached_property
def RelatedObjectDoesNotExist(self):
# The exception can't be created at initialization time since the
# related model might not be resolved yet; `rel.to` might still be
# a string model reference.
return type(
str('RelatedObjectDoesNotExist'),
(self.field.rel.to.DoesNotExist, AttributeError),
{}
)
This is in django/db/models/fields/related.py it raises the said exception above:
def __get__(self, instance, instance_type=None):
if instance is None:
return self
try:
rel_obj = getattr(instance, self.cache_name)
except AttributeError:
val = self.field.get_local_related_value(instance)
if None in val:
rel_obj = None
else:
params = dict(
(rh_field.attname, getattr(instance, lh_field.attname))
for lh_field, rh_field in self.field.related_fields)
qs = self.get_queryset(instance=instance)
extra_filter = self.field.get_extra_descriptor_filter(instance)
if isinstance(extra_filter, dict):
params.update(extra_filter)
qs = qs.filter(**params)
else:
qs = qs.filter(extra_filter, **params)
# Assuming the database enforces foreign keys, this won't fail.
rel_obj = qs.get()
if not self.field.rel.multiple:
setattr(rel_obj, self.field.related.get_cache_name(), instance)
setattr(instance, self.cache_name, rel_obj)
if rel_obj is None and not self.field.null:
raise self.RelatedObjectDoesNotExist(
"%s has no %s." % (self.field.model.__name__, self.field.name)
)
else:
return rel_obj
The problem is that this code:
try:
val = getattr(obj, attr_name)
except related.ReverseSingleRelatedObjectDescriptor.RelatedObjectDoesNotExist:
val = None # Does not catch the thrown exception
except Exception as foo:
print type(foo) # Catches here, not above
won't catch that exception
>>>print type(foo)
<class 'django.db.models.fields.related.RelatedObjectDoesNotExist'>
>>>isinstance(foo, related.FieldDoesNotExist)
False
and
except related.RelatedObjectDoesNotExist:
Raises an AttributeError: 'module' object has no attribute 'RelatedObjectDoesNotExist'
>>>isinstance(foo, related.ReverseSingleRelatedObjectDescriptor.RelatedObjectDoesNotExist)
Traceback (most recent call last):
File "<string>", line 1, in <fragment>
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
which is probably why.
If your related model is called Foo you can just do:
except Foo.DoesNotExist:
Django is amazing when it's not terrifying. RelatedObjectDoesNotExist is a property that returns a type that is figured out dynamically at runtime. That type uses self.field.rel.to.DoesNotExist as a base class.
According to Django documentation:
DoesNotExist
exception Model.DoesNotExist
This exception is raised by the ORM when an expected object is not
found. For example, QuerySet.get() will raise it when no object
is found for the given lookups.
Django provides a DoesNotExist exception as an attribute of
each model class to identify the class of object that could not be
found, allowing you to catch exceptions for a particular model class.
The exception is a subclass of django.core.exceptions.ObjectDoesNotExist.
This is the magic that makes that happen. Once the model has been built up, self.field.rel.to.DoesNotExist is the does-not-exist exception for that model.
If you don't want to import the related model class, you can:
except MyModel.related_field.RelatedObjectDoesNotExist:
or
except my_model_instance._meta.model.related_field.RelatedObjectDoesNotExist:
where related_field is the field name.
Let's say we have the following models:
class MainModel(Model):
pass
class RelatedModel(Model):
main = OneToOneField(MainModel, null=True, related_name="related")
You can get a RelatedObjectDoesNotExist exception with MainModel().related.
You have three options for catching this exception, which you can find by looking at .__class__.__mro__ of the exception:
MainModel.related.RelatedObjectDoesNotExist
RelatedModel.DoesNotExist
django.core.exceptions.ObjectDoesNotExist
MainModel.related.RelatedObjectDoesNotExist
RelatedObjectDoesNotExist is what the question is looking for, but is specific to a nullable OneToOneField:
try:
# Your code here
except MainModel.related.RelatedObjectDoesNotExist:
# Handle exception
RelatedModel.DoesNotExist
Model.DoesNotExist is the parent class of RelatedObjectDoesNotExist. To catch it requires you to be able to import the model in question, but is a more generically useful code pattern.
try:
# Your code here
except OtherModel.DoesNotExist:
# Handle exception
django.core.exceptions.ObjectDoesNotExist
ObjectDoesNotExist is the parent class of Model.DoesNotExist. This will catch this exception for any model, which is helpful if you don't know what model will raise the exception:
from django.core.exceptions import ObjectDoesNotExist
try:
# Your code here
except ObjectDoesNotExist:
# Handle exception
The RelatedObjectDoesNotExist exception is created dynamically at runtime. Here is the relevant code snippet for the ForwardManyToOneDescriptor and ReverseOneToOneDescriptor descriptors:
#cached_property
def RelatedObjectDoesNotExist(self):
# The exception can't be created at initialization time since the
# related model might not be resolved yet; `self.field.model` might
# still be a string model reference.
return type(
'RelatedObjectDoesNotExist',
(self.field.remote_field.model.DoesNotExist, AttributeError),
{}
)
So the exception inherits from <model name>.DoesNotExist and AttributeError. In fact, the complete MRO for this exception type is:
[<class 'django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist'>,
<class '<model module path>.DoesNotExist'>,
<class 'django.core.exceptions.ObjectDoesNotExist'>,
<class 'AttributeError'>,
<class 'Exception'>,
<class 'BaseException'>,
<class 'object'>]
The basic takeaway is you can catch <model name>.DoesNotExist, ObjectDoesNotExist (import from django.core.exceptions) or AttributeError, whatever makes the most sense in your context.
Little bit late but helpful for others.
2 ways to handle this.
1st :
When we need to catch exception
>>> from django.core.exceptions import ObjectDoesNotExist
>>> try:
>>> p2.restaurant
>>> except ObjectDoesNotExist:
>>> print("There is no restaurant here.")
There is no restaurant here.
2nd:
When don't want to handle exception
>>> hasattr(p2, 'restaurant')
False
tdelaney's answer is great for regular code paths, but if you need to know how to catch this exception in tests:
from django.core.exceptions import ObjectDoesNotExist
...
def testCompanyRequired(self):
with self.assertRaises(ObjectDoesNotExist):
employee = Employee.objects.create()
Here is example code:
def someview(request):
try:
instance = SomeModel.objects.get(id=request.GET.get('id'))
except SomeModel.DoesNotExist:
instance = None
except ValueError:
# This error may occur if user manually enter invalid (non-integer)
# id value (intentionally or not) in a browser address bar, e.g.
# http://example.com/?id=2_foo instead of http://example.com/?id=2
# This raises ValueError: invalid literal for int() with base 10: '2_'
instance = None
...
Is there a best practice to get a model instance by pk without writing this boilerplate code over and over? Should I use some predefined shortcut in Django or just roll my own?
I was sure that I should use Django's DetailView or SingleObjectMixin but curiously enough it doesn't handle the ValueError exception from my example https://github.com/django/django/blob/master/django/views/generic/detail.py#L50
Is it implied that I have to specify correct integer regexp for pk kwarg in urlconf? Ok, likely. But what if I get pk from request querystring?
UPD I have special logic to do with instance either it's None or not.
You can also use Django's built in shorcut get_object_or_404() that it's designed for this specifically. That function will raise an Http404 exception in case the object doesn't exist. If you want to get None instead of raising the exception, you can create a helper function to accomplish it very easily:
def get_object_or_none(klass, *args, **kwargs):
try:
return get_object_or_404(klass, *args, **kwargs)
except Http404:
return None
Hope this helps!
The first part of your try/except block can be simplified by using django-annoying:
from annoying.functions import get_object_or_None
instance = get_object_or_None(SomeModel, id=request.GET.get('id'))
FYI, you can also just extract get_object_or_None from the package (see source).
There are many generic class based views that might be helpful, in your case DetailView could work.
from django.views.generic.detail import DetailView
class SomeModelDetailView(DetailView):
model = SomeModel
You can overwrite get_object method to change default behaviour.
def get_object(self, queryset=None):
return SomeModel.objects.get(pk=self.get_pk())
And lastly if object is none you should probably display custom 404 page.