John T
John T

Reputation: 339

IntegrityError when using .save() on OneToOneField in Django

I have 2 models

class User(models.Model):
    email = models.CharField(max_length = 100)
    password = models.CharField(max_length = 100)

class Authentication(models.Model):
    user = models.OneToOneField(User, on_delete = models.CASCADE)
    token = models.CharField(max_length =50, null = True)

Here is the code I use for login in views.py

from user.models import User, Authentication
import uuid
from django.db.utils import IntegrityError

user = User.objects.get(pk = request.user.id)
token = uuid.uuid4().hex
try:
    Authentication.objects.create(user = user, token = token)
except IntegrityError:
    user.authentication.token = token
    user.authentication.save()
return JsonResponse({'token':token})

The problem I meet here is everytime a user login, I will generate a new token and save in the database. The error appears at the line

user.authentication.save()

The error is: django.db.utils.IntegrityError: (1062, "Duplicate entry '30' for key 'user_id' ") The 30 is the user_id existed in Authentication models when login in the second time ( the first time login is always succeed). I solved this problem by using these codes:

except IntegrityError:
    au = Authentication.objects.get(user = user)
    au.token = token
    au.save()

But I dont know why i got this bug. I used to do the first approach many times but not got no bugs at all. Anyone has faced to this problem can show me the way to solve this? Thank you

Upvotes: 1

Views: 803

Answers (2)

akhilsp
akhilsp

Reputation: 1103

The problem is that you are trying to create a new token to a user who already have one, and since you've defined a OneToOneField it is not possible. I suggest you to try the get_or_create() instead of create() method.

Alternatively, you can modify the create method:

try:
    auth = Authentication.objects.create(user = request.user)
except Exception as e:
    #do something
return JsonResponse({'token': auth.token})

In models.py

from user.managers import MyManager

class Authentication(models.Model):
    user = models.OneToOneField(User, on_delete = models.CASCADE)
    token = models.CharField(max_length =50, null = True)
    objects = MyManager()

Then create a file 'managers.py' and put this in it:

from django.db import models
import uuid

class MyManager(models.Manager):
    def create(self, user):
        token = uuid.uuid4().hex
        if user.authentication:
            user.authentication.token = token
            user.authentication.save()
            return user.authentication
        else:
            auth = self.model(user=user, token=token)
            auth.save(using=self._db)
            return auth

Now if you use Authentication.objects.create(user=request.user) the create method in MyManager will be invoked. This also helps to separate token generation from views.py

Cheers!

Upvotes: 1

itzMEonTV
itzMEonTV

Reputation: 20349

You should have look at django builtin auth system. For your approach

try:
    obj = Authentication.objects.get(user=user)
    obj.token = token
    obj.save()
except Authentication.DoesNotExist:
    Authentication.objects.create(user = user, token = token)

Upvotes: 0

Related Questions