[英]django get_or_create depending on result from a custom manager method
I am trying to implement a safe method of creating instances of the model Bar
. 我试图实现一种创建模型Bar
实例的安全方法。 I have a custom manager method to search for any nearby existing Bar
instances based on a x, y
pair of coordinates: Bar.objects.circle_search(x, y, radius)
我有一个自定义管理器方法,可以基于x, y
对坐标搜索附近的任何现有Bar
实例: Bar.objects.circle_search(x, y, radius)
These x, y
values typically come from Foo
. 这些x, y
值通常来自Foo
。 Upon creating a Foo
instance it should then be associated via a foreign key with an existing Bar
instance if Foo.x, Foo.y
are within radius
of Bar.x, Bar.y
. 创建Foo
实例后,如果Foo.x, Foo.y
在Foo.x, Foo.y
的radius
之Bar.x, Bar.y
通过外键将其与现有的Bar
实例Bar.x, Bar.y
。 If no Bar
instances are found then create one. 如果找不到Bar
实例,则创建一个。 Each Bar
instance has x, y
columns that are defined by the Foo
that created it. 每个Bar
实例都有x, y
列,它们由创建它的Foo
定义。
I had something simple like: 我有一些简单的事情,例如:
bar = Bar.objects.circle_search(foo.x, foo.y, radius)
if bar is None:
# fill some kwargs
bar, created = Bar.objects.get_or_create(**kwargs)
however there is an obvious race here where two nearby foo
's could create separate Bar
instances that are close to each other (there shouldn't be any Bar
instances within radius
of each other). 但是,这里有一个明显的竞赛,附近的两个foo
可以创建彼此靠近的单独的Bar
实例(不应在彼此的radius
内存在任何Bar
实例)。
Is there anyway to chain/insert the circle_search
method to be used as the get
part of get_or_create
? 无论如何,是否有链式链接/插入circle_search
方法以用作get_or_create
的get
部分? ie in plain English - get a Bar
that matches the circle_search
, if not then create one. 即用简单的英语-得到一个Bar
的匹配circle_search
,如果没有则创建一个。
# something like?
Bar.object.get_or_create(circle_search(foo.x, foo.y, radius), **kwargs)
The problem is that I cannot rely on using bar.x, bar.y
(populated from foo.x, foo.y
) as unique since very nearby (in x, y
space) Foo
instances would be allowed to create Bar
instances, but they shouldn't because I need to collect all Foo
's within radius
to be associated with the same Bar
. 问题是我不能依靠使用bar.x, bar.y
(从foo.x, foo.y
填充)作为唯一的,因为允许在非常近的( x, y
空间) Foo
实例创建Bar
实例,但是他们不应该,因为我需要收集radius
内的所有Foo
以便与同一Bar
关联。
(Part of me wonders if all this would be safe even with get_or_create
since no IntegrityError
would be raised even if we do end up making a Bar
within radius
of another - in that case is it possible to write some custom logic somewhere that will cause this to throw on any instance creation?) (我的一部分想知道即使使用get_or_create
所有这些方法是否安全,因为即使我们最终在另一个radius
范围内制作一个Bar
也不会引发IntegrityError
-在这种情况下,有可能在某个地方编写一些自定义逻辑来导致此错误引发任何实例创建?)
Sorry if this is difficult to follow, I can try clarify in comments. 抱歉,如果很难执行此操作,请尝试在评论中进行澄清。
Minimal, simple example models for completeness: 完整的最小,简单示例模型:
class BarQuerySet(models.QuerySet):
def circle_search(self, x, y, radius):
# custom SQL to filter on a distance
return qs
class Bar(models.Model):
x = models.FloatField()
y = models.FloatField()
objects = BarQuerySet.as_manager()
class Foo(models.Model):
x = models.FloatField()
y = models.FloatField()
bar = models.ForeignKey(Bar)
Your queries won't raise IntegrityError
so you can't use get_or_create()
, which relies on that. 您的查询不会引发IntegrityError
因此您不能使用依赖get_or_create()
。 You could write a Constraint
so that an IntegrityError
is raised when another row within your constraint exists. 您可以编写一个Constraint
以便在约束中存在另一行时引发IntegrityError
。 But your lookup is probably too complex to pass to get_or_create()
, in which case you should look at the Django codebase for get_or_create()
and replicate it yourself with your own lookup. 但是您的查找可能太复杂而无法传递给get_or_create()
,在这种情况下,您应该查看Django代码库中的get_or_create()
并使用自己的查找自己复制它。
I ended up adding some check in Bar.clean()
and overriding the behaviour get_or_create
in BarQuerySet
, specifically _create_object_from_params
. 我最终在Bar.clean()
添加了一些检查,并覆盖了get_or_create
中的行为BarQuerySet
,特别是_create_object_from_params
。
Seems to return expected results, although not fully tested in a race situation but as it relies almost fully on django's implementation I guess that's safer than me cooking my own method. 似乎可以返回预期的结果,尽管在比赛情况下没有经过全面测试,但是由于它几乎完全依赖django的实现,因此我认为这比我自己编写方法更安全。
class BarQuerySet(models.QuerySet):
# ...
def _create_object_from_params(self, lookup, params, lock=False):
try:
with transaction.atomic(using=self.db):
params = {k: v() if callable(v) else v for k, v in params.items()}
obj = self.create(**params)
return obj, True
except IntegrityError as e:
try:
qs = self.select_for_update() if lock else self
# the default action is to try to get an existing instance from
# the lookup kwargs (but this won't necessarily exist since x and
# y can be slightly different):
# return qs.get(**lookup), False
# instead, we now just grab the nearest source, continuing to
# the raised IntegrityError if we don't grab it for any reason:
obj = qs.circle_search(lookup['x'], lookup['y']).first()
if obj is not None:
return obj, False
except self.model.DoesNotExist:
pass
raise e
class Bar(models.Model):
# ...
def clean(self):
# we only care about checking if we're creating
# a source, hence self._state.adding
if self._state.adding and Bar.objects.circle_search(self.x, self.y, 1).exists():
raise IntegrityError("existing nearby source found")
super().clean()
# also ensure Bar.save() calls `Bar.full_clean()` to perform this check always upon save
(This could perhaps have even been moved one up, to override Bar.objects.get()
, if x, y
are in the arguments to the filter.) (如果x, y
位于过滤器的参数中,则它甚至可能向上移动一个以覆盖Bar.objects.get()
。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.