username is saving but information such as first_name, email and etc are not.
`from django.contrib.auth.models import User
from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers
class RegisterSerializer(serializers.ModelSerializer):
email = serializers.CharField(required=True)
first_name = serializers.CharField(max_length=50, required=True)
last_name = serializers.CharField(max_length=50, required=True)
password = serializers.CharField(
write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True)
is_admin = serializers.BooleanField(default=False)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email',
'password', 'password2', 'is_admin')
def validate(self, attrs):
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError(
{"password": "Password fields didn't match."})
return attrs
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user`
i have searched online for hours, but have not managed to make much progress. if someone could elaborate on my issue and explain what I have done wrong that would be greatly appreciated
Add all the fields that you need to create that exist in model inside create method
user = User.objects.create(
username=validated_data['username'],
first_name =validated_data['first_name'],
last_name =validated_data['last_name'],
# Add other fields here
)
You should also send other validated data's to creation line:
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
first_name=validated_data['first_name'], # <-- add here to all necessary parameters like this
)
user.set_password(validated_data['password'])
user.save()
Related
I have been trying to add attributes to the UserProfile by creating a OneToOneField to User and adding different fields. Now I run this and call the api with the body below. The api is able to successfully get parsed. A user gets created in the user table and user profile table with the correct attributes. However, Django returns an error AttributeError: Got AttributeError when attempting to get a value for field first_name on serializer UserProfileSerializer. This makes sense since the model does not have these attributes, but what is the correct way to pass the json in the same manner and create the user in the User Table and UserProfile Table?
{
"first_name": "Jay",
"last_name" : "Patel",
"email": "tes1t#email.com",
"password": "password",
"tier": "Gold",
"bkms_id": "12234"
}
model.py
# Create your models here.
class UserProfile(models.Model):
MedalType: List[Tuple[str, str]] = [('Bronze', 'Bronze'), ('Silver', 'Silver'), ('Gold', 'Gold')]
bkms_id = models.PositiveIntegerField()
tier = models.CharField(choices=MedalType, max_length=100)
user = models.OneToOneField(to=User, on_delete=models.CASCADE)
def __str__(self):
return self.user.username
serializer.py
from typing import Dict
from django.contrib.auth.models import User
from rest_framework import serializers
from authentication.models import UserProfile
class UserProfileSerializer(serializers.ModelSerializer):
password = serializers.CharField(max_length=65, min_length=8, write_only=True, required=True,
style={'input_type': 'password'})
email = serializers.EmailField(max_length=255, min_length=4, required=True)
first_name = serializers.CharField(max_length=255, min_length=2, required=True)
last_name = serializers.CharField(max_length=255, min_length=2, required=True)
bkms_id = serializers.IntegerField(required=True, min_value=0)
tier = serializers.CharField(required=True)
class Meta:
model = UserProfile
fields = ['first_name', 'last_name', 'email', 'password', 'bkms_id', 'tier']
def validate(self, attrs):
if User.objects.filter(email=attrs.get('email', '')).exists():
raise serializers.ValidationError({'email': 'Email is already in use'})
if UserProfile.objects.filter(bkms_id=attrs.get('bkms_id', '')).exists():
raise serializers.ValidationError({'bkms_id': 'BKMS is already in use'})
return super().validate(attrs)
def create(self, validated_data: Dict):
email = validated_data.pop('email', '')
password = validated_data.pop('password', '')
first_name = validated_data.pop('first_name', '')
last_name = validated_data.pop('last_name', '')
user = User.objects.create_user(email, email, password, first_name=first_name, last_name=last_name)
user.save()
validated_data['user'] = user
return UserProfile.objects.create(**validated_data)
views.py
# Create your views here.
from rest_framework import status
from rest_framework.generics import GenericAPIView
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from authentication.serializers import UserProfileSerializer
class RegisterView(GenericAPIView):
serializer_class = UserProfileSerializer
permission_classes = [AllowAny]
def post(self, request):
serializer = UserProfileSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I would suggest creating different serializers like this:
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(max_length=65, min_length=8, write_only=True, required=True,
style={'input_type': 'password'})
email = serializers.EmailField(max_length=255, min_length=4, required=True)
first_name = serializers.CharField(max_length=255, min_length=2, required=True)
last_name = serializers.CharField(max_length=255, min_length=2, required=True)
class Meta:
model = User
fields = ['first_name', 'last_name', 'email', 'password']
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
bkms_id = serializers.IntegerField(required=True, min_value=0)
tier = serializers.CharField(required=True)
class Meta:
model = UserProfile
fields = ['user', 'bkms_id', 'tier']
def validate(self, attrs):
if User.objects.filter(email=attrs.get('email', '')).exists():
raise serializers.ValidationError({'email': 'Email is already in use'})
if UserProfile.objects.filter(bkms_id=attrs.get('bkms_id', '')).exists():
raise serializers.ValidationError({'bkms_id': 'BKMS is already in use'})
attrs['user'] = {
'email': attrs.get('email', ''),
'first_name': attrs.get('first_name', ''),
'last_name': attrs.get('last_name', ''),
'password': attrs.get('password', '')
}
return super().validate(attrs)
def create(self, validated_data: Dict):
validated_user = validated_data.pop('user', {})
email = validated_user.pop('email', '')
password = validated_user.pop('password', '')
first_name = validated_user.pop('first_name', '')
last_name = validated_user.pop('last_name', '')
user = User.objects.create_user(email=email, password=password, first_name=first_name, last_name=last_name)
user.save()
validated_data['user'] = user
return UserProfile.objects.create(**validated_data)
For a Django project, I have a customized User model:
class User(AbstractUser):
username = None
email = models.EmailField(_('e-mail address'),
unique=True)
first_name = models.CharField(_('first name'),
max_length=150,
blank=False)
last_name = models.CharField(_('last name'),
max_length=150,
blank=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
objects = UserManager()
def __str__(self):
return self.email
I'm creating a new user registration form:
class UserRegistrationForm(forms.ModelForm):
auto_password = forms.BooleanField(label=_('Generate password and send by mail'),
required=False,
initial=True)
password = forms.CharField(label=_('Password'),
widget=forms.PasswordInput)
password2 = forms.CharField(label=_('Repeat password'),
widget=forms.PasswordInput)
class Meta:
model = User
fields = ('email', 'first_name', 'last_name', 'is_staff',
'is_superuser')
def clean_password2(self):
cd = self.cleaned_data
if cd['password'] != cd['password2']:
raise forms.ValidationError(_("Passwords don't match."))
return cd['password2']
My form has a auto_password boolean field. When this checkbox is set, the password and password2 fields must not be checked, as their content (or the absence of content) has no importance. On the opposite, when the auto_password checkbox is unset, the password and password2 must be checked.
Is there a way to optionnally disable the Django form checks whenever needed?
Thanks for the help.
You add this to the the condition in the clean method:
class UserRegistrationForm(forms.ModelForm):
auto_password = forms.BooleanField(
label=_('Generate password and send by mail'),
required=False,
initial=True
)
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput
)
password2 = forms.CharField(
label=_('Repeat password'),
widget=forms.PasswordInput
)
class Meta:
model = User
fields = ('email', 'first_name', 'last_name', 'is_staff',
'is_superuser')
def clean(self):
data = super().clean()
if not data['auto_password'] and data['password'] != data['password2']:
raise forms.ValidationError(_('Passwords don't match.'))
return data
The not data['auto_password'] will thus return False in case the checkbox is checked, and in that case the the check of data['password'] != data['password2'] will not run, nor will it raise a ValidationError.
You can also remove the required=True properties, and check if the password contains at least one character by checking it truthiness:
class UserRegistrationForm(forms.ModelForm):
auto_password = forms.BooleanField(
label=_('Generate password and send by mail'),
# no required=True
initial=True
)
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput
)
password2 = forms.CharField(
label=_('Repeat password'),
widget=forms.PasswordInput
)
class Meta:
model = User
fields = ('email', 'first_name', 'last_name', 'is_staff',
'is_superuser')
def clean(self):
data = super().clean()
manual = not data['auto_password']
if manual and not data['password']:
raise forms.ValidationError(_('Password is empty.'))
if manual and data['password'] != data['password2']:
raise forms.ValidationError(_('Passwords don't match.'))
return data
Can't you just include it in your logic?
if not cd['auto_password'] and (cd['password'] != cd['password2']):
raise forms.ValidationError(_("Passwords don't match."))
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 am trying to allow users the ability to update their profile, but can't seem to figure out how to only raise an error if the 2 fields username, email were modified, or if the user is not that user. As of now, I can't save the updates as the error is continuously popping up since the user has those values obviously. I've also tried excludes but couldn't get it to work right either. Here is my code:
forms.py
class UpdateUserProfile(forms.ModelForm):
first_name = forms.CharField(
required=True,
label='First Name',
max_length=32,
)
last_name = forms.CharField(
required=True,
label='Last Name',
max_length=32,
)
email = forms.EmailField(
required=True,
label='Email (You will login with this)',
max_length=32,
)
username = forms.CharField(
required = True,
label = 'Display Name',
max_length = 32,
)
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name')
def clean_email(self):
email = self.cleaned_data.get('email')
username = self.cleaned_data.get('username')
if (User.objects.filter(username=username).exists() or User.objects.filter(email=email).exists()):
raise forms.ValidationError('This email address is already in use.'
'Please supply a different email address.')
return email
def save(self, commit=True):
user = super().save(commit=False)
user.email = self.cleaned_data['email']
user.username = self.cleaned_data['username']
if commit:
user.save()
return user, user.username
views.py
def update_user_profile(request, username):
args = {}
if request.method == 'POST':
form = UpdateUserProfile(request.POST, instance=request.user)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('user-profile', kwargs={'username': form.save()[1]}))
else:
form = UpdateUserProfile(instance=request.user)
args['form'] = form
return render(request, 'storytime/update_user_profile.html', args)
Just check if another user exists by excluding the current one:
from django.db.models import Q
class UpdateUserProfile(forms.ModelForm):
# ...
def clean_email(self):
# ...
if User.objects.filter(
Q(username=username)|Q(email=email)
).exclude(pk=self.instance.pk).exists():
raise ...
# for checking if both were modified
if self.instance.email != email and self.instance.username != username:
raise ...
One could further argue that this code belongs in the form's clean method as it validates field interdependencies.
So I finally managed to add a location field to my User model and this is the code I have:
model:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
class UserProfile(models.Model):
user = models.OneToOneField(User)
location = models.CharField(('location'),max_length=30, blank=False)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
and admin.py:
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django import forms
from UserProfile.models import UserProfile
from django.contrib.admin.views.main import *
class MyUserCreationForm(UserCreationForm):
username = forms.RegexField(label=("Username"), max_length=30, regex=r'^[\w.#+-]+$', help_text = ("Required. 30 characters or fewer. Letters, digits and #/./+/-/_ only."),error_messages = {'invalid': ("This value may contain only letters, numbers and #/./+/-/_ characters.")})
password1 = forms.CharField(label=("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(label=("Password confirmation"), widget=forms.PasswordInput, help_text = ("Enter the same password as above, for verification."))
email = forms.EmailField(label=("Email address"))
class Meta:
model = User
fields = ("username",)
def clean_username(self):
username = self.cleaned_data["username"]
try:
User.objects.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError(("A user with that username already exists."))
def clean_email(self):
email = self.cleaned_data["email"]
if email == "":
raise forms.ValidationError((""))
return email
def clean_password2(self):
password1 = self.cleaned_data.get("password1", "")
password2 = self.cleaned_data["password2"]
if password1 != password2:
raise forms.ValidationError(("The two password fields didn't match."))
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class MyUserChangeForm(UserChangeForm):
username = forms.RegexField(label=("Username"), max_length=30, regex=r'^[\w.#+-]+$',
help_text = ("Required. 30 characters or fewer. Letters, digits and #/./+/-/_ only."),
error_messages = {'invalid': ("This value may contain only letters, numbers and #/./+/-/_ characters.")})
location = forms.CharField(label=("Location"),max_length=30)
class Meta:
model = User
def __init__(self, *args, **kwargs):
super(UserChangeForm, self).__init__(*args, **kwargs)
f = self.fields.get('user_permissions', None)
if f is not None:
f.queryset = f.queryset.select_related('content_type')
class CustomUserAdmin(UserAdmin):
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'email', 'password1', 'password2')}
),
)
fieldsets = (
(None, {'fields': ('username', 'password')}),
(('Personal info'), {'fields': ('first_name', 'last_name', 'email', 'location')}),
(('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'user_permissions')}),
(('Important dates'), {'fields': ('last_login', 'date_joined')}),
(('Groups'), {'fields': ('groups',)}),
)
#list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff', 'location')
add_form = MyUserCreationForm
form = MyUserChangeForm
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
When I tried creating a user it finally didn't error. But when I went to edit the information about the user, the location field was empty. I checked the database manually using sqlite database browser. And I assumed by creating a UserProfile would add extra tables to the database, but it didn't. What I want is for the location field to be trated just like the email field or the username or any other information stored about the user.
You don't add fields to the old model this way, you're creating a new one that's associated with it. Your data will be in app_userprofile table, and you can access that instance (to save that location or whatever) via user.get_profile(). You can't use straightforward ModelForm for that, I'm afraid, you need to handle the profile yourself.