Filter queryset in django templates - python

I have a model as followed:
class Venture(models.Model):
name = models.CharField(_('name'), max_length=255)
created = models.DateTimeField(editable=False)
modified = models.DateTimeField()
class QuestionSet(models.Model):
title = models.CharField(_(u'title'), max_length=100)
class Question(models.Model):
title = models.CharField(_(u'title'), max_length=255)
qset = models.ForeignKey(QuestionSet, related_name='questions')
class Answer(models.Model):
question = models.ForeignKey(Question, related_name='answers')
responder = models.ForeignKey(User)
venture = models.ForeignKey(Venture, related_name='answers')
text = models.TextField(_(u'answer'), blank=True, null=True)
timestamp = models.DateTimeField(auto_now_add=True)
There exists a set of predefined questions for all users. For each Venture, I have a page for each QuestionSet which lists the Questions in that set and I loop over the questions as followed:
<div> {{ venture.name }} </div>
{% for question in qset.questions.all %}
<div class="qset-question control-group">
{{ question.title }}
{# How do I access the answer for the current venture? #}
</div>
{% endfor %}
The question is what is the best way to get the Answer of that Question for the current venture. I want to output some information about the answer here.
Any help is appreciated.

I solved the problem by creating a custom template tage. Here is the code:
#register.assignment_tag
def question_answer(venture, question):
answers = question.answers.filter(venture=venture)
return answers[0] if answers else None
then used it like this:
{% question_answer venture question as answer %}
{{ answer }}

The simplest solution would be to do a select on Answer for a given 'venture', which will give you a list of Answers. You can then use this to get all the questions for a venture.
So something like this:
ans = Answer.objects.filter(venture=v)
Edit::
Based on what you have said you need to rework your model. There are a number of short coming that I can see (i will let you figure it out). Else you need to do more than one query and give your view multiple query sets.

What about something like this?
models.py
from django.db import models
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _
User = get_user_model()
class Venture(models.Model):
name = models.CharField(_('name'), max_length=255)
created = models.DateTimeField(auto_now_add=True, editable=False)
modified = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Question(models.Model):
title = models.CharField(_(u'title'), max_length=255)
def __str__(self):
return self.title
class VentureQuestion(models.Model):
venture = models.ForeignKey('Venture', related_name='questions')
question = models.ForeignKey('Question', related_name='venture_questions')
def __str__(self):
return "{}: {}".format(self.venture, self.question)
class Answer(models.Model):
question = models.ForeignKey('VentureQuestion', related_name='answers')
responder = models.ForeignKey(User, related_name='answers')
text = models.TextField(_(u'answer'), blank=True, null=True)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return "{}: {}".format(self.responder.username, self.text)
admin.py
from django.contrib import admin
from example.models import Venture, VentureQuestion, Question, Answer
class AnswerInline(admin.StackedInline):
model = Answer
extra = 0
class VentureQuestionAdmin(admin.ModelAdmin):
inlines = [AnswerInline]
admin.site.register(Venture)
admin.site.register(VentureQuestion, VentureQuestionAdmin)
admin.site.register(Question)
admin.site.register(Answer)
That way you can have answers for each Question related to a Venture... (It sounds like that's the functionality you are looking for at least...)
>>> from example.models import Venture
>>> ventures = Venture.objects.all()
>>> for venture in ventures:
... for venture_question in venture.questions.all():
... venture_question.question.title
...
u'What is this?'
u'How does this work?'
u'Does this even work?'
>>> for venture in ventures:
... for venture_question in venture.questions.all():
... venture_question.question.title, [answer.text for answer in venture_question.answers.all()]
...
(u'What is this?', [])
(u'How does this work?', [u'It just does!'])
(u'Does this even work?', [u'Sure it does...', u'I think so'])
and you can have another Venture that uses the same Question but has different Answers
>>> venture = Venture.objects.get(name='Another Venture')
>>> for venture_question in venture.questions.all():
... venture_question.question.title, [answer.text for answer in venture_question.answers.all()]
...
(u'What is this?', [])
after adding an Answer to the new VentureQuestion
>>> venture = Venture.objects.get(name='Another Venture')
>>> for venture_question in venture.questions.all():
... venture_question.question.title, [answer.text for answer in venture_question.answers.all()]
...
(u'What is this?', [u"It's another venture with the same question and it's own answers..."])

Related

Multiple models in Django form

I have the following models:
class Category(models.Model):
label = models.CharField(max_length=40)
description = models.TextField()
class Rating(models.Model):
review = models.ForeignKey(Review, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
rating = models.SmallIntegerField()
class Review(models.Model):
author = models.ForeignKey(User, related_name="%(class)s_author", on_delete=models.CASCADE)
coach = models.ForeignKey(User, related_name="%(class)s_coach", on_delete=models.CASCADE)
comments = models.TextField()
I'd like to create a front-end form which allows a user to review a coach, including a rating for some pre-populated categories.
In my head, the form would look something like:
Coach: _______________ # Selection of all coach users from DB, this works as standard
Category: "Professionalism" # These would be DB entries from the Category model
Rating: _ / 5
Category: "Friendliness"
Rating: _ / 5
Category: "Value"
Rating: _ / 5
Comments:
_________________________________
_________________________________
Submit
I've seen Django Formsets in the documentation but these appear to exist for creating multiple forms from the same model as a single form?
Not looking for a full answer, but if someone could point me in the right direction, it'd be hugely appreciated.
EDIT: Vineet's answer (https://stackoverflow.com/a/65883875/864245) is almost exactly what I'm looking for, but it's for the Admin area, where I need it on the front-end.
Given that the categories are fairly static, you don't want your users to select the categories. The categories themselves should be labels, not fields for your users to select.
You mention in the comment, that the labels will sometimes change. I think there are two questions I would ask before deciding how to proceed here:
Who will update the labels moving forwards (do they have basic coding ability, or are they reliant on using something like the admin).
When the labels change, will their fundamental meaning change or will it just be phrasing
Consideration 1
If the person changing the labels has a basic grasp of Django, and the appropriate permissions (or can ask a dev to make the changes for them) then just hard-coding these 5 things is probably the best way forward at first:
class Review(models.Model):
author = models.ForeignKey(User, related_name="%(class)s_author", on_delete=models.CASCADE)
coach = models.ForeignKey(User, related_name="%(class)s_coach", on_delete=models.CASCADE)
comments = models.TextField()
# Categories go here...
damage = models.SmallIntegerField(
help_text="description can go here",
verbose_name="label goes here"
)
style = models.SmallIntegerField()
control = models.SmallIntegerField()
aggression = models.SmallIntegerField()
This has loads of advantages:
It's one very simple table that is easy to understand, instead of 3 tables with joins.
This will make everything up and down your code-base simpler. It'll make the current situation (managing forms) easier, but it will also make every query, view, template, report, management command, etc. you write easier, moving forwards.
You can edit the labels and descriptions as and when needed with verbose_name and help_text.
If changing the code like this isn't an option though, and the labels have to be set via something like the Django admin-app, then a foreign-key is your only way forward.
Again, you don't really want your users to choose the categories, so I would just dynamically add them as fields, rather than using a formset:
class Category(models.Model):
# the field name will need to be a valid field-name, no space etc.
field_name = models.CharField(max_length=40, unique=True)
label = models.CharField(max_length=40)
description = models.TextField()
class ReviewForm.forms(forms.Form):
coach = forms.ModelChoiceField()
def __init__(self, *args, **kwargs):
return_value = super().__init__(*args, **kwargs)
# Here we dynamically add the category fields
categories = Categories.objects.filter(id__in=[1,2,3,4,5])
for category in categories:
self.fields[category.field_name] = forms.IntegerField(
help_text=category.description,
label=category.label,
required=True,
min_value=1,
max_value=5
)
self.fields['comment'] = forms.CharField(widget=forms.Textarea)
return return_value
Since (I'm assuming) the current user will be the review.author, you are going to need access to request.user and so we should save all your new objects in the view rather than in the form. Your view:
def add_review(request):
if request.method == "POST":
review_form = ReviewForm(request.POST)
if review_form.is_valid():
data = review_form.cleaned_data
# Save the review
review = Review.objects.create(
author=request.user,
coach=data['coach']
comment=data['comment']
)
# Save the ratings
for category in Category.objects.filter(id__in=[1,2,3,4,5]):
Rating.objects.create(
review=review
category=category
rating=data[category.field_name]
)
# potentially return to a confirmation view at this point
if request.method == "GET":
review_form = ReviewForm()
return render(
request,
"add_review.html",
{
"review_form": review_form
}
)
Consideration 2
To see why point 2 (above) is important, imagine the following:
You start off with 4 categories: Damage, Style, Control and Agression.
Your site goes live and some reviews come in. Say Coach Tim McCurrach gets a review with scores of 2,1,3,5 respectively.
Then a few months down the line we realise 'style' isn't a very useful category, so we change the label to 'effectiveness'.
Now Tim McCurrach has a rating of '1' saved against a category that used to have label 'style' but now has label 'effectiveness' which isn't what the author of the review meant at all.
All of your old data is meaningless.
If Style is only ever going to change to things very similar to style we don't need to worry so much about that.
If you do need to change the fundamental nature of labels, I would add an active field to your Category model:
class Category(models.Model):
field_name = models.CharField(max_length=40, unique=True)
label = models.CharField(max_length=40)
description = models.TextField()
active = models.BooleanField()
Then in the code above, instead of Category.objects.filter(id__in=[1,2,3,4,5]) I would write, Category.objects.filter(active=True). To be honest, I think I would do this either way. Hard-coding ids in your code is bad-practice, and very liable to going wrong. This second method is more flexible anyway.
Use this in your app's admin.py file
from django.contrib import admin
from .models import Review, Rating, Category
class RatingInline(admin.TabularInline):
model = Rating
fieldsets = [
('XYZ', {'fields': ('category', 'rating',)})
]
extra = 0
readonly_fields = ('category',)
show_change_link = True
def has_change_permission(self, request, obj=None):
return True
class ReviewAdmin(admin.ModelAdmin):
fields = ('author', 'coach', 'comments')
inlines = [RatingInline]
admin.site.register(Review, ReviewAdmin)
admin.site.register(Rating)
admin.site.register(Category)
You admin page will look like this:
You could "embed" an inline formset into your review form. Then you can call form.save() to save the review and all the associated ratings in one go. Here is a working example:
# forms.py
from django import forms
from . import models
class ReviewForm(forms.ModelForm):
class Meta:
model = models.Review
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ratings = forms.inlineformset_factory(
parent_model=models.Review,
model=models.Rating,
extra=5,
min_num=5,
)(
data=self.data if self.is_bound else None,
files=self.files if self.is_bound else None,
instance=self.instance,
)
def is_valid(self):
return super().is_valid() and self.ratings.is_valid() # AND
def has_changed(self):
return super().has_changed() or self.ratings.has_changed() # OR
def save(self):
review = super().save()
self.ratings.save()
return review
As you can see, the __init__() method sets the attribute self.ratings which you can later recall in your template like this:
<form method="post">
{% csrf_token %}
<div class="review">
{{ form.as_p }}
</div>
<div class="ratings">
{{ form.ratings.management_form }}
{% for rating_form in form.ratings %}
<div class="single_rating">
{{ rating_form.as_p }}
</div>
{% endfor %}
</div>
<button>Save</button>
</form>
Finally, here's how your views.py might look like (using Django's class-based views):
from django.views import generic
from . import models
from . import forms
class ReviewView(generic.UpdateView):
model = models.Review
form_class = forms.ReviewForm

How to get just one item from ForeignKey Model in django?

I have below two models in my app,
class Question(models.Model):
question_text = models.CharField(max_length=200)
question_author = models.ForeignKey(User, on_delete=models.CASCADE)
q_pub_date = models.DateTimeField('Date Question Published')
def __str__(self):
return self.question_text
class Answer(models.Model):
answer_text = models.CharField(max_length=5000)
answer_author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ans_auth')
question = models.ForeignKey(Question, on_delete=models.CASCADE)
a_pub_date = models.DateTimeField('Date Answer Published')
def __str__(self):
return self.answer_text
I want to show latest questions on home page in a list. Each item in list will show question text and one answer out of many posted for that particular question. I am able to show question text but I am not able to find a way to get the answer item for every question item in my view below,
def home(request):
latest_question = Question.objects.order_by('-q_pub_date')[:15]
You can access all answers by using the related_name:
>>> question = Question.objects.get(question_text='random question')
>>> print(question.answer_set.all())
You can read more about this on: https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.ForeignKey.related_name
You could get the answer to a specific question with the reverse relation question_instance.answer_set.all() in views, {{ question_instance.answer_set.all }} in templates.
To select one answer, you can use slice, or first():
{{question_instance.answer_set.all.0 }} # templates
question_instance.answer_set.first() # views
If you want to add a new field votes to the Question and you want the answer which has highest votes?
A property method inside your question model will do it like:
#property
def higher_answer(self):
return self.answer_set.all().order_by('vote').last()

Showing Many-To-Many in different model's form in Django?

Say I have three models, a Professor model, a Course model, and a Review model. The user is allowed to make a Review, which reviews a Professor that teaches a certain Course.
I'm thinking of how to model the many to many relationship of Professor and Course, and how to reference that relationship in Review. My idea so far is to use models.ManyToMany to link Professor and Course.
Models.py (Prof)
class Prof(models.Model):
first_name = models.CharField(max_length = 20, unique = False)
last_name = models.CharField(max_length = 20, unique = False)
def __str__ (self):
return self.first_name + " " + self.last_name
class Course(models.Model):
name = models.CharField(max_length = 20, unique = True)
prof = models.ManyToManyField(Prof)
def __str__ (self):
return self.name
Models.py (Review)
class Review(models.Model):
message = models.TextField(max_length = 4000)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(null = True)
rating = models.IntegerField(
default = 5,
validators = [MaxValueValidator(5), MinValueValidator(0)]
)
prof = models.ForeignKey(Prof, related_name = 'reviews')
course = models.ForeignKey(Course, related_name = 'reviews')
user = models.ForeignKey(User, related_name = 'reviews')
def __str__ (self):
return self.message
forms.py
class ReviewForm(ModelForm):
rating = CharField(widget=TextInput(attrs={'type': 'number','value': 5, 'min': 0, 'max': 5}))
class Meta:
model = Review
fields = ['message', 'rating', 'prof', 'course', 'user']
This is my code so far for displaying the form
<h1>New Review</h1>
<form method="POST">
{% csrf_token %}
<p>{{ review_form.message }}</p>
<p>{{ review_form.rating }}</p>
<p>{{ review_form.prof }}</p>
<!-- The prof chosen's courses should be shown here -->
<button type="submit">Save</button>
</form>
Right now, forms.py shows all the objects under Course, and i'm not sure how to instead show the courses of a professor. Is it possible to filter the form after a prof is chosen from the drop down, to display the courses he/she teacher?
It sounds like you're going about this the right way. You haven't mentioned your urls.py structure yet, or views.py but the most straightforward way to do this is to display the courses by professor, taking the professor's id (or slug-field) in as a parameter - either in the URL (v straightforward) or as the output from a form on a previous page (and reload the template with a professor parameter) or in Ajax, depending on your appetite for shiny-new-things.
In your view, when you call the form, you can then do, along the lines from this answer -
form.courses.queryset = Course.objects.filter(professor__in=[professor.id,])
Note that I've put filtered on a list here, which only has one item - it does give you scope to expand, or to use a queryset for more complicated functions later.
Tweak as appropriate if you're using class-based views. :)

Django ORM, get data

How to get the top 10 users, whose question with most answers? I did it by using 2 requests to db, but I need in one request, if possible.
P.S. I'm using MySQL, there is no 'distinct'
Result format:
User.username | Question.id | number of comments
I have the models:
from django.contrib.auth.models import User
...
class Category(models.Model):
name = models.CharField(max_length=255)
class Question(models.Model):
header = models.CharField(max_length=255)
author = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
creation_date = models.DateTimeField(default=datetime.datetime.now)
view_count = models.IntegerField(default=0)
content = models.TextField()
category = models.ForeignKey(Category, null=True, on_delete=models.SET_NULL)
class Answer(models.Model):
question = models.ForeignKey('Question')
author = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
content = models.TextField()
My realization with 2 requests:
def select5():
print 'SELECT 5'
N = 10
top_n_questions = Answer.objects.all().values('question').annotate(ans_count=Count('question')).order_by('ans_count').reverse()[:N]
top_n_questions_id = []
top_n_questions_rate = []
top_n_users = []
print top_n_questions
for dic in top_n_questions:
top_n_questions_id.append(dic['question'])
top_n_questions_rate.append(dic['ans_count'])
top_n_questions = Question.objects.filter(pk__in=top_n_questions_id)
for question in top_n_questions:
top_n_users.append(question.author)
for i in range(N):
print '%s question id: %d, rate: %d' % (top_n_users[i].username,\
top_n_questions_id[i], top_n_questions_rate[i])
UPD: I found how to do it:
def select6():
print 'SELECT 6'
print 'bla-bla-bla'
top_questions = Question.objects.annotate(answers=Count('answer')).order_by('-answers').select_related('author')[:10]
for question in top_questions:
print '%s question id: %d, rate: %d' % (question.author, question.id, question.answers)
1) By logic you need something like that below (but I think it will work only if question with user has one_to_one relation), so I think it doesn't work, but you can try it =) :
top_users = User.objects.annotate(ans_count=Count('question_set__answer_set')).order_by('-ans_count')[:10]
2) Solution that I suggest (I think is not best, but simple one) is additional field answer_count in Question that store count of answer so I can sort or filter by that value. This field updates after every Answer.objects.create() by post save signal:
#receiver(post_save, sender=Answer)
def update_answer_count(sender, instance, created, **kwargs):
question = instance.question
question.answer_count = sender.objects.filter(question=question).count()
question.save(update_fields=['answer_count'])
so after this you can do:
top_users = User.objects.order_by('-question_set__answer_count')[:10]
and in your template:
{% for user in top_users %}
{% for question in user.question_set.all %}
{{ user.username }}
{{ question.id }}
{{ question.answer_count }}
{% endfor %}
{% endfor %}
This is Laravel's ORM
User::orderBy('answers', 'desc')->take(10)->get();
something like this mate but for DJango!
Entry.objects.all().order_by('answers')[:5]

django how to get count for manytomany field

I have model for question:
class Question(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=120)
description = models.TextField()
answers = models.ManyToManyField('Answer',related_name='answer_name', blank=True)
post_date = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.title
And I have model for answer:
class Answer(models.Model):
user = models.ForeignKey(User)
question = models.ForeignKey(Question)
ans_body = models.TextField()
post_date = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.ans_body
Question creation and answer submission are working perfectly. I cant correctly show answer for particular question. But when I try to get the count of answer for particular question its not showing. It displays 0 count.
In my view I am getting the list of the answer by:
context["question_list"] = Question.objects.all()
And in my template
{% for question in question_list %}
{{ question.title }}
Ans:{{question.answers.count}}
{% endfor %}
When I do this I get the count 0 if there are answers. How can I get the count of the answers for particular questions.
This worked:
{{question.answer_set.count}}
Happy..
You can do something like {{ question.answers.all.count }}, but if you are iterating over more than question it will cause a database query for every question.
If you want to annotate the whole queryset with the count for each question:
from django.db.models import Count
context['question_list'] = Question.objects.all().annotate(
answer_count=Count('answers')
)
Then you can access the count for each question with {{ question.answer_count }}.
Why not use: Question.objects.all().count()
For my project, I have a Field in 'Info' Model
users_like = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="%(app_label)s_%(class)s_likes", blank=True)
I use below code to count the number of like then show it in Admin List Page.
# admin.py
from django.contrib import admin
from .models import Info
class InfoAdmin(admin.ModelAdmin):
list_display = ('id', 'title', 'like_count',)
def like_count(self, obj):
return obj.users_like.all().count()
admin.site.register(Info, InfoAdmin)
The result is:
Hope these can help you!
This will work for you without any problem in your case:
{{question.answer_name.count}}

Categories