При создании внешнего интерфейса для модуля Django я столкнулся со следующей проблемой внутри ядра Django:
Чтобы отобразить ссылку на следующий/предыдущий объект из запроса модели, мы можем использовать дополнительные-экземпляры-методы экземпляра модели: get_next_by_FIELD() или get_previous_by_FIELD(). Где FIELD — поле модели типа DateField или DateTimeField.
Давайте объясним это на примере
from django.db import models
class Shoe(models.Model):
created = models.DateTimeField(auto_now_add=True, null=False)
size = models.IntegerField()
Представление для отображения списка обуви, за исключением тех, где размер равен 4:
def list_shoes(request):
shoes = Shoe.objects.exclude(size=4)
return render_to_response(request, {
'shoes': shoes
})
И пусть следующее будет представлением для отображения одной обуви и соответствующей ссылки на предыдущую и следующую обувь.
def show_shoe(request, shoe_id):
shoe = Shoe.objects.get(pk=shoe_id)
prev_shoe = shoe.get_previous_by_created()
next_shoe = shoe.get_next_by_created()
return render_to_response('show_shoe.html', {
'shoe': shoe,
'prev_shoe': prev_shoe,
'next_shoe': next_shoe
})
Теперь у меня ситуация, что представление show_shoe отображает ссылку на предыдущую/следующую независимо от размера обуви. Но на самом деле я хотел просто обувь, размер которой не равен 4. Поэтому я попытался использовать аргумент **kwargs методов get_(previous|next)_by_created(), чтобы отфильтровать ненужную обувь, как указано по документации:
Оба этих метода будут выполнять свои запросы, используя диспетчер по умолчанию для модели. Если вам нужно эмулировать фильтрацию, используемую пользовательским менеджером, или вы хотите выполнить одноразовую пользовательскую фильтрацию, оба метода также принимают необязательные аргументы ключевого слова, которые должны быть в формате, описанном в разделе Поиск полей.
Редактировать: следите за словом "следует", потому что тогда также (size_ne=4) должно работать, но это не так.
Актуальная проблема
Фильтрация с использованием поиска size__ne ...
def show_shoe(request, shoe_id):
...
prev_shoe = shoe.get_previous_by_created(size__ne=4)
next_shoe = shoe.get_next_by_created(size__ne=4)
...
... не сработало, выдает FieldError: Не удается преобразовать ключевое слово 'size_ne' в поле.
Затем я попытался использовать отрицательный комплекс поиск с использованием объектов Q:
from django.db.models import Q
def show_shoe(request, shoe_id):
...
prev_shoe = shoe.get_previous_by_created(~Q(size=4))
next_shoe = shoe.get_next_by_created(~Q(size=4))
...
... тоже не работает, выдает TypeError: _get_next_or_previous_by_FIELD() получил несколько значений для аргумента 'field'
Поскольку методы get_(previous|next)_by_created принимают только **kwargs.
Фактическое решение
Поскольку эти методы экземпляра используют _get_next_or_previous_by_FIELD(self, field , is_next, **kwargs) я изменил его, чтобы он принимал позиционные аргументы с использованием *args и передал их фильтру, как **kwargs.
def my_get_next_or_previous_by_FIELD(self, field, is_next, *args, **kwargs):
"""
Workaround to call get_next_or_previous_by_FIELD by using complext lookup queries using
Djangos Q Class. The only difference between this version and original version is that
positional arguments are also passed to the filter function.
"""
if not self.pk:
raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
op = 'gt' if is_next else 'lt'
order = '' if is_next else '-'
param = force_text(getattr(self, field.attname))
q = Q(**{'%s__%s' % (field.name, op): param})
q = q | Q(**{field.name: param, 'pk__%s' % op: self.pk})
qs = self.__class__._default_manager.using(self._state.db).filter(*args, **kwargs).filter(q).order_by('%s%s' % (order, field.name), '%spk' % order)
try:
return qs[0]
except IndexError:
raise self.DoesNotExist("%s matching query does not exist." % self.__class__._meta.object_name)
И называя это так:
...
prev_shoe = shoe.my_get_next_or_previous_by_FIELD(Shoe._meta.get_field('created'), False, ~Q(state=4))
next_shoe = shoe.my_get_next_or_previous_by_FIELD(Shoe._meta.get_field('created'), True, ~Q(state=4))
...
наконец сделал это.
Теперь вопрос к вам
Есть ли более простой способ справиться с этим? Должен ли shoe.get_previous_by_created(size__ne=4) работать должным образом, или мне следует сообщить об этой проблеме ребятам из Django в надежде, что они примут мое исправление _get_next_or_previous_by_FIELD()?
Среда: Django 1.7, еще не тестировал на 1.9, но код для _get_next_or_previous_by_FIELD() остался прежним.
Редактировать: Это правда, что сложные поиски с использованием объекта Q не являются частью «поиска полей», вместо этого они скорее являются частью функций filter() и exclude(). И я, вероятно, ошибаюсь, когда полагаю, что get_next_by_FIELD также должен принимать объекты Q. Но поскольку вносимые изменения минимальны, а преимущества использования объекта Q велики, я думаю, что эти изменения должны получить распространение.
теги: django, сложный поиск, запрос, get_next_by_FIELD, get_previous_by_FIELD
(список тегов здесь, потому что у меня недостаточно репутации.)