Django: СУХОЙ код со свойством и набором запросов, которые имеют одинаковую роль? - PullRequest
4 голосов
/ 15 апреля 2019

В моем коде Django для OrderedArticle объектов мне нужно вычислить hist_price, которое является умножением 2 полей: hist_unit_price * quantity.

Первый способ, которым я это сделал, былпростое свойство:

class OrderedArticle(Model):
    @property
    def hist_price(self):
        return self.hist_unit_price * self.quantity

Затем я понял, что, когда мне нужно провести обширные вычисления этих цен, я не могу использовать это свойство по соображениям производительности, и вместо этого я должен вычислить hist_price поуровень базы данных.Вот почему я написал собственный набор запросов для этого:

class OrderOperationQuerySet(Queryset):

    @staticmethod
    def _hist_price(orderable_field):  # can be an OrderedArticle or another object here
        return ExpressionWrapper(
            F(f'{orderable_field}__hist_unit_price') * F(f'{orderable_field}__quantity'),
            output_field=DecimalField())

В настоящее время в моем коде используются свойства hist_price и _hist_price queryset.

Вопрос

Это хорошо работает, но меня раздражает писать одну и ту же бизнес-логику дважды.У меня такое чувство, что я не делаю это "правильным образом" здесь.Я думаю, что я должен убедиться на уровне кода, что независимо от того, использую ли я свойство или набор запросов, он всегда возвращает один и тот же результат.В этом конкретном случае бизнес-логика представляет собой простое умножение между двумя десятичными знаками, поэтому все должно быть в порядке, но в моем коде будут другие случаи, где он намного сложнее.

Видите ли вы способулучшить мой код?Благодарю.

1 Ответ

1 голос
/ 15 апреля 2019

Эта идея похожа на "гибридные атрибуты" из SQLAlchemy , о которых спрашивали раньше - я не был полностью удовлетворен ни одним из ответов из этой цепочки потоков ( такие вещи, как сохранение этого вычисленного значения в дополнительном поле таблицы и постоянное его обновление).

У вас может быть какая-то внутренняя функция, которую используют ваше свойство и функция ExpressionWrapper, при условии, что требуемые операторы перегружены, чтобы принимать либо действительные значения, либо объекты F() (например, основные математические операторы).

def multiplication(x, y):
    return x * y  # trivial here but it could be any mathematical expression really


def _hist_price(orderable_field):
    return ExpressionWrapper(
        multiplication(
            F(f"{orderable_field}__hist_unit_price"),
            F(f"{orderable_field}__quantity")
        ),
        output_field=DecimalField()
    )

@property
def hist_price(self):
    return multiplication(self.hist_unit_price, self.quantity)

Если в одной из этих гибридных функций она усложняется, чем базовые числовые операции, и вы хотите избежать дублирования бизнес-логики, вам нужно написать функцию-обертку, которая может преобразовываться в правильный вывод с использованием функции python для вызывающего свойства и функция, которая может работать с F-объектами для вызывающей стороны набора запросов, чтобы поддерживать перегрузку оператора. Но это приведет к коду, который анализирует аргументы, чтобы решить, что делать, что может быть не интуитивно понятно, так что это действительно компромисс в любом случае.

В грубом псевдокоде одна из этих пользовательских функций будет выглядеть как

def _hybrid_lower(value):
   if isinstance(value, F):  # maybe F would be sufficient or some other class higher in the hierarchy
       # https://docs.djangoproject.com/en/2.2/ref/models/expressions/#func-expressions
       return Func(value, function='LOWER')
   else:
       return value.lower()

и затем вы можете использовать эту пользовательскую функцию в своей функции, которую вызывают и свойство, и набор запросов. Некоторое дублирование кода может оказаться не самым худшим компромиссом, если вам действительно понадобятся действительно сложные функции, такие как операции с базами данных и Python.

...