abdullah celik
abdullah celik

Reputation: 551

Django Rest Framework - Generate a Token for a non-built-in User model class

I wanted to know if I am able to create a Token for a normal model class. Suppose that I have the following class:

class User(models.Model):
    name                    = models.CharField(max_length=30, unique=True)
    profile_pic             = models.FileField(upload_to='None/',default='placeholder.jpg')
    joined_date             = models.DateTimeField(verbose_name='date joined', auto_now_add=True)

Note that my User class does not extend auth.models.User .

Now when I use a signal like this:

@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)

Then Django states that Token.user is not an instance of User. Basically, the same as in this SO thread.

From that link above, I learned that my User class has to extend the built-in auth.models.User class provided by Django for authentication.

But I am curious and would like to know if there is a way to generate a Token for classes that do not extend auth.models.User ? Is that possible ? If yes, how ?

Upvotes: 1

Views: 1889

Answers (1)

VJ Magar
VJ Magar

Reputation: 1052

#1 approach

We are trying here to generate tokens. Token, by definition, are randomly generated sequences of chars. Taking inspiration from the rest auth token model, we can have a similar custom model. We will be overriding the save method of this model to generate the key.

import binascii
import os

from django.db import models
from django.utils.translation import ugettext_lazy as _

class NonBuiltInUserToken(models.Model):
    ...
    key: str = models.CharField(_("Key"), max_length=40, primary_key=True)
    user: User = models.OneToOneField(
            User, related_name='auth_token',
            on_delete=models.CASCADE, 
            verbose_name=_("User")
    )
    created = models.DateTimeField(_("Created"), auto_now_add=True)
    ...

    class Meta:
        verbose_name = _("Token")
        verbose_name_plural = _("Tokens")

    def save(self, *args, **kwargs):
        if not self.key:
            self.key = self.generate_key()
        return super(NonBuiltInUserToken, self).save(*args, **kwargs)

    def generate_key(self):
        return binascii.hexlify(os.urandom(20)).decode()

    def __str__(self):
        return self.key

Copied from this stack overflow answer

#2 approach

You can also try to subclass the Token model of the auth token.

...
from django.db import models
from django.utils.translation import gettext_lazy as _
from rest_framework.auth_token.models import Token
...

class NonBuiltInUserToken(Token):
        """
        Overrides the Token model to use the
        non-built-in user model
        """
        user = models.OneToOneField(
            User, related_name='auth_token',
            on_delete=models.CASCADE, 
            verbose_name=_("User")
        )

If we check the Token model in the rest_framework.authtoken.models, we will see that Token class is abstract if rest_framework.authtoken is not present in the settings.INSTALLED_APPS.

class Meta:
        # Work around for a bug in Django:
        # https://code.djangoproject.com/ticket/19422
        #
        # Also see corresponding ticket:
        # https://github.com/encode/django-rest-framework/issues/705
        abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS

Thus in order to use this token model, you will be required to remove the rest_framework.authtoken from the settings.INSTALLED_APPS

Now, you have a new token model for non-build in the user model. You will be using this model in the signals to create tokens.

Upvotes: 2

Related Questions