How to change the default ordering of a query? - django

I don't want any ordering to be applied to a query. So, I have a QuerySet follow as:
question_obj = Question.objects.filter(pk__in=[100,50,27,35,10,42,68]).order_by()
However, when I retrieve the results, they are always ordered by questionID. I iterate the question_obj and this is the result:
for obj in question_obj:
obj.questionID
The result is displayed such as:
10L
27L
35L
42L
50L
68L
100L

If you want to display the objects in the same order as the list of primary keys, then you could use in_bulk to create a dictionary keyed by pk. You can then use a list comprehension to generate the list of questions.
>>> pks = [100,50,27,35,10,42,68]
>>> questions_dict = Question.objects.in_bulk(pks)
>>> questions = [questions_dict[pk] for pk in pks]
>>> for question in questions:
print question.pk
100
50
27
35
...

If you want an unordered collection, use Python's Set object, documented here: http://docs.python.org/tutorial/datastructures.html#sets
If you want the ordering to be the same as the list you're passing as the value for pk__in, you could try:
ids = [100,50,27,35,10,42,68]
questions = list(Question.objects.filter(pk__in=ids))
question_obj = sorted(questions, key=lambda x: ids.index(x.id))
EDIT: And because it's extremely unclear as to what you mean by 'unordered' in reference to a data structure that is by definition ordered: Random ordering can be accomplished through the following:
.order_by('?')

Luke, use the Source, er, the Docs! Yeah, that's it!
Django QuerySet API - order_by()

You could do some raw SQL (with FIELD()) a lá:
Ordering by the order of values in a SQL IN() clause
which should allow you to retrieve them in the order suggested in the list.
To run custom SQL with the ORM:
https://docs.djangoproject.com/en/dev/topics/db/sql/#executing-custom-sql-directly

Related

Django distinct on case sensitive entries

I have the following query:
>>> z = Restaurant.objects.values_list('city',flat=True).order_by('city').distinct()
>>> z
[u'ELURU', u'Eluru', u'Hyderabad']
As you can see, it is not completely distinct because of the case sensitivity. How do i correct this issue?
You can use annotate in conjunction with Lower (or Upper, etc...) to normalize your values and return truly distinct values like this...
from django.db.models.functions import Lower
z = Restaurant.objects.annotate(
city_lower=Lower('city')).values_list(
'city_lower',flat=True).order_by('city_lower').distinct()
Note: Make sure order_by is set to 'city_lower' and not 'city' to avoid duplicates.
I'm not sure you're going to find a solution to this since django doesn't offer a case-insensitive distinct method (currently). But then maybe it would be better to fix the values in your database anyway since you don't really want your end users to see their city in capitals since it will look ugly.
I'd suggest thinking about making a simple method that you could run either once in a data migration and stopping the city field from ever getting in this state again - or just running this periodically.
something similar to
for restaurant in Restaurant.objects.all():
if restaurant.city != restaurant.city.title():
restaurant.city = restaurant.city.title()
restaurant.save()
Try this;
z = Restaurant.objects.extra(select = {'tmp_city': lower('city')}).values_list('city',flat=True).order_by('city').distinct('tmp_city')
This works, although it is a little messy. I ended up having to use values, since distinct only works on database tables, regardless of whether or not you use annotate, extra, or rawSQL.
You end up creating an extra field with annotate, and then use that field in your list of dictionaries created by values. Once you have that list of dictionaries, you can use groupby to group dictionaries based on the Lower values key in the values list of dicts. Then, depending on how you want to select the object (in this case, just taking the first object of the group), you can select the version of the distinct that you want.
from django.db.models.functions import Lower
from itertools import groupby
restaurant = [g.next() for k, g in groupby(
list(
Restaurant.objects.annotate(city_lower=Lower('message_text')).values_list('city', flat=True)
).order_by('city').values('city_lower', 'city')
), lambda x: x['city_lower'])]

Dynamic creating an "OR" Query in django

