DRF serializer update nested user - python

i'm using Django 1.11 and the Django Rest Framework in order to create an API, where a user can create and update an employee which is related to the django user model.
The stripped down code i've got looks like this:
I've got this model:
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='employee')
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
these two viewsets:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = serializers.UserSerializer
permission_classes = [permissions.IsAuthenticated]
and
class EmployeeViewSet(viewsets.ModelViewSet):
serializer_class = serializers.EmployeeSerializer
permission_classes = [permissions.IsAuthenticated]
and these two serializers:
class UserSerializer(serializers.HyperlinkedModelSerializer)
class Meta
models = User
fields = ('url', 'id', 'username', 'email', 'is_staff', 'first_name', 'last_name', 'password')
read_only_field = ('id',)
def validate(self, data)
# ... Check if data is valid and if so...
return data
and
class EmplyoeeSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Employee
field = ('url', 'uuid', 'user')
read_only_field = ('uuid')
def validate(self, data):
return data
def create(self, validated_data):
user = User(**validated_data['user'])
user.save()
employee = Employee(user=user)
employee.save()
return employee
def update(self, employee, user):
employee.user.username = user.username
employee.user.email = user.email
employee.user.first_name = user.first_name
employee.user.last_name = user.last_name
employee.user.is_staff = user.is_staff
# ... Check if password has changed, and if so...
employee.user.set_password(user.password)
employee.user.save()
employee.save()
return employee
also i've got these two routers in my urls.py
router = routers.DefaultRouter()
router.register(r'api/user', views.UserViewSet, base_name='user')
router.register(r'api/employee', views.UserViewSet, base_name='employee')
Creating and updating an instance of user is no problem.
I can also create an employee which in return will create an user and then an employee assigend to that user.
I can even update the username of the employee and the user will get updated too,
but i can't update first_name, last_name, email, is_staff and password.
DRF keeps telling me that the username is already taken, but when i change the username and other information like first_name and last_name and then send a PUT request to the server, the employee and associated user instance are getting updated properly.
What am i missing?
Why can't i update the employees user instance like i can update the normal user instance when i'm at the user api endpoint? :/
Thanks in advance,
any help would be appreciated.

Finally i found out what i was missing.
The UserSerializers adds django.contrib.auth.validators.UnicodeUsernameValidator and UniqueValidator to the field username which gets checked every time i do a put request.
Adding validators = [] to the Meta Class of UserSerializer solved my problem.

Related

i want to add custom field in django-allauth SignupForm

i wanted to add custom field with django-allauth SingupForm and adding new field like phone number. i already managed to add this field in Postgresql on my own(without migrations,but by my hands).
this is my postgresql screen
In my signup page i have these fields already but i can't managed to add "phone" to my database, i really want to make it! please someone help me.
forms.py
from allauth.account.forms import SignupForm
from django import forms
class CustomSignupForm(SignupForm):
first_name = forms.CharField(max_length=30, label='Voornaam')
last_name = forms.CharField(max_length=30, label='Achternaam')
phone = forms.CharField(max_length=30, label='phone')
def __init__(self, *args, **kwargs):
super(CustomSignupForm, self).__init__(*args, **kwargs)
self.fields['first_name'] = forms.CharField(required=True)
self.fields['last_name'] = forms.CharField(required=True)
self.fields['phone'] = forms.CharField(required=True)
def save(self, request):
user = super(CustomSignupForm, self).save(request)
user.phone = self.cleaned_data.get('phone')
user.save()
return user
def signup(self,request,user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()
return user
settings.py
ACCOUNT_FORMS = {'signup': 'registration.forms.CustomSignupForm'}
What you need is a profile model which is attached to a user so that you can add extra fields for information you might want.
If you're starting a project from the very beginning you can also consider a custom user model so that all data is on one object.
When I do this, I create an accounts app which I put my overrides in for allauth and my model starts something like this (modified to add the receiver function which I don't have at the moment because I'm not using Profile objects);
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import ugettext_lazy as _
User = get_user_model()
class Profile(models.Model):
"""
Profile model
"""
class Meta:
"""
Metadata
"""
app_label = 'accounts'
verbose_name = _('User profile')
verbose_name_plural = _('User profiles')
user = models.OneToOneField(
verbose_name=_('User'),
to=User,
related_name='profile',
on_delete=models.CASCADE
)
# Add your fields here like `phone`
def __str__(self):
"""
String representation
"""
return f'User Profile for: {self.user}'
#receiver(post_save, sender=User)
def create_profile(sender, instance, **kwargs):
"""
Setup a profile as a user is created
"""
Profile.objects.create(user=instance) # Using `create` also saves the object
Your signup form then does something like
def save(self, request):
user = super(CustomSignupForm, self).save(request)
user.profile.phone = self.cleaned_data.get('phone')
user.profile.save()
return user
If you've already got users you'll also need a migration which creates profiles for them;
# Generated by Django 2.2.12 on 2020-05-01 22:03
from django.db import migrations
def create_profiles(apps, schema_editor):
User = apps.get_model('authentication', 'User') # this should match the User model you are using
Profile = apps.get_model('accounts', 'Profile')
for user in User.objects.all():
profile = Profile.objects.create(user=user)
profile.save()
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.RunPython(create_profiles, migrations.RunPython.noop)
]

