I'm trying to get a list of latest 100 posts and also the aggregated count of approved, pending, and rejected posts for the user of that post.
models.py
class BlogPost(models.Model):
POST_STATUSES = (
('A', 'Approved'),
('P', 'Pending'),
('R', 'Rejected')
)
author = models.ForeignKey(User)
title = models.CharField(max_length=50)
description = models.TextField()
status = models.ChoiceField(max_length=1, choices=POST_STATUSES)
views.py
Now I'm getting the the aggregated count like so, but I'm confused on how to merge the count with the title of the posts
top_post_users = list(BlogPost.objects.values_list('user_id', flat=True))[:100]
users = User.objects.filter(pk__in=top_post_users).annotate(approved_count=Count(Case(When(user_posts__status="A", then=1),output_field=IntegerField()))).annotate(pending_count=Count(Case(When(user_posts__status="P", then=1),output_field=IntegerField()))).annotate(reject_count=Count(Case(When(user_posts__status="R", then=1),output_field=IntegerField())))
users.values('approved_count', 'pending_count', 'reject_count')
This is the result I want:
Title Of Post, Author1, 10, 5, 1
Title Of Post2, Author2, 7, 3, 1
Title Of Post3, Author1, 10, 5, 1
How can I merge the returned counts with the titles?
I know I could use a for loop and append each one, but efficiency wise I don't think this is the right way to do it. Is there a more efficient way using django database ORM?
I've tried this
users.values('title', approved_count', 'pending_count', 'reject_count')
...and that works, but it returns more than the latest 100 posts, so I think it's getting all the posts for those users and the aggregated count.
Ultimately, you want a list of BlogPosts:
main_qs = BlogPost.objects
# add filters, ordering etc. of the posts
and you want to display not only the authors next to the title of the post but also enrich the author information with the annotated counts.
from django.db.models import OuterRef, Subquery, Count
# you need subqueries to annotate the blog posts
base_sub_qs = BlogPost.objects.filter(author__pk=OuterRef('author__pk'))
# add subqueries for each count
main_qs = main_qs.annotate(
user_approved_count=Subquery(base_sub_qs.filter(status="A").annotate(
c=Count('*')).values('c'), output_field=IntegerField()),
user_pending_count=Subquery(base_sub_qs.filter(status="P").annotate(
c=Count('*')).values('c'), output_field=IntegerField()),
user_rejected_count=Subquery(base_sub_qs.filter(status="R").annotate(
c=Count('*')).values('c'), output_field=IntegerField()),
)
You can then access these in your template:
{% for post in posts %}
{{ post.title }}
{{ post.author.get_full_name }}
approved: {{ post.user_approved_count }}
pending: {{ post.user_pending_count }}
rejected: {{ post.user_rejected_count }}
{% endfor %}
Documentation: https://docs.djangoproject.com/en/2.1/ref/models/expressions/#subquery-expressions
Related
I am writing an Imageboard in Django but I can't get over this basic issue how to display few answers to each post on the mainpage. I have two models, Thread and Answer, and answer is linked by foreign key to thread. I would like to display each Thread with three latest answers underneath. I would really appreciate some help here.
If your models.py is like this:
class Thread(models.Model):
question = models.TextField()
# you can add other fields
class Answer(models.Model):
thread = models.ForeignKey(Thread, on_delete=models.CASCADE, related_name='thread_ans')
answer = models.TextField()
if_correct = models.BooleanField()
# you can add other fields
views.py
def quest(request):
q1 = Answer.objects.filter(if_correct=True).select_related('thread') # if you want only correct answer in template
q2 = Thread.objects.all().prefetch_related('thread_ans') # For all answers
conext = {
'q1': q1,
'q2': q2,
}
in template
# <!---- For Question and correct answer ------>
{% for i in q1 %}
<p>Ques: i.thread.question</p>
<p>Ans: i.answer</p>
{% endfor %}
# <!---- For Question and all answers with correct or incorrect ------>
{% for i in q2 %}
<p>Ques: i.question</p>
{% for j in i.thread_ans.all %}
<p>j.answer</p> # <!-- Print answer --->
<p>j.is_correct</p> # <!-- Print if correct or not --->
{% endfor %}
{% endfor %}
Use filter tags to show 3 comments , https://docs.djangoproject.com/en/2.2/ref/templates/builtins/
You can find latest 3 answers for each thread inside custom filter tag like this .
answers = Answer.objects.filter(Thread = Thread).order_by('date')[0:3]
I'm thinking this is actually really intuitive, but I cannot figure it out.
So I have a model called SWS_Document. Then I have SWS_Document_Step that has a foreign key to SWS_Document. Following that I have a 3rd model SWES_Step which has a foreign key to SWS_Document_Step. Essentially SWES_Document_Step is a sub-step to SWS_Document_Step.
Example. It would be "Mix the butter into the recipe" would be SWS_Document_Step. While SWES_Document_Step__id=1 would be "Place the butter into a microwave safe bowl." SWES_Document_Step__id=2 would be "Microwave the butter for 30 seconds."
Those are sub-steps to "mix the butter into the recipe."
class SWS_Document(models.Model):
machines = models.ManyToManyField(Machine, related_name='SWS_documents')
document_description = models.CharField(max_length=150, default="")
pub_date = models.DateTimeField(auto_now=True)
class SWS_Document_Step(models.Model):
STEP_TYPE_CHOICES = (
('People', 'People'),
('Quality', 'Quality'),
('Velocity', 'Velocity'),
('Cost', 'Cost'),
)
document_number = models.ForeignKey(SWS_Document, on_delete=models.CASCADE)
sws_sequence_number = models.PositiveIntegerField(editable=True, null=True)
class SWES_Step(models.Model):
STEP_TYPE_CHOICES = (
('People', 'People'),
('Quality', 'Quality'),
('Velocity', 'Velocity'),
('Cost', 'Cost'),
)
sws_document_id = models.ForeignKey(SWS_Document_Step, on_delete=models.CASCADE, null=True)
swes_description = models.CharField(max_length=500)
swes_step_type = models.CharField(max_length=8, choices=STEP_TYPE_CHOICES, blank=True)
pub_date = models.DateTimeField(auto_now=True)
So in my view.py I have taken swsallsteps.
def DocumentView(request, document_id):
# final goal should be to pass a list full of lists...
# e.g. [
#[name1, [hazard1, hazard2], [assessment1, assessment2]],
#[name2, [hazard3, hazard4], [assessment3, assessment4]],
#]
steps = []
swsallsteps = SWS_Document_Step.objects.filter(document_number=document_id)
swesallsteps = SWES_Step.objects.filter(sws_document_id=document_id)
return render(request, 'StandardWorkDocuments/document.html', {
'swsallsteps': swsallsteps,
'swesallsteps': swesallsteps,
})
Then in Document.html I have a few for loops.
{% for step in swsallsteps %}
<button class='collapsible'>{{ step.sws_sequence_number }} - {{ step.sws_work_element_description}} - <em><b>{{step.sws_step_type}}</b></em> - published - {{step.pub_date}}</button>
<div class="content">
{% for swessteps in swesallsteps %}
<p>{{swessteps.swes_description}}</p>
{% endfor %}
</div>
{% endfor %}
Essentially, what I believe I'm trying to get is a queryset in a query. So it would be [[Mix the butter into the recipe, [Place the butter in a microwave safe bowl, microwave the butter for 30 seconds]]
This is what I'm currently getting
This is what I'd hope to get, with a few dumby points put in to show for example
You can (and should) access nested items via the parent item. The Django documentation has a few useful examples on accessing related objects, and in this question you can find a more detailed rundown on accessing related objects; the related_name property for instance can replace the model_set syntax and help readability (if appropriately chosen).
Your second loop should like this:
{% for swes_step in step.swes_step_set.all %}
<p>{{swes_step.swes_description}}</p>
{% endfor %}
You are now accessing the second step level not from a separate queryset but from the parent step. You can scrap the second queryset (swesallsteps).
To avoid issuing multitudes of database queries, you should use prefetch_related to get all data in as few steps as possible:
swsallsteps = (SWS_Document_Step.objects
.filter(document_number=document_id)
.prefetch_related('swes_step_set')
)
I have a website which catalogs hikes, and users can log that they have gone on these hikes. I have a search function, and I want to be able to sort my list of hikes by the amount of times they have been completed by my users. I found this post which seems relevant, but is referring to a specific field in the foreignkey model, whereas I'd like to just count the total number of instances:
Django QuerySet ordering by number of reverse ForeignKey matches
Here is some sample code:
models.py:
class Hikes(models.Model)
...
class UserLog(models.Model)
user = models.ForeignKey(User, on_delete=CASCADE)
hike = models.ForeignKey(Hikes, on_delete=CASCADE)
I need to generate a queryset from my Hikes model that counts the number of times UserLog has referenced each hike, and then orders the hikes from most referenced to least. Something like this:
Hikes.objects.order_by(for each hike, count # of references in UserLog, then place in order by # of references)
So if Hike #1 has 10 UserLog instances, Hike #2 has 20, and Hike #3 has 5, the QuerySet would return:
Hike #2, Hike #1, Hike #3
Any tips?
Edit: thanks to viviwill, here is the working code in a search field:
def hike_list(request):
qs = Hikes.objects.all()
...
if request.GET:
...
searchsort = request.GET.get('sortby', '')
if searchsort == 'sortlocalrepeats':
qs = qs.annotate(count=Count('userlog')).order_by('-count')
In views:
context['rank_hike'] = Hikes.objects.annotate(number_of_hikes=Count('UserLog')).order_by('-number_of_hikes')
In the template:
{% for hike in rank_hike %}
<p>{{ hike }} {{ hike.number_of_entries }}</p>
{% endfor %}
My database is setup something like.
class comments(models.Model):
min = models.FloatField()
max = models.FloatField()
comment = models.CharField(max_length=255)
In my view, I grab a float value from the user. I want to take that value use the Min/Max as parameters and if that float value is between the Min/Max, display the comment that's associated to it. At times there might be more then 1 match, however for now I cant even figure out how to do the first step.
Use the filter() method of a model manager with a combination of __lte/__gte lookups:
def comments(request):
value = float(request.GET['value'])
return render(request, 'comments.html',
{'comments': comments.objects.filter(min__lte=value,
max__gte=value)})
And then in the comments.html:
{% for comment in comments %}
<div>{{ comment.comment }}</div>
{% endfor %}
I have a content that I'd like to rate on multiple criteria.
Imagine this kind of model:
class Content(models.Model):
name = models.CharField(max_length=50)
class Criterion(models.Model):
name = models.CharField(max_length=50)
content = models.ForeignKey(Content)
class ContRate(models.Model):
user = models.ForeignKey(User, help_text="Who rated ?")
crit = models.ForeignKey(Criterion)
rate = models.DecimalField()
The user has a page displaying the content.
From this page, he can also rate the content on the criteria set
The rating will be done with Ajax.
Now I'm trying to implement the view & the template
view.py
#...
def viewcont(request, content_id):
"""The user can view a content & rate it"""
content = get_object_or_404(Content, pk=content_id)
RateFormSet = modelformset_factory(ContRate)
formset = RateFormSet(queryset=ContRate.objects.filter(content=content, user=request.user))
objs = {
'content': content,
'forms': formset,
}
return render_to_response('content/content_detail.html', objs
, context_instance=RequestContext(request)
)
#...
content_detail.html
<!-- ... -->
<div id="rating">
<ul>
{% for crit in content.crit_set.all %}
<li>
{{ crit }}
<div class="rateit"
data-rateit-value="the_actual_rating_if_already_there"
data-rateit-ispreset="true"
crit-id="{{ crit.id }}"></div>
</li>
{% endfor %}
</ul>
</div>
<!-- ... -->
Now how can I use the forms formset to display the actual rates ?
And how can I draw an empty form to be posted by Ajax from any clicked star ?
(I know the javascript/jQuery part)
Not sure what the point of the formset is here. The rates are all available via the criteria object, using the reverse foreign key to ContRate in exactly the same way as you've done from Criteria to Content.
To make this as efficient as possible, you probably want to get the relevant ratings in the view and bring them together into a single datastructure:
content = get_object_or_404(Content, pk=content_id)
criteria = content.criteria_set.all()
user_ratings = ContRate.objects.filter(content=content, user=request.user)
ratings_dict = dict((c.crit_id, c.rate) for c in user_ratings)
for crit in criteria:
crit.user_rating = ratings_dict.get(crit.id)
Now you can pass criteria directly to your template, and there you can iterate through it to show the user_rating for each one.
(Final point: "criteria" is plural, the singular is "criterion". :-)