Django-Registration & Django-Profile, using your own custom form - python

I am making use of django-registration and django-profile to handle registration and profiles. I would like to create a profile for the user at the time of registration. I have created a custom registration form, and added that to the urls.py using the tutorial on:
http://dewful.com/?p=70
The basic idea in the tutorial is to override the default registration form to create the profile at the same time.
forms.py - In my profiles app
from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
from profiles.models import UserProfile
from registration.models import RegistrationProfile
attrs_dict = { 'class': 'required' }
class UserRegistrationForm(RegistrationForm):
city = forms.CharField(widget=forms.TextInput(attrs=attrs_dict))
def save(self, profile_callback=None):
new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'])
new_profile = UserProfile(user=new_user, city=self.cleaned_data['city'])
new_profile.save()
return new_user
In urls.py
from profiles.forms import UserRegistrationForm
and
url(r'^register/$',
register,
{'backend': 'registration.backends.default.DefaultBackend', 'form_class' : UserRegistrationForm},
name='registration_register'),
The form is displayed, and i can enter in City, however it does not save or create the entry in the DB.

You're halfway there - you've successfully built a custom form that replaces the default form. But you're attempting to do your custom processing with a save() method on your model form. That was possible in older versions of django-registration, but I can see from the fact that you specified a backend in your URL conf that you're using v0.8.
The upgrade guide says:
Previously, the form used to collect
data during registration was expected
to implement a save() method which
would create the new user account.
This is no longer the case; creating
the account is handled by the backend,
and so any custom logic should be
moved into a custom backend, or by
connecting listeners to the signals
sent during the registration process.
In other words, the save() method on the form is being ignored now that you're on version 0.8. You need to do your custom processing either with a custom backend or with a signal. I chose to create a custom back-end (if anyone has gotten this working with signals, please post code - I wasn't able to get it working that way). You should be able to modify this to save to your custom profile.
Create a regbackend.py in your app.
Copy the register() method from DefaultBackend into it.
At the end of the method, do a query to get the corresponding User instance.
Save the additional form fields into that instance.
Modify the URL conf so that it points to BOTH the custom form AND the custom back-end
So the URL conf is:
url(r'^accounts/register/$',
register,
{'backend': 'accounts.regbackend.RegBackend','form_class':MM_RegistrationForm},
name='registration_register'
),
regbackend.py has the necessary imports and is basically a copy of DefaultBackend with just the register() method, and the addition of:
u = User.objects.get(username=new_user.username)
u.first_name = kwargs['first_name']
u.last_name = kwargs['last_name']
u.save()

As described in my comment on Django Trac ticket I made a metaclass and mixin to allow multiple inheritance for ModelForm Django forms. With this you can simply make a form which allows registration with fields from user and profile models at the same time without hard-coding fields or repeating yourself. By using my metaclass and mixin (and also fieldset mixin) you can do:
class UserRegistrationForm(metaforms.FieldsetFormMixin, metaforms.ParentsIncludedModelFormMixin, UserCreationForm, UserProfileChangeForm):
error_css_class = 'error'
required_css_class = 'required'
fieldset = UserCreationForm.fieldset + [(
utils_text.capfirst(UserProfileChangeForm.Meta.model._meta.verbose_name), {
'fields': UserProfileChangeForm.base_fields.keys(),
})]
def save(self, commit=True):
# We disable save method as registration backend module should take care of user and user
# profile objects creation and we do not use this form for changing data
assert False
return None
__metaclass__ = metaforms.ParentsIncludedModelFormMetaclass
Where UserCreationForm can be for example django.contrib.auth.forms.UserCreationForm form and UserProfileChangeForm a simple ModelForm for your profile model. (Do not forget to set editable to False in your foreign key to User model.)
With django-registration backend having such register method:
def register(self, request, **kwargs):
user = super(ProfileBackend, self).register(request, **kwargs)
profile, created = utils.get_profile_model().objects.get_or_create(user=user)
# lambda-object to the rescue
form = lambda: None
form.cleaned_data = kwargs
# First name, last name and e-mail address are stored in user object
forms_models.construct_instance(form, user)
user.save()
# Other fields are stored in user profile object
forms_models.construct_instance(form, profile)
profile.save()
return user
Be careful that registration signal is send at the beginning of this method (in method in superclass) and not at the end.
In the same manner you can make a change form for both user and profile information. Example for this you can find in my comment on Django Trac ticket mentioned above.

