How do I import the Django DoesNotExist exception? - django

I'm trying to create a UnitTest to verify that an object has been deleted.
from django.utils import unittest
def test_z_Kallie_can_delete_discussion_response(self):
...snip...
self._driver.get("http://localhost:8000/questions/3/want-a-discussion")
self.assertRaises(Answer.DoesNotExist, Answer.objects.get(body__exact = '<p>User can reply to discussion.</p>'))
I keep getting the error:
DoesNotExist: Answer matching query does not exist.

You can also import ObjectDoesNotExist from django.core.exceptions, if you want a generic, model-independent way to catch the exception:
from django.core.exceptions import ObjectDoesNotExist
try:
SomeModel.objects.get(pk=1)
except ObjectDoesNotExist:
print 'Does Not Exist!'

You don't need to import it - as you've already correctly written, DoesNotExist is a property of the model itself, in this case Answer.
Your problem is that you are calling the get method - which raises the exception - before it is passed to assertRaises. You need to separate the arguments from the callable, as described in the unittest documentation:
self.assertRaises(Answer.DoesNotExist, Answer.objects.get, body__exact='<p>User can reply to discussion.</p>')
or better:
with self.assertRaises(Answer.DoesNotExist):
Answer.objects.get(body__exact='<p>User can reply to discussion.</p>')

DoesNotExist is always a property of the model that does not exist. In this case it would be Answer.DoesNotExist.

One thing to watch out for is that the second parameter to assertRaises needs to be a callable - not just a property. For instance, I had difficulties with this statement:
self.assertRaises(AP.DoesNotExist, self.fma.ap)
but this worked fine:
self.assertRaises(AP.DoesNotExist, lambda: self.fma.ap)

self.assertFalse(Answer.objects.filter(body__exact='<p>User...discussion.</p>').exists())

