Iterating through an unlimited number of child objects in a Django queryset - python

I have a Django model that defines a list of categories. Each category can be a child of another category, and it is possible that depth of this list of categories could go on quite a bit.
models.py
class Category(models.Model):
code = UUIDField(unique=True, primary_key=True)
name = models.CharField(max_length=100, null=True, blank=True, unique=True)
parent_cat = models.ForeignKey('self', null=True, blank=True)
Consider for example, a Category for women's clothing. It may have something like the following:
Women's > Clothing > Dresses > Summer
That all works fine, but separately I want to build a function that can build a single list containing an extrapolated view of every category in the tree. To do that, I have written the following:
queryset = Category.objects.filter(parent_cat=kwargs['code'])
all_children = []
all_children.append(queryset.values())
for c in queryset:
children = Category.objects.filter(parent_cat=c.code)
if children:
all_children.append(children.values())
all_children.append(children)
I realise this code is probably quite sloppy, I'm still learning!
As you probable realise, this will only give me two levels of child objects.
My question is: What is the most efficient way that I can write this code so that it will find every possible category until it reaches the last object with no more children?
Thanks!!

you could try using recursion something along the lines of this:
Replace list with the apprpriate data type.
def parseQueryset(queryset)
for x in queryset:
if isinstancex(x, list):
parseQueryset(x)
else:
#do stuff

You are looking for https://en.wikipedia.org/wiki/Tree_traversal - and you can use django-mptt package: https://django-mptt.github.io/django-mptt/overview.html#what-is-modified-preorder-tree-traversal

Related

Django: Ordering by last item in reverse ForeignKey relation

I'm trying to implement a qs.sort_by operation, but I'm not sure how to go about it.
Given the following models
class Group(models.Model):
name = models.CharField(max_length=300)
( ... )
class Budget(models.Model):
( ... )
amount = models.DecimalField(
decimal_places=2,
max_digits=8,
)
group = models.ForeignKey(
Group,
related_name="budgets",
on_delete=models.CASCADE,
)
Where each Group has either 0 or > 5 budgets assigned
I am trying to sort a Group.objects.all() queryset by the amount of their last (as in, most recently assigned) Budget.
I know that if it was a OneToOne I could do something like the following:
Group.objects.all().order_by('budget__amount')
With ForeignKey relations I was hoping I could do something like
Group.objects.all().order_by('budgets__last__amount')
But alas, that is not a valid operation, but I am not sure on how to proceed otherwise.
Does anyone know on how to perform this sorting operation? (if it is indeed possible)
This should do it:
latest = Budget.objects.filter(group=OuterRef('pk')).order_by('-pk')
Group.objects.annotate(
latest_budget=Subquery(
latest.values('amount')[:1]
)
).order_by('latest_budget')
You can try use sorted with a custom key:
sorted(Group.objects.all(), key=lambda x: x.last__budget__amount)
Perhaps .last__budget__amount is not a valid method, but you get the idea.

Build a list of values from queryset list (pk__in) in special order

