Подтвердить что ты не робот

Аннотировать значение последнего, связанного с Django 1.8, используя условную аннотацию

У меня есть следующие модели:

class City(models.Model):
    ...

class Census(models.Model):
    city = models.ForeignKey(City)
    date = models.DateTimeField()
    value = models.BigIntegerField()

Теперь я хотел бы аннотировать параметр City-queryset со значением последней переписи. Как мне это достичь?

Я пробовал:

City.objects.annotate(population=Max('census__date'))
# --> annotates date and not value

City.objects.annotate(population=Max('census__value'))
# --> annotates highest value, not latest

City.objects.annotate(population=
    Case(
        When(
            census__date=Max('census__date'),
            then='census__value')
        )
    )

# --> annotates 'None'

City.objects.annotate(population=
    Case(
        When(
            census__date=Max('census__date'),
            then='census__value')
        ), output_field=BigIntegerField()
    )

# --> takes forever (not sure what happens at the end, after some minutes I stopped waiting)

Любая помощь очень ценится!

4b9b3361

Ответ 1

У меня также возникла проблема, когда мне нужен объект максимального значения связанного набора, но мне нужен весь объект. Я не смог найти решение, используя аннотацию и Case. Пока я это сделаю, я использую это предварительное решение. Если в каждом городе нет большого количества объектов переписи или если ваше приложение не связано с производительностью, это может сработать для вас.

inner_qs = Census.objects.order_by('-date')
cities = City.objects.prefetch_related(Prefetch("census_set", queryset=inner_qs, to_attr="census_list"))

class City(models.Model):
    @property
    def latest_census(self):
        if hasattr(self, 'census_list') and len(self.census_list) > 0:
            return self.census_list[0]
        return None

Если это не сработает для вас, рассмотрите некоторые из предложенных здесь предложений: http://blog.roseman.org.uk/2010/08/14/getting-related-item-aggregate/

Ответ 2

В настоящий момент они не являются выражением запроса django, чтобы аннотировать не агрегированное поле из связанной модели 1: N на основе выражения sql, содержащего выражение.

Вы можете выполнить это с помощью нескольких обходных решений, таких как разделение запроса и работа с данными в памяти (itertools groupby f.e.) или с помощью необработанных запросов. Но это не будет соответствовать вашим требованиям требований и агностике базы данных.

Я объясню, что я буду делать, если это мое приложение. Для разработчиков трудно получить избыточность в базе данных. В вашем сценарии последний census на city является вычисленным полем... но в этом случае принять во внимание материализацию last_census:

Грязная работа...

class City(models.Model):
    last_census = models.ForeignKey('Census', null=True, 
                                     blank=True, editable=False)
    ...

Для удобства обслуживания вы можете переопределить методы save и delete на census, чтобы поддерживать last_census в актуальном состоянии.

class Census(models.Model):
    ...

    #overriding save
    def save(self, *args, **kwargs):
        super(Census, self).save(*args, **kwargs)
        #updating last_census on referenced city
        max_census = Census.objects.filter( city = self.city ).latest('date')
        self.city.last_census = max_census.city if max_census else None
        self.city.save()

    #overriding delete
    def delete(self, *args, **kwargs):
        #updating last_census on referenced city
        max_census = ( Census.objects
                      .filter( city = self.city )
                      .exclude( id = self.id )
                      .latest('date') )
        self.city.last_census = max_census.city if max_census else None
        self.city.save()
        super(Census, self).delete(*args, **kwargs)

Обратите внимание: если вам удобнее, вы можете кодировать его с помощью сигналов (pre_delete, post_save,...) вместо переопределения методов.

Лучшее...

Ваш запрос сейчас:

City.objects.select_related('last_census__value').all()

Ответ 3

Что-то вроде этого может работать для вас:

У меня есть это, чтобы показать последнюю резервацию для ресторанов.

Reservation.objects.filter(
        restaurant__city_id__gt=0
        ).distinct().annotate(city_count=Count(
        'restaurant_id')
        ).order_by('-reservationdate').filter(city_count__gte=1)[:20]

в вашем случае это может быть что-то вроде:

city = Census.objects.filter(
        city_id__gt=0
        ).distinct().annotate(city_count=Count(
        'city_id')
        ).order_by('-date').filter(city_count__gte=1)[:20]

и ваш html

{% for city in city %}
{{ city.city.name }} {{ city.date }}<br>
{% endfor %}

Ответ 4

Поздно, и я проголодался, поэтому я вижу, что это не будет продолжаться до конца *, но следующий код вернет последнее значение переписи в наборе значений для данного города. Возможно, выбор запроса возможен, но опять же я голоден!

* Я не был уверен, нужно ли вам получить все последние значения для всех городов или конкретного города. Последнее довольно легко, первое немного сложнее. Вы можете легко поместить это как метод в свою модель и вызвать его в каждом городе в цикле внутри шаблона/вида.

Надеюсь, это поможет!

from app.models import *
from django.db.models import F

    City.objects.annotate(
        values=F("census__value"),
        date=F("census__date"))\
        .values('values', 'name').filter(name="Atlanta")\
        .latest('date')