Django: получить первый объект из запроса фильтра или создать - PullRequest
21 голосов
/ 31 мая 2011

В 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]

Ответы [ 6 ]

31 голосов
/ 31 мая 2011

get_or_create () - это просто вспомогательная функция, так что нет ничего плохого в том, чтобы писать свои собственные, как показал pavid или

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

EDIT Как и предполагалось, изменил срез [: 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(...)

Нет ничего плохого в том, чтобы вызывать и ловить исключения!

21 голосов
/ 15 июля 2015

В django 1.6 есть удобный метод first (), который возвращает первый результат в запросе фильтра, или None.

obj = Model.manager.filter(params).first()
if obj is None:
    obj = Model.objects.create(params)
5 голосов
/ 19 февраля 2016

Вот метод менеджера, который позволит вам элегантно расширить эту функцию

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')
5 голосов
/ 23 апреля 2013

Я только что натолкнулся на этот вопрос и хотел внести свой вклад, потому что никто не предложил фактически перехватить IndexError.

try:
    obj = Model.objects.filter(params)[0]
except IndexError:
    obj = Model.objects.create(params)
1 голос
/ 02 августа 2017

Вы можете попробовать это:

result = Model.objects.filter(field__lookup=value)[0]
if not result:
   result = Model.objects.create(...)
return result
0 голосов
/ 11 мая 2012

Это работает для меня:

На ваш взгляд, позвоните примерно так:

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, имя которого соответствует строке запроса (которая указана в форме). Если поиск не удастся (потому что он не существует), создайте новую категорию со строкой в ​​качестве имени. Верните результат для использования в вашем представлении.

...