Django - Filter related objects - python

Let's say I have a list of locations where each location has a list of some objects. I want to make sure that I get these locations, but with a filtered list of objects.
Here's the structure of models.py:
class Location(models.Models):
# fields
class LocationObject(models.Models):
location = models.ForeignKey(Location, related_name="objects_list")
# other fields that are used in filtering
Here's how I do filtering:
locations = Location.objects.all()
if request_square_from:
locations = locations.filter(objects_list__size__gte=request_square_from)
if request_square_to:
locations = locations.filter(objects_list__size__lte=request_square_to)
# Other filters ...
The problem is that, by using this method of filtering, I get in each location a list of objects in which there is at least one object that satisfies the condition in locations.filter(). This is not what I actually need. I need to exclude every object (I mean LocationObject) that doesn't satisfy the condition in the filter() method.
Is there any idea to do that?
Update. A bit of clarification
Here's how this list looks:
Location #1
- Object 1 (size=40)
- Object 2 (size=45)
- Object 3 (size=30)
Location #2
- Object 4 (size=20)
- Object 5 (size=25)
I want to filter each object of location by, let's say, size property. Assume this condition: Location.objects.filter(objects_list__size__gte=40). This will match locations that contain even just a single list entry that has this property. This is not what I need. Expected result should be:
Location #1:
- Object 1 (size=40)
- Object 2 (size=45)

Assuming that you really want those locations that have at least one object that fulfills both conditions, you could do:
locations = locations.filter(objects_list__size__gte=request_square_from,
objects_list__size__lte=request_square_to)
But since you are not sure to have both parameters, you cannot do that. You can, however, achieve it using Q objects:
from django.db.models import Q
# ...
locations = Location.objects.all()
q = Q()
if request_square_from:
q &= Q(objects_list__size__gte=request_square_from)
if request_square_to:
q &= Q(objects_list__size__lte=request_square_to)
if q:
locations = locations.filter(q)

If I am not wrong, what you want is exclude().
filter() is used to filter out all objects that are required. So you would use:
locations = locations.filter(objects_list__size__gte=request_square_from)
This will give you all the objects that satisfy this condition.
But if you want to exclude the matching query. You need to use exclude()
locations = locations.exclude(objects_list__size__gte=request_square_from)
This will give you the objects that do not satisfy the condition and returns the rest of the objects.

According to your update, you want a list of location objects, not locations, so, instead of filtering your locations, filter the location objects!
objects = LocationObject.objects.all()
if request_square_from:
objects = objects.filter(size__gte=request_square_from)
if request_square_to:
objects = objects.filter(size__lte=request_square_to)
You are not restricted to filter foreign keys, you can filter over any Model instance.
If after this, you want the location of any object, you just...
objects[0].location

Related

What's the cleanest way of counting the iteration of a queryset loop?

I need to loop through a queryset and put the objects into a dictionary along with the sequence number, i, in which they appear in the queryset. However a Python for loop doesn't seem to provide a sequence number. Therefore I've had to create my own variable i and increment it with each loop. I've done something similar to this, is this the cleanest solution?
ledgers = Ledger.objects.all()
i = 0
for ledger in ledgers:
print('Ledger no '+str(i)+' is '+ledger.name)
i += 1
enumerate(…) [Python-doc] is designed for that:
ledgers = Ledger.objects.all()
for i, ledger in enumerate(ledgers):
print(f'Ledger no {i} is {ledger.name}')
This is not exactly the answer to your question.
If you'd need the index afterwards at the QuerySet, you can annotate it like that:
from django.db.models import Window, F
ledgers_qs = Ledger.objects.annotate(index=Window(expression=DenseRank(), order_by=F('id').desc()))
# You can iterate over the object indexes and names
for index, name in ledgers_qs.values_list('index', 'name'):
print('Ledger no '+str(i)+' is '+ledger.name)
i += 1
# You can make further QuerySet operations if needed
# because ledgers is a QuerySet with annotated indexes:
ledgers_qs.filter(index__lt=5, name__contains='foo')
With that you get a queryset that you can use for further database operations.
Why did I create this answer?
Because it is often better to work with QuerySet's if possible to be able to enhance the existing code structure without limitations.
DenseRank

Django __in but return the first matching element

