基于 Weppy/pyDal 中的另一列限制数据库插入的最佳方法?

Best way to restrict database insert based on another column in Weppy/pyDal?

如果同一用户已经拥有同名项目,我想限制特定 table 的数据库插入。

Table
_____________
user     | place         | label     |
--------------------------------------------------------------------------
me       | san francisco | work      |
you      | san francisco | leisure   | # This is ok because different user
me       | san francisco | leisure   | # THIS IS NOT ALLOWED - INSERT FAIL

标签对用户来说是唯一的,所以我不希望 "name" 列被强制唯一 -> 许多用户应该能够添加相同的位置,但 "label" 中的任何内容他们想要的标签列。

NOTE: Using Weppy - I don't have enough reputation to create a new tag. I will tag this question once I can/there is a weppy tag.

我找到了一个似乎代码太多的解决方法。需要使用 Place().add_new() 而不是内置的 pyDal 方法:

from weppy.dal import Model, Field, belongs_to


class Place(Model):
    belongs_to('user')

    name = Field() # not using `unique=True` here so others can insert same names.
    label = Field()

    def add_new(self, user_id, name, label):
        user_places = self.db(self.db.Place.user == user_id).select()
        current_names = [x.name for x in user_places]
        if name not in current_names:
            self.create(
                user=user_id,
                name=name,
                label=label
            )

关于唯一性的复杂事情是,您不能确定仅在 Web 应用程序等并发环境中使用应用程序代码就可以尊重它。

例如,如果同一个用户将产生两个并发请求——在这种情况下可能不太可能,但你应该意识到这一点——那么应用程序代码可能会失败,因为可以在检查和其他插入之间插入具有相同值的记录。

这就是为什么你应该首先依赖数据库本身,因为 weppy 0.7 你可以使用数据库 indexes:

class Place(Model):
    belongs_to('user')

    name = Field()
    label = Field()

    indexes = {
        'user_uniq_name': {
            'fields': ['user', 'name'], 'unique': True}
    }

请记住在添加索引后生成迁移。

一旦你有了一个带有唯一约束的索引,你就可以将新记录的创建包装在 try-except 块中:

try:
    rv = Place.create(**some_params)
except:
    # handle the error eg:
    from weppy import abort
    abort(422)

当然你仍然可以在插入之前保留一些应用程序检查,但是由于你需要检查多个值并且自定义验证器只支持单个值(除非使用会话来检查用户),你最好使用 callbacks:

from weppy.dal import before_insert

class Place(Model):
    @before_insert
    def check_name_uniqueness(self, input_fields):
        if self.db(
            (self.user == input_fields['user']) &
            (self.name == input_fields['name'])
        ).count() > 0:
            return True
        return False

来自文档:

All the callbacks method should return None or False (not returning anything in python is the same of returning None) otherwise returning True will abort the current operation.