Custom user model: What else is needed to be production ready? - production

I am new to Django and need to use an email address as a username for my app - plus add some custom fields. So I am following the full example in django docs to make a custom user model.
I am very worried about the part in the example that says:
This example illustrates how most of the components work together,
but is not intended to be copied directly into projects for production
use.
I am not sure what else is needed in order to make the user model production ready.
Is the example missing some crucial security features? (I would like my custom model and authentication to be as good as the default Django one.)
Does anyone have an example that is production ready?

In the end, what I did was follow the full example in the django website, and compare my code with the default Django user model. I believe the default Django user model is production ready, and really, the only difference from that and the custom user model is the use of email as username, so I feel quite reassured that my custom user model is production ready.
Here is my code:
in models.py
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.core.mail import send_mail
class MyUserManager(BaseUserManager):
def create_user(self, email, password=None):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password):
"""
Creates and saves a superuser with the given email and password.
"""
user = self.create_user(email,
password=password
)
user.is_admin = True
user.save(using=self._db)
return user
class MyUser(AbstractBaseUser):
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), max_length=254, blank=False, unique=True, db_index=True)
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
is_admin = models.BooleanField(default=False)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
gender_choices = (
('M', 'Male'),
('F', 'Female'),
)
gender = models.CharField(max_length=1, choices=gender_choices, blank=True)
date_of_birth = models.DateField(null=True, blank=True)
city = models.CharField(max_length=50, blank=True)
phone_number = models.CharField(max_length=15, blank=True)
description = models.TextField(blank=True)
work = models.TextField(blank=True)
objects = MyUserManager()
USERNAME_FIELD = 'email'
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
return self.first_name
def email_user(self, subject, message, from_email=None):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email])
def __unicode__(self):
return self.first_name + ' ' + self.last_name
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
#property
def is_staff(self):
return self.is_admin
Also in my forms.py and views.py I used the User model, so I added the following:
from django.contrib.auth import get_user_model
User = get_user_model()

Related

A bag in the registration system (django rest, jwt)