With registration 0.8 and later:
Create a subclass of registration.backends.default.views.RegistrationView in your views.py or equivalent:
from registration.backends.default.views import RegistrationView
class MyRegistrationView(RegistrationView):
form_class= MyCustomRegistrationForm
def register(self, request, **cleaned_data):
new_user= super(MyRegistrationView, self).register(request, **cleaned_data)
# here create your new UserProfile object
return new_user

Related

Django not logging in properly when custom backend and models are used

I'm trying to implement a custom authentication backend in Django but its behaving in a very strange way. The custom backend authenticate against a different model from the usual Django User model an upon successfully verifying the provided credentials the backend get or create our usual Django User instance which it returns. Login nevertheless doesn't work.
From the documentation I learnt that I just needed to inherit the django.contrib.auth.backends.BaseBackend and override the authenticate() method which I did. As I've mentioned the custom authenticate() method basically verifies a set of credentials (username and password) against ones stored in the database (custom model, not django.contrib.auth.models.User) which if matched it would get or create an instance of django.contrib.auth.models.User via a proxy model which I named Profile; basically the Profile model has a reference of both django.contrib.auth.models.User and my custom model. When logging in though I keep redirected to the login page (It's like Django logs me in but doesn't set something somewhere such that when I try accessing a protected resource I'm redirected back to login). Also when I login which a django.contrib.auth.models.User object it works just fine and I can access the protected pages. Following are the reasons I opted for this authentication approach.
I'm working with an existing database that has it's own User tables with very different schema than what Django provides.(Actually the only fields in this system User table similar to Django's are username, and password)
I utilized Django's inspectb management command to recreate the models of which I'm under strict instructions to leave unmanaged, you know, manage=False in the model's meta class. Basically I can't inherit Django's AbstractUser as it would require new fields to be added.
Thus the best approach I could think of was to use a proxy model that would link both django.contrib.auth.models.User and my custom unmanaged models with a custom Authentication Backend.
I've tried watching for certain variables such as the request.session dictionary and request.user all which are set but still can't login successfully. Also when I use credentials that are not in either of the two models I get a Invalid Credentials message in the login page (desirable behavior).
Actually, my custom authenticate() method works fine and returns a valid user, the issue I think lies in django.contrib.auth.login. What could be the problem?
Here is the my authenticate method
def authenticate(self, request, username=None, password=None):
try:
c_user = CustomUser.objects.get(username=username)
except CustomUser.DoesNotExist:
return None
#pwd_valid = check_password(password, user.password)
if not c_user.password==password:
return None
#Get and return the django user object
try:
profile = Profile.objects.get(custom_user=c_user)
user = profile.user
#Create user if profile has none
if not user:
user = User.objects.create(
username=''.join(secrets.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(24))
)
profile.user = user
profile.save()
#Create new profile if none exists
except Profile.DoesNotExist:
#Create new user for the profile
user = User.objects.create(
username=''.join(secrets.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(24))
)
Profile.objects.create(
user = user,
custom_user = c_user
)
return user
Here is how I added the custom backend to settings.py
AUTHENTICATION_BACKENDS = [
'Authentication.custom.CustomAuth',
'django.contrib.auth.backends.ModelBackend'
]
Can't believe I'm answering my own question. After much debugging and reading documentation I realized I didn't override get_user method of the django.contrib.auth.backends.BaseBackend thus the default one which returns None was always called. I implemented it and it worked like a charm.

Define permissions to existing users for viewing or modifying other users

I'm currently learning my way around the permission framework in Django and I'm looking to define a set of permissions to a user, which defines if they can see or modify other users in the system.
I'm using the django.auth framework on a multi-schema database where each schema has its own set of users.
I want to essentially apply this, but to the built-in users model.
class Meta:
permissions = (
("can_view_users", "Can view all users"),
("can_modify_users", "Can modify all users"),
("can_password_reset_users", "Can reset passwords for all users"),
("can_delete_users", "Can delete all users"),
)
However I cannot seem to find any documentation on applying this logic to the built-in authentication framework.
I have resolved this by deploying my own permission app. I still create and manage users & sessions via the built-in frameworks however utilize my own model for managing user permissions.
It works as I intended and lets me customize as much as I would like from a per user perspective.
Steps
Create new app for managing permissions called users
Create a model called Permissions with ForeignKey to AUTH_USER_MODEL
Create a custom decorator for checking a users permission on calling a view
Apply decorator to views, specifying which permission is required for the view
At this point, pages cannot be utilize if the user does not have the specified permission. This is great as if the user tried to post to users/new they server will respond with HTTP 503.
The next problem was modifying the base template so users would only see the menu options they were permitted to see. I achieved this by using a custom context_processor.
Create a custom context_processor which utilizes Permissions user_has_perms to retrieve all permissions for the authenticated user.
Add the processor to settings.py
Utilize djangos template processing to render fields user has access to
Code
model Permissions
class Permissions(models.Model):
permission_id = models.AutoField(primary_key=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
## User Management ##
can_user_view_staff = models.BooleanField(default = False)
def user_has_perms(user, perm):
user_exists = Permissions.objects.filter(user_id=user).count()
if user_exists:
if perm == None:
## if no permission specified, return all permissions for user ##
return Permissions.objects.filter(user_id=user)
else:
return Permissions.objects.filter(user_id=user).values(perm).last()[perm]
else:
return False
decorator authorization
from django.http import HttpResponseForbidden
from apps.users.models import user_has_perms
## Check if user has permission to load page ##
def authorization_required(permissions=''):
def decorator(view):
def wrapper(request, *args, **kwargs):
can_user = user_has_perms(request.user, permissions)
if can_user or request.user.is_superuser:
return view(request, *args, **kwargs)
else:
return HttpResponseForbidden()
return wrapper
return decorator
views view with decorator
from decorators.authorization import authorization_required
#login_required
#authorization_required('can_user_view_staff')
def users(request):
user_model = get_user_model()
user_list = user_model.objects.all()
context = {
'users': user_list
}
return render(request, 'users/users.html', context)
context_processor
from apps.users.models import user_has_perms
def permissions(request):
if request.user.is_authenticated:
user_perms = user_has_perms(request.user, None)
else:
user_perms = None
return {
'user_perms': user_perms
}
Modify settings.py to add new processor
TEMPLATES = [
{
'OPTIONS': {
'context_processors': [
'context_processors.perm_context.permissions',
],
},
},
]
Update base.html to check user permissions before rendering fields
{% if user_perms.can_user_view_staff or request.user.is_superuser %}
<a class="dropdown-item" href="/users">User Management</a>
{% endif %}

How to make a login view for django custom user?

I have made a custom user model using the AbstractUser , removed the username and replaced it with email and extended the model, tried creating superuser and it worked and also created some users by a signup form , logged in the admin interface and it worked however when tried to create a login form for the users it fails
I tried this but it didn't work
def LoginView(request):
if request.method == 'POST':
form = AuthenticationForm(data=request.POST)
if form.is_valid():
user = form.get_user()
login(request,user)
return redirect('accounts:login')
else:
form = AuthenticationForm()
return render(request,'accounts/login.html', {'form':form})
then i tried this
class LoginView(FormView):
form_class = AuthenticationForm
template_name = 'login.html'
def form_valid(self, form):
email = form.cleaned_data['email']
password = form.cleaned_data['password']
user = authenticate(email=email, password=password)
# Check here if the user is an admin
if user is not None and user.is_active:
login(self.request, user)
return HttpResponseRedirect(self.success_url)
else:
return self.form_invalid(form)
Obviously i expect the user to be logged in
i think the code in this post is badly formatted. mainly it's my fault as i'm new to this platform
I've developed almost the same setup that you're describing (I didn't remove the username field, just stopped using it). If you haven't already seen it, Django's documentation at https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#substituting-a-custom-user-model is quite helpful.
There are a couple of important things that need to be set up correctly for this to work.
The USERNAME_FIELD on your model should be set to the name of your email field.
The AUTH_USER_MODEL needs to point to your custom user model.
class MyUser(AbstractUser):
USERNAME_FIELD = 'email'
AUTH_USER_MODEL = 'customauth.MyUser'
Since you've removed the username field altogether you might need to subclass django.contrib.auth.forms.AuthenticationForm and django.contrib.auth.views.LoginView to avoid breaking things, but Django should handle a different authentication field quite well.
If you do wind up needing to subclass the view, https://ccbv.co.uk/projects/Django/2.2/django.contrib.auth.views/LoginView/ is a great place to look over all the methods to see what's going on.
Edit - On Subclassing and it's necessity
What I was saying about possibly needing to subclass certain things was influenced by https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#writing-a-manager-for-a-custom-user-model. I wasn't sure if there were other parts of the authentication system that would need you to customize them because you removed the username field.
I've read through some of the source code for Django's authentication system. Here's the path that's being followed.
When the POST request is made to Django's authentication view the authentication form is validated. https://github.com/django/django/blob/2.2.2/django/contrib/auth/forms.py#L191
The authenticate function is called. This iterates through the backends set up and tries to authenticate on each of them. https://github.com/django/django/blob/2.2.2/django/contrib/auth/__init__.py#L62
Django's built-in authentication backend gets the user if it exists using the natural key. https://github.com/django/django/blob/2.2.2/django/contrib/auth/backends.py#L16
We can see in the base manager that the natural key used is the field named by USERNAME_FIELD. https://github.com/django/django/blob/2.2.2/django/contrib/auth/base_user.py#L43
If the form is valid, meaning that the user is authenticated properly, the user is then logged in. https://github.com/django/django/blob/2.2.2/django/contrib/auth/views.py#L88
My reaction is that it looks like Django should work out of the box for your use case. You shouldn't need to write a backend. Here's the extent of the code my gut says you should have to write.
from django.contrib.auth import views as auth_views
from django.shortcuts import resolve_url
class LoginView(auth_views.LoginView):
template_name = 'accounts/login.html'
def get_success_url(self):
return resolve_url('accounts:login')
I don't know if this could be of any use to somebody but I can confirm that Django can authenticate you well with its own Login view if you just replace the username with an email field on your custom user model (as long as you specify the USERNAME_FIELD on the custom user model and are indeed using it by declaring it in the settings).
As I was expecting this behavior I designed a custom HTML form with email/password inputs and used the same principles I would use with the original user model authentication. It was failing though and I understood it was because I wasn't adapting my form to the original Login view expectations or would have worked from the start.
Just make sure to remember that the form HTML input tag for the email address needs to have "type" set to "email" but "id" set to "id_username" and "name" to "username".
This means you can just replace username with an email field and authenticate normally. I did not even declare a Login view for my login form to work, the Django view automacally used from the core is just being called at /accounts/login and working on its own. I'm working on Django 3.2

Django-allauth restrict registration to list of emails

I'm using Django-allauth, I have a list of emails and I want to restrict registration to this list. My idea was to check the signing up user email and, if not in the emails list, stop registration process and redirect.
As suggested by Chetan Ganji I tried editing allauth.account.views.SignupView but it does not subscribe the form_valid method. How can i do that? Thank you for help
from allauth.account.views import SignupView
class AllauthCustomSignupView(SignupView):
def form_valid(self, form):
email = form.cleaned_data['email']
auth_user_list = [ 'email_1',
'email_2',
...
]
if not any(email in s for s in auth_user_list):
return reverse('url')
return super(MySignupView, self).form_valid(form)
You can do it by extending the DefaultAccountAdapter class. You have to figure out a way to store and fetch the restricted list on demand.
You can then use the adapters and raise validation error in the registration from. Extend a DefaultAccountAdapter and override the clean_email method. Create an adapter.py in your project directory and extend the default adapter class.
from allauth.account.adapter import DefaultAccountAdapter
from django.forms import ValidationError
class RestrictEmailAdapter(DefaultAccountAdapter):
def clean_email(self,email):
RestrictedList = ['Your restricted list goes here.']
if email in RestrictedList
raise ValidationError('You are restricted from registering. Please contact admin.')
return email
Finally, point the account adapter in settings.py to your extended class.
ACCOUNT_ADAPTER = 'YourProject.adapter.RestrictEmailAdapter'
Maybe try with this code
class AllauthCustomSignupView(SignupView):
def form_valid(self, form):
email = form.cleaned_data['email']
auth_user_list = [ 'email_1',
'email_2',
...
]
if email in auth_user_list:
return reverse('blocked-email') # whatever url, make sure that the url is defined inside form_valid or in approriate location.
else:
return super(AllauthCustomSignupView, self).form_valid(form)
class BlockedEmailView(TemplateView):
template_name = "blocked-email.html"
Add below line to your urls.py
url(r'^signup/$', views.AllauthCustomSignupView.as_view(), name="signup"),
url(r'^blocked/email$', views.BlockedEmailView.as_view(), name="blocked-email"),
Also, you will need to change the action attribute of the form that SignupView has. So, your will have to override the template of that view, keep everything else the same, just change the action to point to "signup/".

Django allauth custom login form not rendering all fields in custom user model

I am trying to implement login of that consists a custom user model. Is it possible to inherit from allauth.account.forms.LoginForm and add a custom field to the custom login form?
Idea is to assign user with a role at the time of login by overriding
login() method.
I have followed allauth configuration and mentioned what forms to use for login in settings.py with following code
AUTH_USER_MODEL = 'core.User'
ACCOUNT_SIGNUP_FORM_CLASS = 'core.forms.SignupForm'
ACCOUNT_FORMS = {'login': 'core.forms.CoreLoginForm'}
I am using no auth backends other than django.contrib.auth.backends.ModelBackend and allauth.account.auth_backends.AuthenticationBackend. custom signup is working fine for me without any issues. But custom loginform is not rendering all fields in the user model. Allauth LoginForm was inherited as per accepted answer in this SO Post and a choicefield was added to the custom login form.
from allauth.account.forms import LoginForm
class CoreLoginForm(LoginForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(CoreLoginForm, self).__init__(*args, **kwargs)
role = forms.ChoiceField(widget=forms.Select(), choices=User.roles, initial=User.roles[0])
Upon a ./manage.py runserver it says Module "core.forms" does not define a "SignupForm" class. I have already defined a SignupForm in core.forms as below and signup will work if CoreLoginForm is inherited from forms.Form instead of LoginForm. So if I do
class CoreLoginForm(forms.Form):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(CoreLoginForm, self).__init__(*args, **kwargs)
role = forms.ChoiceField(widget=forms.Select(), choices=User.roles, initial=User.roles[0])
I can render custom login form to html page. But problem here is that I have to redefine every methods in the class including authenticate(), perform_login() etc. This will end up in copying whole LoginForm and pasting it in forms.py of the app. I dont want to do this as I think this is against DRY principle. Is there a simple way to add a custom field to custom loginform and override login() method?
TIA
Probably you have solved your problem already but i ll leave this solution for other guys who ve spent more then 10 minutes on this problem like me :)
I have found an easy way to add a field to Allauth login form:
As you have done - add to settings.py :
ACCOUNT_FORMS = {'login': 'core.forms.CoreLoginForm'}
and after that in your forms.py you need to add :
from django import forms
from allauth.account.forms import LoginForm
class CoreLoginForm(LoginForm):
def __init__(self, *args, **kwargs):
super(CoreLoginForm, self).__init__(*args, **kwargs)
## here i add the new fields that i need
self.fields["new-field"] = forms.CharField(label='Some label', max_length=100)

Categories