django: order a QuerySet - python

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.

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

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.

Python: Iterating through tuples which are related as subclassed tuples

I have the following tuples:
ReadElement = namedtuple('ReadElement', 'address value size')
LookupElement = namedtuple('LookupElement', ReadElement._fields[0:2] + ('lookups', ReadElement._fields[2]))
and I want to iterate through them as follows:
mytuples = [ReadElement(1,2,3), LookupElement(1,2,3,4)]
for address, value, lookups?, size in mytuples
if lookups is not None:
addLookups(lookups)
print address, value, lookups?, size
def addLookups(*items):
return sum(items)
How could I iterate through similar tuples using the same piece of code?
I think what I am looking for is a Union type of the two named tuples, so that that union type preserves the order of the tuples in the loop.
From laike9m post I can see how I can use the isinstance operator without having to unpack the tuples in the loop however I would like to avoid special casing the data and just go straight through without any if statements.
If these were objects I could do something like mytuples[0].execute() without having to worry about what type they were as long as they were were subclassed from the same parent and had that method implemented.
It seems that my question maybe a variant of the following Why are Super-class and Sub-class reversed? . In the case above I only have two items one subclass and one superclass where they are very similar to each other and therefore could also be made into a single class.
First, your namedtuple definition is wrong, should be:
LookupElement = namedtuple('LookupElement', ReadElement._fields[0:2] + ('lookups', ReadElement._fields[2]))
Second, you don't need to do worry about all that:
>>> for nt in mytuples:
print(nt)
ReadElement(address=1, value=2, size=3)
LookupElement(address=1, value=2, lookups=3, size=4)
I'm going to sleep so maybe I can't answer your futher question. I think the best way is to check whether the field you want exists before using it.
I don't know exactly what you want, here's what I'll do:
mytuples = [ReadElement(1,2,3), LookupElement(1,2,3,4)]
for nt in mytuples
if 'lookups' in nt._fields:
print nt.address, nt.value, nt.lookups, nt.size
else:
print nt.address, nt.value, nt.size

How to use Python Decorator to change only one part of function?

I am practically repeating the same code with only one minor change in each function, but an essential change.
I have about 4 functions that look similar to this:
def list_expenses(self):
explist = [(key,item.amount) for key, item in self.expensedict.iteritems()] #create a list from the dictionary, making a tuple of dictkey and object values
sortedlist = reversed(sorted(explist, key = lambda (k,a): (a))) #sort the list based on the value of the amount in the tuples of sorted list. Reverse to get high to low
for ka in sortedlist:
k, a = ka
print k , a
def list_income(self):
inclist = [(key,item.amount) for key, item in self.incomedict.iteritems()] #create a list from the dictionary, making a tuple of dictkey and object values
sortedlist = reversed(sorted(inclist, key = lambda (k,a): (a))) #sort the list based on the value of the amount in the tuples of sorted list. Reverse to get high to low
for ka in sortedlist:
k, a = ka
print k , a
I believe this is what they refer to as violating "DRY", however I don't have any idea how I can change this to be more DRYlike, as I have two seperate dictionaries(expensedict and incomedict) that I need to work with.
I did some google searching and found something called decorators, and I have a very basic understanding of how they work, but no clue how I would apply it to this.
So my request/question:
Is this a candidate for a decorator, and if a decorator is
necessary, could I get hint as to what the decorator should do?
Pseudocode is fine. I don't mind struggling. I just need something
to start with.
What do you think about using a separate function (as a private method) for list processing? For example, you may do the following:
def __list_processing(self, list):
#do the generic processing of your lists
def list_expenses(self):
#invoke __list_processing with self.expensedict as a parameter
def list_income(self):
#invoke __list_processing with self.incomedict as a parameter
It looks better since all the complicated processing is in a single place, list_expenses and list_income etc are the corresponding wrapper functions.

Sorting a modified Queryset in Django

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

Categories