带替换的 Django 过滤器
Django filter with replace
假设我有以下模型,它有一个方法 variants():
class Example(models.Model):
text = models.CharField(max_length=255)
def variants(self):
return Example.objects.filter(text=remove('xy', self.text))
思路是在去除文本中的某些字符后,获取所有文本相同的对象。例如,如果 self.text 是 'axxyy',它应该匹配具有文本 'a' 的对象。函数 remove() 不涉及数据库,它 returns 一个新的字符串,其中给定的字符被删除了。这很好用。
但是,我需要在比较的两边执行相同的操作,这样 variants() 的行为如下:
def variants(self):
return Example.objects.filter(remove('xy', text)=remove('xy', self.text))
在那种情况下,如果 self.txt 是 'axxyy',它应该与 'a'、'ax、'axx'、'xayy' 等匹配,但应该例如,与 'aa' 匹配,因为 'a' != 'aa' 在移除后。再一次,我不想从数据库中删除'xy',只是为了比较。
我可以用 Python 做到这一点,但我想知道是否有办法在数据库级别做到这一点?我一直在阅读有关 Func() 表达式的文档,例如 Replace,但尚未找到解决方案。
使用 django 的 Replace 函数进行注释,然后过滤该注释。
from django.db.models.functions import Replace
from django.db.models import Value
...
def variants(self):
return Example.objects.annotate(
removed_x=Replace('text', Value('x'), Value('')),
removed_xy=Replace('removed_x', Value('y'), Value('')),
).filter(
removed_xy=self.text.replace('x' , '').replace('y', '')
)
请注意,替换参数 Value('')
是可选的,因为它实际上是 Replace()
的默认值,但在示例中更明确。
随着字符数量的增加,这不能很好地扩展,但如果您的数据库支持正则表达式 (Postgres),可能会有更好的解决方案
我正在回答我自己的问题,因为我能够在 Tim Nyborg 的回答的帮助下完成这项工作。我不确定这是否是执行此操作的最漂亮和最有效的方法,但它似乎可以非常快速地处理我正在处理的数据量,并且它可以很好地扩展以适应越来越多的替换模式。此解决方案仅适用于 Postgres,因为它使用 'regexp_replace' 进行替换。
下面是示例实现:
def variants(self):
patterns = [
('[xyz️w]', ''),
('[\u00A0]', ' '),
# etc.
]
# Replace characters in self.text
text_i = self.text
for old, new in patterns:
text_i = re.sub(old, new, text_i)
# Replace the same characters in all other objects
queryset = Example.objects.all()
for i, value in enumerate(patterns):
old, new = value
queryset = queryset.annotate(
**{f'text_{i}': Func(F(f'text_{i - 1}' if i > 0 else 'text'),
Value(old), Value(new), Value('g'),
function='regexp_replace')})
else:
# Filter all where replaced texts are the same
queryset = queryset.filter(**{f'text_{i}': text_i}).exclude(pk=self.pk)
return queryset
正则表达式本身解决了大部分问题,因为它可以一次替换多个字符,但我的模式相当复杂,所以我将它们拆分为列表并用动态构建的字段名称进行注释。
话虽如此,如果我真的继续使用这个解决方案,或者更确切地说,向模型添加一个字段,为每个对象存储 'text_i' 的值(已完成替换),我还不是 100%然后就这样做:
def variants(self):
return Example.objects.filter(text_i=self.text_i).exclude(pk=self.pk)
我只需要考虑 'text_i' 是否足够稳定以达到我的目的,这样我就不必在初始创建后不断更新它们。
假设我有以下模型,它有一个方法 variants():
class Example(models.Model):
text = models.CharField(max_length=255)
def variants(self):
return Example.objects.filter(text=remove('xy', self.text))
思路是在去除文本中的某些字符后,获取所有文本相同的对象。例如,如果 self.text 是 'axxyy',它应该匹配具有文本 'a' 的对象。函数 remove() 不涉及数据库,它 returns 一个新的字符串,其中给定的字符被删除了。这很好用。
但是,我需要在比较的两边执行相同的操作,这样 variants() 的行为如下:
def variants(self):
return Example.objects.filter(remove('xy', text)=remove('xy', self.text))
在那种情况下,如果 self.txt 是 'axxyy',它应该与 'a'、'ax、'axx'、'xayy' 等匹配,但应该例如,与 'aa' 匹配,因为 'a' != 'aa' 在移除后。再一次,我不想从数据库中删除'xy',只是为了比较。
我可以用 Python 做到这一点,但我想知道是否有办法在数据库级别做到这一点?我一直在阅读有关 Func() 表达式的文档,例如 Replace,但尚未找到解决方案。
使用 django 的 Replace 函数进行注释,然后过滤该注释。
from django.db.models.functions import Replace
from django.db.models import Value
...
def variants(self):
return Example.objects.annotate(
removed_x=Replace('text', Value('x'), Value('')),
removed_xy=Replace('removed_x', Value('y'), Value('')),
).filter(
removed_xy=self.text.replace('x' , '').replace('y', '')
)
请注意,替换参数 Value('')
是可选的,因为它实际上是 Replace()
的默认值,但在示例中更明确。
随着字符数量的增加,这不能很好地扩展,但如果您的数据库支持正则表达式 (Postgres),可能会有更好的解决方案
我正在回答我自己的问题,因为我能够在 Tim Nyborg 的回答的帮助下完成这项工作。我不确定这是否是执行此操作的最漂亮和最有效的方法,但它似乎可以非常快速地处理我正在处理的数据量,并且它可以很好地扩展以适应越来越多的替换模式。此解决方案仅适用于 Postgres,因为它使用 'regexp_replace' 进行替换。
下面是示例实现:
def variants(self):
patterns = [
('[xyz️w]', ''),
('[\u00A0]', ' '),
# etc.
]
# Replace characters in self.text
text_i = self.text
for old, new in patterns:
text_i = re.sub(old, new, text_i)
# Replace the same characters in all other objects
queryset = Example.objects.all()
for i, value in enumerate(patterns):
old, new = value
queryset = queryset.annotate(
**{f'text_{i}': Func(F(f'text_{i - 1}' if i > 0 else 'text'),
Value(old), Value(new), Value('g'),
function='regexp_replace')})
else:
# Filter all where replaced texts are the same
queryset = queryset.filter(**{f'text_{i}': text_i}).exclude(pk=self.pk)
return queryset
正则表达式本身解决了大部分问题,因为它可以一次替换多个字符,但我的模式相当复杂,所以我将它们拆分为列表并用动态构建的字段名称进行注释。
话虽如此,如果我真的继续使用这个解决方案,或者更确切地说,向模型添加一个字段,为每个对象存储 'text_i' 的值(已完成替换),我还不是 100%然后就这样做:
def variants(self):
return Example.objects.filter(text_i=self.text_i).exclude(pk=self.pk)
我只需要考虑 'text_i' 是否足够稳定以达到我的目的,这样我就不必在初始创建后不断更新它们。