The detail description of the registration system and the bug are described below
The registration system works as follows:
Registration by email and password (the user is not created immediately), if the data is valid, an email is sent to the mail with the generated token, which is embedded in the link. The user clicks on the link, and we use the token to confirm that this user with this email is really the one he is trying to impersonate. Then the user is created with his set of characteristics. The problem is that after the user has given his email and password in order for us to send him an email, it is saved by his unique email as a user, without specifying the user (we have several categories of users). And in the case when the validity period of the token ends, the user's email is saved in the database as unique, and the user cannot repeat the request.
The common description of the bag:
The user registers on the site, verification comes to his email (which expires in 1 hour, for example), how to process such a task so that the user can pass verification in a day or two
class RegisterView(generics.GenericAPIView):
"""Register View
This class is made to do responses on
requests connected with a registration.
This view gets a request according to the mask:
{
"email": "user#example.com",
"password": "string"
}
Then, this view configures access token and sends it
on given email into the message body - link.
Then this token should be passed to other view -
VerifyEmail (rel url localhost/auth/email-verify/)
"""
serializer_class = RegisterSerializer
def post(self, request):
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user_data = serializer.data
user_from_db = User.objects.get(email=user_data['email'])
token = RefreshToken.for_user(user_from_db).access_token
current_site = get_current_site(request)
relative_link = reverse('email-verify')
abs_url = 'http://' + str(current_site) + str(relative_link) + '?token=' + str(token)
email_body = 'Hi! ' \
+ user_from_db.email \
+ ' ! Use link below to verify email \n' \
+ abs_url
data_to_send = {
'domain': current_site,
'email_subject': 'Verify your email',
'email_body': email_body,
'to_address': user_from_db.email
}
Util.send_email(data_to_send)
return Response(user_data, status=status.HTTP_201_CREATED)
class VerifyEmail(views.APIView):
"""Verify Email View
This view checks if the given token is valid, and make a mark
in the db with current user - if token is valid -
the user is verified, if not - sends a response with a
short problem description.
"""
serializer_class = EmailVerificationSerializer
token_param_config = openapi.Parameter(
'token',
in_=openapi.IN_QUERY,
description='Insert token from email',
type=openapi.TYPE_STRING
)
#swagger_auto_schema(manual_parameters=[token_param_config])
def get(self, request):
token = request.GET.get('token')
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
user = User.objects.get(id=payload['user_id'])
if not user.is_verified:
user.is_verified = True
user.save()
return Response({'email': 'Successfully activated'}, status=status.HTTP_200_OK)
except jwt.ExpiredSignatureError:
return Response({
'error': 'Activation link expired'},
status=status.HTTP_400_BAD_REQUEST
)
except jwt.exceptions.DecodeError:
return Response({
'error': 'Invalid token'},
status=status.HTTP_400_BAD_REQUEST
)
class UserManager(BaseUserManager):
"""
This class is needed to determine class 'Manager' of custom users.
To inherit from BaseUserManager, we get a lot of the same code, which
was used by Django to create 'User'.
"""
def create_user(self, email, password=None):
""" Creates and returns a user with email, pswrd and nickname"""
if email is None:
raise TypeError('Users must have an email. / '
'У пользователя должет быть email')
user = self.model(
email=self.normalize_email(email)
)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password):
"""Creates and returns a user with super-admin permissions"""
if password is None:
raise TypeError('Superusers must have a password.')
user = self.create_user(email, password)
user.is_superuser = True
user.is_staff = True
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
"""Model User - base model
This model is made to be inherited by other models.
So, this model has a number of main parameters to use
them in child classes
"""
# email is used to connect with users
email = models.EmailField(db_index=True, unique=True)
# is active - the way not to remove information.
# when a user will want to delete his info from
# the site, we will suggest just deactivate his profile
is_active = models.BooleanField(default=True)
# # Этот флаг определяет, кто может войти в административную часть нашего
# # сайта. Для большинства пользователей это флаг будет ложным.
is_staff = models.BooleanField(default=False)
is_verified = models.BooleanField(default=False)
# the point of user creation
created_at = models.DateTimeField(auto_now_add=True)
# when the profile was last time updated
updated_at = models.DateTimeField(auto_now=True)
# USERNAME_FIELD and REQUIRED_FIELDS is required parameters in
# current schema, when we use Manager
# USERNAME_FIELD shows what the field will be used
# to log in the system
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
# object connects to the manager class
objects = UserManager()
def __str__(self):
return self.email
def tokens(self):
refresh_token = RefreshToken.for_user(self)
return {
'refresh': str(refresh_token),
'access': str(refresh_token.access_token)
}
class CustomUser(models.Model):
""" Custom User Model """
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=50, blank=False)
last_name = models.CharField(max_length=50, blank=False)
terms_is_accepted = models.BooleanField(default=False, blank=False)
phone_number = models.CharField(max_length=13, blank=False)
telegram_nick = models.CharField(max_length=20, blank=True)
linkedin = models.URLField(max_length=100, blank=False)
profile_photo = models.ImageField(upload_to="photos/%Y/%m/%d/", blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
location = models.ManyToManyField(Location, blank=True)
credo = models.TextField(max_length=1000, blank=True)
specialization = models.CharField(max_length=150, choices=SPECIALIZATIONS, blank=False)
hard_skills = models.CharField(max_length=1000, blank=True)
soft_skills = models.CharField(max_length=1000, blank=True)
language = models.CharField(max_length=100, choices=LANGUAGE, blank=True)
work_experience = models.ManyToManyField(Experience, blank=True)
education = models.ManyToManyField(Education, blank=True)
additional_education = models.CharField(max_length=500, blank=True)
other_info = models.TextField(blank=True)
portfolio_link = models.URLField(blank=True, null=True)
def __str__(self):
return str(self.user) if self.user else ''

Django:unable to login after creating custom super user

After creating a superuser, for some reason, I was unable to log into the Django database. I think somehow the user I created is not accepted as a staff member account, though I set is_staff, True by default. By the way, creating a custom super user like that turned out to be a headache, lots of errors occurred and I am unable to get answers in the forums.
from asyncio.windows_events import NULL
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
class CustomUserManager(BaseUserManager,):
def create_superuser(self, email, user_name, password, about='Hi it is me', **other_fields):
other_fields.setdefault('is_staff', True)
other_fields.setdefault('is_superuser', True)
other_fields.setdefault('is_active', True)
if other_fields.get('is_staff') is not True:
raise ValueError('super user must be assigned to is_staff= True')
if other_fields.get('is_superuser') is not True:
raise ValueError(
'Super user must be assigned to is_super_user=True')
return self.create_user(email, user_name=user_name, password=password, about='Hi it is me', **other_fields)
def create_user(self, email, user_name, password, about=None, **other_fields):
if not email:
raise ValueError(('You must provide an email address'))
# for example ignore the case sensitivity
email = self.normalize_email(email)
user = self.model(email=email, user_name=user_name, about=about,)
user.set_password(password)
user.save()
return user
class NewUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=255, unique=True)
user_name = models.CharField(max_length=32, unique=True)
about = models.CharField(max_length=512, blank=True, null=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['user_name']

djangorestframework-simplejwt getting "No active account found with the given credentials" although I know my user exists

I'm fairly new to Django and feel I must be missing something. I have an application where I want two user types (UserType1 and UserType2). I am using the base user model with boolean flags and then created OneToOne fields on these objects to the user (no extra data added at this point).
This gives me the following user models:
from django.conf import settings
from django.contrib.auth.models import AbstractUser, UserManager
from django.core.exceptions import ValidationError
from django.db import models
from django_extensions.db.models import TimeStampedModel
class User(AbstractUser):
"""
Default user model for the platform
"""
email = models.EmailField()
is_user_type_1 = models.BooleanField(default=False)
is_user_type_2 = models.BooleanField(default=False)
# Use default UserManager for now
objects = UserManager()
class UserType1(TimeStampedModel):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True,
related_name='user_type1',
)
class UserType2(TimeStampedModel):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True,
related_name='user_type2'
)
I then wrote serializes for these classes where I only want to expose and endpoint for UserType1 and UserType2 (rather than User itself). This should allow the users to create an instance of UserType1 or UserType2 which will also create the row in the User table (although maybe not super important for this). Here are the serializers:
from django.db import transaction
from rest_framework import serializers
from .models import User, UserType1, UserType2
class UserSerializer(serializers.ModelSerializer):
"""
Serializer for the `User` model
"""
class Meta:
model = User
fields = (
'username',
'email',
'password',
'first_name',
'last_name',
'is_active',
)
extra_kwargs = {'password': {'write_only': True}}
class UserType1Serializer(serializers.ModelSerializer):
user = UserSerializer(required=True)
class Meta:
model = UserType1
fields = (
'user',
'created',
'modified',
)
#transaction.atomic
def create(self, validated_data):
user_data = validated_data.pop('user')
user_data.is_user_type_1 = True
user_data.is_user_type_2 = False
user = UserSerializer.create(UserSerializer(), validated_data=user_data)
user_type_1, created = UserType1.objects.update_or_create(
user=user,
)
return user_type_1
class UserType2Serializer(serializers.ModelSerializer):
user = UserSerializer(required=True)
class Meta:
model = UserType2
fields = (
'user',
'created',
'modified',
)
#transaction.atomic
def create(self, validated_data):
user_data = validated_data.pop('user')
user_data.is_user_type_1 = False
user_data.is_user_type_2 = True
user = UserSerializer.create(UserSerializer(), validated_data=user_data)
user_type2, created = UserType2.objects.update_or_create(
user=user,
)
return user_type2
The User model was registered in my settings:
AUTH_USER_MODEL = 'user.User'
After I create a superuser, I am successfully able to get my tokens and use them to access other views:
{
"refresh": "xxxx",
"access": "xxxx"
}
However, if I create a user from admin (or I have fixtures that create users and user_type1/user_type2 objects), every time I try to get the tokens I am getting:
{
"detail": "No active account found with the given credentials"
}
but I know they exist in the DB since I can see them properly in the admin console. I thought this was something to do with hashing of the password but was unable to solve it.
This also contains the following admin.py file:
from django.contrib import admin
from .models import User, UserType1, UserType2
admin.site.register(User)
admin.site.register(UserType1)
admin.site.register(UserType2)
Any help would be greatly appreciated!!
Register your custom User model in the admin with:
from django.contrib.auth.admin import UserAdmin
admin.site.register(models.User, UserAdmin)
Or follow the documentation on how to register a custom User model in admin.
A full example
create a new User via admin panel and check if the password is hashed.
Then make the api call to your jwt endpoint again.

