radiac / django-tagulous

Fabulous Tagging for Django
http://radiac.net/projects/django-tagulous/
Other
336 stars 66 forks source link

CastTaggedQuerySet.get_or_create is incompatible with update_or_create in Django #135

Closed mnieber closed 3 years ago

mnieber commented 3 years ago

(Django version 3.2.6, tagulous version 1.20)

The Django code for update_or_create calls get_or_create as follows:

obj, created = self.select_for_update().get_or_create(defaults, **kwargs)

However, pydoc.help says that the signature of get_or_create is the following:

get_or_create(**kwargs) method of tagulous.models.tagged.CastTaggedQuerySet instance                                           
    Look up an object with the given kwargs, creating one if necessary.                                                        
    Return a tuple of (object, created), where created is a boolean                                                            
    specifying whether an object was created.                                    

It looks like a change to the signature in Django broke tagulous.

mnieber commented 3 years ago

As a work-around people can use this stand-alone update_or_create function that is compatible with tagulous (but not Django)

from django.db import transaction
from django.db.models.utils import resolve_callables

def update_or_create(query_set, defaults=None, **kwargs):
    """
    Look up an object with the given kwargs, updating one with defaults
    if it exists, otherwise create a new one.
    Return a tuple (object, created), where created is a boolean
    specifying whether an object was created.
    """
    defaults = defaults or {}
    query_set._for_write = True
    with transaction.atomic(using=query_set.db):
        # Lock the row so that a concurrent update is blocked until
        # update_or_create() has performed its save.
        obj, created = query_set.select_for_update().get_or_create(
            defaults=defaults, **kwargs
        )
        if created:
            return obj, created
        for k, v in resolve_callables(defaults):
            setattr(obj, k, v)
        obj.save(using=query_set.db)
    return obj, False
radiac commented 3 years ago

Thanks for spotting this - should be fixed in develop now, 1.2.1 is building to pypi with the change at the moment. Do reopen if you still have issues.