I have this model
from django.db import models
class TranslatedString(models.Model):
lang = models.CharField()
key = models.CharField()
value = models.CharField()
I have these instances of this model:
a = TranslatedString(lang="en_US", key="my-string", value="hello world")
b = TranslatedString(lang="en_AU", key="my-string", value="g'day world")
c = TranslatedString(lang="ja_JP", key="my-string", value="こんにちは世界")
And I have this list of languages a user wants
preferred_langs = ["en_CA", "en_US", "en_AU", "fr_CA"]
which is ordered by preference. I would like to return the value that matches the first item in that list. Even though both a and b would match a query like
TranslatedString.objects.filter(key="my-string", lang__in=preferred_langs).first()
I want it to be ordered by the list, so that I always get a.
I can make a query for each element in preferred_langs and return as soon as I find a matching value, but is there a better option? I'd like to do it in one query.
You can use a generator expression over preferred_langs to produce a mapping of preferred languages to their respective indices in the list as When objects for a Case object to be annotated as a field so that you can order the filtered result by it:
from django.db.models import Case, Value, When
TranslatedString.objects.filter(key="my-string", lang__in=preferred_langs).annotate(
preference=Case(*(When(lang=lang, then=Value(i)) for i, lang in preferred_langs))
).order_by('preference').first()
If you don't mind retrieving all preferred translations from the database, this may be accomplished tersely by sorting the models in Python:
preferred_langs = ["en_CA", "en_US", "en_AU", "fr_CA"]
strings = list(TranslatedString.objects.filter(key="my-string", lang__in=preferred_langs))
strings.sort(key=lambda s: preferred_langs.index(s))
first_choice = strings[0]
print(first_choice.lang) # outputs "en_US"
This will perform a single (but potentially large) query. However, if the sequence of preferred languages is fairly short, the sorting should occur in negligible time.

Check if queryset already exists

I have a list of querysets such as ([qSet1, qSet2, qSet3],[qSet3, qSet2],[qSet1, qSet3])
Then, I want to add another queryset, but only if it not already exists in list. Sets can have the same content, but in different order: [qSet1, qSet2], [qSet2, qSet1]. That querysets must be considered as the same => must not to be added twice.
How can I do this?
To check for sameness for a set of QuerySets (pun intended), you can make use of the underlying SQL query gotten from the .query method which Django provides for QuerySets. This method returns a string literal.
Say you have a list of tuples of QuerySets:
list_of_querysets = [(qSet1, qSet2, qSet3), (qSet3, qSet2), (qSet1, qSet3)]
To check if a new arbitrary tuple qsets_arb = (qSet_arb1, qSet_arb2) already has a match in the list, you would do:
def get_sql(tuple_of_querysets):
'''Return a set of SQL statements from a tuple of QuerySets'''
return set([str(queryset.query) for queryset in tuple_of_querysets])
# ordering of items in sets do not matter: set([q1, q2]) = set([q2, q1])
if get_sql(qsets_arb) in map(get_sql, list_of_querysets[:]):
print("This tuple of QuerySets has already been included")
else:
list_of_querysets.append(qsets_arb)
That should pretty much do what you want.
If what you want to compare is the content of the lists, and you do not want order to make a difference, you should use a set:
set([qSet1, qSet2]) == set([qSet2, qSet1])
This is assuming that the same queryset is not twice in the same list.

TestCase self.assertEqual does not match a similar string

I'am trying to create a model unittest for a ManyToMany relationship.
The aim is to check, if there is the right category saved in the table Ingredient.
class IngredientModelTest(TestCase):
def test_db_saves_ingredient_with_category(self):
category_one = IngredientsCategory.objects.create(name='Food')
first_Ingredient = Ingredient.objects.create(name='Apple')
first_Ingredient.categories.add(category_one)
category_two = IngredientsCategory.objects.create(name='Medicine')
second_Ingredient = Ingredient.objects.create(name='Antibiotics')
second_Ingredient.categories.add(category_two)
first_ = Ingredient.objects.first()
self.assertEqual('Apple', first_.name)
self.assertEqual(first_.categories.all(), [category_one])
self.assertEqual(first_, first_Ingredient)
for self.asserEqual(first_.categories.all(), [category_one]) in the second last row I get this weird assert:
AssertionError: [<IngredientsCategory: Food>] != [<IngredientsCategory: Food>]
I tried many other different ways, but none of it worked. Does any one suppose how I can get the information of first_.categories.all() to compare it with something else?
That'll be because they're not equal - one is a QuerySet, the other is a list - they just happen to have the same str representations.
You could either cast the QuerySet to a list with list(first_.categories.all()), or a possible solution for this situation may be:
self.assertEqual(first_.categories.get(), category_one)

Python- Use set on a List of objects <class 'xyz.models.abc'>

set https://docs.python.org/2/library/sets.html#sets.Set
I have a list of 10000 class objects(django model). although they are all unique but I want to shrink this list on the basis of a class attribute.
class mymodel():
field1 =
field2 =
objects I got is different on the basis of field2.
but I want to shrink on the basis of field1. field1 is common for few objects
for example current list have 10000 objects. if I shrink it by the field1 it will contain around 3000 unique objects.
I dont think set provide a facility for this? any other function or approach?
Is your "list" of django objects a queryset? If so a quicker, less memory intensive approach would be to run a distinct query
objects.order_by('field1').distinct('field1')
Please note, this only works with PostgreSQL
result = []
seen = set()
for obj in objects:
if obj.field1 not in seen:
seen.add(obj.field1)
result.append(obj)
I took a different approach using a dictionary comprehension.
unique_objs = {obj.field1:obj for obj in objects}.values()

Categories