I want to create an AND queryset in django. The query is Give me all customers with certain id's. I have id's stored in a python list. My initial thought was the following
ids = range(1,21)
specific_customers = []
customers = Customer.objects.all() //Get them all
for customer in customers:
if customer.pk in ids:
specific_customers.append(customer)
but I don't think this is the preffered and best way to do it. Any better or faster ideas?
Use the in field lookup:
ids = range(1,21)
specific_customers = Customer.objects.filter(pk__in=ids)
Alternatively, if the use of range() is not just an example, and this really will always be a range of numbers you should use range lookup instead:
specific_customers = Customer.objects.filter(pk__range=(1, 21))
Both examples are equivalent to your original code.

How can I retrieve a list of field for all objects in Django?

I want to retrieve a list of all of the values for one field from a query in django. For example, I have a query of users, but rather than a queryset (or list) of user objects, I want a list just the usernames (strings). In a sense this is asking to restrict only to one column of data.
Have you tried
list(User.objects.all().values_list('username', flat=True))
If you only pass in a single field, you can also pass in the flat parameter. If True, this will mean the returned results are single values, rather than one-tuples. Additionally, casting it to a list makes the returned value a list instead of a queryset
To get the list of usernames:
>>> User.objects.all().values('username')
>>> [{'username': u'u1'}, {'username': u'u2'}]
>>> User.objects.all().values_list('username')
>>> [(u'u1',), (u'u2',)]
If you want just strings, a list comprehension can do the trick:
>>> usr_names = User.objects.all().values('username')
>>> [u['username'] for u in usr_names]
>>> [u'u1', u'u2']
Using values_list:
>>> usr_names = User.objects.all().values_list('username')
>>> [u[0] for u in usr_names]
>>> [u'u1', u'u2']
As you wrote:
"but rather than a queryset (or list) of user objects"
Soulution above still queryset
usr_names = User.objects.all().values_list('username')
This solution:
usr_names = [str(elem) for elem in list(User.objects.all().values_list('username'))]
It will return a list of strings
The simplest way you can get the list of objects of an attribute is to first get a query-set of that attribute alone using values_list then converting the django query-set to a python set using set() and finally to a list using list().
In your scenario:
user_names = list(set(User.objects.all().values_list('username', flat=True)))
You replace 'field_name' with your own
users_name = [i.field_name for i in Users.objects.all()]
To get the list and easily understands it since value list returns tuples in a list
users= User.objects.all().values_list("username")
usernames= []
for item in categories:
usernames.append(item[0])

How to convert a Django QuerySet to a list?

