Reputation: 326
I am building a simple school-registration app in Django and am struggling to figure out how to create a report. I see it as pivoting the data but that may not really be what I'm trying to do. Extensive research and playing with pandas, chartit, etc has yielded no answers so I'm hoping you can help.
class SemesterPeriod(models.Model):
name = models.CharField(max_length=50)
class Schedule(models.Model):
semester = text
course = Foreign Key
semester_period = models.ForeignKey(SemesterPeriod)
class Student(models.Model):
first_name = models.CharField(max_length=50)
class StudentEnroll(models.Model):
schedule = ForeignKey('Schedule')
student = ForeignKey('Student')
The data might look like this for two students
SemesterPeriod
1st_Period
2nd_Period
3rd_Period
Schedule
Fall, Math, 1st_Period
Fall, History, 2nd_Period
Fall, Science, 3rd_Period
Student
Chris
Kim
StudentEnroll
Math, Chris
Science, Kim
Science, Chris
History, Kim
Now, what I want to display is something like this:
Student 1st_Period 2nd_Period 3rd_Period
Chris Math Science
Kim History Science
OR......
Student 1st_Period 2nd_Period 3rd_Period
Chris X X
Kim X X
You can see, I hope, how I'm "pivoting" the data. Since I'm not summarizing or aggregating it I don't know if that is the right term to use. In any case, I can't figure out how to do this efficiently. I could make tons of database queries but there has to be an easier way.
Upvotes: 2
Views: 2189
Reputation: 17751
You should be able to do two DB queries, (1) where you get all the student enrolls (fetched with appropriate other data), and (2) where you get a sorted list of your periods. You could probably get away with optimizing the second query away (but personally I wouldn't bother).
In your view process this StudentEnroll list using a defaultdict:
from collections import defaultdict
enrolls = StudentEnroll.objects.filter(semester=semester).values_list(
'student__name', 'schedule__course', 'schedule__semester_period__name')
# Fetch all the enrolled data; this puts them in a list of tuples like
# [("Chris", "Math", "1st_Period"), ("Kim", "Science", "3rd_Period"), ... ]
periods = SemesterPeriod.objects.values_list('name', flat=True).order_by('name')
# get names of all periods in a flat list like: ['1st_Period', '2nd_Period', '3rd_Period']
period_dict = dict([(period,i) for i,period in enumerate(periods)])
# enumerate(periods) is a generator that has data in the form
# [(0, '1st_Period'), (1, '2nd_Period'), (2, '3rd_Period')] -- it enumerates the list.
# I then use a list comprehension to reverse it:
# [(period,i) for i,period in enumerate(periods)] is
# [('1st_Period', 0), ('2nd_Period', 1), ...]
# and then convert that list of paired tuples into a dictionary:
# {'1st_Period': 0, '2nd_Period': 1, '3rd_Period': 2}
# period_dict['3rd_Period'] returns 2; indicating that classes that are third period
# should fall into a students schedule in the third slot of the list
# (first element of list is 0)
students_dict = defaultdict(lambda: [""]*len(periods))
# a defaultdict that when a new student (not previously stored) is seen
# initializes that student to have an empty schedule.
# the empty schedule is an empty list that is as long as the number of periods,
# with each period an empty string ''
# student_dict once fully populated will be of form
# {'Chris': ['Math', '', 'Science'], 'Kim': ['', 'History', 'Science']}
# Note student_dict['Chris'] = ['Math', '', 'Science'] and
# student_dict['Chris'][0] = 'Math'.
for (student_name, course, period) in enrolls:
students_dict[student_name][period_dict[period]] = course
# go through the list of enrolls and assign courses to the schedule in the appropriate spots.
student_list = list(student_dict.items())
# [['Chris', ['Math', '', 'Science']], ['Kim', ['', 'History', 'Science']],]
Then return student_list and periods to your template through the context variables, and use something like
<table>
<thead>
<tr>
<th>Student</th>
{% for per in periods %}
<th>{{ per }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for student_row in student_list %}
<tr><th>{{ student_row.0 }}</th>
{% for class in student_row.1 %}
<td>{{ class }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
Upvotes: 4