1

I have an ExtendedUser model like this, that just points to Django's User model:

class ExtendedUser(models.Model):
    user = models.OneToOneField(User)

When a request comes in, I want to get or create an User and its related ExtendedUser if the user that I'm looking for doesn't exist. I have this code :

def get_or_create_user(username):
    with transaction.atomic():
        user, created = User.objects.get_or_create(
            username=username,
            defaults={
                'first_name': "BLABLA",
            },
        )

        if created:
             extended_user = ExtendedUser()
             extended_user.user = user
             extended_user.save()

    return user

I wrap it inside a transaction, to be sure I don't create an User but not its associated ExtendedUser.

But, when two requests come in simultaneously that will create the same user, this fails from time to time with an IntegrityError, for both requests. So I end up with no user being created..

IntegrityError: (1062, "Duplicate entry 'BLABLABLA' for key 'username'")

Note that I use MySQL, but I force READ COMMITTED isolation level.

What do I do wrong ? How should I handle this ? The models need to stay as they are.

7
  • create only if the record doesn't exists .. either by handling ObjectDoesNotExist exception or by checking exists() . Commented Feb 2, 2018 at 14:17
  • @SajiXavier this naive approach is the best way to have more race conditions - a record can be inserted between when you check if it exists and when you try to create it. The whole point of Queryset.get_or_create() is actually to try and handle this situation, but it's alas not failsafe either. Commented Feb 2, 2018 at 14:21
  • As hard as the Django maintainers try to make get_or_create failsafe, there are indeed occasional issues. You could of course wrap the call in a try/except block, but for this concrete use case using the post_save signal as explained in Dalvtor's answer is the simplest and safest solution. Commented Feb 2, 2018 at 14:29
  • I don't see how post_save signal will help this problem at all. Wrapping the get_or_create in a try/except is exactly what you need to do here. Commented Feb 2, 2018 at 14:37
  • @brunodesthuilliers according to the question both requests are failing with IntegrityError, so even signal handling will not work becuse User itself is not getting created. Maybe need to use table LOCK feature (i know LOCK works with postgreSQL) Commented Feb 2, 2018 at 14:41

1 Answer 1

1

Maybe you should solve this problem using signals.

After a User is saved, a signal will be fired. It is in the signal handler where you should create your ExtendedUser.

from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        ExtendedUser.objects.create(user=instance)
Sign up to request clarification or add additional context in comments.

5 Comments

That's indeed the canonical solution here. Been using it in a dozen projects without any issue.
This doesn't solve the problem at all, which is IntegrityError: (1062, "Duplicate entry 'BLABLABLA' for key 'username'") on the get_or_create call. This is a wrong use of signals, which are for decoupling separate apps rather than chaining a series of always-required actions together.
This will not work, as just after calling my user = get_or_create_user(username) function, then I try to access the user.extendeduser. So the ExtendedUser should be created at the same time as the User, so the slower query can retrieve it along with the user.
@fougerejo this WILL work. Django's signals are NOT async, so the the ExtendedUser is garanteed to have been created when User.objects.create() returns. I've been using this for years on dozen projects without a single issue. This wont of course solve the occasional race condition, though - but I'm afraid there's actually no 101% failsafe solution with MySQL here.
Ok I see. But then at the end, what's the difference between using signals and creating the ExtendedUser right away?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.