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 %}
Related
I am able to render list of all courses and list of topics corresponding to the courses in different templates.
I need help to view list of all courses and when each course is clicked,a new page should show the list of associated topics
models.py
class Topic(models.Model):
topic_name = models.CharField(max_length=200, null=True)
topic_file = models.FileField(upload_to = "topic_file", blank=True, null=True)
def __str__(self):
return self.topic_name
class Course(models.Model):
course_name = models.CharField(max_length=200, null=True)
course_image = models.ImageField(upload_to="images", blank=True, null=True)
related_topic = models.ManyToManyField(Topic)
def __str__(self):
return self.course_name
Views.py
def view_course(request):
course_list = Course.objects.all()
context = {'course_list':course_list}
return render(request, 'upskill/view_course.html',context)
def course_topic(request,pk):
course_topic_list = Course.objects.get(id=pk)
var = course_topic_list.related_topic.all
context = {'var':var}
return render(request, 'upskill/course_topic.html',context)
Here is how you could get the related topics inside of a template.
{% for course in course_list %}
... data
{% for topic in course.related_topic.all %}
...data
{% endfor %}
{% endfor %}
If you don't want to do a query every single iteration of the {{course}} loop, I recommend you this in your views:
course_list = Course.objects.all().prefetch_related('related_topic')
And for a single object:
def course_topic(request,pk):
course = Course.objects.prefetch_related('related_topic').get(id=pk)
context = {'course ':course }
return render(request, 'upskill/course_topic.html',context)
And then in the template:
{{course.data...}}
{% for topic in course.related_topic.all %}
...data
{% endfor %}
To only have topics:
def topic_view(request, pk)
topics = Topic.objects.filter(course__pk=pk) #The reverse name of Course model.
# You can set a related name the "related_topic" field.
# Then access the above filter with that related name.
.... data
context = {
"topics":topics
}
return render(request, 'template.html', context)
Accessing primary keys in Django class based view
Let's start from the beginning. I have 2 models, Recipe, and Ingredient. They look like this.
In models.py
class Recipe(models.Model):
name=models.CharField(max_length=20, help_text='Enter the name of this recipe')
description=models.TextField(max_length=75, help_text='Describe your recipe')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('recipe-detail', kwargs={'pk': self.pk`})
class Ingredient(models.Model):
recipe=models.ForeignKey(Recipe, on_delete=models.CASCADE)
ingredient=models.CharField(max_length=100)
class Meta:
ordering = ['ingredient']
def __str__(self):
return self.ingredient
What I want to be able to do is have a detail view, where I can access the Recipe attributes, like the name and description, as well as, be able to loop through the ingredients. This is what I have working so far:
In views.py
def recipe_detail_view(request, pk):
recipe = get_object_or_404(Recipe, pk=pk)
context = {
'recipe': recipe,
'ingredients': Ingredient.objects.filter(recipe=pk)
}
return render(request, 'recipes/recipe_detail.html', context=context)
In urls.py
# ...
path('recipes/<str:pk>', views.recipe_detail_view, name='recipe-detail')
# ...
In template
<h1 class="title is-1">{{ recipe.name }}</h1>
<p>{{ recipe.description }}</p>
<h3 class="title">Ingredients</h3>
{% for ingredient in ingredients %}
<h4 class="">{{ ingredient.ingredient.title }}</h3>
{% endfor %}
I am wondering how I could turn this into a class based view however. More specifically, I am wondering how I can access and pass in the primary key to the filter like so:
class RecipeDetailView(generic.DetailView):
model = Recipe
template_name = 'recipes/recipe_detail.html'
context_object_name='recipe'
extra_context = {
'ingredients': Ingredient.objects.filter(recipe=pk),
}
Can anyone help?
You can use get_context_data and get_object to get the data you want to your template.
class RecipeDetailView(generic.DetailView):
model = Recipe
template_name = 'recipes/recipe_detail.html'
context_object_name='recipe'
def get_context_data(self, **kwargs)
ctx = super().get_context_data(**kwargs)
ctx['ingredients'] = Ingredient.objects.filter(recipe=self.get_object().pk)
return ctx
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.
Given the Django example in making queries:
class Author(models.Model):
name = models.CharField(max_length=50)
...
def __str__(self):
return self.name
class Entry(models.Model):
...
authors = models.ManyToManyField(Author)
I'd like to have an author DetailView which contains a list of entries for that author. What I have so far:
class AuthorDetailView(DetailView):
model = Author
def get_context_data(self, **kwargs):
context = super(AuthorDetailView, self).get_context_data(**kwargs)
context['entries'] = Entry.objects.filter(
authors__name=self.object.name)
return context
and in my template:
{% for entry in entries %}
…
{% endfor %}
I'd also prefer to not filter by name but that specific author since name could be non unique.
You could use reverse relationship
context['entries'] = self.object.entry_set.all()
This gives you all Entry objects of that Author.
EDIT:
And why are you using author__name?
You can filter by the object directly:
context['entries'] = Entry.objects.filter(authors=self.object)
I have a reddit-like website where users post links and those links can be commented on. I want to display the number of comments under each link before being taken to the comment page for that link. What is the most query efficient way to do this in django that doesn't slow down the rendering of my site? I assume I'll have to go through a for loop of each link on the page to count the number of posts for each one and populate a list of some sort with the number returned from the .count() of each queryset? Here's what I have:
class Post(models.Model):
newlinktag = models.ForeignKey('NewLink', null=False)
childtag = models.ForeignKey('Post', blank=True, null=True)
postcontent = models.CharField(max_length=1024)
def __unicode__(self):
return self.postcontent
class NewLink(models.Model):
posttag = models.ForeignKey('PageInfo') #the page each link belongs to
linkcomment = models.CharField(max_length=512)
postlinkdate = models.DateTimeField(auto_now_add=True) #submission datestamp.
url = models.URLField(max_length = 1024)
linkowner = models.ForeignKey(User, null=True, blank=True)
def __unicode__(self):
return self.url
Jape gave you a good answer, but it is always more efficient to preform counting in the database rather than in python loops.
views.py
from django.db.models import Count
def view(request):
# Calculate the counts at the same time we fetch the NewLink(s)
links = NewLink.objects.annotate(post_count=Count('post_set'))
return render(request, 'template.html', {'links': links})
html
{% for link in links %}
{{ link.post_count }}
{% endfor %}
In your model, I would create a cached_property and then when you run the for loop in your template, call the property to get the count.
For example,
models.py:
class NewLink(models.Model):
posttag = models.ForeignKey('PageInfo') #the page each link belongs to
linkcomment = models.CharField(max_length=512)
postlinkdate = models.DateTimeField(auto_now_add=True) #submission datestamp.
url = models.URLField(max_length = 1024)
linkowner = models.ForeignKey(User, null=True, blank=True)
def __unicode__(self):
return self.url
# Might also want to flush this after a post_save method in your signals
#cached_property
def linkcomment_count(self):
return self.linkcomment.count()
views.py:
def view(request):
# Could do a 'select_related' relationship to save hits on the database
comments = NewLink.objects.all()
return render(request, 'template.html', {'comments': comments})
html:
{% for link in comments %}
{{ link.linkcomment_count }}
{% endfor %}
Did I understand your question correctly?