Django - custom validation for different users - django

I have a date time field called bk_time. Now, I would like to write custom validation for bk_time based on different users.
For example, I have users Staff_A, Staff_B and Superuser:
Staff_A can only set the time = Mon-Fri 9am-12am
Staff_B can only set the time = Monday only
Superuser no limitation
I have referred Django Doc Validators. But it seems not working for multiple validation
I have tried to write save_formsetDjango Doc Admin.But it seems not able to raise ValidationError
models.py
class Location(models.Model):
name = models.CharField('Location', max_length=100)
class Room(models.Model):
room_label = models.CharField('Room Lebel', max_length=100)
bk_time= models.DateTimeField('Booking Time')
admin.py
class RoomInline(admin.StackedInline):
model = Room
extra = 0
class LocationAdmin(admin.ModelAdmin):
list_display = ['id', 'name']
fields = ('name')
inlines = [RoomInline]
If this is relevant, I'm using Django 1.4.

I think this has to come on the form validation, and not on the field validation. This is because your validation depends on two independent fields.
In particular, this is very similar to an authentication: your validation depends on the user and on another field. Take a look how Django implements its authentication (from django.contrib.auth):
class AuthenticationForm(forms.Form):
[...]
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username and password:
self.user_cache = authenticate(username=username,
password=password)
if self.user_cache is None:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
elif not self.user_cache.is_active:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',
)
return self.cleaned_data
In your case, you want to raise a ValidationError on a given constraint, and return cleaned_data otherwise.

Related

Stripe with Django - make form clean() method return value that isn't a form field