Get auto-generated field from model in serializer, without returning it

I have a serializer which is used to invite a new user. In the User Model which this serializer used, there is an autogenerated field for an activation key (which gets emailed to the user). What is the best way to cleanly get the activation key in the serializer, without adding it the the User object which will be returned in the response? For obvious reasons, I don't want the activation key being returned to the front end.
models.py:
class User(AbstractBaseUser):
...
activation_key = models.UUIDField(unique=True, default=uuid.uuid4)
...
serializers.py:
class UserInvitationSerializer(serializers.ModelSerializer):
email = serializers.EmailField()
class Meta:
model = User
fields = ('id', 'email', 'first_name', 'last_name')
def create(self, validated_data):
user = User.objects.create(**validated_data)
random_pass = User.objects.make_random_password(length=10)
user.set_password(random_pass)
user.save()
activation_key = ?
send_mail('Email Address Verification Request', 'confirm/email/(?P<activation_key>.*)/$', 'info#mydomain',[user.email])
return user
activation_key is automatically generated.
In create() you already have the User instance created so you can grab the activation key from that object:
class UserInvitationSerializer(serializers.ModelSerializer):
email = serializers.EmailField()
class Meta:
model = User
fields = ('id', 'email', 'first_name', 'last_name')
def create(self, validated_data):
user = User.objects.create(**validated_data)
random_pass = User.objects.make_random_password(length=10)
user.set_password(random_pass)
user.save()
activation_key = user.activation_key
send_mail('Email Address Verification Request', 'confirm/email/(?P<activation_key>.*)/$', 'info#mydomain',[user.email])
return user
Considering that you explictily specified in fields what this ModelSerializer should return, activation_key won't be displayed in the API response.

django admin add error to form field

I have the following code:
Model:
class MyUser(AbstractUser):
profile = models.OneToOneField(Profile, null=True, on_delete=models.PROTECT)
My profile model contains only some personal fields like full_name, gender, birthdate, etc.
Admin:
class MyUserInline(admin.StackedInline):
model = MyUser
fieldsets = .....
filter_horizontal = ....
class MyUserChangeForm(UserChangeForm):
class Meta(UserChangeForm.Meta):
model = MyUser
def clean_password(self):
return ""
class ProfileAdmin(admin.ModelAdmin):
inlines = (MyUserInline,)
change_user_password_template = None
form = MyUserChangeForm
change_password_form = AdminPasswordChangeForm
def save_model(self, request, obj, form, change):
if not Profile.objects.filter(myuser__username=obj.myuser.email).exclude(pk=obj.id).exists():
obj.myuser.username = obj.myuser.email
obj.save()
else:
# *MY PROBLEM IS HERE!*
What I am going to do is to delete username field from admin change user page and set their email as their username as well. In AbstractUser email is not unique but username is. The problem is when changing a user email, if email is duplicated it would raise exception, but I want to show error to say this email is duplicated. Is the only way to do that changing my MyUser model and add email as a unique field or I can do something else?
tnx

Django: authtools do create the user profile when the user is created

I am using the authtools plugin to manage the user and its profile in django, but when I create the user, it does not create its profile, I have to go to the admin part of the site and create it manually.
I separated the applications into account and profile.
This is the profiles model:
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True)
slug = models.UUIDField(default=uuid.uuid4, blank=True, editable=False)
email_verified = models.BooleanField("Email verified", default=True)
This is the signal.py, that is inside of the profiles application:
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_handler(sender, instance, created, **kwargs):
if not created:
return
profile = models.Profile(user=instance)
profile.save()
logger.info('New user profile for {} created'.format(instance))
This is the admin.py of the account app:
class UserProfileInline(admin.StackedInline):
model = Profile
class NewUserAdmin(NamedUserAdmin):
inlines = [UserProfileInline]
list_display = ('is_active', 'email', 'name', 'permalink',
'is_superuser', 'is_staff',)
# 'View on site' didn't work since the original User model needs to
# have get_absolute_url defined. So showing on the list display
# was a workaround.
def permalink(self, obj):
url = reverse("profiles:show",
kwargs={"slug": obj.profile.slug})
# Unicode hex b6 is the Pilcrow sign
return format_html('{}'.format(url, '\xb6'))
admin.site.unregister(User)
admin.site.register(User, NewUserAdmin)
admin.site.register(Profile)
When I signup a user, both the user and the profile objects are created, only they are not linked. Why is that?
Thank you
Use this below your profile model in models.py. I hope you are generating slug by another slugify signal.
def user_created_receiver(sender, instance, created, *args, **kwargs):
if created:
Profile.objects.get_or_create(user = instance)
post_save.connect(user_created_receiver, sender = User)

