How do I test Django QuerySets are equal? - python

I am trying to test my Django views. This view passes a QuerySet to the template:
def merchant_home(request, slug):
merchant = Merchant.objects.get(slug=slug)
product_list = merchant.products.all()
return render_to_response('merchant_home.html',
{'merchant': merchant,
'product_list': product_list},
context_instance=RequestContext(request))
and test:
def test(self):
"Merchant home view should send merchant and merchant products to the template"
merchant = Merchant.objects.create(name='test merchant')
product = Product.objects.create(name='test product', price=100.00)
merchant.products.add(product)
test_client = Client()
response = test_client.get('/' + merchant.slug)
# self.assertListEqual(response.context['product_list'], merchant.products.all())
self.assertQuerysetEqual(response.context['product_list'], merchant.products.all())
EDIT
I am using self.assertQuerysetEqual instead of self.assertListEqual. Unfortunately this still doesn't work, and the terminal displays this:
['<Product: Product object>'] != [<Product: Product object>]
assertListEqual raises: 'QuerySet' object has no attribute 'difference' and
assertEqual does not work either, although self.assertSetEqual(response.context['product_list'][0], merchant.products.all()[0]) does pass.
I assume this is because the QuerySets are different objects even though they contain the same model instances.
How do I test that two QuerySets contain the same data? I am even testing this correctly? This is my 4th day learning Django so I would like to know best practices, if possible. Thanks.

By default assertQuerysetEqual uses repr() on the first argument. This is why you were having issues with the strings in the queryset comparison.
To work around this you can override the transform argument with a lambda function that doesn't use repr():
self.assertQuerysetEqual(queryset_1, queryset_2, transform=lambda x: x)

Use assertQuerysetEqual, which is built to compare the two querysets for you. You will need to subclass Django's django.test.TestCase for it to be available in your tests.

I just had the same problem. The second argument of assertQuerysetEqual needs to be a list of the expected repr()s as strings. Here is an example from the Django test suite:
self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"], ordered=False)

I ended up solving this issue using map to repr() each entry in the queryset inside the self.assertQuerysetEqual call, e.g.
self.assertQuerysetEqual(queryset_1, map(repr, queryset_2))

An alternative, but not necessarily better, method might look like this (testing context in a view, for example) when using pytest:
all_the_things = Things.objects.all()
assert set(response.context_data['all_the_things']) == set(all_the_things)
This converts it to a set, which is directly comparable with another set. Be careful with the behaviour of set though, it might not be exactly what you want since it will remove duplicates and ignore the order of objects.

I found that using self.assertCountEqual(queryset1, queryset2) also solves the issue.

Related

What am I doing wrong with my function within this django view?

