How do i create a custom django backend for django-registration? - python

I've had a read of this
http://docs.b-list.org/django-registration/0.8/backend-api.html
and i've had a shot at making my own backend. I am doing this because I want to create a backend that disallows having the same email used for registration, and I wanted to change the email-error message. I also wanted to add in my own field!
Here is what I came up with:
from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from registration.forms import attrs_dict
class customRegistrationForm(RegistrationForm):
email2 = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
maxlength=75)),
label=_("Confirm email"))
def clean_email(self):
"""
Validate that the email is alphanumeric and is not already
in use.
"""
try:
email = User.objects.get(email__iexact=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
raise forms.ValidationError(_("That email already exists - if you have forgotten your password, go to the login screen and then select \"forgot password\""))
def clean(self):
"""
Verifiy that the values entered into the two email fields
match. Note that an error here will end up in
``non_field_errors()`` because it doesn't apply to a single
field.
"""
if 'email' in self.cleaned_data and 'email2' in self.cleaned_data:
if self.cleaned_data['email'] != self.cleaned_data['email2']:
raise forms.ValidationError(_("The two email fields didn't match."))
return super(RegistrationForm,clean)
The above goes in my init.py file (whetever that is)
Then, I have in my urls.py code:
url(r'^accounts/register/$',
register,
{ 'backend': 'myapp.forms.customRegistrationForm' },
name='registration_register'),
... #other urls here!
Now, when i go to the /accounts/register page, I get the following error:
AttributeError at /accounts/register/
'customRegistrationForm' object has no attribute 'registration_allowed'
Which is weird. It seems to be telling me I need a "registration_allowed" method added to my subclass. BUT, the subclass is a subclass of the RegistrationForm, which works fine and doesn't have that stuff defined... I know I can add these members in, but it seems to beat the purpose of extension, right?
UPDATE
Here is the code now that it works!
I broke up the varying classes into varying init.py files in different folders - one called "forms" and one called "backends", both of which sit in a folder "djangoRegistration" under my main project.
/forms/init.py
from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from registration.forms import attrs_dict
class customRegistrationForm(RegistrationForm):
def __init__(self, *args, **kw):
super(RegistrationForm, self).__init__(*args, **kw)
self.fields.keyOrder = [
'username',
'email',
'email2',
'password1',
'password2'
]
email2 = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
maxlength=75)),
label=_("Confirm email"))
def clean_email(self):
"""
Validate that the email is alphanumeric and is not already
in use.
"""
try:
email = User.objects.get(email__iexact=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
raise forms.ValidationError(_("That email already exists - if you have forgotten your password, go to the login screen and then select \"forgot password\""))
def clean(self):
"""
Verifiy that the values entered into the two email fields
match. Note that an error here will end up in
``non_field_errors()`` because it doesn't apply to a single
field.
"""
if 'email' in self.cleaned_data and 'email2' in self.cleaned_data:
if self.cleaned_data['email'] != self.cleaned_data['email2']:
raise forms.ValidationError(_("The two email fields didn't match."))
return super(RegistrationForm,clean)
/backends/init.py
from registration.backends.default import DefaultBackend
from dumpstownapp.djangoRegistration.forms import customRegistrationForm
class customDefaultBackend(DefaultBackend):
def get_form_class(self, request):
"""
Return the default form class used for user registration.
"""
return customRegistrationForm
and finally, my urls.py just references the new backend:
url(r'^accounts/register/$',
register,
{ 'backend': 'myapp.djangoRegistration.backends.customDefaultBackend' },
name='registration_register'),
#more urls here! yay!
As a final note, I had to add some code to "order" the way the fields were presented, which is what the init method in the customRegistrationForm is doing
thanks!

You're trying to use a form as a backend, but that's not what a backend is at all. As the document you link to explains, a backend is a class that implements certain methods, including registration_allowed. The form doesn't implement any of those, which is not surprising, because it's meant for user input and validation, not the backend actions.
However, that page does give a hint as to the correct way to implement this. One of the methods that the backend can define is get_form_class(), which returns the form class to use. So, it seems that what you need is a custom backend that inherits from registration.backends.default.DefaultBackend and overrides just the get_form_class method, which simply returns customRegistrationForm.

Related

Where is this self.add_error adds the error to?

I'm having a hard time trying to figure out the following code:
from django import forms
from django.contrib.auth.hashers import check_password
class CheckPasswordForm(forms.Form):
password = forms.CharField(label='password_check', widget=forms.PasswordInput(
attrs={'class': 'form-control',}),
)
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
def clean(self):
cleaned_data = super().clean()
password = cleaned_data.get('password')
confirm_password = self.user.password
if password:
if not check_password(password, confirm_password):
self.add_error('password', 'password is wrong')
here, I don't get the self.add_error('password', 'password is wrong') part. In the documentation, it says that the password here is the field in add_error('field', 'error').
So, is the error being added to the password field? or is it being added to the following part?
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
because if I have to access this error in the html template, I would have to do something like this,
{% if password_form.password.errors %}
and if it means accessing errors from password field, it should mean that the error is added to the password field... but the self.add_error part confuses me
Form clean method is responsible for validation of your form when the data is sent to it
normally it will run validation on all field using clean_<fieldname>() method and if there are errors store them in fieldname.errors
As documented
Since the field validation methods have been run by the time clean()
is called, you also have access to the form’s errors attribute which
contains all the errors raised by cleaning of individual fields.
Note that any errors raised by your Form.clean() override will not be
associated with any field in particular. They go into a special
“field” (called __all__)
if you don't want error to end into __all__ you can attach it to particular field in your case you added it to password
If you want to attach errors to a specific field in the form, you
need to call add_error().

Adding specific permission to users with different roles in django

I am new to django and I am a bit confused on how the permission works, or if that is what I am supposed to use in my case.
So, I have my user/model:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
ROLE_CHOICES = (
(0, ('Student')),
(1, ('Proffesor')),
(2, ('Administration'))
)
role = models.IntegerField(choices=ROLE_CHOICES, default=2)
And then I have my views in election/views.py:
class MainPage(View)
class ElectionList(LoginRequiredMixin, View)
class ElectionDetail(LoginRequiredMixin, View)
#only administration can create elections
class CreateElection(LoginRequiredMixin, CreateView)
How can I restrict a simple user (student, for example) to create an election?
Django already has a Permission and Group models and per-group permissions, so the cleanest "django compatible" way here would be to define "Students", "Professors" and "Administrators" as groups, setup their permissions (eventually adding custom ones if needed), add your users to the appropriate group(s), and test if your current user has the needed permissions for a given action using the permission_required decorator or since you're using a class-based view the PermissionRequiredMixin.
As a side note: since you're using ints for your role values, you may want to add pseudo_constants for them in your model:
class User(AbstractUser):
ROLE_STUDENT = 0
ROLE_PROFESSOR = 1
ROLE_ADMINISTRATOR = 2
ROLE_CHOICES = (
(ROLE_STUDENT, 'Student'),
(ROLE_PROFESSOR, 'Professor'),
(ROLE_ADMINISTRATOR, 'Administration')
)
So you can query / filter your model using sensible human-readable values instead of magic numbers, ie:
students = User.objects.filter(role=User.ROLE_STUDENT)
instead of
students = User.objects.filter(role=0)
But if you use contrib.auth.models.Group for permissions you may not even need this field at all, as you can get your queryset from the groups members.
You can use UserPassesTestMixin
eg.,
class LoginAndPermission(LoginRequiredMixin, UserPassesTestMixin):
def test_func(self):
return self.request.user.is_student
def get_login_url(self):
if self.request.user.is_authenticated():
# User is logged in but does not have permission
return "/permission-denied-url/"
else:
# User is not logged in
return "/login/"
class ElectionDetail(LoginAndPermission, View):
My solution could be an alternative of Django's Decorator.
I'm pretty interesting by your question.
When I have function in my views and I don't want to display this one to a user group, I have a templatetags file :
from django import template
from django.contrib.auth.models import Group
register = template.Library()
#register.filter(name='has_group')
def has_group(user, group_name):
group = Group.objects.get(name=group_name)
return group in user.groups.all()
Then, in my HTML file :
{% if request.user|has_group:"admin" %}
<li>Some part</li>
{% endif %}
I think it's possible in my templatetags to user permission directly in my views.py file, but I don't know How to do that.
Anyway, my method works very well up to now ;)
Read the Permission and Authorization documentation in https://docs.djangoproject.com/en/1.11/topics/auth/default
from django.contrib.auth.mixins import AccessMixin
class AddElectionPermission(AccessMixin):
raise_exception = True
permission_denied_message = 'permission deny'
def dispatch(self, request, *args, **kwargs):
if request.user.role != 0:
return self.handle_no_permission()
return super(AddElectionPermission, self).dispatch(request, *args, **kwargs)
#only administration can create elections
class CreateElection(LoginRequiredMixin, AddElectionPermission, CreateView)