Django multiple users, backend and generic views

I'm trying to learn auth django system and I have several questions.
I want to create multiple users with email authentication. So, I used AbtractBaseUser and AbtractBaseManager. I read django doc and this question on the website : Implementing multiple user types with Django 1.5
and I would like to use it. So, I try to implement the second point : all fields for my two user types are in 1 models and with user_type variable I can choose fields to show. My code is down below :
models.py
# -*- coding: utf-8 -*-
from django.db import models
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
TYPE_USER = (
('student', _('Student')),
('employee', _('Employee')),
)
class MyuserManager(BaseUserManager):
def create_user(self, email, last_name, first_name, date_of_birth, user_type, password=None):
if not email:
raise ValueError("users must have an email address")
user = self.model(
email = self.normalize_email(email),
last_name = last_name,
first_name = first_name,
date_of_birth = date_of_birth,
user_type = user_type,
)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password, last_name, first_name, date_of_birth, user_type):
user = self.create_user(email,
password = password,
date_of_birth = date_of_birth,
last_name = last_name,
first_name = first_name,
user_type = user_type,
)
user.is_admin = True
user.is_staff = True
user.save()
return user
class MyUser(AbstractBaseUser):
"""Based model user"""
email = models.EmailField(
verbose_name = 'email',
max_length=255,
unique=True,
)
last_name = models.CharField(max_length=30)
first_name = models.CharField(max_length=30)
date_of_birth = models.DateField()
user_type = models.CharField(_('Type'), choices=TYPE_USER, max_length=20, default='student')
phone = models.CharField(max_length=20, blank=True)
friends = models.ManyToManyField("self", blank=True)
faculty = models.ForeignKey(Faculty, null=True, blank=True)
desk = models.CharField(max_length=30, blank=True)
campus = models.ForeignKey(Campus, null=True, blank=True)
job = models.ForeignKey(Function, null=True, blank=True)
cursus = models.ForeignKey(Cursus, null=True, blank=True)
year = models.IntegerField(null=True, blank=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
objects = MyuserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['last_name', 'first_name', 'date_of_birth', 'user_type']
Question : Do you think it's correct ? or should i create 1 class with common fields and 2 classes for distinct type of user ?
Next, i would like to use my own backend so i implement a backend.py file in my project
backend.py
from fbLike.models import MyUser
from django.contrib.auth.backends import ModelBackend
class EmailAuthBackend(ModelBackend):
def authenticate(self, email=None, password=None, **kwargs):
try:
user = MyUser.objects.get(email=email)
if user.check_password(password):
return user
except MyUser.DoesNotExist:
return None
def get_user(self, user_id):
try:
return MyUser.objects.get(pk=user_id)
except:
return None
For me, it seems that is a correct implementation.
Questions : But, how to proceed with multi-table inheritence ? is it possible ? how to know what is the type of user before check password if i have for example :
class BaseUser(AbstractBaseUser):
email = models.EmailField(max_length=254, unique=True)
# common fields
class Student(BaseUser):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
# ...
class employee(BaseUser):
company_name = models.CharField(max_length=100)
# ...
Finally, with my models.py, backend.py i would try to use the CRUD system in my views.py to create and update users.
Below my files :
forms.py
class StudentProfileForm(forms.ModelForm):
phone = forms.CharField(required=True)
faculty = forms.ModelChoiceField(Faculty.objects.all())
campus = forms.ModelChoiceField(Campus.objects.all())
cursus = forms.ModelChoiceField(Cursus.objects.all())
year = forms.IntegerField(required=True)
class Meta:
model = MyUser
fields = ('email', 'password', 'first_name', 'last_name',
'date_of_birth', 'phone', 'faculty', 'campus', 'cursus', 'year'
)
widgets = {
'password': forms.PasswordInput(render_value=True),
}
class EmployeeProfileForm(forms.ModelForm):
date_of_birth = forms.DateField(widget = AdminDateWidget)
phone = forms.CharField(required=True)
faculty = forms.ModelChoiceField(Faculty.objects.all())
campus = forms.ModelChoiceField(Campus.objects.all())
job = forms.ModelChoiceField(Function.objects.all())
desk = forms.IntegerField(required=True)
class Meta:
model = MyUser
fields = ('email', 'password', 'first_name', 'last_name',
'date_of_birth', 'phone', 'faculty', 'campus', 'job', 'desk'
)
widgets = {
'password': forms.PasswordInput(render_value=True),
}
Here, I think it's too complex but i don't know how to reduce complexity.
views.py
class RegisterProfile(CreateView):
model = MyUser
template_name = 'fbLike/user_profile.html'
form_class = StudentProfileForm
second_form_class = EmployeeProfileForm
def get_object(self):
return super(RegisterProfile, self).get_object()
def get_success_url(self):
return reverse('login',)
def get_context_data(self, **kwargs):
context = super(RegisterProfile, self).get_context_data(**kwargs)
if 'studenForm' not in context:
context['studentForm'] = self.form_class
if 'employeeForm' not in context:
context['employeeForm'] = self.second_form_class
return context
def form_valid(self, form):
self.object = MyUser.objects.create_user(**form)
return super(RegisterProfile, self).form_valid(form)
def form_invalid(self, form):
return super(RegisterProfile, self).form_invalid(form)
def post(self, request, *args, **kwargs):
self.object = None
if request.POST['profileType'] == 'student':
form = self.form_class(request.POST, prefix='st')
form.user_type = 'student'
else:
form = self.second_form_class(request.POST, prefix='em')
form.user_type = 'employee'
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
class UpdateProfile(UpdateView):
model = MyUser
template_name = 'fbLike/modify_profile.html'
def get_form_class(self):
if self.request.user.user_type == 'student':
return StudentProfileForm
return EmployeeProfileForm
def get_success_url(self):
return reverse('welcome',)
For CreateProfile, I would like to send 2 forms in my template and thanks to javscript, i can choose the correct form. But when i submit the form, i have an error with the user password. When i check my users in the admin page, the hashpassword system seems to fail.
So, my first attempt to solve : it was to override save method in my form to use the request.user.set_password but it doesn't work.
I know it's a long explanation. But everyone could give me an example of CreateView class with 2 forms please ? If it isn't possible to use a class but a function how to implement this function ?
I thank you in advance
I will attempt to answer your multiple questions, but please in the future - one question per post. I realize you are new, but StackOverflow is not a discussion forum, have a look at the help section (link at the bottom of each page) specifically the section on asking questions.
I want to create multiple users with email authentication. [...] Do
you think it's correct ? or should i create 1 class with common fields
and 2 classes for distinct type of user ?
Its correct if it works with you. These kinds of questions are better suited for codereview.stackexchange.com. Especially as there isn't a bug.
But, how to proceed with multi-table inheritence ? is it possible ?
how to know what is the type of user before check password if i have
for example [...]
First, authentication backends are different topic altogether; they don't have anything to do with multiple table inheritance (I think you mean relationships or multiple user profiles).
You only need multiple authentication backends if your password authentication algorithm is different for each user type. If all user passwords are stored in the same location/system/database, then you don't need multiple authentication backends.
You should implement a method in your base user class, that returns the type of user. You can then use user_passes_check decorator to restrict access in your views.
This way you will achieve what I think is what you are after "this section only for students", etc.
Finally, with my models.py, backend.py i would try to use the CRUD system in my views.py to create and update users [..] Here, I think it's too complex but i don't know how to reduce
complexity.
You don't need to repeat all the fields of your model in your ModelForm, you only need to override fields if you need to modify them, or to add extra "non-model" fields to the form. You can start with the simple ModelForm, like this:
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('name', 'email', 'password')
A common use of adding an additional field is to ask for password confirmation, by having an extra password field.
For CreateProfile, I would like to send 2 forms in my template and
thanks to javscript, i can choose the correct form. But when i submit
the form, i have an error with the user password. When i check my
users in the admin page, the hashpassword system seems to fail. So, my
first attempt to solve : it was to override save method in my form to
use the request.user.set_password but it doesn't work.
Use a form wizard to implement a multi-step registration process. The first step, you ask the basic information and account type to be created; on the second step, you only send the relevant form instead of sending two forms and then switching them around using javascript.
I don't see anywhere in your code where you use set_password, so can't comment on the second part.

Categories

Resources