如何在Django模型上定义字符串唯一性的度量标准?

时间:2018-02-22 19:02:09

标签: python django database-design django-models relational-database

假设我有一个代表科学文章的模型。做一些研究,我可能不止一次找到同一篇文章,标题大致相同:

  • Some Article Title
  • Some Article Title

请注意,第二个标题字符串略有不同:它在“标题”之前有一个额外的空格。

如果问题是因为可能有更多或更少的间距,那么很容易,因为我可以在保存前修剪它。

但是说可能存在更多由空格以外的字符组成的小差异:

  • Comparison of machine learning techniques to predict all-cause mortality using fitness data: the Henry ford exercIse testing (FIT) project.
  • Comparison of machine learning techniques to predict all-cause mortality using fitness data: the Henry ford exercIse testing (FIT).

这是我在这里用作示例的随机文章

这些标题明确指的是相同的独特作品,但由于某种原因,第二部分缺少一些字母。

在这种情况下定义唯一性的最佳方法是什么?

在我看来,我正在考虑一些计算levenshtein距离的函数,并根据某个阈值确定字符串是否是相同的标题。 但是可以在django模型上执行,还是在数据库级别定义此行为?

2 个答案:

答案 0 :(得分:0)

我的第一个想法是levenshtein距离,所以它可能是去这里的方式;)你可以自己实现它或者找到已经知道如何计算它的代码(它们有很多)然后......

...在模型验证中使用它:

https://docs.djangoproject.com/en/2.0/ref/models/instances/#validating-objects

如果您确定新对象违反了这种特殊类型的唯一性,则基本上可以在自定义validate_unique中引发异常。另一方面,您可能需要在那里加载所有其他对象。

如果您自己创建这些对象,则必须在保存之前明确调用full_clean()。如果文章来自某种形式,在该表单上调用is_valid()就足够了。

答案 1 :(得分:0)

此处有2个选项,其中0是完美的。

选项1

这假设你已经实现了一个函数titles_are_similar(title_1: str, title_2: str): bool,它决定了这两个标题是否相似。使用您选择的任何类型的模糊字符串比较来实现此功能。

我们需要使用增强型validator

  • 我说"增强"因为当一个典型的django验证器出于显而易见的原因而没有这样做时,它会选择性地接受你当前试图保存的对象。
  • 当前对象的id是必需的。当您更改并保存现有的实例/行x时,验证应失败,因为该表已包含"类似的"属于此确切实例/行x的值。

验证器本身将使用values_list来降低性能影响。

def title_unique_enough_validator(value, exclude_obj=None):
    query_set = Article.objects.all()
    if exclude_obj:
        query_set = query_set.exclude(pk=exclude_obj.pk)  # pk -> id
    old_titles = query_set.values_list("title", flat=True)
    if any(titles_are_similar(old_title, new_title) for old_title in old_titles):
        raise ValidationError("Similar title already exists")  # also use _()

如果您将使用:title = models.CharField(validators=[title_unique_enough_validator], ...),每次尝试修改和保存现有对象时都会得到ValidationError,因为此对象未传递给验证器,因此不会从检查中排除(我在上面提到过)。相反,我们会覆盖Article.clean()方法(docs):

class Article(Model):
...
def clean(self):
    super().clean()
    title_unique_enough_validator(value=self.title, obj=self)

它可以很好地处理表单。但还有其他两个主要问题。

问题1

引用docs

  

但是,请注意,与Model.full_clean()一样,当调用模型的save()方法时,不会调用模型的clean()方法。

要解决此问题,请覆盖.save()方法:

class Article(...):
...
    def save(self, *args, **kwargs)
        title_unique_enough_validator(value=self.value, obj=self)  # can raise ValidationError
        return super().save(*args, **kwargs)

但是,django在调用ValidationError时不希望有save()。因此,每次从Python代码手动调用article.save()(没有djano表单)时,都需要将其包装到try ... except块中。否则,您的软件将在ValidationError500

问题2

您是否曾明确致电Article.objects.update()?如果是这样,坏消息(docs):

  

update()在SQL级别进行更新,因此不会在模型上调用任何save()方法

作为一种变通方法,您可能希望Article模型为create a custom model manager并覆盖update():只是将其设为无法使用(raise NotImplemented),或者执行额外的检查那里。只是可以防止它违反约束的东西。

选项2

使用database constraints

为什么我没有先列出此选项?那么,你会遇到吨和大量的问题。 Django不知道数据库约束可能会做什么。每当约束阻止它做它想做的事情时,它就会以OperationalErrordocs)而死。

由于我必须使用django与许多unmanaged models合作,我可以确认你需要废话加载django类,以便它可以偶尔处理OperationalError没有爆炸每一个血腥的时间。特别痛苦的是如果你使用django.contrib.admin来处理它,因为它只是一堆无休止的意大利面条。

所以,严肃地说,避免数据库限制,除非你已经必须使用unmanaged models或者你是一个受虐狂寻找冒险。