Django ORM: фильтрация по конкатенированным полям
В моем приложении у меня есть номер документа, который состоит из нескольких полей модели Document model, таких как:
{{doc_code}}{{doc_num}}-{{doc_year}}
doc_num - это целое число в модели, но для пользователя это пятизначная строка, где пустые пробелы заполняются нулем, например 00024, или 00573.
doc_year - это поле даты в модели, но в полном номере документа это две последние цифры года.
Таким образом, для пользователей номер документа будет, например, TR123.00043-22.
Я хочу реализовать поиск на странице списка документов.
Один из подходов заключается в автогенерации поля full_number из полей doc_code, doc_num и doc_year в методе сохранения модели Document и фильтрации по этому full_number.
Перед использованием фильтра в запросе необходимо использовать функцию Concat. Сначала конкатенируем поле full_code
docs = Document.annotate(full_code=Concat('doc_code', 'doc_num', Value('-'), 'doc_year', output_field=CharField()))
и затем фильтровать по полю full_code
docs = docs.filter(full_code__icontain=keyword)
Но как передать doc_num как пятизначную строку и doc_year как две последние цифры года в функцию Concat?
Или что может быть лучшим решением для этой задачи?
Concat принимает только имена полей и строковые значения, так что у вас не так много вариантов, насколько я знаю.
Как вы заметили, вы можете установить дополнительное поле при сохранении. Это, вероятно, лучший подход, если вы собираетесь использовать его в нескольких местах.
Функция сохранения будет выглядеть примерно так
def save(self, *args, **kwargs):
super().save()
self.full_code = str(self.doc_code) + f"{doc_num:05d}") + '-' + time.strftime("%y", doc_year))
self.save()
doc_num требует python>= 3.6, другие методы для более ранних питонов можно посмотреть здесь
doc_year предполагает, что это тип datetime. Если это просто четырехзначное число int, то вместо него должно работать что-то вроде str(doc_year)[-2:].
Альтернативный вариант, если вы собираетесь использовать его только в редких случаях, вы можете пройтись циклом по набору записей, добавляя дополнительное поле
docs=Document.objects.all() #or whatever filter is appropriate
for doc in docs:
doc.full_code = f"{doc.doc_code}{doc.doc_num}-{time.strftime("%y", doc_year)}
#or f"{doc.doc_code}{doc.doc_num}-{str(doc_year)[-2:]} if doc_year not datetime
затем преобразуйте его в список, чтобы не делать еще один вызов БД и не потерять новое поле, и отфильтруйте его с помощью понимания списка.
filtered_docs = [x for x in list(docs) if search_term in x.full_code]
передайте filtered_docs вашему шаблону, и готово.