Как агрегировать пользовательскую функцию модели, если она не является частью базы данных в django?

#python #django

#python #django

Вопрос:

У меня есть проект, который мне нужно открывать и закрывать заявки. Итак, это моя модель билета:

 class Ticket(models.Model):
    issue = models.CharField(max_length=100)
    user = models.ForeignKey('Users', blank=True, null=True, related_name="tickets")
    date_opened = models.DateTimeField('Date opened')
    date_closed = models.DateTimeField('Date closed', blank=True, null=True)

    def __str__(self):
        return self.issue

    def time_to_solve(self):
        time_to_solve = self.date_opened - self.date_closed
        out = [ time_to_solve.hours//60 ]
        return '{:d} hours'.format(*out) 
  

и я хочу вычислить среднее значение разницы во времени между date_opened и date_closed .

В моем views.py Я создал представление :

 class Dashboard(ListView):
    model = Ticket
    template_name = 'assets/dashboard.html'
    def get_context_data(self, **kwargs):
        context = super(Dashboard, self).get_context_data(**kwargs)
        context['time_to_complete'] = Q(status__contains='closed')).aggregate(time_opened = Avg('time_to_solve'))
        return context
  

К сожалению, это не работает, потому что «time_to_solve» не является частью базы данных.

Как я могу этого добиться?

Ответ №1:

Вы можете агрегировать только поля модели, но это не сложно сделать в python:

 tickets = Ticket.objects.filter(status__contains='closed') 
average = sum(map(lambda x: x.time_to_solve(), tickets)) / tickets.count()
  

В этом случае time_to_solve должно вернуться что-то вроде количества секунд, и вы можете отформатировать это так, как вам нужно сразу после этого.

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

Комментарии:

1. Я думал, что смогу использовать django без большого количества python, но теперь я вижу, что это неизбежно. Дело не в том, что мне не нравится программирование на Python (которым я много занимаюсь на самом деле), но я пытаюсь сократить кривую обучения. Если это правильный способ сделать это, я собираюсь попробовать его прямо сейчас.

2. Я получаю «неподдерживаемые типы операндов для : ‘int’ и ‘str'». Есть идеи?

3. @RobDel Это потому, что time_to_solve возвращает строку. Вы должны написать это как простое return self.date_closed - self.date_opened , таким образом, вы получите timedelta, которое вы можете использовать для вычисления среднего (вы можете добавлять и делить временные интервалы). Затем вам просто нужно отформатировать полученное среднее значение в виде строки.

4. Как я уже сказал, time_to_solve должен возвращать число (я бы использовал секунды), и вы можете отформатировать его позже.

Ответ №2:

Я не думаю, что вы можете сделать это напрямую с помощью ORM. Вы можете сделать это на Python, но это приведет к извлечению всех закрытых строк тикетов из базы данных. Если вы хотите сделать это в SQL, вам нужно будет выразить свой запрос как необработанный SQL. Если вы используете PostgreSQL, вам может оказаться полезным следующее: работа с датами и временем в PostgreSQL.

Комментарии:

1. На самом деле это MySQL, но должен быть способ сделать это .. это очень просто.

Ответ №3:

Нашел ответ от друга в #irc — django на Freenode:

 average = Ticket.objects.extra(
            select={ 'date_difference': 'AVG(time_to_sec(TIMEDIFF(date_closed,date_opened)))'}).first().date_difference
        context['average'] = "{:.2f}".format(average/86400)
        return context
  

Таким образом, она возвращает среднее значение с точностью до 2 десятичных знаков и выполняет все на уровне базы данных, поэтому ее намного легче запускать, чем выборку всех строк.