python - 如何将链接模型中的交叉表样式项聚合到其父项?
问题描述
我希望从如下所示的模型结构构建一个交叉表视图。我想要一个矩阵,其中日历日期在 LHS 下方,课程名称在顶部,每个单元格中的内容表示该日期/课程上存在预订实例。
简化模型结构:
class Calendar(models.Model):
crCalDate = models.DateField(primary_key=True)
class Course(models.Model):
ceName = models.CharField()
class CourseBooking(models.Model):
cbCourse = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='booked_course')
cbDay = models.ForeignKey(Calendar, on_delete=models.CASCADE, related_name='booked_day')
目前我的视图函数的查询集:
class DayView(ListView):
def get_queryset(self):
# construct index of course tuples
course_index = [(cn['id'], 'course' + str(i)) for i, cn in enumerate(Course.objects.order_by('id').values('id'))]
coursebookings = CourseBooking.objects.filter(cbDay__crCalDate__gte=dt.datetime.today()).values('id', 'cbDay__crCalDate', 'cbCourse__id', 'cbCourse__ceName')
calendar_dates = list(Calendar.objects.filter(crCalDate__gte=dt.datetime.today()).order_by('crCalDate').values('crCalDate', 'crSunrise', 'crSunset'))
for cb in coursebookings:
for i, cal in enumerate(calendar_dates):
if cal['crCalDate'] == cb['cbDay__crCalDate']:
course = [ci[1] for ci in course_index if ci[0] == cb['cbCourse__id']]
if course:
calendar_dates[i][course[0]] = 'Occupied'
break
return calendar_dates
这一切都感觉相当乏味和昂贵,我想知道我是不是走错了路,没有成功地尝试annotate()
和aggregate()
函数来实现合适的摘要查询。
作为奖励,我真的希望能够显示每个日历日每个课程的预订行数,而不是我目前插入的文本。表示这一点的附加模型如下:
class BookLine(models.Model):
blTime = models.TimeField(verbose_name='Booking Time')
blDay = models.ForeignKey(CourseBooking, on_delete = models.CASCADE, db_index=True, related_name='bookinglines')
非常感谢任何有关实现此目标的更好方法的指导,甚至验证此方法(除了我上面的奖金要求)。
解决方案
看起来还不错。您在 for 循环之外有三个数据库查询,因此这不是 N+1 查询问题。我不能说我一眼就能理解接下来发生的事情。
您可以只使用一个查询来获取使用和CourseBooking
值注释的对象或值的列表。您还可以按这些值进行过滤。然后,您可以只运行一次遍历所有这些对象或值,设置一个 dict-of-dicts 。ceName
crCalDate
calendar_dates[date][course]
然后,您必须获取任何课程字典中存在的一组课程,并将其转换为列表以用作顶部标题。然后处理日期,将每个课程字典替换为具有适当表元素值的课程列表。
我认为这段代码可能会更清晰,但如果你拥有的代码正在工作,那么你可能不值得花时间更改它。但是,它可以让您计数,最简单的方法是将内部课程字典制作成 collections.Counter 对象。请注意,对于任何键,计数器的默认值为 0,您永远不会得到 KeyError。
table = {}
for obj in course_bookings: # annotated with date_anno and course_anno
date, course = obj.date_anno, obj.course_anno
if date not in table:
table[date] = Counter() # or make table a DefaultDict
table[date][course] += 1
course_headers = set()
for course_dict in table:
course_headers.update( course_dict.keys() )
course_headers = sorted( list( course_headers))
date_column = sorted( table.keys() )
然后,您将通过迭代date_column
并course_headers
以正确的顺序提取计数来构建列表列表,然后再将它们传递给呈现。或者,您可以使用我最近发现的一个技巧:
class DotDict( dict):
def __getattr__( self, key):
return self[attr] # or with a default None, self.get( attr, None)
这是一个 dict 在所有方面,除了你不能有用地使用setattr
它,因为它getattr
与索引它一样......所以 Django 模板引擎可以引用{{dotdict.key}}
而不需要花哨的自定义模板标签。从 Counter 继承也同样有效。
推荐阅读
- c# - 如何使用 .net 核心从 Ionic 和 Web 对移动设备进行身份验证?
- swift - UICollectionView 项、部分和单元格之间有什么区别?
- javascript - 尝试使用行表的内容设置输入文本,但它不起作用
- android - Firebase 登录不适用于较低的 android 版本
- java - 为什么我的微服务在 Spring Cloud 中没有看到属性?
- google-chrome - “在收到来自 content.js 的呼叫时无法启动 popup.js”
- testing - 使用 TestCafe 评估颜色变化
- android - 在 Android 上访问字体数据
- vue.js - 如何让图像自动重新加载到 vue 组件中
- c++ - 来自 MSVC 外部“C”的故事