Sorting a modified Queryset in Django - python

I'm pull all records from DB and then creating a modified set which is then passed onto the render - this works fine and the added scoring values all all nicely used. Now I would like to sort this modified set but obviously I cannot use the orderby statement as the score values are not columns in the table. However, the .sort function doesn't seem to exist, when I try in the shell. Now I could manually create a list, but is there a simpler way and more pythonic way to do this?
mylist = AboutMe.objects.all()
for record in mylist:
record.Score = GenerateScoreFunction(weight_factors, myid, record.id)
return render(request, 'static/list.html', {'list': mylist, 'loggedin': loggedin})
The type of mylist is a Queryset

You can use the sorted() function to return a sorted list based on a given key:
return render(request, 'static/list.html', {'list': sorted(mylist, key=lambda r: r.Score),
'loggedin': loggedin})

The most Django way is to see if you can replace the GenerateScoreFunction with annotate and aggregate functions:
https://docs.djangoproject.com/en/1.6/ref/models/querysets/#annotate
https://docs.djangoproject.com/en/1.6/ref/models/querysets/#id5

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

how can i pass a list to objects.get in Django

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

django: order a QuerySet

I have a view like this:
def profile (request):
articles = Post.thing.all()
newSet = set()
def score():
for thing in articles:
Val = some calculation...
....
newSet.update([(thing,Val)])
score()
context = {
'articles': articles.order_by('id'),
'newSet':newSet,
}
return render(request,'my_profile/my_profile.html',context)
and the outcome is a Queryset which looks like this:
set([(<thing: sfd>, 1), (<thing: quality>, 0), (<thing: hello>, -1), (<thing: hey>, 4), (<thing: test>, 0)
I am now trying to order the set by the given Values so its a list which starts with the highest Value, but when i do newSet.order_by/filter/split/join
it does not work since 'set' object has no attribute join/filter/split.
Can anybody give me a hint how to sort the querySet i could not find anything helpful on my own.
I need this to work out in the view so it cannot/should not be done in the model. Thanks for any advise.
the outcome is a Queryset which looks like this
Actually this is a set (python builtin type), not a QuerySet (django's orm type).
set is an unordered collection type. To "sort" it you first have to turn it into a list - which is actually as simple as newlist = list(newset),
then you can sort the list in-place with newlist.sort(). Since you want this list to be sorted on it's items second elements, you'll need to use the key argument to tell sort on what you want to sort:
newlist.sort(key=lambda item: item[1])
or you can just change your score() function to store (score, obj) tuples instead in which case list.sort() will naturally do the RightThing (it will sort your tuples on their first item).
While we're at it: instead of calling newSet.update() with a list of a single item, you can just use newSet.add() instead, ie:
def score():
for thing in articles:
val = some calculation...
....
newset.add((thing, val))
# or if you don't want to specify a callback
# for `list.sort()`:
# newset.add((val, thing))
And finally: do you really need a set at all here ? Why not using a list right from the start ?
I think you might be slightly confused here between a set, a list and a QuerySet? A set is unordered, while a list is not. Sets don't expose the methods you listed above (filter, order_by, split, join). A QuerySet is a Django-specific class which has many useful methods.
I think it would be simpler to make newSet a list of tuples, and then to order that list by value using list.sort(key=lambda x: x[1]).
If your calculation of val is eligible for it though, I'd recommend using annotate and then doing away with newDict or newSet, and simply pass back the queryset of articles, which would be much simpler, maybe faster, and orderable by using articles.order_by('value'). If you post the calculation of val, I'll try to tell you if that's feasible.

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.

Django, grouping query items

say I have such model:
class Foo(models.Model):
name = models.CharField("name",max_length=25)
type = models.IntegerField("type number")
after doing some query like Foo.objects.filter(), I want to group the query result as such:
[ [{"name":"jb","type:"whiskey"},{"name":"jack daniels","type:"whiskey"}],
[{"name":"absolute","type:"vodka"},{name:"smirnoff ":"vodka"}],
[{name:"tuborg","type":beer}]
]
So as you can see, grouping items as list of dictionaries. List of group query lists intead of dictionary would also be welcome :)
Regards
You can do this with the values method of a queryset:
http://docs.djangoproject.com/en/1.1/ref/models/querysets/#values-fields
values(*fields)
Returns a ValuesQuerySet -- a QuerySet
that evaluates to a list of
dictionaries instead of model-instance
objects.
You can do the grouping in your view by using itertools.groupby().
Check out the regroup template tag. If you want to do the grouping for display in your template then this should be what you need. Otherwise you can read the source to see how they accomplish the grouping.
You can sort of do this by using order_by:
Foo.objects.order_by( "type" );
drinks = Foo.objects.all( )
Now you have an array of drinks ordered by type. You could use this or write a function to create the structure you want without having to sort it with a linear scan.

Categories