dmandres dmandres - 2 months ago 16
Python Question

django - display the human readable when running Avg()

I'm figuring out this piece by piece. I want users to be able to rate blog posts based on 4 criteria (difficulty_rating, workload_rating, book_rating, attendance_rating). I have used choices in my models.py to give users a choice.

After that, thanks to a user from StackOverflow, I was able to calculate the average for each of these values and display them at the top of every single post.

The problem is that they show up as floats with long decimals. Is there a way to show the human readable instead? Which comes from my model tuples?

I have done an {% if %} statement inside my template hardcoding everything, but I'm pretty sure there has to be a better way.

models.py

class Post(models.Model):
STATUS_CHOISES = (

('draft', 'Draft'),
('published', 'Published'),

)
category = models.ForeignKey(Category)
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250, unique=True)
content = models.TextField()
seo_title = models.CharField(max_length=250)
seo_description = models.CharField(max_length=160)
author = models.ForeignKey(User, related_name='blog_posts', default=settings.AUTH_USER_MODEL)
published = models.DateTimeField(default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=9, choices=STATUS_CHOISES, default='draft')

def get_absolute_url(self):
return reverse('blog:post_detail', args=[self.slug])


def avg_ratings(self):
return self.comments.aggregate(
Avg('difficulty_rating', output_field=FloatField()),
Avg('workload_rating', output_field=FloatField()),
Avg('book_rating', output_field=FloatField()),
Avg('attendance_rating', output_field=FloatField()),
)


def __str__(self):
return self.title



class Comment(models.Model):
difficulty_rating_choices = (

(1, 'Very Easy'),
(2, 'Easy'),
(3, 'Moderate'),
(4, 'Hard'),
(5, 'Very Hard'),

)

workload_rating_choices = (

(1, 'Very Light'),
(2, 'Light'),
(3, 'Moderate'),
(4, 'Heavy'),
(5, 'Very Heavy'),

)

book_rating_choices = (

(1, '$'),
(2, '$$'),
(3, '$$$'),
(4, '$$$$'),
(5, '$$$$$'),

)

attendance_rating_choices = (

(1, 'Not Required'),
(2, 'Required'),


)
post = models.ForeignKey(Post, related_name="comments")
user = models.CharField(max_length=250)
email = models.EmailField()
title = models.CharField(max_length=250)
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False)
difficulty_rating = models.IntegerField(choices=difficulty_rating_choices)
workload_rating = models.IntegerField(choices=workload_rating_choices)
book_rating = models.IntegerField(choices=book_rating_choices)
attendance_rating = models.IntegerField(choices=attendance_rating_choices)


def approved(self):
self.approved = True
self.save()

def __str__(self):
return self.title


views.py

def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
template = 'blog/post/post_detail.html'
context = {

'post': post,

}
return render(request, template, context)


template

{% with avg_ratings=post.avg_ratings %}
<h1>Difficulty: {{ avg_ratings.difficulty_rating__avg }}</h1>
<h1>Workload: {{ avg_ratings.workload_rating__avg }}<h1>
<h1>Book Cost: {{ avg_ratings.book_rating__avg }}<h1>
<h1>Attendance: {{ avg_ratings.attendance_rating__avg }}<h1>

{% endwith %}

ivo ivo
Answer Source

Use a django template filter https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#floatformat

For example:

{{ avg_ratings.difficulty_rating__avg|floatformat:"-3" }}

If by "human readable" you want to substitute words for ranges of values, create your own custom template filter to do that.

See https://docs.djangoproject.com/en/1.11/howto/custom-template-tags/#howto-writing-custom-template-filters

You could do something like (add the ranges for your levels):

from django import template

register = template.Library()

@register.filter
def rating_humanize(value): # Only one argument.
    if value < 1.0:
        return 'Very Easy'
    elif value < 2.0:
        return 'Easy'
    else:
        return 'Hard'

A better implementation would be to make a list of range to string mappings and then find the matching range and return the string for it.