python social auth with django custom user model

I'm using python-social-auth (not django-social-auth because it's depreciated) for authentication in Django REST backend applciation with Custom User Model described below.
from django.contrib.auth.models import AbstractBaseUser, UserManager
class User(AbstractBaseUser):
class Gender():
MALE = 0
FEMALE = 1
UNKNOWN = 2
CHOICES = [(MALE, 'Male'), (FEMALE, 'Female'), (UNKNOWN, 'Unknown')]
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, digits and '
'#/./+/-/_ only.'),
validators=[
validators.RegexValidator(r'^[\w.#+-]+$', _('Enter a valid username.'), 'invalid')
])
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
email = models.EmailField(blank=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(_('active'), default=True)
date_joined = models.DateTimeField(default=timezone.now)
gender = models.IntegerField(choices=Gender.CHOICES, default=Gender.UNKNOWN)
birthday = models.DateField(default=timezone.now)
facebook_id = models.CharField(max_length=30, blank=True)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
objects = UserManager()
def __unicode__(self):
return self.username
def save(self, *args, **kwargs):
""" ensure instance has usable password when created """
if not self.pk and self.has_usable_password() is False:
self.set_password(self.password)
super(User, self).save(*args, **kwargs)
Notice that I don't implement a custom UserManager. Social auth pipline is also straightforward.
AUTHENTICATION_BACKENDS = (
'social.backends.facebook.FacebookOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
SOCIAL_AUTH_PIPELINE = (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
'social.pipeline.social_auth.associate_by_email',
'social.pipeline.user.create_user',
'social.pipeline.social_auth.associate_user',
'social.pipeline.social_auth.load_extra_data',
'social.pipeline.user.user_details'
)
However, when I try to authenticate with Facebook, it gives an error as below
TypeError at /api-token/login/facebook/
'is_superuser' is an invalid keyword argument for this function
The problem is, probably, python-social-auth try to use django's own User instead of custom User Model that I defined.
In django-social-auth there is a setting's parameter like SOCIAL_AUTH_USER_MODEL but I couldn't find any way to do it in python-social-auth
How can I make it possible to use my custom user model in python-social-auth?
There has to be a Custom UserManager as well described below. I removed is_superuser=is_superuser field while creating new user.
class UserManager(BaseUserManager):
def _create_user(self, username, email, password,
is_staff, is_superuser, **extra_fields):
"""
Creates and saves a User with the given username, email and password.
"""
now = timezone.now()
if not username:
raise ValueError('The given username must be set')
email = self.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=is_staff, is_active=True, last_login=now,
date_joined=now, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, username, email=None, password=None, **extra_fields):
return self._create_user(username, email, password, False, False,
**extra_fields)
def create_superuser(self, username, email, password, **extra_fields):
return self._create_user(username, email, password, True, True,
**extra_fields)

Different user profiles with django-profiles & django-registration

My models.py:
USER_TYPES = (
('D', 'Demo' ),
('F', 'Free' ),
('P', 'Premium'),
)
class BaseProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
user_type = models.CharField(max_length=1, blank=True, choices=USER_TYPES)
class DemoProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
demo = models.CharField(max_length=10, blank=True)
...
class FreeProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
free = models.CharField(max_length=10, blank=True)
...
class PremiumProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
premium = models.CharField(max_length=10, blank=True)
...
class ProxyProfile(BaseProfile):
class Meta:
proxy = True
def get_profile(self):
if self.user_type == 'D':
return DemoProfile._default_manager.get(user__id__exact=self.user_id)
elif self.user_type == 'F':
return FreeProfile._default_manager.get(user__id__exact=self.user_id)
else:
return PremiumProfile._default_manager.get(user__id__exact=self.user_id)
I use BaseProfile to map user_id to specific user_type. I wanted to use ProxyProfile as proxy which loads user_type depending profiles to ModelForm as shown below
Content of my forms.py:
class ProfileForm(ModelForm):
...
class Meta:
model = ProxyProfile
exclude = ('user','user_type')
...
ProfileForm is provided to django-profiles using following code in urls.py:
urlpatterns += patterns('',
url(r'^profiles/edit/', edit_profile,
{'form_class': ProfileForm},
name='profiles_edit_profile'),
(r'^profiles/',include('profiles.urls')),
)
I've also set in settings.py:
AUTH_PROFILE_MODULE = 'main.ProxyProfile'
During user registration all db data is filled correctly (it looks like everything is OK).
I register using form passed to django-registration:
urlpatterns += patterns('',
url(r'^register/$', register,
{'form_class': UserRegistrationForm},
name='registration.views.register'),
(r'', include('registration.urls')),
)
from forms.py:
class UserRegistrationForm(RegistrationFormUniqueEmail, RegistrationFormTermsOfService):
utype = forms.ChoiceField(choices=USER_CHOICES)
def save(self, profile_callback=None):
new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
password.self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
new_base_profile = BaseProfile(user=new_user, user_type=self.cleaned_data['utype'])
if self.cleaned_data['utype'] == "D":
new_profile = DemoProfile(user=new_user)
if self.cleaned_data['utype'] == "F":
new_profile = FreeProfile(user=new_user)
if self.cleaned_data['utype'] == "P":
new_profile = PremiumProfile(user=new_user)
new_profile.save()
new_base_profile.save()
return new_user
And registration phase works OK.
I've problem with profile edit/details pages.
My profiles filtered in ProxyProfile model and used as FormModel in ProfileForm
are not rendered (I can't see profile specific fields are not rendered to HTML page)
Maybe there is some other way (more like Django way :)) to do this
(select and render profile model depending on user_type field which is related to User model).
Thanks in advance :)
Ok, finally I've had an idea how I can do this :)
In my models.py:
class BaseManager(models.Manager):
def get(self, **kwargs):
self.u = kwargs['user__id__exact']
self.bt = BaseProfile.manager.get(user__id__exact=self.u)
if self.bt.user_type == 'F':
return FreeProfile.objects.get(pk=self.u)
elif self.bt.user_type == 'I':
return PremiumProfile.objects.get(pk=self.u)
else:
return None
class BaseProfile(models.Model):
objects = BaseManager()
manager = UserManager()
user = models.OneToOneField(User, primary_key=True)
user_type = models.CharField(max_length=1, blank=True, choices=USER_TYPES)
class FreeProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
free = models.CharField(max_length=10, blank=True)
...
class PremiumProfile(models.Model):
user = models.OneToOneField(User, primary_key=True)
premium = models.CharField(max_length=10, blank=True)
...
In custom manager - BaseManager I return profile object by overwriting get() method used by get_profile. I have to use UserManager named simply 'manager' to prevent recursive call of custom manager when assigning self.bt
OK, this is a half way to achive what I want, now I can view different profiles attached to users using django-profiles app.
Next, I want to use ModelForm to prepare edit form for user profiles. Users can have different profiles so I've applied the magic trick presented in this snippet: http://djangosnippets.org/snippets/2081/
And now in my forms.py I have:
class FreeForm(forms.ModelForm):
class Meta:
model = FreeProfile
class PremiumForm(forms.ModelForm):
class Meta:
model = PremiumProfile
next, simple model forms for each profile are assembled in ProfileForm:
class ProfileForm(ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs['instance'].user
profile_kwargs = kwargs.copy()
profile_kwargs['instance'] = self.user
self.bt = BaseProfile.manager.get(user__id__exact=self.user.id)
if self.bt.user_type == 'F':
self.profile_fields = FreeForm(*args, **profile_kwargs)
elif self.bt.user_type == 'P':
self.profile_fields = PremiumForm(*args, **profile_kwargs)
super(ProfileForm, self).__init__(*args, **kwargs)
self.fields.update(self.profile_fields.fields)
self.initial.update(self.profile_fields.initial)
class Meta:
model = BaseProfile
def save(self):
...
In settings.py:
AUTH_PROFILE_MODULE = 'main.BaseProfile'
And it works like a charm but I wonder if it is the Django way to achieve support for multiple different profiles using django-profiles?
It worries me that I have to use get() few more times before I render profile details or edit form.
But after 4 days of struggling with Django to get this done finally I can sleep well tonight :)
Cheers

Resources