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 вашему шаблону, и готово.

Вернуться на верх