保持 django 本地化字段的唯一性



我试图避免将重复的本地化项目存储在 Django-rest-framework 应用程序中,带有 PostgreSQL 数据库的 django-localalized-fields 包中,我找不到任何方法来做到这一点。

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

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

当我没有通过单独定义它们来覆盖序列化程序中的创建/更新时,序列化程序可以正确运行(创建/更新)。

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

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

我希望在跟踪如何在序列化程序中修复更新或在数据库中放置唯一约束方面提供一些帮助。由于 django-localized-fields 在 PostgreSQL 中使用 hstore,对于使用 hstore 的应用程序来说,保持唯一性必须是一个足够常见的问题。

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

"en"=>"英语单词!">

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

数据库中的约束

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

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 数据库索引以加快查找速度可能是一个好主意:

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

最新更新