I am integrating Stripe payment processing into my Django app, and I can't figure out the 'correct' way to verify the customer's card information and insert a row into my Users table that contains the user's Stripe Customer ID.
Ideally, I'd love to do something along the lines of the following, in which my CheckoutForm verifies card details and raises a form ValidationError if they are incorrect. However, using this solution, I can't figure a way to get the customer.id that's generated out of the clean() function.
forms.py
class CheckoutForm(forms.Form):
email = forms.EmailField(label='E-mail address', max_length=128, widget=forms.EmailInput(attrs={'class': 'form-control'}))
stripe_token = forms.CharField(label='Stripe token', widget=forms.HiddenInput)
def clean(self):
cleaned_data = super().clean()
stripe_token = cleaned_data.get('stripe_token')
email = cleaned_data.get('email')
try:
customer = stripe.Customer.create(
email=email,
source=stripe_token,
)
// I can now get a customer.id from this 'customer' variable, which I want to insert into my database
except:
raise forms.ValidationError("It looks like your card details are incorrect!")
views.py
# If the form is valid...
if form.is_valid():
# Create a new user
user = get_user_model().objects.create_user(email=form.cleaned_data['email'], stripe_customer_id=<<<I want the customer.id generated in my form's clean() method to go here>>>)
user.save()
The only other solution I can think of is to run the stripe.Customer.create() function in views.py after the form is validated. That'll work, but it doesn't seem like the 'right' way to code things, since as I understand it all validation of form fields is supposed to be done within forms.py.
What's the proper Django coding practice in this situation? Should I just move my card validation code to views.py, or is there a cleaner way to keep the card validation code within forms.py and get the customer.id out of it?
I don't think that proper Django coding practice is any different from Python coding practice in this situation. Since Django form is just a class, you can define property for customer. Something like this:
class CheckoutForm(forms.Form):
email = forms.EmailField(label='E-mail address', max_length=128, widget=forms.EmailInput(attrs={'class': 'form-control'}))
stripe_token = forms.CharField(label='Stripe token', widget=forms.HiddenInput)
_customer = None
def clean(self):
cleaned_data = super().clean()
stripe_token = cleaned_data.get('stripe_token')
email = cleaned_data.get('email')
try:
self.customer = stripe.Customer.create(
email=email,
source=stripe_token,
)
except:
raise forms.ValidationError("It looks like your card details are incorrect!")
#property
def customer(self):
return self._customer
#customer.setter
def customer(self, value):
self._customer = value
Then it the views.py after form.is_valid(), you'd call this property.
if form.is_valid():
customer = form.customer
Or maybe #property is an overkill and you could simply do it like this:
class CheckoutForm(forms.Form):
email = forms.EmailField(label='E-mail address', max_length=128, widget=forms.EmailInput(attrs={'class': 'form-control'}))
stripe_token = forms.CharField(label='Stripe token', widget=forms.HiddenInput)
customer = None
def clean(self):
cleaned_data = super().clean()
stripe_token = cleaned_data.get('stripe_token')
email = cleaned_data.get('email')
try:
self.customer = stripe.Customer.create(
email=email,
source=stripe_token,
)
except:
raise forms.ValidationError("It looks like your card details are incorrect!")
... and still form.customer in views.py.
I guess both should work, but I haven't tested the code.

How do I prevent the creation of a user if validation on the nested serializer fails?

I am creating a REST api for user registration, and I have a nested serializer where I store additional information about a user.
The User serializer asks for first_name, last_name, email, and password.
The nested serializer asks for agreed_terms_of_service
email, password, and agreed_terms_of_service are required.
But if a user keys in their email and password and DOES NOT check the agreed_terms_of_service box, it returns and error, but still creates a user with the email and password.
Then when the user goes to 'remedy the situation', the email address is already in use.
If I update instead of create, I feel like I would run into a situation where people are overwriting other users...
I am wondering how people handle this with django rest serializers and what is the best practice?
VIEWS.PY
def serialize(self, request):
if request.method =='POST':
data = json.loads(request.body)
#first validation
if data['password'] != data['password2']:
raise serializers.ValidationError({'msgType':'error','message':'Passwords do not match.'})
#move to serializer
else:
serializer = userSerializer(data = data)
data['username'] = data['email']
if serializer.is_valid(raise_exception=True):
serializer.save()
response = {'msgType':'success', 'message':'Your account has been created successfully.'}
elif serializer.errors:
raise serializers.ValidationError({'msgType':'error', 'message': serializer.errors})
return Response(response)
SERIALIZERS.PY
class nestedSerializer(serializers.ModelSerializer):
class Meta:
model = Nested
fields = ('agreed_terms_of_service')
def validate(self, data):
return data
class userSerializer(serializers.ModelSerializer):
nested = nestedSerializer()
class Meta:
model = User
fields = ('pk','email', 'password', 'username','first_name','last_name','nested')
def validate(self, data):
email = data['email']
try:
User.objects.get(email = email)
except User.DoesNotExist:
return data
else:
raise serializers.ValidationError({'msgType':'error', 'message':'A user with this email address already exists.'})
return data
def create(self, validated_data):
nested_data = validated_data.pop('extend')
email = validated_data['email']
user = User.objects.create(**validated_data)
user.username = user.id
user.set_password(validated_data['password'])
user.save()
nested = Nested.objects.create(user=user, **nested_data)
return user
Models.py
class Nested(models.Model):
user = models.OneToOneField(User)
personalid = models.CharField(max_length=255)
agreed_terms_of_service = models.BooleanField()
city = models.CharField(max_length=255, blank=True, null=True)
Thank you for your help in advance. It is much appreciated.
First, I'd change your current validate() function to validate_email() (because all you're doing is validating that the email is not already in use). You should use validate() if you want access to multiple fields in your function. See the documentation here to read more about when you should use field-level validation and object-level validation: http://www.django-rest-framework.org/api-guide/serializers/#validation
Second, in your view, you do:
if data['password'] != data['password2']:
raise serializers.ValidationError({'msgType':'error','message':'Passwords do not match.'})
If you're verifying that "password" and "confirm password" field match, I'd do that check in the validate() function of your serializer (since you'll be accessing both the 'password' and the 'password2' field.
Third, in your create method, I'd use User.objects.create_user to create a user (create_user will handle the hashing of the password, etc. That way, you don't need to explicitly do user.set_password(validated_data['password'])). See the answer here for more information: How to create a user in Django?
Lastly, to address the main issue. Your "agreed_terms_of_service" is a Boolean field, which means it accepts both True and False. What I'd try is this:
class nestedSerializer(serializers.ModelSerializer):
class Meta:
model = Nested
fields = ('agreed_terms_of_service')
def validate_agreed_terms_of_service(self, data):
print(data) # to verify if data is even a boolean
if data == True or data == 'True':
return data
raise serializers.ValidationError({'msgType':'error', 'message':'Please accept the terms and conditions.'})
and in your create function for your userSerializer, add a print statement at the beginning to see if create is being executed before the "agreed_terms_of_service" validation.
def create(self, validated_data):
print("Creating the object before validating the nested field.")
# I'd be surprised if DRF executes a create function before
# even validating it's nested fields.
# rest of the create code goes here
When you add the statements above, what does it print for "data" and does it print "data" before "creating the object"?

