django count of foreign key model - python

Hi i want to display a count of answers to my question model
my model:
class Question(models.Model):
text = models.TextField()
title = models.CharField(max_length=200)
date = models.DateTimeField(default=datetime.datetime.now)
author = models.ForeignKey(CustomUser)
tags = models.ManyToManyField(Tags)
def __str__(self):
return self.title
class Answer(models.Model):
text = models.TextField()
date = models.DateTimeField(default=datetime.datetime.now)
likes = models.IntegerField(default=0)
author = models.ForeignKey(CustomUser)
question = models.ForeignKey(Question)
my view:
def all_questions(request):
questions = Question.objects.all()
answers = Answer.objects.filter(question_id=questions).count()
return render(request, 'all_questions.html', {
'questions':questions, 'answers':answers })
Right now view displays count of all answers. How can i filter it by the Question model?

You can use .annotate() to get the count of answers associated with each question.
from django.db.models import Count
questions = Question.objects.annotate(number_of_answers=Count('answer')) # annotate the queryset
By doing this, each question object will have an extra attribute number_of_answers having the value of number of answers associated to each question.
questions[0].number_of_answers # access the number of answers associated with a question using 'number_of_answers' attribute
Final Code:
from django.db.models import Count
def all_questions(request):
questions = Question.objects.annotate(number_of_answers=Count('answer'))
return render(request, 'all_questions.html', {
'questions':questions})
In your template, then you can do something like:
{% for question in questions %}
{{question.number_of_answers}} # displays the number of answers associated with this question

See the docs
You can annotate the Query, like:
from django.db.models import Count
questions = Question.objects.annotate(num_answer=Count('answer'))
but, refactor the code to this.
Remove the count of answers:
def all_questions(request):
questions = Question.objects.all()
return render(request, 'all_questions.html', {'questions':questions })
Now, in all_question.html. Just use :
{% for question in questions %}
Title: {{question.title}}
Count Answers: {{question.answer_set.all|length}}
{% for answer in question.answer_set.all %}
{{answer.text}}
{% endfor %}
{% endfor %}
It is more efficienty.

Related

Django: Sort given results of method on model

