I have a UserModel and a UserProfile Model and I would like to use FormWizard from the formtools to both register the user and setup a userprofile with necessary inputs from the user.
I am struggling with some concepts and trying to patch this together with no success so far.
I get the error message:
AttributeError at /accounts/register/
'MyRegistrationView' object has no attribute 'request'
forms.py
class UserForm(forms.ModelForm):
password1 = forms.CharField(widget=forms.PasswordInput())
password2 = forms.CharField(widget=forms.PasswordInput())
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2', 'first_name', 'last_name')
class UserProfileForm(forms.ModelForm):
course_accid = CourseModelChoiceField(Course.objects.all(), empty_label=None)
class Meta:
model = UserProfile
fields = ('picture','reg_completed')
url.py
urlpatterns = solid_i18n_patterns ('',
url(r'^accounts/register/$', RegistrationWizard.as_view([UserForm, UserProfileForm]), name='registration_register'),
url(r'^accounts/', include('registration.backends.simple.urls')),
views.py
from formtools.wizard.views import SessionWizardView
from registration.signals import *
from django.core.files.storage import FileSystemStorage
from django.conf import settings
class MyRegistrationView(RegistrationView):
def get_success_url(self, user):
return '/index/'
TEMPLATES = {"0": "registration/registration_form_wiz.html",
"1": "registration/registration_form_wiz.html",
}
class RegistrationWizard(SessionWizardView):
form_list = [UserForm, UserProfileForm]
file_storage = FileSystemStorage(location=settings.MEDIA_ROOT + '/photos')
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def done(self, form_list, **kwargs):
userreg = MyRegistrationView()
for form in form_list:
if isinstance(form, UserForm):
userreg.register(form)
elif isinstance(form, UserProfileForm):
userprofile = form.save(commit=False)
user = self.request.user
userprofile.user = user
userprofile.save()
return HttpResponseRedirect('/index/')
Any help for what is wrong or if another approach would be better, is very welcome.
Thanks!
Related
In my django project I am using django.allauth to manage my users. I want the user to provide both a user name and an email address.
If I use the allauth SignupView it the email field must be filled, however if I override it with my own view, it does not make it mandatory. What am I missing?
settings.py
# User model
AUTH_USER_MODEL = 'accounts.CustomUser'
# django-allauth settings
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
LOGIN_REDIRECT_URL = '/home'
LOGOUT_REDIRECT_URL = '/home'
SITE_ID = 1
ACCOUNT_AUTHENTICATION_METHOD = 'username_email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_USERNAME_REQUIRED = True
models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
logged_in = models.BooleanField(default=False)
last_active_time = models.DateTimeField(null=True)
login_datetime = models.DateTimeField(null=True, blank=True)
email_confirmed = models.BooleanField(default=False)
date_of_birth = models.DateField(null=True)
forms.py
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = get_user_model()
fields = ('username', 'email')
urls.py
from .views import MySignupView
urlpatterns = [
path('signup/', MySignupView.as_view(), name='signup'),
]
views.py
from allauth.account.views import SignupView
from .forms import CustomUserCreationForm
SIGNUP_SUCCESS = 'You have successfully signed up!'
class MySignupView(SignupView):
form_class = CustomUserCreationForm
success_url = reverse_lazy('home')
template_name = 'account/signup.html'
def form_valid(self, form):
messages.success(self.request, SIGNUP_SUCCESS)
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["button_text"] = 'Sign Up'
return context
In django.allauth you don't need to create a custom form for a username and an email address.
If you want to create a custom form anyway, you need to add function def signup (self, request, user): in your CustomUserCreationForm:
from allauth.account.forms import SignupForm
class CustomUserCreationForm(SignupForm):
class Meta:
model = get_user_model()
fields = ('username', 'email', 'date_of_birth')
def signup(self, request, user):
user.date_of_birth = self.cleaned_data['date_of_birth']
user.save()
Then, in your settings.py point to this form:
ACCOUNT_SIGNUP_FORM_CLASS = 'yourapp.forms.CustomUserCreationForm'
See django.allauth configuration for more info.
My forms.py file
class CreateUserForm(UserCreationForm):
phone=PhoneNumberField()
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2', 'phone']
widgets={
'username':forms.TextInput(attrs={'placeholder':"Username"}),
'email':forms.TextInput(attrs={'placeholder':"Email Address"}),
'password1': forms.PasswordInput(attrs={'placeholder': 'Your Password'}),
'password2': forms.PasswordInput(attrs={'placeholder': 'Confirm Password'}),
'phone':PhoneNumberField(),
}
My models.py file
class CustomerReg(models.Model):
user=models.OneToOneField(User, null=True, blank=True, on_delete=models.CASCADE)
name=models.CharField(max_length=200, null=True)
email=models.EmailField(max_length=254)
def create_profile(sender, **kwargs):
if kwargs['created']:
user_profile=CustomerReg.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender=User)
My views.py file
def registerPage(request):
if request.user.is_authenticated:
return redirect('form')
else:
form=CreateUserForm()
if request.method == 'POST':
form = CreateUserForm(request.POST)
if(form.is_valid()):
form.save()
user=form.cleaned_data.get['username']
user=User.objects.get(username=user)
phone=form.cleaned_data.get['phone']
user.phone_number=phone
user.save()
return redirect('login')
context = {'form': form}
return render(request, 'customer/register.html', context)
Upon clicking the register page an error is displayed
AttributeError at /register/
'str' object has no attribute 'User'
So is there no way I can save the data from my form and hence I would have to make new Custom User Model to save phone number of a user on register page?
My admin.py
from django.contrib import admin
from .models import *
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .forms import UserChangeForm,UserCreationForm
# Register your models here.
admin.site.register(Customer)
class UserAdmin(BaseUserAdmin):
form = UserChangeForm
add_form = UserCreationForm
list_display = ('email', 'phone')
admin.site.register(User, UserAdmin)
If your model is created correctly, the mistake you are making is your variable user is not a User object. The error informs you that user variable represents a string (from the form's input), which does not have User as its attribute. In this case, you might have to do something like
user = User.objects.get(username=user)
in order to assign the User object to variable user.
Now that user is a User object, then, you can do
user.phone_number = phone
user.save()
Please try this:
def registerPage(request):
if request.user.is_authenticated:
return redirect('form')
else:
form=CreateUserForm()
if request.method == 'POST':
form = CreateUserForm(request.POST)
if form.is_valid():
form.save()
username=form.cleaned_data['username']
user=User.objects.get(username=username)
phone=form.cleaned_data['phone']
user.phone=phone
user.save()
return redirect('login')
context = {'form': form}
return render(request, 'customer/register.html', context)
Are you display phone field for admin then,
admin.py
from .forms import UserChangeForm,UserCreationForm
from django.contrib import admin
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
class UserAdmin(BaseUserAdmin):
form = UserChangeForm
add_form = UserCreationForm
list_display = ('email', 'phone') # You can add field to display field here..
list_filter = ('name')
fieldsets = (
(None, {'fields': ('email')}),
('Personal info', {'fields': ('name','phone')}),
('Permissions', {'fields': ('name',)}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('name' ,'user','email','email', 'phone')
}),
)
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ()
admin.site.register(User, UserAdmin)
I am trying to extend the User model to create a Profile model. The following code successfully displays a form with the additional fields I specified as location and bio. But when I submit the form only the original username, first_name, last_name, email, and password fields are stored in the database at http://127.0.0.1:8000/admin, none of my custom fields are stored in the Profile section I added to admin . I also get the following error:
IntegrityError at /accounts/register/
NOT NULL constraint failed: accounts_profile.user_id
Request Method: POST
Request URL: http://127.0.0.1:8000/accounts/register/
Django Version: 1.11.2
Exception Type: IntegrityError
Exception Value:
NOT NULL constraint failed: accounts_profile.user_id
Exception Location: /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/backends/sqlite3/base.py in execute, line 328
Python Executable: /Library/Frameworks/Python.framework/Versions/3.6/bin/python3.6
models.py:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
forms.py:
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import Profile
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required = True)
class Meta:
model = User #model User comes with username, email, first name, last name , pass1 and pass2 fields
fields = (
'username',
'email',
'first_name',
'last_name',
'password1',
'password2'
)
def save(self, commit = True):
user = super(RegistrationForm, self).save(commit = False)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('location','bio')
views.py:
from django.shortcuts import render, redirect
from .forms import RegistrationForm,ProfileForm
def register(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
profile_form = ProfileForm(request.POST)
if form.is_valid() and profile_form.is_valid():
form.save()
profile_form.save()
return redirect('login')
else:
form = RegistrationForm()
profile_form = ProfileForm()
return render(request, 'accounts/register.html', {'form': form, 'profile_form': profile_form})
urls.py:
from django.conf.urls import url
from django.contrib.auth.views import login
from . import views
urlpatterns = [
# /accounts/
url(r'^$', views.index, name = 'accounts'),
# /accounts/register/
url(r'^register/$', views.register, name='register'),
url(r'^login/$', login, {'template_name': 'accounts/login.html'}, name='login'),
]
admin.py:
from django.contrib import admin
from .models import Profile
# Register your models here.
admin.site.register(Profile)
# Register your models here.
Any help would be greatly appreciated.
you have to set the relation manually then your view should look like this:
if form.is_valid() and profile_form.is_valid():
user_object = form.save()
a = profile_form.save(commit=False)
a.user = user_object
a.save()
return redirect('login')
you can't do register and profile POST at a time.because your profile model is 1to1field it belongs to Userwhich means existing user can create one user profile.So initially user should create their registered account than only they can create profile.
I am following a tutorial that has created the following registration form:
from django.contrib.auth.forms import UserCreationForm
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required = True)
class Meta:
model = User
fields = (
'username',
'first_name',
'last_name',
'email',
'password1',
'password2'
)
def save(self, commit = True):
user = super(RegistrationForm, self).save(commit= False)
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.email = self.cleaned_data['email']
if commit:
user.save()
return User
Why isemail = forms.EmailField(required = True) the only field mentioned outside of class Meta, what is the purpose of this?
email field on the contrib.auth.AbstractUser (which is subclassed by User) has:
email = models.EmailField(_('email address'), blank=True)
which means that it is allowed to be blank.
Because we want it to be required in the form (for the purposes of the tutorial I assume), we must declare it explicitly.
If you want to create user registration system in django you can create forms.py file paste within it :
from django import forms
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
class RegisterUserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'input'}))
password2 = forms.CharField(label="Repeat password", widget=forms.PasswordInput(attrs={'class': 'input'}))
class Meta:
model = User
fields = ['username', 'email']
widgets = {
'username': forms.TextInput(attrs={'class': 'input'}),
'email': forms.EmailInput(attrs={'class': 'input'})
}
# Validating password
def clean_password2(self):
cd = self.cleaned_data
if cd['password2'] != cd['password']:
raise ValidationError("Password don't match")
return cd['password2']
And in views.py
-*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.http import HttpResponseForbidden, HttpResponse
from django.shortcuts import render
# Create your views here.
from django.views.generic import CreateView
from account.forms import RegisterUserForm
class RegisterUserView(CreateView):
form_class = RegisterUserForm
template_name = "account/register.html"
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated():
return HttpResponseForbidden()
return super(RegisterUserView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
user = form.save(commit=False)
user.set_password(form.cleaned_data['password'])
user.save()
return HttpResponse('User registered')
We override the dispath() method to make sure that the user can access the form if and only if he's not authenticated .
And for form_valid method we encrypt the password using set_password() method and then we commit to the database.
You probably will redirect the user if success rather than returning HttpResponse() as i did .
Because the default UserCreationForm doesn't have the EmailField which represents the email. But it has the other fields and there's no need to add them.
If you added a special field that is not included in the UserCreationForm like the EmailField you have to add it there.
I'm coding a REST API with Django REST framework. The API will be the backend of a social mobile app. After following the tutorial, I can serialise all my models and I am able to create new resources and update them.
I'm using AuthToken for authentication.
My question is:
Once I have the /users resource, I want the app user to be able to register. So, is it better to have a separate resource like /register or allow anonymous users to POST to /users a new resource?
Also, some guidance about permissions would be great.
Django REST Framework 3 allow override create method in serializers:
from rest_framework import serializers
from django.contrib.auth import get_user_model # If used custom user model
UserModel = get_user_model()
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = UserModel.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
)
return user
class Meta:
model = UserModel
# Tuple of serialized model fields (see link [2])
fields = ( "id", "username", "password", )
Serialized fields for classes inherited from ModelSerializer must be declared patently in Meta for Django Rest Framework v3.5 and newest.
File api.py:
from rest_framework import permissions
from rest_framework.generics import CreateAPIView
from django.contrib.auth import get_user_model # If used custom user model
from .serializers import UserSerializer
class CreateUserView(CreateAPIView):
model = get_user_model()
permission_classes = [
permissions.AllowAny # Or anon users can't register
]
serializer_class = UserSerializer
I went ahead and made my own custom view for handling registration since my serializer doesn't expect to show/retrieve the password. I made the url different from the /users resource.
My url conf:
url(r'^users/register', 'myapp.views.create_auth'),
My view:
#api_view(['POST'])
def create_auth(request):
serialized = UserSerializer(data=request.DATA)
if serialized.is_valid():
User.objects.create_user(
serialized.init_data['email'],
serialized.init_data['username'],
serialized.init_data['password']
)
return Response(serialized.data, status=status.HTTP_201_CREATED)
else:
return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)
I may be wrong, but it doesn't seem like you'll need to limit permissions on this view since you'd want unauthenticated requests ...
The simplest solution, working in DRF 3.x:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password', 'email', 'first_name', 'last_name')
write_only_fields = ('password',)
read_only_fields = ('id',)
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name']
)
user.set_password(validated_data['password'])
user.save()
return user
No need for other changes, just make sure that unauthenticated users have the permission to create a new user object.
write_only_fields will make sure passwords (actually: their hash we store) are not displayed, while the overwritten create method ensures that the password is not stored in clear text, but as a hash.
I typically treat the User view just like any other API endpoint that required authorization, except I just override the view class's permission set with my own for POST (aka create). I typically use this pattern:
from django.contrib.auth import get_user_model
from rest_framework import viewsets
from rest_framework.permissions import AllowAny
class UserViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects
serializer_class = UserSerializer
def get_permissions(self):
if self.request.method == 'POST':
self.permission_classes = (AllowAny,)
return super(UserViewSet, self).get_permissions()
For good measure, here is the serializer I typically use with it:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = (
'id',
'username',
'password',
'email',
...,
)
extra_kwargs = {
'password': {'write_only': True},
}
def create(self, validated_data):
user = get_user_model().objects.create_user(**validated_data)
return user
def update(self, instance, validated_data):
if 'password' in validated_data:
password = validated_data.pop('password')
instance.set_password(password)
return super(UserSerializer, self).update(instance, validated_data)
djangorestframework 3.3.x / Django 1.8.x
I updated Cahlan's answer to support custom user models from Django 1.5 and return the user's ID in the response.
from django.contrib.auth import get_user_model
from rest_framework import status, serializers
from rest_framework.decorators import api_view
from rest_framework.response import Response
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
#api_view(['POST'])
def register(request):
VALID_USER_FIELDS = [f.name for f in get_user_model()._meta.fields]
DEFAULTS = {
# you can define any defaults that you would like for the user, here
}
serialized = UserSerializer(data=request.DATA)
if serialized.is_valid():
user_data = {field: data for (field, data) in request.DATA.items() if field in VALID_USER_FIELDS}
user_data.update(DEFAULTS)
user = get_user_model().objects.create_user(
**user_data
)
return Response(UserSerializer(instance=user).data, status=status.HTTP_201_CREATED)
else:
return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)
#cpury above suggested using write_only_fields option. This however did not work for me in DRF 3.3.3
In DRF 3.0 the write_only_fields option on ModelSerializer has been moved to PendingDeprecation and in DRF 3.2 replaced with a more generic extra_kwargs:
extra_kwargs = {'password': {'write_only': True}}
All of the answers so far create the user, then update the user's password. This results in two DB writes. To avoid an extra unnecessary DB write, set the user's password before saving it:
from rest_framework.serializers import ModelSerializer
class UserSerializer(ModelSerializer):
class Meta:
model = User
def create(self, validated_data):
user = User(**validated_data)
# Hash the user's password.
user.set_password(validated_data['password'])
user.save()
return user
A little late to the party, but might help someone who do not want to write more lines of code.
We can user the super method to achieve this.
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(
write_only=True,
)
class Meta:
model = User
fields = ('password', 'username', 'first_name', 'last_name',)
def create(self, validated_data):
user = super(UserSerializer, self).create(validated_data)
if 'password' in validated_data:
user.set_password(validated_data['password'])
user.save()
return user
A Python 3, Django 2 & Django REST Framework viewset based implementation:
File: serializers.py
from rest_framework.serializers import ModelSerializers
from django.contrib.auth import get_user_model
UserModel = get_user_model()
class UserSerializer(ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = UserModel.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
)
return user
class Meta:
model = UserModel
fields = ('password', 'username', 'first_name', 'last_name',)
File views.py:
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from django.contrib.auth import get_user_model
from .serializers import UserSerializer
class CreateUserView(CreateModelMixin, GenericViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
File urls.py
from rest_framework.routers import DefaultRouter
from .views import CreateUserView
router = DefaultRouter()
router.register(r'createuser', CreateUserView)
urlpatterns = router.urls
While there are many answers to this question, none of the answers (as of my writing) addresses the critical security concern, the password validation that is defined in settings.AUTH_PASSWORD_VALIDATORS. So it is possible to create a password like '1' which must not be acceptable. So I have fixed this major security issue. Here is my solution:
In serializers.py:
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers
class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['username', 'first_name', 'last_name', 'email', 'password', ]
extra_kwargs = {
'password': {'write_only': True}
}
def validate_password(self, value):
validate_password(value)
return value
def create(self, validated_data):
user = get_user_model()(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
In views.py:
from rest_framework import mixins, viewsets
from rest_framework.permissions import AllowAny, IsAuthenticated
from . import forms, serializers
class SignupViewSet(mixins.CreateModelMixin,
viewsets.GenericViewSet):
permission_classes = [AllowAny]
serializer_class = serializers.SignupSerializer
API Response:
Now, if you try with a simple password like '1', this response will be returned automatically:
{
"password": [
"This password is too short. It must contain at least 8 characters.",
"This password is too common.",
"This password is entirely numeric."
]
}
In case of a password like '12345678', the response is:
{
"password": [
"This password is too common.",
"This password is entirely numeric."
]
}
In this way, the end-client will know exactly what else are required for the password to be valid.
# This work nicely, but serializer will reamain as it is, like
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers
class SignupSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['username', 'first_name', 'last_name', 'email', 'password', ]
extra_kwargs = {
'password': {'write_only': True}
}
def validate_password(self, value):
validate_password(value)
return value
def create(self, validated_data):
user = get_user_model()(**validated_data)
user.set_password(validated_data['password'])
user.save()
return user
To simplify, modify your view to
from rest_framework import mixins, viewsets
from rest_framework.permissions import AllowAny, IsAuthenticated
from . import forms, serializers
class SignUpUserView(mixins.CreateModelMixin, viewsets.GenericViewSet):
permission_classes = [AllowAny]
queryset = get_user_model().objects.all() #Add this line
serializer_class = SignUpSerializer