This should be easy... I'm taking a course and the way this is structured the view.py file can stand alone for the purpose of this quiz - it's more like a challenge. Ok, so the request is this:
"Now, create a view named article_list that selects all Article instances and returns an HttpResponse like "There are 5 articles." Be sure to use the len() of the Article queryset to get the number of articles."
As a point of reference, the first part works. Anyway, the view.py file looks like this (The first part is where I import Article... guess I don't need the render import.):
from django.http import HttpResponse
from django.shortcuts import render
from .models import Article
# Write your views here
def article_list(request):
articles = Article.objects.all()
article_count = articles.len()
output = "There are {} articles.".format(str(article_count))
return HttpResponse(output)
I'm not sure if the len function and/or the str function should be chained like this or if they should wrap the objects. I tried putting articles.len().str() and that didn't seem to work.
Thanks in advance for any help,
Bruce
You can try this:
article_count = len(articles)
output = "There are {} articles.".format(article_count)
len is a function that you call on a object and the format method already calls the str method for you.
Using .all() just to count the number of records introduces an unnecessary overhead:
If you only need to determine the number of records in the set (and
don’t need the actual objects), it’s much more efficient to handle a
count at the database level using SQL’s SELECT COUNT(*). Django
provides a count() method for precisely this reason.
Directly get the count from the database using .count():
articles_count = Article.objects.count()
output = "There are {} articles.".format(article_count)
This is a generally accepted way to count the records unless the record instances are actually needed, or, if there is a specific requirement, as in your case:
Be sure to use the len() of the Article queryset to get the number of articles.
Taking into account how the problem was stated, #Andoni's answer should be accepted.
Note: Don't use len() on QuerySets if all you want to do is determine the number of records in the set. It's much more efficient to handle a count at the database level, using SQL's SELECT COUNT(*), and Django provides a count() method for precisely this reason. See count() below.
Source

Django method returning

With a method to get all related connections, is it better to simply return a queryset, or iterate through the queryset to return a list of IDs?
example code:
class Foo(models.Model):
bar = models.ForeignKey(Bar, related_name="foos")
class Bar(models.Model):
is_active = models.BooleanField(default=True)
def get_foos(self):
#this is the method to focus on for the question
to return a queryset all that would need to be in get_foos is the following:
return self.foos.all()
but to get a list of ids, it would have to look as such:
foos = self.foos.all()
ids = []
for foo in foos:
ids.append(foo.pk)
return ids
I was looking through an API, and they used the latter, but I don't really understand why you would ever actually do that if you could just do a one-liner like the former! Can someone please explain the benefits of these methods and if there are specific cases in which one is better than the other?
You can get Foo ids in one line:
ids = self.foos.values_list('pk', flat=True)
Using id list is more efficient if used in other expressions, e.g.:
my_query.filter(foo__pk__in=ids)
If you really saw something like your second snippet in some published app, then you can send them a patch (and probably more than one, since chances are you'll find a lot of bad code in this app). Point is : there's a lot of very poorly written Django apps around. As Niekas posted, you can get the same result with whay better performances in a no-brainer one-liner.
Now wrt/ why return a list of pks instead of a full queryset, well, sometimes that's really just what you need, and sometimes that's just silly - depends on the context, really.
I don't think this is a better vs worse scenario. The ID list can also be generated with a one-liner:
list(self.foos.values_list('pk', flat=True))
It's sometimes good practice for a function to return the minimal or least powerful representation of the data requested. Maybe you don't want callers to have access to the queryset object.
Maybe those IDs are going to the template rendering engine, and you'd rather not have templates with write access to querysets, for example.

Using extra in django querystet combined with django lookups (not constant parameters in extra)

I want to call a postgresql function in django queryset and parameters of this function are related to current row.
Lets assume I have following queryset:
queryset = Baz.objects.filter(foo = 'foo', foo__bar = 'bar').
and I would like to add an extra argument that calls a function, and argument of this function should be name that django lookup foo_baz resolves to.
In ideal world i would like to write:
queryset.extra(were = "my_function(foo__baz)")
that woul render to:
my_function("FOO_TABLE".baz)
You reference columns like this with F() objects generally, but I'm not sure if you can use them with extra() in a case like this. Hopefully that's a starting point for you to experiment though.
Here is response for this problem from django developers:
extra often creates more problem than it solves. The current trend is to
deprecate it rather than extend it.
The recommended way to achieve your goal is to use raw SQL.

How to exclude results with get_object_or_404?

In Django you can use the exclude to create SQL similar to not equal. An example could be.
Model.objects.exclude(status='deleted')
Now this works great and exclude is very flexible. Since I'm a bit lazy, I would like to get that functionality when using get_object_or_404, but I haven't found a way to do this, since you cannot use exclude on get_object_or_404.
What I want is to do something like this:
model = get_object_or_404(pk=id, status__exclude='deleted')
But unfortunately this doesn't work as there isn't an exclude query filter or similar. The best I've come up with so far is doing something like this:
object = get_object_or_404(pk=id)
if object.status == 'deleted':
return HttpResponseNotfound('text')
Doing something like that, really defeats the point of using get_object_or_404, since it no longer is a handy one-liner.
Alternatively I could do:
object = get_object_or_404(pk=id, status__in=['list', 'of', 'items'])
But that wouldn't be very maintainable, as I would need to keep the list up to date.
I'm wondering if I'm missing some trick or feature in django to use get_object_or_404 to get the desired result?
Use django.db.models.Q:
from django.db.models import Q
model = get_object_or_404(MyModel, ~Q(status='deleted'), pk=id)
The Q objects lets you do NOT (with ~ operator) and OR (with | operator) in addition to AND.
Note that the Q object must come before pk=id, because keyword arguments must come last in Python.
The most common use case is to pass a Model. However, you can also pass a QuerySet instance:
queryset = Model.objects.exclude(status='deleted')
get_object_or_404(queryset, pk=1)
Django docs example:
https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#id2
There's another way instead of using Q objects. Instead of passing the model to get_object_or_404 just pass the QuerySet to the function instead:
model = get_object_or_404(MyModel.objects.filter(pk=id).exclude(status='deleted'))
One side effect of this, however, is that it will raise a MultipleObjectsReturned exception if the QuerySet returns multiple results.
get_object_or_404 utilizes the get_queryset method of the object manager. If you override the get_queryset method to only return items that aren't "deleted" then get_object_or_404 will automatically behave as you want. However, overriding get_queryset like this will likely have issues elsewhere (perhaps in the admin pages), but you could add an alternate manager for when you need to access the soft deleted items.
from django.db import models
class ModelManger(models.Manger):
def get_queryset(self):
return super(ModelManger, self).get_queryset().exclude(status='deleted')
class Model(models.Model):
# ... model properties here ...
objects = ModelManager()
all_objects = models.Manager()
So if you need only non-deleted items you can do get_object_or_404(Models, id=id) but if you need all items you can do get_object_or_404(Models.all_objects, id=id).

Hide filter items that produce zero results in django-filter

I have an issue with the django-filter application: how to hide the items that will produce zero results. I think that there is a simple method to do this, but idk how.
I'm using the LinkWidget on a ModelChoiceFilter, like this:
provider = django_filters.ModelChoiceFilter(queryset=Provider.objects.all(),
widget=django_filters.widgets.LinkWidget)
What I need to do is filter the queryset and select only the Provider that will produce at least one result, and exclude the others.
There is a way to do that?
Basically, you need to apply filters, and then apply them again, but on newly-generated queryset. Something like this:
f = SomeFilter(request.GET)
f = SomeFilter(request.GET, queryset=f.qs)
Now when you have correct queryset, you can override providers dynamically in init:
def __init__(self, **kw):
super(SomeFilter, self).__init__(**kw)
self.filters['provider'].extra['queryset'] = Provider.objects.filter(foo__in=self.queryset)
Not pretty but it works. You should probably encapsulate those two calls into more-efficient method on filter.
Maybe the queryset can be a callable instead of a 'real' queryset object. This way, it can be generated dynamically. At least this works in Django Models for references to other models.
The callable can be a class method in you Model.
If I understand your question correctly I believe you want to use the AllValuesFilter.
import django_tables
provider = django_filters.AllValuesFilter(
widget=django_filters.widgets.LinkWidget)
More information is available here: http://github.com/alex/django-filter/blob/master/docs/ref/filters.txt#L77

Categories