Обновите цель прокси-сервера ассоциации sqlalchemy при обновлении родительской таблицы ORM - PullRequest
2 голосов
/ 05 марта 2020

У меня есть Flask проект SQLAlchemy с 3 таблицами ORM, Category, Vendor и CategoryVendorMap, и я использую Flask -Marshmallow для определения схем для API.

Отношения: Категория имеет много Продавцы в виде CategoryVendorMap. CategoryVendorMap также имеет флаг is_default, указывающий поставщика по умолчанию для категории. Это поле is_default также было денормализовано в таблице категорий.

Есть ли способ установить значение is_default объекта CategoryVendorMap путем доступа только к Category ORM Mapper? По сути, я хочу установить поле Category.default_vendor_id, чтобы установщик для этого поля обновил соответствующие CategoryVendorMap ассоциации. Я знаю, что мог бы просто обновить объекты CategoryVendorMap напрямую, однако в конечном итоге мои модели экспонируются через API. и я хотел бы избежать выставления этого объекта и создания пользовательских контроллеров logi c для обработки этого варианта использования.

class CategoryVendorMap(db.Model):
    __tablename__ = "CategoryVendorMap"
    __table_args__ = (
      db.PrimaryKeyConstraint('category_id', 'vendor_id'),
    )
    category_id = db.Column(db.Integer, db.ForeignKey('Category.category_id'))
    vendor_id = db.Column(db.Integer, db.ForeignKey('Vendor.vendor_id'))
    is_default = db.Column(db.Boolean, default=False)

    category = db.relationship('Category', backref=db.backref("category_vendors", cascade="all, delete-orphan"))
    vendor = db.relationship('Vendor')


class Category(db.Model):
    __tablename__ = 'Category'

    @property    
    def default_vendor_id(self):
        if self.category_vendors:
            default_vendor_id = [cat_vendor_map for cat_vendor_map in self.category_vendors if cat_vendor_map.is_default]
            return default_vendor_id[0].vendor_id if default_vendor_id else None
        return

    @default_vendor_id.setter
    def default_vendor_id(self, vendor_id):
      return int(vendor_id)

    category_id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128), unique=False, nullable=False)    

    vendors = association_proxy("category_vendors", "vendor", 
      creator=lambda vendor: CategoryVendorMap(vendor=vendor))

class Vendor(db.Model):
    __tablename__ = 'Vendor'

    vendor_id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True, nullable=False)

1 Ответ

1 голос
/ 10 марта 2020

Вы можете попробовать использовать Гибридные атрибуты и иметь собственные логики c в .setter:

@hybrid_property
def default_vendor_id(self):
    res = [_cv for _cv in self.category_vendors if _cv.is_default]
    return res and res[0].vendor_id or None

@default_vendor_id.setter
def default_vendor_id(self, value):
    # @note: need thorough testing in case a new vendor is added and set to be a default
    # also new objects will not have the `vendor_id` set yet
    for _cv in self.category_vendors:
        _cv.is_default = bool(_cv.vendor_id == value)

Однако, сеттер должен быть протестирован очень хорошо, чтобы охватить угловые случаи, некоторые из которых упоминаются в комментарии.

Вы можете также рассмотреть возможность создания проверочного ограничения (или уникального отфильтрованного индекса) для таблицы CategoryVendorMap, чтобы убедиться, что что существует только одно is_default истинное значение на категорию.


Другой вариант, который необходимо учитывать, - это фактическое перемещение флага is_default из таблицы отношений в модель Category напрямую. В этом случае, конечно, вы должны проверить, что поставщик по умолчанию является одним из существующих.

...