Creating a login page for custom Django users? [duplicate]

I want to create a SINGLE form which gives the ability to the admin to create a new user with extended profile. Please note that, I don't want to use admin and registration apps.
I have extended the user with the UserProfile model. I have read all the documents related to extending user profile. But, I really don't know how to save these information.
I coded the following django form for this issue:
class CreateUserForm(forms.Form):
username = forms.CharField(max_length=30)
first_name = forms.CharField()
last_name = forms.CharField()
password1=forms.CharField(max_length=30,widget=forms.PasswordInput()) #render_value=False
password2=forms.CharField(max_length=30,widget=forms.PasswordInput())
email=forms.EmailField(required=False)
title = forms.ChoiceField(choices=TITLE_CHOICES)
def clean_username(self): # check if username dos not exist before
try:
User.objects.get(username=self.cleaned_data['username']) #get user from user model
except User.DoesNotExist :
return self.cleaned_data['username']
raise forms.ValidationError("this user exist already")
def clean(self): # check if password 1 and password2 match each other
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:#check if both pass first validation
if self.cleaned_data['password1'] != self.cleaned_data['password2']: # check if they match each other
raise forms.ValidationError("passwords dont match each other")
return self.cleaned_data
def save(self): # create new user
new_user=User.objects.create_user(username=self.cleaned_data['username'],
first_name=self.cleaned_data['first_name'],
last_name=self.cleaned_data['last_name'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
return new_user
Is it OK? however it gives me an error in first_name and last_name. Says django doesn't expect first_name and last_name in save() method.
create_user only supports the username, email and password arguments. First call create_user, then add the extra values to the saved object.
new_user=User.objects.create_user(self.cleaned_data['username'],
self.cleaned_data['email'],
self.cleaned_data['password1'])
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()

Django permissions outside of the admin and in a custom form

I have an app with 2 user levels; Superuser, and Staff. Superuser will login to this app and create a new user and assign the user either Superuser or Staff permissions, but they are not logging in through the default Django admin, they will be logging into a custom admin. How do I display the permission checkboxes in my form and how do I save them for that new user being created? Is it merely just a normal checkbox or is there something I need to override to be able to accomplish this?
Here is my CreateUserForm as it is now.
class CreateUserForm(forms.Form):
username = forms.EmailField(max_length=50)
email = forms.EmailField()
first_name = forms.CharField(max_length=150)
last_name = forms.CharField(max_length=150)
password1 = forms.CharField(max_length=30, widget=forms.PasswordInput(render_value=False), label='Password')
password2 = forms.CharField(max_length=30, widget=forms.PasswordInput(render_value=False), label='Password Confirmation')
address_1 = forms.CharField(max_length=50)
address_2 = forms.CharField(max_length=50)
city = forms.CharField(max_length=50)
province = forms.CharField(max_length=2)
country = forms.CharField(max_length=50)
postal_code = forms.CharField(max_length=10)
work_phone = forms.CharField(max_length=20)
mobile_phone = forms.CharField(max_length=20)
fax = forms.CharField(max_length=20)
url = forms.CharField()
comments = forms.CharField(widget=forms.Textarea)
def clean_username(self):
try:
User.objects.get(username=self.cleaned_data['username'])
except User.DoesNotExist:
return self.cleaned_data['username']
raise forms.ValidationError("Sorry, this username has already been taken. Please choose another.")
def clean_email(self):
try:
User.objects.get(email=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
raise forms.ValidationError("Sorry, this email has already been taken. Please choose another.")
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("You must type the same password each time.")
if ' ' in self.cleaned_data['username']:
raise forms.ValidationError("username must not contain spaces")
return self.cleaned_data
def save(self):
new_user = User.objects.create_user(
username = self.cleaned_data['username'],
email = self.cleaned_data['email']
)
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.set_password(self.cleaned_data['password'])
new_user.is_active = True
new_user.save()
new_profile = UserProfile(
# TODO:
)
Thanks
If you mean the permission checkboxes for is_staff and is_superuser, then you're probably best off using a ModelForm w/ the User model. This will automatically take care of everything for you.
class UserForm(forms.ModelForm):
class Meta:
model = User
exclude = ('last_login', 'date_joined')
EDIT per OP update: You can just add 2 forms.BooleanField() fields to your form for is_superadmin and is_staff (or name them differently), and much like you're already doing in the save method you can do new_user.is_staff = self.cleaned_data['is_staff']. You may consider using a choice field instead, w/ a dropdown w/ 3 entries, "Normal User", "Staff User", "Admin User", and then set is_staff and is_superadmin according to the user's selection.
It's also still possible to use a ModelForm on the User model and just add the necessary extra fields in addition. It may or may not be worth it to you, depending how custom you're getting.
Another note about your form - when it comes to editing existing users, this won't be able to be used for that w/ the current clean methods on username/email. But if you tweak those to exclude the current instance (if set) from the lookup, you will be able to use this form for editing existing users (though since it's not a model form you'd need to populate the initial data manually).

Creating an Add user form in Django

I want to create a SINGLE form which gives the ability to the admin to create a new user with extended profile. Please note that, I don't want to use admin and registration apps.
I have extended the user with the UserProfile model. I have read all the documents related to extending user profile. But, I really don't know how to save these information.
I coded the following django form for this issue:
class CreateUserForm(forms.Form):
username = forms.CharField(max_length=30)
first_name = forms.CharField()
last_name = forms.CharField()
password1=forms.CharField(max_length=30,widget=forms.PasswordInput()) #render_value=False
password2=forms.CharField(max_length=30,widget=forms.PasswordInput())
email=forms.EmailField(required=False)
title = forms.ChoiceField(choices=TITLE_CHOICES)
def clean_username(self): # check if username dos not exist before
try:
User.objects.get(username=self.cleaned_data['username']) #get user from user model
except User.DoesNotExist :
return self.cleaned_data['username']
raise forms.ValidationError("this user exist already")
def clean(self): # check if password 1 and password2 match each other
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:#check if both pass first validation
if self.cleaned_data['password1'] != self.cleaned_data['password2']: # check if they match each other
raise forms.ValidationError("passwords dont match each other")
return self.cleaned_data
def save(self): # create new user
new_user=User.objects.create_user(username=self.cleaned_data['username'],
first_name=self.cleaned_data['first_name'],
last_name=self.cleaned_data['last_name'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
return new_user
Is it OK? however it gives me an error in first_name and last_name. Says django doesn't expect first_name and last_name in save() method.
create_user only supports the username, email and password arguments. First call create_user, then add the extra values to the saved object.
new_user=User.objects.create_user(self.cleaned_data['username'],
self.cleaned_data['email'],
self.cleaned_data['password1'])
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()