Django:从过滤查询中获取第一个对象或创建

37
在Django中,queryset提供了一个名为get_or_create的方法,该方法可以返回对象或创建新对象。与get方法一样,如果查询返回多个对象,get_or_create也可能引发异常。是否有一种优雅的方法来实现这一点?
objects = Model.manager.filter(params)
if len(objects) == 0:
   obj = Model.objects.create(params)
else:
   obj = objects[0]

1
只需将此代码作为自定义模型管理器的方法添加(或将其添加到基本管理器中,如果您感到有点大胆),并将其命名为一些描述性的内容即可。 - BenTrofatter
7个回答

52

get_or_create()只是一个方便的函数,所以自己写一个函数也没什么问题,就像pavid所示的那样。

result = Model.objects.filter(field__lookup=value)[0]
if not result:
   result = Model.objects.create(...)
return result

编辑 如建议所述,在过滤器后将[:1]切片(返回单个条目列表)更改为[0](返回实际对象)。但是,如果查询没有匹配项,则这样做会引发异常。

这也会引发类似的异常:

Model.objects.filter(field_lookup=value).latest()

重新查看这个问题,我不确定原帖作者是想返回多个对象/行,还是只是想找到一种避免在检索单个对象/行时引发异常的方法。

这里有另一个选择?

results = Model.objects.filter(...)
if results.exists():
    return results
else:
    return Model.objects.create(...)

还有另一个:

result = None
try:
    result = Model.objects.get(...)
except Model.DoesNotExist:
    result = Model.objects.create(...)

抛出和捕获异常没有错!


哪个解决方案更有效?你有任何想法吗? - psoares
1
有时候Python会比你想象的更容易。这帮助我解决了一个有些不相关的问题:在我的当前项目中,我(愚蠢地)额外调用了一次数据库以抛出DNE错误,因为尽管我想要一个过滤结果,但我不知道如何检查空查询集(这是我研究清单上的事项)。如果不是这样,那就太简单了!哈哈 - j_syk
1
按照现有的写法,这将返回一个只有一个元素的列表或者一个对象。可能应该添加“if result: result = result[0]”来进行判断。 - claymation
-1 是因为 [0] 抛出了一个异常,这在答案中没有提到。 - necromancer
-1 是因为 [0] 抛出了一个异常,这在答案中没有提到。 - abisson

37

从django 1.6开始,有一个方便的方法first()用于返回过滤查询的第一个结果,或者返回None。

obj = Model.manager.filter(params).first()
if obj is None:
    obj = Model.objects.create(params)

11

这里有一个管理器方法,可以优雅地扩展此函数。

class FilterOrCreateManager(models.Manager):
"""Adds filter_or_create method to objects
"""
def filter_or_create(self, **kwargs):
  try:
    created = False
    obj = self.filter(**kwargs).first()
    if obj is None:
      obj = self.create(**kwargs)
      created = True
    return (obj,created)
  except Exception as e:        
    print(e)

然后,确保你将管理器添加到你想要在其上使用此功能的任何模型中:

class MyObj(models.Model):
  objects = FilterOrCreateManager()

之后,您可以像使用 get_or_create 一样使用它:

obj_instance, created = MyObj.filter_or_create(somearg='some value')

4
这是目前为止最佳的答案,在我看来应该是被采纳的答案。 - Michel Rugenbrink
为什么要吞掉异常? - fahrradflucht

7

我刚看到这个问题,想要贡献一下,因为还没有人建议实际捕获IndexError异常。

try:
    obj = Model.objects.filter(params)[0]
except IndexError:
    obj = Model.objects.create(params)

4
一行代码实现:
obj = Model.objects.filter(params).first() or Model.objects.create(params)

1
你可以尝试这个:

result = Model.objects.filter(field__lookup=value)[0]
if not result:
   result = Model.objects.create(...)
return result

0
这对我有用:
在你的视图中,调用类似以下代码的内容:
obj = Category.objects.get_or_create_category(form.cleaned_data.get('name'))

在您的模型管理器中,创建一个像这样的函数:
class CategoryManager(models.Manager):

    def get_or_create_category(self, query):
        try:
            result = Category.objects.filter(name = query)[0]
        except:
            result = Category.objects.create(name = query)
        return result

逻辑很简单。首先,尝试检索第一个名称与表单提供的查询字符串匹配的Category对象。如果检索失败(因为它不存在),则创建一个新的Category,并将该字符串作为其名称。返回结果以供视图使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接