I would like to implement temporary users in my project. The reason is that people should be able to auth in my application via Facebook Oauth or Facebook Account Kit.
I've got 2 decisions, but each one is not ideal.
1) Create User and TemporaryUser models. The first one will have all info about regular user + unique db fields. This TemporaryUser will have only phone_number or facebook_id. Temporary user will be created on auth/ endpoint and response will have auth_token to be able perform registration later with all needed fields.
The issue is: how will I determine that user is temporary and his auth_token is legit only for registration/ endpoint?
2) Create basic User model with 2 types: regular or temporary. This model will have only general fields. Also there will be different models with OneToOne relation with User. Only Users with type regular will be able to have that instances with OneToOne relation.
The issue is: User model should have USERNAME_FIELD , REQUIRED_FIELDS to be able to login by admin panel + users with different types should have different managers.
UPDATED
class User(AbstractBaseUser, TimeStampedModel, PermissionsMixin):
is_regular = models.BooleanField(
default=False
)
id = models.CharField(
max_length=11,
primary_key=True,
default=custom_uuid,
editable=False
)
phone_number = PhoneNumberField(
null=True
)
facebook_id = models.CharField(
max_length=255,
null=True
)
objects = UserManager()
USERNAME_FIELD = 'phone_number'
REQUIRED_FIELDS = []
From discussion, there are many ways to tackle this issue. But using two different models for authentication purpose is a bad idea. Because Django Auth was designed assuming one User Model.
This is how I would have approached:
Rather than using facebook_id, I would have stored email from facebook's graph API. While creating the password, I would have used an unique UUID.
Put a temporary flag(a Model Boolean Field) to track if the user is registered through social media. So when he tries to login to normal path or registration, I would know that he does not have a password, so I would send him to reset his password. After successful reset password, I would have removed that temporary flag.
Other information which are absolute necessary, I would have used a separate model to store them. For example:
class Profile(models.Model):
...
phone_number = models.CharField(max_length=30)
user = models.OneToOneField(settings.AUTH_USER_MODEL)
And checked with login that if the User has a profile or not:
if not hasattr(user, 'profile'):
# redirect to more information page
For other permissions for accessing admin site should be restricted creation through normal registration or social registration. For these users, those fields(is_staff, is_superuser etc should be False by default. For admin user creation, you can use createsuperuser command. For staff users, you can later assign a normal user to staff by making the is_staff flag True from adminsite or Django Shell.
Update(from comments...)
You can use custom Backend for authenticating facebook user:
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
class CustomModelbackend(ModelBackend):
def authenticate(self, phone_no=None, password=None, facebook_id=None, **kwargs):
# Override
UserModel = User
try:
user = UserModel.objects.get(Q(phone_no=phone_no) | Q(facebook_id=facebook_id))
if password and user.check_password(password):
return user
elif facebook_id:
return user
except Exception as e:
# log exception
and add it to AUTHENTICATION_BACKENDS in settings.py:
AUTHENTICATION_BACKENDS = ['path.to.custom_backend.CustomBackend']
and call authenticate method like this:
authenticate(phone_no=phone_number, password=password) # for normal auth
authenticate(facebook_id=facebook_id) # for social auth
But you can't make your phone_no unique as there is chance of the phone_no being empty for social login, but you can put it in USERNAME_FIELD. So you will see warnings when you run django developer server(runserver command)
Update (2)
You can try like this:
NORMAL_USER = "N"
OAUTH_USER = "O"
AT_USER = "A"
USER_TYPES=(
(NORMAL_USER, 'Normal User'),
(OAUTH_USER, 'Oauth User'),
(AT_USER, 'Account Toolkit User')
)
class User(...):
username = models.CharField(max_length=255, default=uuid.uuid4, unique=True) # it might be unnecessary
account_type = models.CharField(max_length=2, choices = USER_TYPES, default=NORMAL_USER)
identifier = models.CharField(max_length=255)
...
class Meta:
unique_togather = ('account_type', 'identifier',)
# for Oauth Creation
User.objects.create(account_type=OAUTH_USER, identifier=facebook_id) # or email
# for Toolkit Creation
User.objects.create(account_type=AT_USER, identifier=phone_number)
# For normal User
User.objects.create(identifier=username, username=username)
#backend, and does not need to use ModelBackend, it will work with adminsite
class CustomBackend(...):
def authenticate(self, **kwargs):
try:
identifier= kwargs.get('username')
password=kwargs.get('password')
account_type=kwargs.get('account_type', NORMAL_USER)
user = User.objects.get(identifier=identifier, account_type=account_type)
if user.check_password(password):
return user
except Exception:
# log exception
# authentication
from django.contrib.auth import authenticate
user = authenticate(username=identifier, password=password, account_type=OAUTH_USER)
Related
My app has Users that can be Doctors/Patients/Secretaries. To create a Doctor, therefore, I perform two POST requests: one for the creation of a User and one for a Doctor. The way I do this, the User has to be created first so that I can later create the Doctor (Doctor requires a 'User' field). I am using Django Rest Framework to create the API.
class User(AbstractUser):
# defined roles so when I retrieve user, I know to perform a
# request to api/doctors/ or api/secretaries/ etc depending on role.
ROLES = (
('d', 'Doctor'),
('s', 'Secretary'),
('p', 'Patient'),
)
role = models.CharField(
max_length=1, choices=ROLES, blank=True, default='p', help_text='Role')
class Doctor(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
national_id = models.CharField(max_length=32, blank=False)
...
Since I'm new to Django, I don't know if two requests is the standard/best way of creating this User/Doctor.
This comes to mind as I am also thinking of the GET methods which will be performed later on (two GET requests when a Doctor logs in if I want to retrieve all of their info (User/Doctor)?)
I read about subclassing, which would be something like Doctor(User), then the only necessary request would be a single POST to create a Doctor (which would alongside create the User). I am, however, skeptical of subclassing the User as I read at least 3 SO answers stating it could cause problems in the future.
have a look at this good tutorial https://simpleisbetterthancomplex.com/tutorial/2018/01/18/how-to-implement-multiple-user-types-with-django.html which explain 2 different approches
extend AbstractUser with flags is_doctor, is_secretary, is_patient
class User(AbstractUser):
is_doctor = models.BooleanField('Doctor status', default=False)
is_secretary = models.BooleanField('Secretary status', default=False)
is_patient = models.BooleanField('Patient status', default=False)
using roles which suites your case:
class Role(models.Model):
'''
The Role entries are managed by the system,
automatically created via a Django data migration.
'''
ROLE_CHOICES = (
('d', 'Doctor'),
('s', 'Secretary'),
('p', 'Patient'),
)
id = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, primary_key=True)
def __str__(self):
return self.get_id_display()
class User(AbstractUser):
roles = models.ManyToManyField(Role)
Problem
Determine best practices for handling multiple user types and adding attributes to a user in Django.
Solution
The following is are recommendations based on design patterns that have been commonly used in Django since version 0.96. These recommendations are present in Django’s documentation (see: References).
Roles
Use Django’s built in permissions module for role and group management instead of rolling your own role and group management.
Instead of a model for each type, create a single UserProfile model, relegating user types to being managed by permissions model.
I recommend using a data migration to add groups so that default groups are automatically seeded on initial migrate call—this reduces overhead for anyone setting up your project for the first time.
OneToOne
Use Django OneToOne field instead of ForeignKey.
UserProfile and Signals
Create a signal that creates a UserProfile on User create.
Example
class UserProfile(models.Model):
user = models.OneToOne(“User”, on_delete=models.CASCADE)
national_id = models.CharField(max_length=32,
def create_profile(sender, **kwargs):
user = kwargs["instance"]
if kwargs["created"]:
user_profile = UserProfile(user=user)
user_profile.save()
post_save.connect(create_profile, sender=User)
References
Django permissions (groups): https://docs.djangoproject.com/en/3.1/topics/auth/default/#groups
Django Data Migrations: https://docs.djangoproject.com/en/3.1/topics/migrations/#data-migrations
Django extending User model recommendation: https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#extending-django-s-default-user
Django post_save signal: https://docs.djangoproject.com/en/3.1/ref/signals/#post-save
in your case (the model you made) you can create the doctor and the user in one post request to the doctor creation by overriding the create function for the
from rest_framework.generics import CreateAPIView, ListAPIView
class CreateDoctorViewSet(CreateAPIView, ListAPIView):
def create(self, request, *args, **kwargs):
data = self.request.data
user_dict_keys = ["username", "email", "first_name", "last_name"]
user_dict = {key: data.pop(key, None) for key in user_dict_keys}
user_dict['role'] = "d"
user_serializer = UserSerializer(data=user_dict)
# if it's not valid it will return the exception details for the requester
user_serializer.is_valid(raise_exception=True)
user = user_serializer.create(user_dict)
user.set_password(data['password'])
data.pop("password", None)
user.save()
response = super().create(request, *args, **kwargs)
if response.statu_code == 201:
return response
# if an error happened while in the doctor model (model error or serializer error) >> delete the created user
user.delete()
return response
or made a little more DRY :
from rest_framework.generics import CreateAPIView, ListAPIView
def create_user(self):
data = self.request.data
user_dict_keys = ["username", "email", "first_name", "last_name"]
user_dict = {key: data.pop(key, None) for key in user_dict_keys}
user_dict['role'] = "d"
user_serializer = UserSerializer(data=user_dict)
# if it's not valid it will return the exception details for the requester
user_serializer.is_valid(raise_exception=True)
user = user_serializer.create(user_dict)
user.set_password(data['password'])
data.pop("password", None)
user.save()
return user
class CreateDoctorViewSet(CreateAPIView, ListAPIView):
def create(self, request, *args, **kwargs):
user = create_user(request)
response = super().create(request, *args, **kwargs)
if response.statu_code == 201:
return response
# if an error happened while in the doctor model (model error or serializer error) >> delete the created user
user.delete()
return response
Personal Advices:
in the case you're providing the User model has one role so it's better to make the user field in the Doctor class OneToOne instead of ForeignKey.
Of course if you have cases where there are people for example converting from Doctor to Secretary and you want them to switch between roles on the same account you can keep the ForeignKey on Doctor model but you have to make multiple roles possible in the user model.
What you mean by subclassing it is called Multi table inheritance. And there is no problem in using it, no side effects and it is perfectly compatible with Django Rest Framework (which you have tagged). This is the way it works:
class User(AbstractUser):
# Your common fields for all user types.
class Doctor(User):
national_id = models.CharField(max_length=32, blank=False)
class Secretary(User):
# Your specific fields for secretary model
class Patient(User):
# Your specifict fields for patient model
In background, it uses a OneToOne relationship for each subtype.
Advantajes of using Multi table inheritance:
It is simple and elegant: you don't have to take care of different tables, queries, etc; Django does it for you.
It also unsures a good and formalized structure of your database: different tables for common and specific data in OneToOne relationship.
It is suitable for your needs? That depends.
If each subtype has its own specific fields -> use multi table inheritance without doubt.
If each subtype has the same set of fields but different behaviours (different class/model methods, code, etc) -> use proxy models.
If all the subtypes have the same set of fields and the same behaviour (same class/model methods, code, etc) -> use role based approach (one field identifying the role).
(extra) If you have dozens of subtypes, each one of them has different fields and you don't care too much about database formalization -> Don't use Multi table inheritance. In this case, you can use a mix of role based approach with JSON fields (for storing all the specifict fields) and proxy models (for handling different behaviours)**.
I am just starting to play around with Django REST Framework for a side project, and I am trying to force all usernames to be lowercase (since by default they are case sensitive and I don't want people to have usernames like kevin and Kevin for example).
This is the code I am using to create a user:
# The serializer
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
token = serializers.SerializerMethodField()
def create(self, validated_data):
user = get_user_model().objects.create(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
def get_token(self, user):
token = Token.objects.create(user=user)
return token.key
class Meta:
model = get_user_model()
fields = ('username', 'first_name', 'last_name', 'email', 'password', 'token')
# The View
class RegisterUserView(CreateAPIView):
model = User
serializer_class = UserSerializer
permission_classes = (permissions.AllowAny, )
# And the URL pattern
urlpatterns = [
path(r'user/register/', RegisterUserView.as_view()),
]
So the user can create a new account by posting at least a username and password to user/register, and in the response he'll get the full user object (with first name, last name, email and the auth token). This works.
However, I am struggling with forcing lowercase usernames. For example when I add something like validated_data['username'] = validated_data['username'].lower() to the create function in the serializer, the server simply generates an error 500 "UNIQUE constraint failed: auth_user.username" when trying to create a user with the same username (but different case). That's of course not ideal, that error 500.
I've found some suggestions to add a custom user manager, something like this:
lass MyUserManager(BaseUserManager):
def get_by_natural_key(self, username):
return self.get(username__iexact=username)
But that didn't do anything either, after hooking it up via a custom user class and adding to the settings file. I could still create users with capitals in their username.
What's the simplest solution to get this to simply work?
If you're using a custom user model and want this enforced throughout Django, you can create a custom validator. For example, create a validators.py as a sibling to the models.py which contains your custom user model:
from django.core import validators
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
#deconstructible
class MyUsernameValidator(validators.RegexValidator):
"""
Validator for usernames.
- Only ASCII lowercase letters, numbers and underscore are supported.
- Must start with a letter.
"""
regex = r'^[a-z][a-z0-9_]+$'
message = _(
'Enter a valid username. This value may contain only lowercase ASCII letters, '
'numbers, and underscores. Must start with a letter.'
)
flags = 0
Then in your custom user model, include:
from .validators import MyUsernameValidator
...
class User(AbstractBaseUser, PermissionsMixin):
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = [
'first_name', 'last_name', 'email',
]
username = models.CharField(
max_length=16,
unique=True,
help_text=_(
'Required. 16 characters or fewer. Lowercase letters, digits _ only; must start with a letter.'
),
validators=[MyUsernameValidator()],
error_messages={
'unique': _("A user with that username already exists."),
},
)
There are some slightly simpler methods, but this will enforce it through Django, as long as you stick to the ORM. Good luck!
Probably the reason you are getting "UNIQUE constraint failed: auth_user.username" error is because username validation is run on unprocessed version of the username (i.e with uppercase letters), but when inserting you are trying to create a user with username converted to lowercase. To exemplify, let's say you have a user in the db with the following username:
my_username
Then you are trying to create a user with the following username:
My_Username
This passes validation because there is no user in the db with username "My_Username", but while creating, you are trying to create a user with username "my_username", which exists in the database, so you are getting an IntegrityError.
There are many ways to solve this problem, you can modify the username before passing it to the serializer, so in the serializer context username will always be lowercase. You can also use a custom ModelManager as you tried already, but you'd need to convert the uesrname to lowercase in the manager before saving. Either way, you need to validate your data with the correct version of the username, to do this you can add a validation to your serializer like this:
class UserSerializer(serializers.ModelSerializer):
...
validate_username(self, value):
try:
get_user_model().objects.get(username=value.lower())
except ObjectDoesNotExist:
# Good to go
pass
else:
# Username in use
raise serializers.ValidationError(...)
return value
I have researched a bit in the google and found out many different solutions for handling multiple user type. To make my question more clear, here is the requirement
1) There is two types of user - (one is general and another is developer)
2) Both user types will have same form field - (username, email, password, password(confirm))
The below is what I have tried.
class customUser(AbstractBaseUser):
user_choice = (
('G', 'General'),
('D', 'Developer'),
)
user_type = models.charField(choices=user_choices, max_length=2, default='G')
class mainUser(customUser):
type = models.OneToOneField('customUser')
Do I have to handle explicitly for username, email, password and re-password field?
I am using django allauth for single user registration and login. Do I have to now work it differently?
If all you need is different logic in views, I'd do what Jens suggests in the comments. Extend the base User model with a Profile, something like this:
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
user_choice = (
('G', 'General'),
('D', 'Developer'),
)
user_type = models.charField(choices=user_choices, max_length=2, default='G')
Then either add a way to select user_type at signup, or just provide 2 different views to signup.
And in your views, where you need logic that depends on account type:
def my_view(request):
if request.user.profile.user_type == 'G':
# do stuff for General users
else:
# do Developer stuff
I'm trying to use a custom user model in my Django project. After running migrations, the database table is named accounts_listuser while Django appears to be looking for them in accounts_user. How can I change this so Django looks at the correct table (accounts_user) for the user model instead of accounts_listuser?
In my settings.py:
# Auth user models
AUTH_USER_MODEL = 'accounts.User'
AUTHENTICATION_BACKENDS = (
'accounts.authentication.PersonaAuthenticationBackend'
)
The accounts/models.py file:
class User(models.Model):
email = models.EmailField(primary_key=True)
last_login = models.DateTimeField(default=timezone.now)
REQUIRED_FIELDS = ()
USERNAME_FIELD = 'email'
is_authenticated = True
is_anonymous = False
And the file (accounts/authentication.py) where the query fails:
from django.contrib.auth import get_user_model
User = get_user_model()
# .. user's email retrieved ..
def get_user(self, email):
try:
return User.objects.get(email=email)
except User.DoesNotExist:
return None
The full source code can be found here if it helps.
Not sure what you mean by correct table in this case, but this behavior sounds about right since your model is called user and is declared in an app named accounts.
You can overwrite the auto-generated table name using class Meta attribute db_table: https://docs.djangoproject.com/en/1.10/ref/models/options/#db-table
Also just a note, the django docs recommend that your custom user model inherits from AbstractBaseUser, otherwise you will be responsible for implementing quite a few things on your own. https://docs.djangoproject.com/en/1.10/topics/auth/customizing/#specifying-a-custom-user-model
I've created UserProfile model in my application:
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True,
verbose_name=_('user'),
related_name='user_profile')
locality = models.CharField(max_length=85)
voivodship = models.CharField(max_length=20,
choices=Vovoidship.choices,
validators=[Vovoidship.validator])
postcode = models.IntegerField()
street = models.CharField(max_length=75)
building = models.SmallIntegerField()
premises = models.CharField(max_length=80)
phonenumber = PhoneNumberField(blank=True)
#staticmethod
def post_save_create(sender, instance, created, **kwargs):
if created:
profile, created = UserProfile.objects.get_or_create(user=instance)
post_save.connect(UserProfile.post_save_create, sender=User)
Now, I felt into my own trap. I don't want to loose constraints and keep the requirement in the database that address fields need to be filled up. I'm using django-allauth. While using the setting ACCOUNT_SIGNUP_FORM_CLASS = 'management.forms.SignupForm' solves the problem for traditional sign up form, if the user logs in first time using the social account I got hit by constraint violation for obvious reasons:
IntegrityError at /accounts/google/login/callback/
null value in column "postcode" violates not-null constraint
DETAIL: Failing row contains (4, , , null, , null, , ).
Hence the question, how to correctly implement the request for filling up the information for fields in the application UserProfile? I'm surprised that django-allauth doesn't have a build in handler for that the same way as ACCOUNT_SIGNUP_FORM_CLASS is done.
As I'm new to Django please assume I rather don't know something than it should be obvious. Thanks.
I think you need to:
1.- Create your custom Signup Class, for you to do the additional work
class SignupForm(forms.Form):
locality = forms.CharField(max_length=85)
voivodship = forms.CharField(max_length=20)
postcode = forms.IntegerField()
etc.
def signup(self, request, user):
# I think the profile will exist at this point based on
# your post_save_create. But if not, just create it here
if user.user_profile:
user.user_profile.locality = self.cleaned_data['locality']
user.user_profile.voivodship = self.cleaned_data['voivodship']
user.user_profile.postcode = self.cleaned_data['postcode']
...
user.user_profile.save()
2.- Set ACCOUNT_SIGNUP_FORM_CLASS = 'yourproject.yourapp.forms.SignupForm' to have allauth use your form
3.- Set SOCIALACCOUNT_AUTO_SIGNUP=False to ensure the form is presented even with social signup.
With some credits to davka I've managed to form a working solution which required creating UserProfile object inside signup() method of the SignupForm class, but because of database/model constrains it has be be filled with data during creation. The result:
class SignupForm(ModelForm):
first_name = CharField()
last_name = CharField()
class Meta:
model = UserProfile
exclude = ['user', 'phonenumber']
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
profile, created = UserProfile.objects.get_or_create(
user=user, defaults={
'locality': self.cleaned_data['locality'],
'voivodship': self.cleaned_data['voivodship'],
'postcode': self.cleaned_data['postcode'],
'street': self.cleaned_data['street'],
'building': self.cleaned_data['building'],
'premises': self.cleaned_data['premises'],
})
if created: # This prevents saving if profile already exist
profile.save()
The solution doesn't totally fit into DRY principle, but shows the idea. Going further it could probably iterate over results matching model fields.
Two elements need to be set correctly in settings.py:
ACCOUNT_SIGNUP_FORM_CLASS = 'yourapp.forms.SignupForm' to enable this form in allauth
SOCIALACCOUNT_AUTO_SIGNUP = False this - contrary to the intuition - let the allauth display the form before finishing the signup if the user selected social sign in but don't have an account; it works safely if the account already exists (username and/or e-mail address depending on other settings) as just does't allow to finish registration (why they call it sign up?) and the user is forced to log in and link social account.