Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django override ASCIIUsernameValidator

Tags:

django

Using Django 1.10 I'd like to allow the \ character in a username because I'm working in a Windows environment utilizing 'django.contrib.auth.middleware.RemoteUserMiddleware', and the remote user comes in as domain\username (I have no desire to drop the domain as it is used in some business logic).

I can change django\contrib\auth\validators.py easily enough and have the desired affect by amending the line regex = r'^[\w.@+-]+$' to be regex = r'^[\w.@+-\\]+$' however, I thought one could override this class easily but I failed.

I've found some useful links (and many other similar here on SO):

https://stackoverflow.com/a/39820162/4872140

https://stackoverflow.com/a/1214660/4872140

https://docs.djangoproject.com/en/1.10/releases/1.10/#official-support-for-unicode-usernames https://docs.djangoproject.com/en/1.10/ref/contrib/auth/#django.contrib.auth.models.User.username_validator

But the info is dated, or doesn't quite show exactly/completely how to solve my issue (in the case of the last two). I'm well into the app so changing the AUTH_USER_MODEL is not attractive. settings.py:

AUTH_USER_MODEL = 'myapp.MyUser'

I tried anyway, thinking I may be able to use proxy on the User model like the following which results in the error "cannot proxy the swapped model 'myapp.DomainUser'":

class DomainASCIIUsernameValidator(ASCIIUsernameValidator):
    regex = r'^[\w.@+-\\]+$'

class DomainUser(User):
    username_validator = DomainASCIIUsernameValidator()

    class Meta:
        proxy = True

Is there a way to replace the regex in ASCIIUsernameValidator (and UnicodeUsernameValidator) in a way that the User model stays as is. If you subclass the user model as described at https://docs.djangoproject.com/en/1.10/ref/contrib/auth/#django.contrib.auth.models.User.username_validator are you stuck with specifying that in AUTH_USER_MODEL?

I've read through the discussion at https://groups.google.com/forum/#!topic/django-developers/MBSWXcQBP3k/discussion and feel like creating a custom user from the start may be way to go as a default case.

like image 685
AMG Avatar asked Dec 05 '16 20:12

AMG


1 Answers

It took me an entire day and many different answers to get this working. I split up the code into the different parts. First, we have our validator:

validators.py

from django.contrib.auth.validators import UnicodeUsernameValidator
from django.utils.translation import ugettext_lazy as _


class DomainUnicodeUsernameValidator(UnicodeUsernameValidator):
    """Allows \."""
    regex = r'^[\w.@+-\\]+$'
    message = _(
        'Enter a valid username. This value may contain only letters, '
        'numbers, and \/@/./+/-/_ characters.'
    )

I added changes for the message to also say \ was allowed. You can also easily add DomainASCIIUsernameValidator.

models.py

from django.contrib.auth.models import User

from .validators import DomainUnicodeUsernameValidator


class DomainUser(User):
    class Meta:
        proxy = True

    def __init__(self, *args, **kwargs):
        self._meta.get_field(
            'username'
        ).validators[0] = DomainUnicodeUsernameValidator()

        super().__init__(*args, **kwargs)

This grabs the validator we set up before. It also proxys the default user and sets the validator for the username field. You can set the validator as a list to include the new validator and the max length validator (which is probably safer in case the order changes).

forms.py

from django.contrib.auth.forms import UserChangeForm
from django.utils.translation import ugettext_lazy as _

from .models import DomainUser


class DomainUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = DomainUser
        help_texts = {
            'username': _('Required. 150 characters or fewer. Letters, digits and \/@/./+/-/_ only.'),  # NOQA
        }

Here, I am extending the UserChangeForm and setting the model to the proxy model with the username validation change.

If you would like to allow for users to add user names with \, then you will also need to to change the UserAddForm in a similar way.

Lastly, in admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

from .forms import DomainUserChangeForm


class DomainUserAdmin(UserAdmin):
    form = DomainUserChangeForm


admin.site.unregister(User)
admin.site.register(User, DomainUserAdmin)

We set the form for our admin that will be used in the django admin pages. Then unset the User admin and replace it with our custom DomainUserAdmin.

like image 108
Michael B Sopko Avatar answered Oct 22 '22 00:10

Michael B Sopko