I have the following:
answers = Answer.objects.filter(id__in=[answer.id for answer in answer_set.answers.all()])
then later:
for i in range(len(answers)):
# iterate through all existing QuestionAnswer objects
for existing_question_answer in existing_question_answers:
# if an answer is already associated, remove it from the
# list of answers to save
if answers[i].id == existing_question_answer.answer.id:
answers.remove(answers[i]) # doesn't work
existing_question_answers.remove(existing_question_answer)
I get an error:
'QuerySet' object has no attribute 'remove'
I've tried all sorts to convert the QuerySet to a standard set or list. Nothing works.
How can I remove an item from the QuerySet so it doesn't delete it from the database, and doesn't return a new QuerySet (since it's in a loop that won't work)?
Why not just call list() on the Queryset?
answers_list = list(answers)
This will also evaluate the QuerySet/run the query. You can then remove/add from that list.
You could do this:
import itertools
ids = set(existing_answer.answer.id for existing_answer in existing_question_answers)
answers = itertools.ifilter(lambda x: x.id not in ids, answers)
Read when QuerySets are evaluated and note that it is not good to load the whole result into memory (e.g. via list()).
Reference: itertools.ifilter
Update with regard to the comment:
There are various ways to do this. One (which is probably not the best one in terms of memory and time) is to do exactly the same :
answer_ids = set(answer.id for answer in answers)
existing_question_answers = filter(lambda x: x.answer.id not in answers_id, existing_question_answers)
It is a little hard to follow what you are really trying to do. Your first statement looks like you may be fetching the same exact QuerySet of Answer objects twice. First via answer_set.answers.all() and then again via .filter(id__in=...). Double check in the shell and see if this will give you the list of answers you are looking for:
answers = answer_set.answers.all()
Once you have that cleaned up so it is a little easier for you (and others working on the code) to read you might want to look into .exclude() and the __in field lookup.
existing_question_answers = QuestionAnswer.objects.filter(...)
new_answers = answers.exclude(question_answer__in=existing_question_answers)
The above lookup might not sync up with your model definitions but it will probably get you close enough to finish the job yourself.
If you still need to get a list of id values then you want to play with .values_list(). In your case you will probably want to add the optional flat=True.
answers.values_list('id', flat=True)
By the use of slice operator with step parameter which would cause evaluation of the queryset and create a list.
list_of_answers = answers[::1]
or initially you could have done:
answers = Answer.objects.filter(id__in=[answer.id for answer in
answer_set.answers.all()])[::1]
You can directly convert using the list keyword.
For example:
obj=emp.objects.all()
list1=list(obj)
Using the above code you can directly convert a query set result into a list.
Here list is keyword and obj is result of query set and list1 is variable in that variable we are storing the converted result which in list.
Try this values_list('column_name', flat=True).
answers = Answer.objects.filter(id__in=[answer.id for answer in answer_set.answers.all()]).values_list('column_name', flat=True)
It will return you a list with specified column values
Use python list() function
list(). Force evaluation of a QuerySet by calling list() on it. For
example:
answers = list(answer_set.answers.all())
Why not just call
.values('reqColumn1','reqColumn2') or .values_list('reqColumn1','reqColumn2') on the queryset?
answers_list = models.objects.values('reqColumn1','reqColumn2')
result = [{'reqColumn1':value1,'reqColumn2':value2}]
OR
answers_list = models.objects.values_list('reqColumn1','reqColumn2')
result = [(value1,value2)]
You can able to do all the operation on this QuerySet, which you do for list .
def querySet_to_list(qs):
"""
this will return python list<dict>
"""
return [dict(q) for q in qs]
def get_answer_by_something(request):
ss = Answer.objects.filter(something).values()
querySet_to_list(ss) # python list return.(json-able)
this code convert django queryset to python list
instead of remove() you can use exclude() function to remove an object from the queryset.
it's syntax is similar to filter()
eg : -
qs = qs.exclude(id= 1)
in above code it removes all objects from qs with id '1'
additional info :-
filter() used to select specific objects but exclude() used to remove

django - reorder queryset after slicing it

I fetch the latest 5 rows from a Foo model which is ordered by a datetime field.
qs = Foo.objects.all()[:5]
In the following step, I want to reorder the queryset by some other criteria (actually, by the same datetime field in the opposite direction). But reordering after a slice is not permitted. reverse() undoes the first ordering, giving me a differet queryset. Is there a way to accomplish what I want without creating a list from the queryset and doing the ordering using it?
order_by gives you SQL in-database ordering. You're already using that, and then slicing on it. At that point, the results are retrieved into memory. If you want to change their order, you need to use Python in-memory sorting to do it, not the ORM in-database sorting.
In your case, Daniel has already given the best solution: since you simply want to sort by the same field, but in the other order, just reverse the list you have:
qs = Foo.objects.all()[:5]
objs = reversed(qs)
If you had wanted to sort by some other field, then you'd use the sorted() function with a custom key function:
qs = Foo.objects.all()[:5]
objs = sorted(qs, key=lambda o: o.some_other_field)
No, there's no way of doing that. order_by is an operation on the database, but when you slice a queryset it is evaluated and doesn't go back to the database after that.
Sounds like you already know the solution, though: run reversed() on the evaluated qs.
qs = reversed(Foo.objects.all()[:5])
Late answer, but this just worked for me:
import random
sorted(queryset[:10], key=lambda x: random.random())