保持 django-localized-fields 的唯一性

Maintain uniqueness on django-localized-fields

我试图避免将重复的本地化项目存储在 Django-rest-framework 应用程序中,django-localized-fields 包与 PostgreSQL 数据库我找不到任何方法来完成这项工作。

(https://pypi.org/project/django-localized-fields/)

我已经尝试在序列化程序中编写自定义重复检测逻辑,它适用于创建,但对于更新,本地化字段变为空(​​它们是必填字段,所以我收到非空约束错误)。似乎是 django-localized-fields 实用程序导致了这个问题。

当我没有通过单独定义来覆盖序列化程序中的 create/update 时,序列化程序可以正确运行 (create/update)。

我也试过向模型中的数据库添加独特的选项,但这不起作用 - 仍然会创建重复项。使用标准的唯一方法,或 django-localized-fields 文档中的方法 (uniqueness=['en', 'ro']).

我也试过 Django 中的 UniqueTogetherValidator,它似乎也不支持 HStore/localizedfields。

如果能帮助我找出如何修复序列化程序中的更新或在数据库中放置唯一约束,我将不胜感激。由于 django-localized-fields 在 PostgreSQL 中使用 hstore,因此对于使用 hstore 来保持唯一性的应用程序来说,这一定是一个足够普遍的问题。

对于那些不熟悉的人,Hstore 在数据库中将项目存储为 key/value 对。下面是 django-localized-fields 如何在数据库中存储语言数据的示例:

"en"=>"english word!","es"=>"","fr"=>"","frqc"=>"","fr-ca"=>""

django-localized-fields 仅针对同一语言约束唯一值。如果你想实现一行中的值不与另一行中的值冲突,你必须在 Django 和数据库级别验证它们。

Django 中的验证

在 Django 中,您可以创建自定义函数 validate_hstore_uniqueness,每次模型验证时都会调用它。

def validate_hstore_uniqueness(obj, field_name):

    value_dict = getattr(obj, field_name)
    cls = obj.__class__
    values = list(value_dict.values())

    # find all duplicite existing objects
    duplicite_objs = cls.objects.filter(**{field_name+'__values__overlap':values})
    if obj.pk:
        duplicite_objs = duplicite_objs.exclude(pk=obj.pk)

    if len(duplicite_objs):
        # extract duplicite values
        existing_values = []
        for obj2 in duplicite_objs:
            existing_values.extend(getattr(obj2, field_name).values())

        duplicate_values = list(set(values) & set(existing_values))

        # raise error for field
        raise ValidationError({
            field_name: ValidationError(
                _('Values %(values)s already exist.'),
                code='unique',
                params={'values': duplicate_values}
            ),
        })


class Test(models.Model):
    slug = LocalizedField(blank=True, null=True, required=False)

    def validate_unique(self, exclude=None):
        super().validate_unique(exclude)

        validate_hstore_uniqueness(self, 'slug')

DB 中的约束

对于数据库约束,您必须使用约束触发器。

def slug_uniqueness_constraint(apps, schema_editor):
    print('Recreating trigger quotes.slug_uniqueness_constraint')

    # define trigger
    trigger_sql = """
        -- slug_hstore_unique
        CREATE OR REPLACE FUNCTION slug_uniqueness_constraint() RETURNS TRIGGER
        AS $$
            DECLARE
                duplicite_count INT;
            BEGIN
                EXECUTE format('SELECT count(*) FROM %I.%I ' ||
                               'WHERE id !=  and avals("slug") && avals()', TG_TABLE_SCHEMA, TG_TABLE_NAME)
                    INTO duplicite_count
                    USING NEW.id, NEW.slug;

                IF duplicite_count > 0 THEN
                    RAISE EXCEPTION 'Duplicate slug value %', avals(NEW.slug);
                END IF;

                RETURN NEW;
            END;
        $$ LANGUAGE plpgsql;

        DROP TRIGGER IF EXISTS slug_uniqueness_constraint on quotes_author;
        CREATE CONSTRAINT TRIGGER slug_uniqueness_constraint
            AFTER INSERT OR UPDATE OF slug ON quotes_author
            FOR EACH ROW EXECUTE PROCEDURE slug_uniqueness_constraint();

    """

    cursor = connection.cursor()
    cursor.execute(trigger_sql)

并在迁移中启用它:

class Migration(migrations.Migration):

    dependencies = [
        ('quotes', '0031_auto_20200109_1432'),
    ]

    operations = [
        migrations.RunPython(slug_uniqueness_constraint)
    ]

创建 GIN 数据库索引以加快查找速度可能是个好主意:

CREATE INDEX ON test_table using GIN (avals("slug"));