Multileobjectsretourned with get or create

j'écris une petite commande de django pour copier des données d'un terminal D'API json dans une base de données Django. Au point où je crée les objets, avec obj, created = model.objects.get_or_create(**filters) je suis MultipleObjectsReturned erreur. C'est surprenant pour moi, parce que ma compréhension de get_or_create c'est que si j'essaye de créer un objet qui existe déjà, il suffit de 'get' à la place.

Je ne suis pas certain de l'intégrité de la base de données que je suis en train de cloner, mais même s'il y a plusieurs objets identiques dedans, quand je les charger dans ma base de données Django locale, get_or_create ne devrait-il pas le faire pour que je n'obtienne jamais plus d'une copie?

quelqu'un Peut-il expliquer cela? Je suis heureux de donner plus de détails, je ne voulais pas engorger le lecteur vers le bas.

8
demandé sur Brian Peterson 2013-07-31 06:26:38

3 réponses

Comme son nom l'indique, get_or_create model.objects.get()ou model.objects.create() S.

c'est l'équivalent conceptuel de:

try:
   model.objects.get(pk=1)
except model.DoesNotExist:
   model.objects.create(pk=1)

la source est où vous trouvez des réponses définitives à ces types de questions. Indice: recherche def get_or_create. Comme vous pouvez le voir, cette fonction uniquement des captures DoesNotExist dans le try/except.

def get_or_create(self, **kwargs):
    """
    Looks up an object with the given kwargs, creating one if necessary.
    Returns a tuple of (object, created), where created is a boolean
    specifying whether an object was created.
    """
    assert kwargs, \
            'get_or_create() must be passed at least one keyword argument'
    defaults = kwargs.pop('defaults', {})
    lookup = kwargs.copy()
    for f in self.model._meta.fields:
        if f.attname in lookup:
            lookup[f.name] = lookup.pop(f.attname)
    try:
        self._for_write = True
        return self.get(**lookup), False
    except self.model.DoesNotExist:
2
répondu Yuji 'Tomita' Tomita 2016-03-03 11:34:13

exemple de code

Imaginez que vous ayez le modèle suivant:

class DictionaryEntry(models.Model):
    name = models.CharField(max_length=255, null=False, blank=False)
    definition = models.TextField(null=True, blank=False)

et le code suivant:

obj, created = DictionaryEntry.objects.get_or_create(
    name='apple', definition='some kind of fruit')

get_or_create

Dans le cas où vous n'avez pas vu le code pour get_or_create:

 # simplified
 def get_or_create(cls, **kwargs):
     try:
         instance, created = cls.get(**kwargs), False
     except cls.DoesNotExist:
         instance, created = cls.create(**kwargs), True
     return instance, created

à propos de webservers...

Maintenant, imaginez que vous avez un serveur web 2 processus de travail qui ont tous les deux leurs propres accès simultanés pour la base de données.

 # simplified
 def get_or_create(cls, **kwargs):
     try:
         instance, created = cls.get(**kwargs), False # <===== nope not there...
     except cls.DoesNotExist:
         instance, created = cls.create(**kwargs), True
     return instance, created

si le timing est correct (ou erroné selon la façon dont vous voulez l'exprimer), les deux processus peuvent faire la recherche et ne pas trouver l'item. Ils peuvent tous les deux créer l'article. Tout est très bien...

MultipleObjectsReturned: get() returned more than one KeyValue -- it returned 2!

Tout va bien... jusqu'à ce que vous appelez get_or_create un troisième temps, "la troisième fois est un charme", disent-ils.

 # simplified
 def get_or_create(cls, **kwargs):
     try:
         instance, created = cls.get(**kwargs), False # <==== kaboom, 2 objects.
     except cls.DoesNotExist:
         instance, created = cls.create(**kwargs), True
     return instance, created

unique_together

Comment avez-vous pu résoudre cela? Peut-être imposer une contrainte à la base de données niveau:

class DictionaryEntry(models.Model):
    name = models.CharField(max_length=255, null=False, blank=False)
    definition = models.TextField(null=True, blank=False)
    class Meta:
        unique_together = (('name', 'definition'),)

retour à la fonction:

 # simplified
 def get_or_create(cls, **kwargs):
     try:
         instance, created = cls.get(**kwargs), False
     except cls.DoesNotExist:
         instance, created = cls.create(**kwargs), True # <==== this handles IntegrityError
     return instance, created

Disons que vous avez la même race qu'avant, et ils ont tous deux fait de ne pas trouver l'article et procéder à l'insertion; ce faisant, ils vont commencer une transaction et l'un d'eux va gagner la course, tandis que l'autre va voir un IntegrityError.

mysql ?

L'exemple utilise un TextFieldmysql traduit en LONGTEXT (dans mon cas). L'ajout de l' unique_together la contrainte échoue syncdb.

django.db.utils.InternalError: (1170, u"BLOB/TEXT column 'definition' used in key specification without a key length")

Donc, pas de chance, vous pourriez avoir à traiter avec MultipleObjectsReturned manuellement.

possible solutions

  • il est possible de remplacer le TextField avec un CharField.
  • Il peut être possible d'ajouter un CharField qui peut être un fort hachage de la TextField, que vous pouvez calculer à l' pre_save en unique_together.
18
répondu dnozay 2015-04-08 17:12:03

une autre situation qui pourrait causer des erreurs Multibleobjectsretourned avec L'API get_or_create () semble être si plusieurs threads appellent cette API en même temps avec le même ensemble de paramètres de requête.

uniquement en se basant sur l'essai... attraper... créer une ligne unique en Python ne fonctionnerait pas. Si vous essayez d'utiliser cette API, je pense que vous devriez avoir une contrainte d'unicité correspondante sur les colonnes appropriées de la base de données.

Voir: https://code.djangoproject.com/ticket/12579

2
répondu AdvilUser 2014-02-06 11:00:31