Sending different errors depending on url Django

I am working in Django and I have a situation where I have written a custom validator that lives in the model.py
This validator should return a validationError when the input is bad.
In the project I am working on, we are using Django Rest Framework for our API and the Django admin panel for our admin panel. They connect to the same DB
My problem is that when the request comes from the API I need to return a 'serializers.ValidationError' (which contains a status code of 400), but when the request comes from the admin panel I want to return a 'django.core.exceptions.ValidationError' which works on the admin panel. The exceptions.ValidationError does not display correctly in the API and the serializers.ValidationError causes the admin panel to break. Is there some way I can send the appropriate ValidationError to the appropriate place?
here is my validation function (it lives in the model)
def validate_unique(self, *args, **kwargs):
super(OrganizationBase, self).validate_unique(*args, **kwargs)
qs = self.__class__._default_manager.filter(organization_type="MEMBER")
if not self._state.adding and self.pk is not None:
qs = qs.exclude(pk=self.pk)
if qs.exists():
raise serializers.ValidationError("Only one organization with \'Organization Type\' of \'Member\' is allowed.") #api
raise exceptions.ValidationError("Only one organization with \'Organization Type\' of \'Member\' is allowed.") #admin
Those two lines at the end are the two errors written together for illustration's sake, in this case only the #api one would run
Basically I want to send errorA when the request is coming from the admin panel and errorB when the request is coming from the API
Thank you
For raising different error classes write different validators.
rest framework api:
You can use the UniqueValidator or a custom validation function. check link [1]
eg:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = (....)
def validate(self, data):
# my validation code
raise serializers.ValidationError(....)
return data
admin panel:
for the admin panel you can use a custom form [2].
eg:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def clean(self):
cleaned_data = super(MyForm, self).clean()
# my validation code
raise exceptions.ValidationError(....)
return cleaned_data
class MyAdmin(admin.ModelAdmin):
form = MyForm
In both the serializer and form you can access the instance object if not none.
[1] http://www.django-rest-framework.org/api-guide/validators/#uniquevalidator
[2] https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form

