0

I have read a lot of questions related to clashes reverse accessor but nothing seems to help me.

I'm trying to customize the Token field in DRF to be able to add some fields to it (I would like to have a Token per Company and Companies can create FiscalEntities and each FiscalEntity will have its own Token)

I have followed the following question: How to use custom token model in Django Rest Framework

core/models

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


from company.models import Company
from fiscalentity.models import FiscalEntity


class CustomAuthenticationToken(models.Model):
    """
    The default authorization token model.
    """
    key = models.CharField(_("Key"), max_length=40, primary_key=True)
    company = models.OneToOneField(
        Company, related_name='auth_token',
        on_delete=models.CASCADE, verbose_name=_("Company")
    )
    created = models.DateTimeField(_("Created"), auto_now_add=True)
    # Fiscal entity can be null because if it is null this token belongs to the parent Company
    fiscal_entity = models.ForeignKey(FiscalEntity, null=True, on_delete=models.CASCADE)

    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(CustomAuthenticationToken, self).save(*args, **kwargs)

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

    def __str__(self):
        return self.key


class BearerAuthentication(authentication.TokenAuthentication):
    """
    Simple token based authentication using utvsapitoken.

    Clients should authenticate by passing the token key in the 'Authorization'
    HTTP header, prepended with the string 'Bearer '.  For example:

    Authorization: Bearer 956e252a-513c-48c5-92dd-bfddc364e812
    """
    keyword = 'Bearer'
    model = CustomAuthenticationToken

core/authentication

from rest_framework.authentication import TokenAuthentication

from core.models import CustomAuthenticationToken


class CustomTokenAuthentication(TokenAuthentication):
    model = CustomAuthenticationToken

app/settings

INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework.authtoken',
    'core',
    ...
]

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 5,
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'core.models.BearerAuthentication',
        'core.authentication.CustomTokenAuthentication'
    ),
}

AUTHENTICATION_BACKENDS = [
    'core.models.BearerAuthentication',
]

Trying to run the migrations I get the following errors:

authtoken.Token.user: (fields.E304) Reverse accessor for 'Token.user' clashes with reverse accessor for 'CustomAuthenticationToken.company'.
    HINT: Add or change a related_name argument to the definition for 'Token.user' or 'CustomAuthenticationToken.company'.
authtoken.Token.user: (fields.E305) Reverse query name for 'Token.user' clashes with reverse query name for 'CustomAuthenticationToken.company'.
    HINT: Add or change a related_name argument to the definition for 'Token.user' or 'CustomAuthenticationToken.company'.
core.CustomAuthenticationToken.company: (fields.E304) Reverse accessor for 'CustomAuthenticationToken.company' clashes with reverse accessor for 'Token.user'.
    HINT: Add or change a related_name argument to the definition for 'CustomAuthenticationToken.company' or 'Token.user'.
core.CustomAuthenticationToken.company: (fields.E305) Reverse query name for 'CustomAuthenticationToken.company' clashes with reverse query name for 'Token.user'.
    HINT: Add or change a related_name argument to the definition for 'CustomAuthenticationToken.company' or 'Token.user'.

I have tried omitting the

company = models.OneToOneField(
        Company, related_name='auth_token',
        on_delete=models.CASCADE, verbose_name=_("Company")
    )

but doing so I get a new error saying Invalid field name(s) given in select_related: 'user'. Choices are: (fiscal_entity)

Thank you for your help in advance

Bruno Francisco
  • 3,841
  • 4
  • 31
  • 61

1 Answers1

1

The answer I was following (How to use custom token model in Django Rest Framework) will work for Custom Tokens that has a default Token implementation. Since I'm using BearerAuthentication I need to override the method authenticate_credentials that inherits from TokenAuthentication. At the end my end BearerAuthentication ends up like this:

class BearerAuthentication(authentication.TokenAuthentication):
    """
    Simple token based authentication using utvsapitoken.

    Clients should authenticate by passing the token key in the 'Authorization'
    HTTP header, prepended with the string 'Bearer '.  For example:

    Authorization: Bearer 956e252a-513c-48c5-92dd-bfddc364e812
    """
    keyword = 'Bearer'
    model = CustomAuthenticationToken

    def authenticate_credentials(self, key):
        model = self.get_model()
        try:
            token = model.objects.select_related('company').get(key=key)
        except model.DoesNotExist:
            raise exceptions.AuthenticationFailed(_('Invalid token.'))

        if not token.company.is_active:
            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

        return token.company, token
Bruno Francisco
  • 3,841
  • 4
  • 31
  • 61