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.
Queryset.get_or_create()is actually to try and handle this situation, but it's alas not failsafe either.get_or_createfailsafe, there are indeed occasional issues. You could of course wrap the call in a try/except block, but for this concrete use case using thepost_savesignal as explained in Dalvtor's answer is the simplest and safest solution.post_savesignal will help this problem at all. Wrapping theget_or_createin atry/exceptis exactly what you need to do here.