Django получает значение для аннотированного поля на основе условия?
У меня есть пара простых моделей, и я хочу аннотировать запрос с полем, значение которого основано на условии.
class Book(models.Model):
price = models.DecimalField('Price', null=True, default=None, max_digits=32, decimal_places=2)
...
class Config(models.Model):
city_prices = models.JSONField(default={"Paris": 10, "London": 15}, null=True)
...
Я пытался сделать запрос к этой модели следующим образом:
from django.db.models import F, When, Case, Subquery
config = Config.objects.first()
Book.objects.annotate(custom_price=Case(
When(price__isnull=False, then=F('price')),
When(price__isnull=True, then=Subquery(config.city_prices.get(F('city'))))
)
первое "When" работает хорошо, но второе выдает ошибку.
AttributeError: 'NoneType' object has no attribute 'all'
Попробовал избавиться от "F()" на втором "When" и хардкорном названии города, но получил другую ошибку.
When(price__isnull=True, then=Subquery(config.city_prices.get('London')))
AttributeError: 'int' object has no attribute 'all'
Эта ошибка показывает, что я получаю значение "London", но Subquery пытается запросить его. Поэтому я сделал вывод, что когда в предыдущем запросе я пытался использовать "F('city')", то получил обратно None, и я думаю, что это из-за того, что F('city') ссылается на модель Config, а не на Book.
Я пробовал разные подходы, но они тоже безуспешны.
>>>from django.db.models.expressions import RawSQL
>>>Books.objects.annotate(custom_price=RawSQL('SELECT d.city_prices ->> %s FROM CONFIG_CONFIG d WHERE d.id = 1', (F('price'),)))
ProgrammingError: can't adapt type 'F'
Я где-то здесь прочитал, что F() не может работать с RawSQL. Думаю, проблема в том, что я получаю dict в конце запроса и хочу извлечь из него значение. Как я могу достичь своей цели - получить значение для аннотированного поля на основе условия?
Первый подход: рассчитать custom_price
по заявке. Это проще, но вы не можете заказать книги по custom_price
from django.db.models import F, When, Case, Subquery
config = Config.objects.first()
books = list(Book.objects.all())
for book in books:
if book.price is not None:
book.custom_price = book.price
else:
book.custom_price = config.city_prices.get(book.city)
Второй подход: вычислить Case-When выражение для config.city_prices и аннотировать его:
from django.db.models import F, When, Case, Subquery, Value
config = Config.objects.first()
whens = []
for city, price in config.city_prices.items():
whens.append(When(city=city, then=Value(price))
Book.objects.annotate(custom_price=Case(*whens))