在django-localized-fields上保持唯一性

时间:2019-07-19 01:31:52

标签: django python-3.x postgresql django-rest-framework hstore

我试图避免将重复的本地化项目存储在Django-rest-framework应用程序中,而Django数据库的django-localalized-fields程序包与PostgreSQL数据库一起存储,我找不到任何方法可以使此项工作。

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

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

当我不通过单独定义它们而不覆盖序列化器中的创建/更新时,序列化器可以正常运行(创建/更新)。

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

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

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

对于不熟悉的人,Hstore将项目作为键/值对存储在数据库中。这是django-localized-fields如何在数据库中存储语言数据的示例:

“ en” =>“英语单词!”,“ es” =>“”,“ fr” =>“”,“ frqc” =>“”,“ fr-ca” =>“”

1 个答案:

答案 0 :(得分:1)

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')

数据库约束

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

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 != $1 and avals("slug") && avals($2)', 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 db索引来加快查找速度。

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