djangodjango-admindjango-admin-filters

The calculated field on my django admin page is returning the average grade for the wrong column


I'm trying to add a computed column to one of my models on the admin page, specifically adding an average mark from a foreign key in my Mark model, and adding it to the relevant Student in my student model.

When I run an aggregate avg command within my StudentAdmin class, it returns the average of the grades id field, not the mark field.

Here is the relevant code:

models.py

class Student(models.Model):
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    email = models.EmailField(unique=True)

    def __str__(self) -> str:
        return self.last_name
    
    class Meta:
        ordering = ['last_name']
    
class Module(models.Model):
    module = models.CharField(max_length=255)

class Grade(models.Model):
    mark = models.PositiveSmallIntegerField(null=True)
    module_id = models.ForeignKey(Module, on_delete=models.PROTECT)
    student_id = models.ForeignKey(Student, on_delete=models.PROTECT)

    def __str__(self) -> str:
        return self.mark
...
admin.py
...
@admin.register(models.Student)
class StudentAdmin(admin.ModelAdmin):
    list_display = ['id', 'first_name', 'last_name', 'average_grade']

    def average_grade(self, student):
        return student.average_grade
    
    def get_queryset(self, request):
        return super().get_queryset(request).annotate(
            average_grade = Avg('grade')
        )
    

How do I specify which column from Grade to use?


Solution

  • To access the attributes of related models, we use __(double underscore) after model name, so change your query as below:

    return super().get_queryset(request).annotate(
            average_grade = Avg('grade__mark')
        )
    

    There is also error in your code, in your Grade model, mark is represented as integer, but str method returns a string, so you have to convert it to str before returning.

    class Grade(models.Model):
    ...
    def __str__(self) -> str:
        return str(self.mark)
    

    Advice: You should consider providing related_name in your ForeignKey fields as below:

    module_id = models.ForeignKey(Module, related_name="module_grades",on_delete=models.PROTECT)
    student_id = models.ForeignKey(Student, related_name="grades",on_delete=models.PROTECT)