why django inbuilt auth views not recognizing the customized form?

I understand that when we need to make the django inbuilt views, parameter specification should be made before the inbuilt view function could use it.
now I want to customize the form for django auth viewpassword_reset_confirm
and in the url, I import my customized form
from Myapp.forms import PasswordSetForm
from django.contrib.auth import urls,views
and for the url
url(r'^accounts/ ^reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$ ',
'django.contrib.auth.views.password_reset_confirm',{'set_password_form':PasswordSetForm},
name='password_reset_confirm'),
url(r'^accounts/', include('django.contrib.auth.urls')),
in my form.py i import the original SetPasswordForm that is used as default form by django password_reset_confirm funciton
from django.contrib.auth.forms import SetPasswordForm
and then customize the form
class PasswordSetForm(SetPasswordForm):
error_messages = {
'invalid_password': _("Please enter a valid password as instructed"),
'password_mismatch': _("The two password fields didn't match."),
}
#new_password1 = forms.CharField(label=_("New password"),
# widget=forms.PasswordInput)
#new_password2 = forms.CharField(label=_("New password confirmation"),
# widget=forms.PasswordInput)
new_password1 = forms.CharField(widget=forms.PasswordInput, min_length=6, label='New Password' )
new_password2 = forms.CharField(widget=forms.PasswordInput, min_length=6, label='Confirm new password')
def clean_new_password1(self):
new_password1 = self.cleaned_data.get("new_password1")
# password must contain both Digits and Alphabets
# password cannot contain other symbols
if new_password1.isdigit() or new_password1.isalpha() or not new_password1.isalnum():
raise forms.ValidationError(
self.error_messages['invalid_password'],
code='invalid_password',
)
return new_password1
as you could see more checking are done for the new_password1
But after trying many times the page is still using the default SetpasswordForm as the default labels for the two password is displayed in my html instead of my customized label(in the html {{form.new_password2.label}} is used to display label) and no additional checking on the new_password1 is done
I have tried to create another MyPasswordSetForm that does not inherit the SetPassordForm and pass this to the password_reset_confirm but it does not make any difference, the default form is still used.
I have googled lot and ask questions on this, seems this is the right way to go, but What could be the problem here?
Thank you very much for helping.
Oops, that URL regexp looks wrong wrong wrong:
r'^accounts/ ^reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$ '
It should be:
r'^accounts/reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$'

