简体   繁体   English

django get_or_create取决于自定义管理器方法的结果

[英]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.yFoo.x, Foo.yradiusBar.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_createget部分? 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM