Как объединить элементы стиля кросс-таблицы в связанной модели с ее родителем? - PullRequest
0 голосов
/ 05 ноября 2019

Я хочу построить представление кросс-таблицы из структуры модели, показанной ниже. Мне нужна матрица с календарными датами вниз по LHS, названиями курсов в верхней части, и что-то в каждой ячейке, обозначающее наличие экземпляра Booking на эту дату / курс.

Упрощенная структура модели:

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')

Любое руководство по лучшим средствам для достижения этого высоко ценится, или даже для проверки этой методологии (помимо моего требования к бонусу выше).

1 Ответ

0 голосов
/ 05 ноября 2019

Это выглядит не так уж плохо. У вас есть три запроса к базе данных за пределами ваших циклов for, так что это не проблема N + 1 запросов. Я не могу сказать, что мог понять, что последовало с первого взгляда.

Вы можете использовать только один запрос, чтобы получить список CourseBooking объектов или значений, помеченных ceName и crCalDateценности. Вы также можете фильтровать по этим значениям. Затем вы можете выполнить всего один проход по всем этим объектам или значениям, настроив диктовку calendar_dates[date][course].

Затем вам нужно будет получить набор курсов, присутствующих в любом из курсов. -dicts и преобразовать его в список для использования в качестве верхних заголовков. Затем проработайте даты, заменив каждый из курсов-диктов списком курсов соответствующим значением элемента таблицы.

Я думаю, что этот код, вероятно, будет более понятным, но если код, который у вас есть, работает, он может нестоит потратить время на его изменение. Тем не менее, он позволил бы вам рассчитывать, проще всего, превратив внутренние диктанты курса в коллекции. Обратите внимание, что счетчики имеют значение по умолчанию 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)

, который является во всех отношениях диктом, кроме того, что вы не можете использовать setattr на нем, потому что getattr сделан так же, какиндексировать его ... так, чтобы механизм шаблонов Django мог ссылаться на {{dotdict.key}}, не нуждаясь в причудливых пользовательских тегах шаблонов. Работает одинаково хорошо, наследуя от Counter.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...