Is it possible to filter a Django queryset by model property?
i have a method in my model:
#property
def myproperty(self):
[..]
and now i want to filter by this property like:
MyModel.objects.filter(myproperty=[..])
is this somehow possible?
Nope. Django filters operate at the database level, generating SQL. To filter based on Python properties, you have to load the object into Python to evaluate the property--and at that point, you've already done all the work to load it.
I might be misunderstanding your original question, but there is a filter builtin in python.
filtered = filter(myproperty, MyModel.objects)
But it's better to use a list comprehension:
filtered = [x for x in MyModel.objects if x.myproperty()]
or even better, a generator expression:
filtered = (x for x in MyModel.objects if x.myproperty())
Riffing off #TheGrimmScientist's suggested workaround, you can make these "sql properties" by defining them on the Manager or the QuerySet, and reuse/chain/compose them:
With a Manager:
class CompanyManager(models.Manager):
def with_chairs_needed(self):
return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))
class Company(models.Model):
# ...
objects = CompanyManager()
Company.objects.with_chairs_needed().filter(chairs_needed__lt=4)
With a QuerySet:
class CompanyQuerySet(models.QuerySet):
def many_employees(self, n=50):
return self.filter(num_employees__gte=n)
def needs_fewer_chairs_than(self, n=5):
return self.with_chairs_needed().filter(chairs_needed__lt=n)
def with_chairs_needed(self):
return self.annotate(chairs_needed=F('num_employees') - F('num_chairs'))
class Company(models.Model):
# ...
objects = CompanyQuerySet.as_manager()
Company.objects.needs_fewer_chairs_than(4).many_employees()
See https://docs.djangoproject.com/en/1.9/topics/db/managers/ for more.
Note that I am going off the documentation and have not tested the above.
Looks like using F() with annotations will be my solution to this.
It's not going to filter by #property, since F talks to the databse before objects are brought into python. But still putting it here as an answer since my reason for wanting filter by property was really wanting to filter objects by the result of simple arithmetic on two different fields.
so, something along the lines of:
companies = Company.objects\
.annotate(chairs_needed=F('num_employees') - F('num_chairs'))\
.filter(chairs_needed__lt=4)
rather than defining the property to be:
#property
def chairs_needed(self):
return self.num_employees - self.num_chairs
then doing a list comprehension across all objects.
I had the same problem, and I developed this simple solution:
objects = [
my_object
for my_object in MyModel.objects.all()
if my_object.myProperty == [...]
]
This is not a performatic solution, it shouldn't be done in tables that contains a large amount of data. This is great for a simple solution or for a personal small project.
PLEASE someone correct me, but I guess I have found a solution, at least for my own case.
I want to work on all those elements whose properties are exactly equal to ... whatever.
But I have several models, and this routine should work for all models. And it does:
def selectByProperties(modelType, specify):
clause = "SELECT * from %s" % modelType._meta.db_table
if len(specify) > 0:
clause += " WHERE "
for field, eqvalue in specify.items():
clause += "%s = '%s' AND " % (field, eqvalue)
clause = clause [:-5] # remove last AND
print clause
return modelType.objects.raw(clause)
With this universal subroutine, I can select all those elements which exactly equal my dictionary of 'specify' (propertyname,propertyvalue) combinations.
The first parameter takes a (models.Model),
the second a dictionary like:
{"property1" : "77" , "property2" : "12"}
And it creates an SQL statement like
SELECT * from appname_modelname WHERE property1 = '77' AND property2 = '12'
and returns a QuerySet on those elements.
This is a test function:
from myApp.models import myModel
def testSelectByProperties ():
specify = {"property1" : "77" , "property2" : "12"}
subset = selectByProperties(myModel, specify)
nameField = "property0"
## checking if that is what I expected:
for i in subset:
print i.__dict__[nameField],
for j in specify.keys():
print i.__dict__[j],
print
And? What do you think?
i know it is an old question, but for the sake of those jumping here i think it is useful to read the question below and the relative answer:
How to customize admin filter in Django 1.4
It may also be possible to use queryset annotations that duplicate the property get/set-logic, as suggested e.g. by #rattray and #thegrimmscientist, in conjunction with the property. This could yield something that works both on the Python level and on the database level.
Not sure about the drawbacks, however: see this SO question for an example.
Related
I have a model that I'm creating properties as helpers to run common queries.
One of them is a declarative select on the model (I think that's the proper vocab, but please let me know if I'm describing anything incorrectly!):
class Device:
id = Column(Integer)
children = relationship(Children)
#property
def active_children(self):
return self.children.filter(Children.status=='active').all()
This example is returning a list of children objects such as
[<Children obj>,
<Children obj>,
...]
But I also have a more complex query property that I ended up writing using the generative select method:
#property
def problem_children(self):
stmt = select(Children).\
join(Issues,
and_(Children.id == Issues.device_child_id,
Issues.log_ts.between(Children.activate_ts, Children.deactivate_ts)).\
filter(Children.status == 'active').\
filter(Issues.type == 1).\
filter(Issues.user != 'SYS').\
order_by(Children.name)
return db.session.execute(stmt).all()
This returns a list of tuples with only one Children object:
[(<Children obj>,),
(<Children obj>,),
...]
I can solve this by instead setting the last line to a list comprehension instead of using all() but that feels not-elegant. I've been looking through the documentation and I haven't found the answer yet, any insight would be greatly helpful!
#property
def problem_children(self):
stmt = ...
return [x[0] for x in db.session.execute(stmt)]
(note the above code is untested since it's adapted from my situation, sorry if there's typos or issues other than my described issue!)
My question: is there a better way to standardize my property returns so that my property API stays consistent?
I'm working on a small project using Django / Rest Framework I would like to know if there is any way
to pass a list like that
mycontact = Contact.objects.get(id=[14,26,44,88])
instead of
mycontact = Contact.objects.get(id=14)
This is my full code :
#action(methods=['post'], detail=False)
def listin(self, request):
mylist = List.objects.get(id=request.data['listId'])
mycontact = Contact.objects.filter(id__in=[14,26,44,88])
mycontact.list.add(mylist)
return Response("Good")
i'm looking for a solution because I have manyTomanyField
get accepts only one argument
If you want to pass a list of ids you can do this
mycontact = Contact.objects.filter(id__in=[14,26,44,88]) # this will return an iterable queryset
or
mycontact = list(Contact.objects.filter(id__in=[14,26,44,88])) # this will return a list
Jay's answer is likely what you are looking for. But, in case you need an alternative
contacts = [Contact.objects.get(id=id) for id in (14, 26, 44, 88)]
You can use get() for getting single unique object, for getting multiple id objects you can use filter().
Try using below statement
mycontact = Contact.objects.filter(id__in=[14,26,44,88])
When you want to use list of parameters to find, can use in operator.
Also, if your database table has too many records, consider using in_bulk.
mycontact = Contact.objects.in_bulk([14,26,44,88])
UPD:
In your case, loop through the mycontact queryset and set the mylist to each object:
mylist = List.objects.get(id=request.data['listId'])
mycontact = Contact.objects.filter(id__in=[14,26,44,88])
for contact in mycontact:
contact.list.add(mylist)
or simply do it vice-versa:
mylist = List.objects.get(id=request.data['listId'])
mylist.contact_set.set([14,26,44,88])
Note: I used default related name contact_set
Goal
Querying for all products, slicing them, returning subset of those products with an added key:value , in other words, enriched.
Code that works but I can't use
I can't use this code because I use a paginator, the paginator accesses the count of the QuerySet. If I pass the sliced QuerySet then that count is just for that sliced part, not the overall QuerySet, hence why I can't use it.
products_qs = final_qs[paginator.get_offset(request):
paginator.get_offset(request) + paginator.get_limit(request)]
for product in products_qs:
product.raw['super_cool_new_key'] = ms_response.get('results').get(product.id)
This works great, when I print the data I can see that super_cool_new_key enrichment in every product. Awesome. Problem? Well, I have had to slice it and now the count method is no longer true. Of course, I can do something like:
products_qs.count = final_qs.count
and move on with my life, but it feels... hacky, or maybe not?
Code I would like for it to work, but doesn't
for i in range(paginator.get_offset(request),
paginator.get_offset(request) + paginator.get_limit(request)):
product = final_qs[i]
product.raw['super_cool_new_key'] = ms_response.get('results').get(product.id)
When I see the output of the data, the super_cool_new_key is not there. I can't wrap my head around as to why?
Maybe I am having a thick day and I don't understand accessing by reference, so I remove the middlemonkey.
final_qs = final_qs.all()
for i in range(paginator.get_offset(request),
paginator.get_offset(request) + paginator.get_limit(request)):
final_qs[i].raw['super_cool_new_key'] = ms_response.get('results').get(final_qs[i].id, '')
Suspicions
It's obvious it's something about the code difference that is the culprit of why one way works and the other way doesn't. My dollar is on the following:
The slice
The iteration
Looking into Django Docs for QuerySet :
Iteration. A QuerySet is iterable, and it executes its database query the first time you iterate over it.
Then about slicing:
Slicing. As explained in Limiting QuerySets, a QuerySet can be sliced, using Python’s array-slicing syntax. Slicing an unevaluated QuerySet usually returns another unevaluated QuerySet, but Django will execute the database query if you use the “step” parameter of slice syntax, and will return a list
I can't be the slicing then, because I don't do a slice with a "step" parameter. Since it returns an unevaluated QuerySet the code I want to work, should in theory work. (Isn't that always the case?ha ha)
Ok so that clears up the fact that when, in the first option of coding, I did an iteration of for x in x_container the QuerySet was executed. Could that be the answer? So I modified the code:
Spoiler Alert: still does not work
final_qs = final_qs.all()
for i in range(paginator.get_offset(request),
paginator.get_offset(request) + paginator.get_limit(request)):
product = final_qs[i]
product.raw['super_cool_new_key'] = ms_response.get('results').get(product.id)
Emmm... help?
A suggested answer that, spoiler alert, did not work
from django.db.models import When, Case, Value, CharField
when = [ When(id=k, then=Value(v)) for k,v in ms_response.get('results').items()]
p = final_qs[paginator.get_offset(request)
:paginator.get_offset(request) + paginator.get_limit(request)]
p = p.annotate(super_cool_new_key=Case(
*when,
default=Value(''),
output_field=CharField()
)
)
I also tried it without slicing but with .all().annotate() . Still didn't work. It doesn't work, not due to an Exception happening, but because when I see the output, that super_cool_new_key is not there, meaning it didn't enrich the objects, which is the whole point.
It sounds like what you are looking for is similar to the answer here which utilizes When and Case. For your use it will be something along the following:
from django.db.models import When, Case, Value, CharField
ms_response = {5458: 'abc', 9900: 'def'}
whens = [
When(id=k, then=Value(v)) for k, v in ms_response.items()
]
qs = YourModelName.objects.all().annotate(
super_cool_key=Case(
*whens,
default=Value('xyz'),
output_field=CharField()
)
)
when you then call qs.get(id=5458).super_cool_key it will then return 'abc'
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)
As mentioned by the title, in Django:
Say I have a model name QuestionRecord, with two fields: full_score and actual_score.
I want to realize the SQL:
select * from QuestionRecord as QR where QR.full_score!=QR.actual_score.
Maybe using raw sql is OK, but I want to implement it like this:
class QuestionRecord_QuerySet(models.query.QuerySet):
def incorrect(self):# Find out those whose full_score and actual_score are not equal
return self.filter(...) # **What should I write here??**
class QuestionRecord_Manager(models.Manager):
def get_query_set(self):
return QuestionRecord_QuerySet(self.model)
class QuestionRecord(models.Model):
objects = QuestionRecord_Manager()
Is there any way to do it?
Sure, that's what the "F" object is for!
from django.db.models import F
# snip
return self.exclude(full_score = F('actual_score'))
Use QuerySet.exclude here, as you want to not get the results that match.
If you really want to use QuerySet.filter, you can use a negated "Q" object: self.filter(~Q(full_score = F('actual_score'))).