In Django, how can I sort the results of a method on my model?
class Flashcard(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
deck = models.ForeignKey(Deck, on_delete=models.CASCADE)
question = models.TextField()
answer = models.TextField()
difficulty = models.FloatField(default=2.5)
objects = FlashcardManager()
def __str__(self):
return self.question
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,related_name='profile')
bio = models.TextField(max_length=500,null=True, default='',blank=True)
def __str__(self):
return f'{self.user.username} Profile'
def avg_diff_user(self):
avg_diff = Flashcard.objects.filter(owner = self.user).aggregate(Avg('difficulty'))['difficulty__avg']
return avg_diff
So with avg_diff_user, I get each user's average difficulty rating. Which I can then use in my leaderboard template as follows:
<ol>
{% for user in leaderboard_list %}
<li>{{user.username}}: {{user.profile.avg_diff_user|floatformat:2}}</li>
{% endfor %}
</ol>
The results show, but it's not sorted - how can I sort by avg_diff_user? I've read many similar questions on SO, but to no avail. I've tried a different method on my model:
def avg_diff_sorted(self):
avg_diff_sorted = Flashcard.objects.all().annotate(get_avg_diff_user=Avg(Flashcard('difficulty'))['difficulty__avg'].order_by(get_avg_diff_user))
return avg_diff_sorted
Which I don't think is right and didn't return any results in my template. I also tried the following, as suggested in https://stackoverflow.com/a/930894/13290801, which didn't work for me:
def avg_diff_sorted(self):
avg_diff_sorted = sorted(Flashcard.objects.all(), key = lambda p: p.avg_diff)
return avg_diff_sorted
My views:
class LeaderboardView(ListView):
model = User
template_name = 'accounts/leaderboard.html'
context_object_name = 'leaderboard_list'
def get_queryset(self):
return self.model.objects.all()
something like:
leaderboard_list = User.objects.all().annotate(avg_score=Avg('flashcard__difficulty').order_by('-avg_score')
will sort you the users by their average score.
I don't use ListView that often by if you just used a standard view like:
def LeaderboardView(request):
leaderboard_list = ...
context = {'leaderboard_list':leaderboard_list}
return render(request, 'accounts/leaderboard.html', context)
In your html you could do the same:
{% for user in leaderboard_list %}
...
{% endfor %}

Django loop is returning multiple posts

I am trying to get django to display posts from a single catgory on that category's page, instead, it displays all the categories and the posts under each category in the template.
Here are my models (I trimmed them down):
class Category(models.Model):
...
title = models.CharField(max_length=255, verbose_name="Title", null=True, blank=True)
slug = models.SlugField(max_length=200, unique=True)
class Petition(models.Model):
...
category = models.ManyToManyField(Category, verbose_name="Category", related_name='petitions')
...
views.py:
class CategoryMixin(object):
def get_context_data(self, **kwargs):
context = super(CategoryMixin, self).get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
class CategoryView(CategoryMixin, generic.ListView):
model = Category
categories = Category.objects.all() # this will get all categories, you can do some filtering if you need (e.g. excluding categories without posts in it)
queryset = Category.objects.all()
template_name = 'petition/category_list.html'
class CategoryIndexView(generic.ListView):
model = Category
template_name = 'petition/category.html'
context_object_name = 'category_list'
def get_queryset(self):
return Category.objects.prefetch_related('petitions').order_by('-created_on')[:10]
class PetitionIndexView(generic.ListView):
template_name = 'petition/home.html'
context_object_name = 'petition_list'
queryset = Petition.objects.order_by('-created_on')
def get_queryset(self):
queryset_list = Petition.objects.order_by('-created_on')
#SEARCH QUERY LOGIC
query = self.request.GET.get("q")
if query:
queryset_list = queryset_list.filter(
Q(title__icontains = query) |
Q(petition__icontains = query) |
Q(created_by__first_name__icontains = query) | #icontains = only text fields. Use text field within foreign key field
Q(created_by__last_name__icontains = query)
).distinct()
paginator = Paginator(queryset_list, 10) # Show 10 posts per page
page = self.request.GET.get('page')
try:
queryset = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
queryset = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
queryset = paginator.page(paginator.num_pages)
return queryset
context = {
'object_list' : queryset,
'title' : 'List'
}
urls.py:
url(r'^$',views.PetitionIndexView.as_view(), name="homepage"),
url(r'^petition/(?P<slug>[\w-]+)/$',views.DetailView.as_view(), name="detail"),
url(r'^categories/$', views.CategoryView.as_view(), name='category_list'),
url(r'^category/(?P<slug>[\w-]+)/$',views.CategoryIndexView.as_view(), name="category_detail"),
And my template (category.html for the CategoryIndexView) is in this format,
category.html:
{% for category in category_list %}
{% for petition in category.petitions.all %}
...
{{ petition.title }}
...
{% end for %}
{% end for %}
This shows posts in Category A, B and C in the same template.
With the views I set, how can I get it to show only posts for category A when category A is clicked?
Also, the ordering is not working properly. It is not adhering to the ordering I gave it in the view
You're using the wrong view class. A ListView is to show a list of instances of the class; so a CategoryIndexView would be to show the list of categories. But you don't want a list of categories; you want a single category, along with a list of its petitions. So, you should use a DetailView.
class CategoryDetailView(generic.DetailView):
model = Category
template_name = 'petition/category.html'
context_object_name = 'category'
def get_queryset(self):
return Category.objects.prefetch_related('petitions')
And the template is just:
{% for petition in category.petitions.all %}
...
{{ petition.title }}
...
{% end for %}
Edit: I changed the context_object_name from 'category_list' to 'category' and the template is returning the right posts now.
I'm guessing that every time you change categories, the whole loop executes again, without changing the contents of category_list (which initially is ["A", "B", "C"]). In that case, even though you click on the button to change category, the loops displays the contents of the categories "A", "B" and "C" again regardless.
Have you tried adding an if statement that checks in which category you are at the moment and only show the posts in that category?
{% for category in category_list %}
{% if category.name == ACTUAL_CATEGORY_TO_DISPLAY %}
{% for petition in category.petitions.all %}
...
{{ petition.title }}
...
{% end for %}
{% end if %}
{% end for %}
Edit:
As #Daniel Roseman pointed out in a comment, this is a quite inefficient solution. I've just posted it because I'm guessing if this is really what is happening to you.
If this is the case you shouldn't be doing this. One of the proper solutions is making a new request that retrieves all the posts for that given category every time you change it.
Anyhow, I'm just guessing here. For us to give you a better insight you should post the template code that does the category change.

Django making querys inside template

I have the following problem. I need to get the information of the question based on the questionitem.question_id
I have the following files.
# models.py
class Question(models.Model):
qtype_id = models.ForeignKey(QuestionType, on_delete=models.CASCADE )
q_discription = models.CharField(max_length=150)
def __iter__(self):
return self.pk
class QuestionItem(models.Model):
exam_id = models.ForeignKey(Exam, on_delete=models.CASCADE)
question_id = models.ForeignKey(Question, on_delete=models.CASCADE)
question_pontuation = models.FloatField(default=0.0)
def __iter__(self):
return self.pk
# views.py
def ExamDetail(request, exam_id):
exam = get_object_or_404(Exam, pk=exam_id)
questionitems = QuestionItem.objects.filter(exam_id= exam_id).values_list('id', flat=True)
questions = Question.objects.filter(pk__in=questionitems)
context = {
'exam': exam,
'questions': questions,
'questionitem': questionitems,
}
return render(request, 'evaluation/exam_detail.html' , context)
And now on exam_detail.html i have something like this:
{% for questionitem in exam.questionitem_set.all %}
Question {{questionitem.id}}</a>: {{question.q_discription}}
{% endfor%}
but nothing shows, and i need to show the description of the question in questionitem.question_id, and i cant change the models.py.
Probably, you have problem in the way, you are accessing queryset in loop. Change code to :-
{% for questionitem in exam.questionitem_set.all %}
Question {{questionitem.id}}</a>: {{questionitem.question_id.q_discription}}
{% endfor%}
q_discription is the part of Question class, which is accessible using question_id on questionitem instance, that you receive in the loop.

How to render multiple forms - radiobuttons for answers

I can't figure out how to create a page with multiple Questions and their Answers. The main thing is that I want User to take a Quiz which contain's multiple Questions and Questions have multiple Answers.
The first thing I want is to render at least one Question with it's answers, and if it works, then figure out how to render multiple questions on one page (whole Quiz), but it doesn't render anything except base.html.
But when I try to print question_form in a view, it returns:
Exception Type: TypeError at /language-tests/question
Exception Value:'Answer' object is not iterable
Do you have any ideas, what's wrong?
question.html
{% extends 'base.html' %}
{% block content %}
{{ question_form }}
{% endblock %}
FORM
class QuestionForm(forms.Form):
def __init__(self, question, *args, **kwargs):
super(QuestionForm, self).__init__(*args, **kwargs)
choice_list = [x for x in question.get_answers_list()]
self.fields["answers"] = forms.ChoiceField(choices=choice_list,
widget=forms.RadioSelect)
VIEW - simple view just for see if the question form can be rendered
def question(request):
question_form = forms.QuestionForm(question=models.Question.objects.get(pk=1))
return render(request,'question.html',context={'question_form':question_form})
MODELS
class LanguageQuiz(models.Model):
name = models.CharField(max_length=40)
language = models.OneToOneField(sfl_models.Language)
max_questions = models.IntegerField()
def __str__(self):
return '{} test'.format(self.name)
def __unicode__(self):
return self.__str__()
class Question(models.Model):
language_quiz = models.ForeignKey(LanguageQuiz,related_name='questions')
text = models.TextField()
def get_answers_list(self):
return self.answers.all()
class Answer(models.Model):
question = models.ForeignKey(Question,related_name='answers')
text = models.TextField()
correct = models.BooleanField()
A Django form field choices argument should be an iterable of 2-tuples, see https://docs.djangoproject.com/en/1.9/ref/models/fields/#choices
You probably want to say something like
choice_list = [(x.id, x.text) for x in question.get_answers_list()]
Your exception 'Answer' object is not iterable is because Django is trying to iterate over this 2-tuple, but finding instead the Answer object.

How to call a model method?

I am trying to display only objects, which are not older then 4 days. I know I can use a filter:
new = Books.objects.filter(pub_date__gt = datetime.now() - timedelta(days=4))
but I really want to use a modal method for exercise.
The method is defined in model Book and is called published_recetnly.
So my question is how to call a modal method in views.py?
This is my current code:
views.py
def index(request):
new = Books.objects.filter(pub_date__gt = datetime.now() - timedelta(days=4))
return render_to_response('books/index.html', {'new':new}, context_instance=RequestContext(request))
index.html
{% if book in new %}
{{ book.title }}
{% endif %}
models.py
class Book(models.Model)
pub_date = models.DateTimeField('date published')
def published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=4) <= self.pub_date <= now
Maybe you should use a manager in this case. It's more clear and you can use it to retrieve all published recently books.
from .managers import BookManager
class Book(models.Model)
pub_date = models.DateTimeField('date published')
objects = BookManager()
Set like this your managers file:
class BookManager(models.Manager):
def published_recently(self,):
return Books.objects.filter(pub_date__gt = datetime.now() - timedelta(days=4))
And now, you can filter more clearly in your views file.
Books.objects.published_recently()

Categories