I'm trying to create a custom user using the Django Rest Framework. I got it to the point to where I can create a regular user, but I'm unsure on how to extend it to the custom user model.
models.py:
from django.db import models
from django.contrib.postgres.fields import ArrayField
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
languages = ArrayField(models.CharField(max_length=30, blank=True))
serializers.py:
from rest_framework import serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ('first_name', 'last_name', 'email', 'username', 'password')
def create(self, validated_data, instance=None):
user = super(UserSerializer, self).create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
views.py:
#api_view(['POST'])
#permission_classes((AllowAny,))
def create_user(request):
serialized = UserSerializer(data=request.data)
if serialized.is_valid():
serialized.save()
return Response(serialized.data, status=status.HTTP_201_CREATED)
else:
return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)
How do I extend the languages field to the UserSerializer to where it can process it? Would it be better to create a signal for every time a user is created it then creates a userprofile automatically?
You can do it two ways, either by creating a profile Serializer or without it.
With Serializer,
class UserProfileSerializer(serializers.ModelSerializer):
languages = serializers.ListField(child=serializers.CharField(max_length=30, allow_blank=True))
class Meta:
model = UserProfile
fields = ('languages',)
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
userprofile = UserProfileSerializer(required=False)
class Meta:
model = User
fields = ('first_name', 'last_name', 'email', 'username', 'password', 'userprofile')
def create(self, validated_data, instance=None):
profile_data = validated_data.pop('userprofile')
user = User.objects.create(**validated_data)
user.set_password(validated_data['password'])
user.save()
UserProfile.objects.update_or_create(user=user,**profile_data)
return user
without a profile serializer
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
languages = serializers.ListField(child=serializers.CharField(max_length=30, allow_blank=True), source="userprofile.languages")
class Meta:
model = User
fields = ('first_name', 'last_name', 'email', 'username', 'password', 'lanugages')
def create(self, validated_data, instance=None):
profile_data = validated_data.pop('userprofile')
user = User.objects.create(**validated_data)
user.set_password(validated_data['password'])
user.save()
UserProfile.objects.update_or_create(user=user,**profile_data)
return user
Related
I'm trying to implement a registration mechanism that should have case insensitive query on username and email with class base view in a Django project.
I tried many methods but none of them worked. Anybody Has A Solution??
views.py:
class RegisterView(FormView):
template_name = 'users/user_register.html'
form_class = UserRegistrationForm
redirect_authenticated_user = True
success_url = reverse_lazy('index')
def form_valid(self, form):
user = form.save()
if user is not None:
login(self.request, user)
return super(RegisterView, self).form_valid(form)
def get(self, *args, **kwargs):
if self.request.user.is_authenticated:
return redirect('index')
return super(RegisterView, self).get(*args, **kwargs)
forms.py:
class UserRegistrationForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
You can use the clean_<field_name> method inside your form.
class UserRegistrationForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
def clean_username(self):
username = self.cleaned_data.get('username')
If User.objects.filter(username__iexact=username).exists():
raise forms.ValidationError('Username Already Exists')
return username
# For username, you have to create your own usermanager class and point it in your user model class. For email id, django uses the normalize_email function in BaseUserManager which always change the email ids in lowercase. You can customize that function in BaseUserManager, If you want.
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
class UserManager(BaseUserManager):
def get_by_natural_key(self, username):
return self.get(username__iexact=username)
class User(AbstractBaseUser):
objects = UserManager()
# other changes
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email')
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(validated_data['username'], validated_data['email'])
user.set_password(validated_data['password'])
user.save()
return user
views.py
class RegisterAPI(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
print("user = ",user)
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
# Create your views here.
class LoginAPI(KnoxLoginView):
permission_classes = (permissions.AllowAny,)
def post(self, request, format=None):
serializer = AuthTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
login(request, user)
return super(LoginAPI, self).post(request, format=None)
# Get User API
class UserAPI(generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated,]
serializer_class = UserSerializer
def get_object(self):
return self.request.user
The password is not getting encrypted in specified format, and hence the login API is also not working. It shows -
"non_field_errors": [
"Unable to log in with provided credentials."
]
Also, in the admin panel of django, it shows this invalid password format
Update your "RegisterSerializer" class "create" method.
def create(self, validated_data):
user = User(validated_data['username'], validated_data['email'])
user.password = make_password(validated_data['password'])
user.save()
return user
And don't forget to import make_password method.
from django.contrib.auth.hashers import make_password
And you create method is not properly indented.
Serializer.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email')
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(validated_data['username'], validated_data['email'])
user.set_password(validated_data['password'])
user.save()
return user
I am not able to create a User Registration page using Django where I have used a Profile model with the OnetoOne field with the default User model.
views.py
def SignUpView(request):
if request.method == 'POST':
user_form = SignUpForm(data=request.POST)
profile_form = ProfileForm(data=request.POST)
if user_form.is_valid() and profile_form.is_valid():
new_user = user_form.save(commit=False)
new_profile = profile_form.save(commit=False)
new_profile.user = new_user
userName = new_user.username
password = new_profile.password1
new_user.save(commit=True)
new_profile.save(commit=True)
user = authenticate(username = userName, password = password)
login(request, user)
return redirect('blog-home')
else:
user_form = SignUpForm()
profile_form = ProfileForm()
context = {
'user_form': user_form,
'profile_form': profile_form,
}
return render(request, 'user/signup.html', context=context)
forms.py:
class SignUpForm(UserCreationForm):
class meta:
model = User
fields = ['username', 'first_name', 'last_name', 'password1', 'password2']
class ProfileForm(forms.ModelForm):
class meta:
model = Profile
fields = ['user', 'email']
models.py:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
# img =
date_of_birth = models.DateField(blank=True, null=True)
bio = models.CharField(blank=True, max_length=500)
location = models.CharField(blank=True, max_length=50)
email = models.EmailField(unique=True, max_length=200)
def __str__(self):
return f'Profile for {self.user.username}'
It is displaying an error message on the signup page as :
ValueError at /signup/
ModelForm has no model class specified.
It could be because there's a typo in your meta class declaration.
Change it to - class Meta. Note the case.
Did you try this to get the User model?
from django.contrib.auth import get_user_model
User = get_user_model()
Change it to - class Meta
class SignUpForm(UserCreationForm):
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'password1', 'password2']
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['user', 'email']
If this isn't a form based on a model, don't inherit from forms.ModelForm, just use an ordinary forms.Form.
I am wrote a serializer for the User model in Django with DRF:
the model:
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import BaseUserManager
from django.db import models
from django.utils.translation import ugettext
class BaseModel(models.Model):
# all models should be inheritted from this model
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class User(AbstractBaseUser, BaseModel):
username = models.CharField(
ugettext('Username'), max_length=255,
db_index=True, unique=True
)
email = models.EmailField(
ugettext('Email'), max_length=255, db_index=True,
blank=True, null=True, unique=True
)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ('email', 'password',)
class Meta:
app_label = 'users'
the serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ['email', 'username', 'password']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = super().create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
def update(self, user, validated_data):
user = super().update(user, validated_data)
user.set_password(validated_data['password'])
user.save()
return user
It works. But I probably do two calls instead of one on every create/update and the code looks a little bit weird(not DRY).
Is there an idiomatic way to do that?
$python -V
Python 3.7.3
Django==2.2.3
djangorestframework==3.10.1
You can create your own user manager by overriding BaseUserManager and use set_password() method there. There is a full example in django's documentation. So your models.py will be:
# models.py
from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
class MyUserManager(BaseUserManager):
def create_user(self, email, username, password=None):
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
username=username,
)
user.set_password(password)
user.save(using=self._db)
return user
class BaseModel(models.Model):
# all models should be inheritted from this model
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class User(AbstractBaseUser, BaseModel):
username = models.CharField(
ugettext('Username'), max_length=255,
db_index=True, unique=True
)
email = models.EmailField(
ugettext('Email'), max_length=255, db_index=True,
blank=True, null=True, unique=True
)
# don't forget to set your custom manager
objects = MyUserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ('email', 'password',)
class Meta:
app_label = 'users'
Then, you can directly call create_user() in your serializer's create() method. You can also add a custom update method in your custom manager.
# serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ['email', 'username', 'password']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = models.User.objects.create_user(
username=validated_data['username'],
email=validated_data['email'],
password=validated_data['password']
)
return user
I hope this will solve the issue,
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ['email', 'username', 'password']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
return models.User.objects.create_user(**validated_data)
def update(self, user, validated_data):
password = validated_data.pop('password', None)
if password is not None:
user.set_password(password)
for field, value in validated_data.items():
setattr(user, field, value)
user.save()
return user
The create_user() method uses the set_password() method to set the hashable password.
I'm trying to create basic CRUD operations for OneToOne field.
The user is not required to set the profile when signing in. How do I create/update/delete profile when needed (assuming the user is already in the DB)?
My models are the default User models from Django REST and:
class UserProfile(models.Model):
user = models.OneToOneField(User)
location = models.CharField(max_length=50,blank=True)
title = models.CharField(max_length=80,blank=True)
#picture = models.ImageField(upload_to='user_imgs', blank=True)
website = models.URLField(blank=True)
My Viewsets are:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_fields = ['id', 'username', 'email', 'first_name', 'last_name']
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
filter_fields = ['user_id', 'location', 'title', 'website']
And serializes:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
email = serializers.EmailField()
fields = ('id','username', 'email', 'first_name', 'last_name')
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
user_id = serializers.CharField(source='user.id')
class Meta:
model = UserProfile
fields = ('user_id', 'location','title','website')
I belive you want to restrict the profile creation to the current logged in user. You can filter the queryset of profiles to the current user, this way only that user's profile will be accessible by the logged in user.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_fields = ['id', 'username', 'email', 'first_name', 'last_name']
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
filter_fields = ['user_id', 'location', 'title', 'website']
def get_queryset(self):
return super(UserProfileViewSet, self).get_queryset().filter(
user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=user)
You make the user field read only and is being saved in the above method perform_create and assigned always to the current user.
class UserProfileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UserProfile
fields = ('user', 'location','title','website')
read_only_fields = ('user',)
It should focus to define view how to receive request and process raw data, not the model and serializer of the field definition.
I give you a CRUD example for basic User operation as the reference:
lu = LibraryUser(library_membership_number= '...', user_id = user)
class ExampleAPIView(APIView):
def get(self, request):
username = request.query_params.get('username', '')
user = User.objects.get(username=username)
return Response(ExampleSerializer(user).data)
def post(self, request):
username = request.data.get('username', '')
email = request.data.get('email', '')
password = request.data.get('password', '')
user = User.objects.create_user(username=username, email=email, password=password)
user.save()
Response({'status': 'ok'}})
def put(self, request):
username = request.data.get('username', '')
old_password = request.data.get('old_password', '')
new_password = request.data.get('new_password', '')
user = authenticate(username=username, password=old_password)
if not user:
return Response({'status': 'fail'}})
user.set_password(new_password)
return Response({'status': 'ok'}})
def delete(self, request):
username = request.query_params.get('username', '')
user.objects.get(username=username).delete()
return Response({'status': 'ok'}})
Accord to the example, these are my definitions for each method:
GET: Retrieve the user profile
POST: Create a new user
PUT: Change the user of the password
DELETE: Delete the user
So, it will implement Basic CRUD api for user instance.
I hope that it can help you how to design api.
If you don't still understand how to operate model, I will more introduce the example:
class ExampleAPIView(APIView):
def get(self, request):
username = request.query_params.get('username', '')
userprofile = UserProfile.objects.get(user__username=username)
return Response(ExampleSerializer(userprofile).data)
def put(self, request):
username = request.data.get('username', '')
userprofile = UserProfile.objects.get(user__username=username)
if not userprofile :
return Response({'status': 'fail'}})
userprofile.location = ...
userprofile.title = ...
userprofile.website = ...
userprofile.save()
return Response({'status': 'ok'}})