This is how I do such a test.
from foo.models import Answer
def test_z_Kallie_can_delete_discussion_response(self):
...snip...
self._driver.get("http://localhost:8000/questions/3/want-a-discussion")
try:
answer = Answer.objects.get(body__exact = '<p>User can reply to discussion.</p>'))
self.fail("Should not have reached here! Expected no Answer object. Found %s" % answer
except Answer.DoesNotExist:
pass # all is as expected

Related

Can we use queryset.first() instead of catching exception?

I normally use MyModel.objects.filter(pk=1).first() instead of MyModel.objects.get(pk=1) when I am not entirely sure whether there is any object with pk=1 or not.
Is it a bad practice doing so for this scenario or I need to
try:
MyModel.objects.get(pk=1)
except MyModel.DoesNotExist:
print('not found')
I normally do MyModel.objects.filter(pk=1).first() for the less code.
Nothing wrong with that. You get None if the object does not exist. It saves hitting the database with more than one query, compared to using .exists()
I would call the following self-documenting without a comment, but if you think not, then comment it
obj = MyModel.objects.filter(pk=1).first()
if obj is None:
# no such object exists...
...
else
# process obj
...
Has just occurred to me, this is a use for the "walrus" operator if you are using an up to date Python
if obj := MyModel.objects.filter(pk=1).first() is None:
but not sure I prefer it!
You can use django built-in method get_object_or_404 this will return 404 page if object does not exits
from django.shortcuts import render, get_object_or_404
obj = get_object_or_404(MyModel, id=pk)

Testing when a ValidationError is raised

I am new to programming and Django in general. I am trying to test one of my functions to make sure that a validation error is raised. The test confirms that the error is raised but also says the test Failed. How is this possible?
**models.py**
def check_user_words(sender, instance, **kwargs):
for field in instance._meta.get_fields():
#field_name = getattr(instance, field.attname)
if (isinstance(field, models.CharField) and
contains_bad_words(getattr(instance, field.attname))):
raise ValidationError("We don't use words like '{}' around here!".format(getattr(instance, field.attname)))
#tests.py
from __future__ import unicode_literals
import datetime
from django.test import TestCase
from django.utils import timezone
from django.test import TestCase
from django.urls import reverse
from .models import Question, Choice, contains_bad_words, check_user_words
from django.core.exceptions import ValidationError
def create_question(question_text, days):
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
class ContainsBadWordsTests(TestCase):
def test_check_user_words(self):
question = create_question(question_text="What a minute bucko", days=1)
with self.assertRaises(ValidationError):
check_user_words(question)
question.full_clean()
#after running python manage.py test polls
......
raise ValidationError("We don't use words like '{}' around here!".format(getattr(instance, field.attname)))
ValidationError: [u"We don't use words like 'What a minute bucko' around here!"]
models.py How I import
from __future__ import unicode_literals .... (and others)
filepath = "polls/static/polls/blacklist.yaml"
config = yaml_loader(filepath)
blacklist = [word.lower() for word in config['blacklist']]
def contains_bad_words(user_input_txt):
""" remove punctuation from text
and make it case-insensitive"""
user_typ = user_input_txt.encode()
translate_table = maketrans(string.punctuation, 32 * " ")
words = user_typ.translate(translate_table).lower().split()
for bad_word in blacklist:
for word in words:
if word == bad_word:
return True
return False
#receiver(pre_save)
def check_user_words(sender, instance, **kwargs):
for field in instance._meta.get_fields():
if (isinstance(field, models.CharField) and
contains_bad_words(getattr(instance, field.attname))):
raise ValidationError("We don't use words like '{}' around here!".format(getattr(instance, field.attname)))
We need to see more of your code (specifically create_question() and how check_user_words is connected to a signal) to be sure, but I think the issue is that you are using a post_save signal handler to execute check_user_words().
If this is the case, then the reason your test is failing is that create_question() will cause the post_save signal to fire, and check_user_words() will be executed immediately - i.e., before the with self.assertRaises context, and hence your test fails.
If this is the case, then try this:
def test_check_user_words(self):
with self.assertRaises(ValidationError):
create_question(question_text="What a minute bucko", days=1)
This test should now pass, because the validation error will be thrown as soon as you try to create the question.
Note however that doing this in a signal will result in an uncaught exception when something tries to save an object. Depending on what your use case is, you might be better off doing this in the clean() method of the model itself (see docs here), because this will cause appropriate errors to be reported on model forms etc:
def clean(self):
for field in instance._meta.get_fields():
if (isinstance(field, models.CharField) and contains_bad_words(getattr(instance, field.attname))):
raise ValidationError("We don't use words like '{}' around here!".format(getattr(instance, field.attname)))
(and then drop your signal handler). Then you can test this with:
q = create_question(question_text="What a minute bucko", days=1)
with self.assertRaises(ValidationError):
q.clean()

Easy way to run "explain" on query sets in django

It seems like it should be easy to run "explain" directly off of a queryset in Django, but I don't see anything obvious for how to do it, and "explain" is a difficult thing to search for in the docs.
Well, there seems to be nothing out there except a toolbar so I wrote my own mixin to give me an explain() method on my querysets:
from django.db import connections
from django.db.models.query import QuerySet
class QuerySetExplainMixin:
def explain(self):
cursor = connections[self.db].cursor()
cursor.execute('explain %s' % str(self.query))
return cursor.fetchall()
QuerySet.__bases__ += (QuerySetExplainMixin,)
Hopefully this is useful to others.
QuerySet.explain(), available in Django 2.1.0 and above, is now the official way to explain queries.
Just a slight modification to guidoism's answer. This prevents getting a ProgrammingError: syntax error at or near ... error caused by the parameters not being correctly escaped in the raw query:
from django.db import connections
from django.db.models.query import QuerySet
class QuerySetExplainMixin:
def explain(self):
cursor = connections[self.db].cursor()
query, params = self.query.sql_with_params()
cursor.execute('explain %s' % query, params)
return '\n'.join(r[0] for r in cursor.fetchall())
QuerySet.__bases__ += (QuerySetExplainMixin,)
To use, simply invoke explain() at the end of your queryset, e.g.:
print SomeModel.objects.filter(...).explain()

Django - Celery: #transaction and #task don't stack

I want to run a Django - Celery task with manual transaction management, but it seems that the annotations do not stack.
e.g.
def ping():
print 'ping'
pong.delay('arg')
#task(ignore_result=True)
#transaction.commit_manually()
def pong(arg):
print 'pong: %s' % arg
transaction.rollback()
results in
TypeError: pong() got an unexpected keyword argument 'task_name'
while the reverse annotation order results in
---> 22 pong.delay('arg')
AttributeError: 'function' object has no attribute 'delay'
It makes sense, but I'm having trouble finding a nice workaround. The Django docs don't mention alternatives to the annotation, and I don't want to make a class for each celery Task when I don't need one.
Any ideas?
Previously Celery had some magic where a set of default keyword arguments
were passed to the task if it accepted them.
Since version 2.2 you can disable this behaviour, but the easiest is to
import the task decorator from celery.task instead of celery.decorators:
from celery.task import task
#task
#transaction.commit_manually
def t():
pass
The decorators module is deprecated and will be completely removed in 3.0,
and the same for the "magic keyword arguments"
Note:
For custom Task classes you should set the accept_magic_kwargs attribute to False:
class MyTask(Task):
accept_magic_kwargs = False
Note2: Make sure your custom decorators preserves the name of the function using functools.wraps, otherwise the task will end up with the wrong name.
The task decorator generates a class x(Task) from your function with the run method as your target. Suggest you define the class and decorate the method.
Untested e.g.:
class pong(Task):
ignore_result = True
#transaction.commit_manually()
def run(self,arg,**kwargs):
print 'pong: %s' % arg
transaction.rollback()

django order_by FieldError exception can not be catched

from django.core.exceptions import FieldError
#This is a method of a class
def _order_item_list(self, item_list, order_items_by, previous_order_by):
if order_items_by == previous_order_by:
order_items_by = '-' + order_items_by
try:
result = item_list.order_by(order_items_by)
except FieldError:
result = item_list
return result, order_items_by
Now when I order by valid fields following the generated link,everything works perfect. When I edit a link and add some dummy fieldnames for ordering, it should be catched by this exception and the original list should be returned. But it is not happening, instead I always get a FieldError from django.
FieldError at ...
Cannot resolve keyword u'fgsdffds' into field. Choices are: ...
The reason the exception is not caught is because the QuerySet has not been evaluated yet.
To validate an arbitrary (user-specified) value used for a model field or order_by value, simply check to see if that model has a field by that name.
For example, say you have a model called Ticket and an arbitrary GET parameter called field_name. Here's how you might handle creating a valid QuerySet in views.py:
from django.db.models import FieldDoesNotExist
from myapp.models import Ticket
def index(request):
default_field = 'id'
field_name = request.GET.get('field_name', default_field)
try:
Ticket._meta.get_field_by_name(field_name)
except FieldDoesNotExist:
field_name = default_field
tickets = Ticket.objects.all().order_by(field_name)
return ...
This means there's a typo, or the exception happens elsewhere. Insert a debug line:
import pdb; pdb.set_trace()
before the try-except and see how the code is executed. Try PUDB or IPDB debuggers instead of the standard one. Many questions disappear when you have a debugger and can see exactly what goes wrong.
I faced the same problem and surely it was because the exception is later. In order to raise exception in try-catch block I modified the code in following manner:
try:
result = item_list.order_by(order_items_by)
**result = list(result)**
except FieldError:
result = item_list
This worked for me.