i have a model with ratings, the results of a filter query must be in a special order for chartit (comparing ratings for trainee's) but I can't find the right way to do it. (ok I'm new to Django and python ;)
class Bewertung(models.Model):
auffassung = models.PositiveSmallIntegerField()
interesse = models.PositiveSmallIntegerField()
arbeitsabw = models.PositiveSmallIntegerField()
aufmerksamkeit = models.PositiveSmallIntegerField()
arbeitsgenauigkeit = models.PositiveSmallIntegerField()
verhalten = models.PositiveSmallIntegerField()
ausb_sach = models.PositiveSmallIntegerField(null=True, blank=True)
ausb_fuehr = models.PositiveSmallIntegerField(null=True, blank=True)
the query:
qs = Bewertung.objects.filter(pk__in=pk_list)
I want to compare the integer values in a multi bar chart e.g.
auffassung_from_pk(1,2,3) interesse_from_pk(1,2,3) .. n
but every try ends in a list with a lot of unordered values
(Auffassung_from_pk(1), interesse_from_pk(1), Auffassung_from_pk(2) ..)
I can't find a way to solve it nice and efficient in an python way.
so I need a little help, can you help?
#Sachin Kukreja correct, separate it and order it. so every field (e.g. auffassung must be one list with every result from the queryset.)
if I have 3 resulting query sets (pk_list=(1,2,3)) I need something like ((1,2,1),(2,3,3)...) ((auffassung),(interesse))
#Rajez there are no multiple filters
i set all my approach's to zero to start new. I have only this (in the Django shell)
for q in qs:
print(q.auffassung)
print(q.interesse)
i am really struggling at this at the moment
try it:
import itertools
qs = Bewertung.objects.filter(pk__in=pk_list)
values = qs.values_list('auffassung', 'interesse')
result = list(itertools.chain(*values))

How to get separate total amount of records for given list of ids in django

I am working with a question answer forum.
What I am trying to do this is pretty straightforward.
In questions list page, I want to show total number of answers a question have.
I know how to get all answers amount from a question record -
answer_records = Answer.objects.filter(question = question_record).count()
But, I want a single query which generates separate answer records amount for all given question records. So far I have tried -
answer_records = Answer.objects.filter(question__in = question_records).count()
It only generates single count of all question records.
Here is my model -
class Question(models.Model):
question_text = models.CharField(max_length = 500)
pub_date = models.DateTimeField('date published')
views = models.BigIntegerField(default = 0)
status = models.BooleanField(default = True)
def __str__(self):
return self.question_text
class Answer(models.Model):
question = models.ForeignKey(Question, on_delete = models.CASCADE, blank = True, null = True)
answer_text = models.TextField(default = '')
pub_date = models.DateTimeField(default = timezone.now)
def __str__(self):
return self.answer_text
How can I do that? Thanks for your help!
The way to go here is a list comprehensions:
[Answer.objects.filter(question = question_record).count() for question_record in Question.ojects.all()]
Here you want to do GROUP BY on questions and return the COUNT of answers for each question. For achieving this, you may use .annotate() as:
Answer.objects.filter(question__in= question_records) \
.values('question') \
.annotate(question_count= COUNT('question'))
Also take a look at Django's Document on "Generating aggregates for each item in a QuerySet". You will find it useful.
values() : specifies which columns are going to be used to "group by"
Django docs:
"When a values() clause is used to constrain the columns that are
returned in the result set, the method for evaluating annotations is
slightly different. Instead of returning an annotated result for each
result in the original QuerySet, the original results are grouped
according to the unique combinations of the fields specified in the
values() clause"
annotate() : specifies an operation over the grouped values
Django docs:
The second way to generate summary values is to generate an independent summary for each object in a QuerySet. For example, if you
are retrieving a list of books, you may want to know how many authors
contributed to each book. Each Book has a many-to-many relationship
with the Author; we want to summarize this relationship for each book
in the QuerySet.
Per-object summaries can be generated using the annotate() clause.
When an annotate() clause is specified, each object in the QuerySet
will be annotated with the specified values.

Is it possible to sort using sorted() with attributes from two different models?

I'm trying to sort a list of posts where votes have priority over the date.
I have my own app called UserPost and I'm using the django-voting app to do votes.
class UserPost(models.Model):
user = models.ForeignKey(User)
datetime = models.DateTimeField(auto_now_add=True)
text = models.CharField(max_length=255, blank=True)
is_deleted = models.BooleanField(default=False)
vote = models.ForeignKey(Vote)
Right now, I'm sorting without votes taking precedence yet:
posts_list = sorted(posts_list, key=attrgetter('datetime'))
What's the best way to go about this?
Thanks!
Tuples are sorted lexicographically, therefore if you return a tuple for sorted's key= argument you can sort by votes then by dates:
posts_list = sorted(posts_list, key=lambda post: (Vote.objects.get_score(post)['score'], post.datetime))
Alternatively, you might want to also look at the ordering option in a django Model's Meta class or the order_by method on django Queryset. They will do the sorting on the database in one query, so can be much faster. Alternatively, you can try the posts_list.get_score_in_bulk() to reduce the number of queries to two (one for posts_list, and one for get_score_in_bulk), like so:
scores = Vote.objects.get_score_in_bulk(posts_list)
posts_list = sorted(posts_list, key=lambda post: (scores[post.id]['score'], post.datetime))
You can sort it like that:
def posts_sorting(post1, post2):
# put here the way you compare posts
pass # return 1 if post2 before post1, -1 otherwise, 0 if they are "equal"
posts_list = sorted(posts_list, cmp=posts_sorting)
where posts_sorting is the function that compares your posts.

Create an instance that represents the average of multiple instances

I have a Review Model like the one defined below (I removed a bunch of the fields in REVIEW_FIELDS). I want to find the average of a subset of the attributes and populate a ModelForm with the computed information.
REVIEW_FIELDS = ['noise']
class Review(models.Model):
notes = models.TextField(null=True, blank=True)
CHOICES = ((1, u'Quiet'), (2, u'Kinda Quiet'), (3, u'Loud'))
noise = models.models.IntegerField('Noise Level', blank-True, null=True, choices=CHOICES)
class ReviewForm(ModelForm):
class Meta:
model = Review
fields = REVIEW_FIELDS
I can easily add more fields to the model, and then add them to the REVIEW_FIELDS list. Then I can easily iterate over them in javascript, etc. In my view, I want to compute the average of a bunch of the integer fields and populate a ReviewForm with the attribute values. How can I do something like this?
stats = {}
for attr in REVIEW_FIELDS:
val = reviews.aggregate(Avg(attr)).values()[0]
if val:
stats[attr] = int(round(val,0))
r.attr = stats[attr]
r = Review()
f = ReviewForm(r)
How can I create a ReviewForm with the average values without hard-coding the attribute values? I'd like to be able to add the field, add the value to the list, and have it automatically added to the set of statistics computed.
If I'm doing something fundamentally wrong, please let me know. I'm relatively new to django, so I might be re-inventing functionality that already exists.
After looking at the docs, I pass a dictionary to the ReviewForm when instantiating it:
f = ReviewForm(stats)
It seems to work pretty well! If anyone has any suggestions on a better way to do this, I'm all ears!

Categories