I've read through previous questions, and tried reading up in the docs but I've had no real luck with this.
I'm using Django-tables2 to display data of students.
One of the columns in the table (current standing) is populated using an accessor to a model manager in the student model, as follows:
models.py
class Student(models.Model):
#a bunch of fields
def get_current_standing(self):
current_standing = AcademicStanding.objects.get(year=self.enrol_term, student=self).standing
return current_standing
tables.py
class StudentTable(tables.Table):
current_standing = tables.Column(accessor='get_current_standing')
class Meta:
model = Student
fields = ["last_name", "first_name", "campus_id", "current_standing"]
The table populates and displays correctly, but sorting by generates an error. I can adjust the Column as follows:
current_standing = tables.Column(accessor='get_current_standing', order_by='academicstanding.standing')
But because the relationship is 1:N I get multiple results, where (as shown by the manager in the students model), I only want the academic standing for a student for that particular year of enrolment. This method also doesn't group the entries according to the standing.
Finally, is this method of populated the table using a model manager as an accessor correct? What am I missing to enable the correct, expected functionality?
Ok that was kinda fun. Would have been faster if you'd given me a repository, but you gave plenty of information, so I was able to make my own without trouble. :D It's up here, (commit e7f70e at time of writing).
For tidiness' sake, I did everything important in the QuerySet
s, so take a look at students/managers.py
.
In here, you'll see the annotation that I was having trouble figuring out in the comments above. I've translated it to something more standalone below:
Student.objects.annotate(current_standing=models.Subquery(
AcademicStanding.objects \
.filter(student=models.OuterRef('id')) \
.filter(year__year=2018) \
.values('standing'),
)
)
Which works on the following models:
class Student(models.Model):
name = models.CharField(max_length=20, ...)
...
class AcademicStanding(models.Model):
student = models.ForeignKey(
'students.Student',
related_name='academic_standings',
on_delete=models.CASCADE,
)
standing = models.PositiveIntegerField()
year = models.DateField()
...
The query utilises a models.Subquery
to do, well, a subquery, using OuterRef
to refer to the current row's id
, meaning each Student
will get their own AcademicStanding
associated with them, as opposed to the first AcademicStanding
in the set, without that filter.
With our table defined as follows, everything "just works", as the value we're interested in is now natively on the queryset. All the performance remains, too, as this is actually calculation done within the database, instead of in Python with multiple queries.
class StudentTable(tables.Table):
class Meta:
model = Student
fields = ['name', 'current_standing']
One cool thing I learned on the way through, was about RequestConfig
. This is something within django-tables2
that takes the current request, and automatically does what's needed to the table it's given to match the GET parameters and such. You can see this in action here. It was completely new to me.
For your Question 2 - I suspect that you'll find that much easier to reason about and implement with the annotation I've detailed above, but if not, please post another question for it.
(So I can get more answer points). :P