Django custom registration fields

I'm becoming increasingly bewildered by the range of answers on offer to the seemingly simple problem of adding custom fields to the django-registration register form/flow. This should be a default, documented aspect of the package (not to sound ungrateful, just that it is such a well-equipped package), but solutions to the problem are dizzying.
Can anyone give me the most simple solution to getting UserProfile model data included in the default registration register page?
Update:
I eventually used Django Registration's own signals to give me this hacky fix. It is particularly ugly because, I had to use try on the POST attribute dealing with my Boolean since I found that the checkbox returned nothing if left empty.
Would appreciate any advice on improving this, or best practice.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)
Thanks
What you have looks like a workable approach.
I've looked through the django-registration code, and based on the following comments in the register view I've come up with another solution. I'm not totally sure this is cleaner, but if you aren't a fan of signals this is good. This also provides a much easier avenue if you intend to make more customizations.
# from registration.views.register:
"""
...
2. The form to use for account registration will be obtained by
calling the backend's ``get_form_class()`` method, passing the
``HttpRequest``. To override this, see the list of optional
arguments for this view (below).
3. If valid, the form's ``cleaned_data`` will be passed (as
keyword arguments, and along with the ``HttpRequest``) to the
backend's ``register()`` method, which should return the new
``User`` object.
...
"""
You could create a custom backend and override those mentioned methods:
# extend the provided form to get those fields and the validation for free
class CustomRegistrationForm(registration.forms.RegistrationForm):
receive_email = forms.BooleanField(initial=True, required=False)
# again, extend the default backend to get most of the functionality for free
class RegistrationBackend(registration.backends.default.DefaultBackend):
# provide your custom form to the registration view
def get_form_class(self, request):
return CustomRegistrationForm
# replace what you're doing in the signal handler here
def register(self, request, **kwargs):
new_user = super(RegistrationBackend, self).register(request, **kwargs)
# do your profile stuff here
# the form's cleaned_data is available as kwargs to this method
profile = new_user.userprofile
# use .get as a more concise alternative to try/except around [] access
profile.receive_email = kwargs.get('receive_email', False)
profile.save()
return new_user
To use the custom backend, you can then provide separate urls. Before including the default urls, write 2 confs that point at your custom backend. Urls are tested in the order defined, so if you define these two before including the defaults, these two will capture before the default ones are tested.
url(r'^accounts/activate/(?P<activation_key>\w+)/$',
activate,
{'backend': 'my.app.RegistrationBackend'},
name='registration_activate'),
url(r'^accounts/register/$',
register,
{'backend': 'my.app.RegistrationBackend'},
name='registration_register'),
url(r'^accounts/', include('registration.backends.default.urls')),
The docs actually describe all this, but they aren't particularly accessible (no readthedocs). They are all included in the project, and I was browsing them here.
I eventually used Django Registration's own signals to give me this fix.
I will clean up the try/except flow at some point. dokkaebi also points out above that I might be able to assess the request.GET parameters for when a checkbox is left empty.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)

Categories

Resources