I have this form class :
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
self.notvalidate = kwargs.pop('notvalidate',False)
super(MyForm, self).__init__(*args, **kwargs)
email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,maxlength=75)))
(...)
if not notvalidate:
def clean_email(self):
email = self.cleaned_data.get("email")
if email and User.objects.filter(email=email).count() > 0:
raise forms.ValidationError(
_(u"Email already used."))
return email
Although in init I set self.notvalidate value to either True(if was given) or False inside the body of MyForm I'm getting name 'notvalidate' is not defined (or if I check for self.notvalidate - name 'self' is not defined). What is wrong ?
Move the if not notvalidate into the clean_email method, and reference it using self.notvalidate.
def clean_email(self):
if not self.notvalidate:
email = self.cleaned_data.get("email")
if email and User.objects.filter(email=email).count() > 0:
raise forms.ValidationError(
_(u"Email already used."))
return email
Also, you may want to rename the flag to should_validate_email and lose the negation.
What are you trying to achieve is changing the class level attribute clean_email but you want to do that using instance attribute self.notvalidate, so you are doing contradictory things here. Simplest way to not validate would be to check in clean_email and return e.g
def clean_email(self):
if self.notvalidate:
return
....
But if due to some mysterious reason you do not want clean_mail method to be existing in the class at all, you need to create a class using metaclass or simpler way would be to call a function to create class e.g.
def createFormClass(validate):
class MyClass(object):
if validate:
def clean_email(self):
pass
return MyClass
MyClassValidated = createFormClass(True)
MyClassNotValidated = createFormClass(False)
Though I will strongly suggest NOT to do this.
Change notvalidate to self.notvalidate.
Related
Is there a way to current_user in a custom field validator. It's almost as if the validator is instantiated at system set up time when there are no users rather then during the session call.
Thanks!
I haven't found a way to set validators at runtime, but there is a way to define a validation function for a field on a form class, and that function can be based on data passed to form at construction time. Here's an example
class MyForm(flask_wtf.FlaskForm):
user_id_field = wtforms.StringField("User ID")
def __init__(self, valid_user_id, **kwargs):
super().__init__(**kwargs)
self.valid_user_id = valid_user_id
def validate_user_id_field(self, field):
"""
Function name must be 'validate_' + name of field you want to validate
"""
if field.data != self.valid_user_id
raise wtforms.validators.ValidationError("Wrong user id!")
# In endpoint definition:
my_form = MyForm(formdata=flask.request.form, valid_user_id=flask_login.current_user.id)
my_form.validate()
Edit
Ha, turns out setting validators at runtime is incredibly easy, it's just that documentation doesn't mention it anywhere.
If you have a form with field some_field, you can simply set self.some_field.validators = [...] in form's constructor, e.g.
class MyForm(flask_wtf.FlaskForm):
some_field = wtforms.HiddenField()
def __init__(self, some_data, **kwargs):
super().__init__(**kwargs)
self.some_field.validators = [MyValidator(some_data)]
# In endpoint
form = MyForm(formdata=flask.request.form, some_data=some_data)
Or you can set validators directly in endpoint handler:
form = MyForm(formdata=flask.request.form)
form.some_field.validators = [MyValidator(some_data)]
Have you tried doing something like the below? I don't know if it works but it allows you to define a static class at runtime and pass the current_user as an argument which the class inherits from global scope and doesnt mutate.
def form_generator(current_user):
class web_form:
user_email = StringFiled('Email', validators=[Email()])
def validate_user_email(self, field)
if Model.query.filter(field.data == current_user.email).first()
return
else:
return ValidationError("Email is not current user's")
return web_form()
In your flask route then try:
form = form_generator(current_user)
Sorry this is untested, i just saw it and posted a response. But if you try it let me know if it succeeds, or more interestingly why it failed!!
While adding Validators at runtime is possible, it is not a good solution. The validator would have to be added after any initialization of the form!
In my opinion a better solution is to simply use a local import:
class ValidateIfUserMayDoThis:
def __init__(self, message):
self.message
def __call__(self, form, field):
from flask_login import current_user # DO THE IMPORT HERE!!!!
if current_user.username in ['Donald']:
raise ValidationError(self.message)
I have a filter where I need to access the request.user. However, django-filter does not pass it. Without using the messy inspect.stack() is there a way to get the current user in the method member_filter below?
class ClubFilter(django_filters.FilterSet):
member = django_filters.MethodFilter(action='member_filter')
class Meta:
model = Club
fields = ['member']
def member_filter(self, queryset, value):
# get current user here so I can filter on it.
return queryset.filter(user=???)
For example this works but feels wrong...
def member_filter(self, queryset, value):
import inspect
request_user = None
for frame_record in inspect.stack():
if frame_record[3] == 'get_response':
request_user = frame_record[0].f_locals['request'].user
print(request_user)
is there maybe a way to add this to some middleware that injects user into all methods? Or is there a better way?
Yes, you can do it, and it's very easy.
First, define __init__ method in your ClubFilter class that will take one extra argument:
class ClubFilter(django_filters.FilterSet):
# ...
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(ClubFilter, self).__init__(*args, **kwargs)
With having your user saved into attribute inside ClubFilter, you can use it in your filter. Just remember to pass current user from your view inside FilterSet.
Try self.request.user.
Why it must work.
you can access the request instance in FilterSet.qs property, and then filter the primary queryset there.
class ClubFilter(django_filters.FilterSet):
member = django_filters.MethodFilter(action='member_filter')
class Meta:
model = Club
fields = ['member']
#property
def qs(self):
primary_queryset=super(ClubFilter, self).qs
return primary_queryset.filter(user=request.user)
I have a class like so:
class EmailForm(forms.Form):
users = forms.MultipleChoiceField(required=False, widget=MultipleHiddenInput())
subject = forms.CharField(max_length=100)
message = forms.Textarea()
def __init__(self, users, *args, **kwargs):
super(EmailForm, self).__init__(*args, **kwargs)
self.users.choices = users
# self.fields['users'].choices = []
The commented line at the bottom works perfectly if I use it instead of self.users.
Am I right in thinking that users, subject and message are class level so that is why they are popped out of the attribute list?
So self.fields is the per object copy of the attributes in case I want to change them in some way?
Thanks.
The Form class uses the DeclarativeFieldsMetaclass, which enables the declarative syntax for the fields.
The implementation means that the form class and instance does not actually have an attribute self.field_name for each field. That is why trying to use self.users gives an error.
The fields of the form instance can be accessed as self.fields, which is created when you call super in the __init__ method.
The fields of the form class can be accessed as self.base_fields.
Let's take, for example, a User Schema where the site admin sets the number of requested phone numbers:
class MySchema(Schema):
name = validators.String(not_empty=True)
phone_1 = validators.PhoneNumber(not_empty=True)
phone_2 = validators.PhoneNumber(not_empty=True)
phone_3 = validators.PhoneNumber(not_empty=True)
...
Somehow I thought I could simply do:
class MySchema(Schema):
name = validators.String(not_empty=True)
def __init__(self, *args, **kwargs):
requested_phone_numbers = Session.query(...).scalar()
for n in xrange(requested_phone_numbers):
key = 'phone_{0}'.format(n)
kwargs[key] = validators.PhoneNumber(not_empty=True)
Schema.__init__(self, *args, **kwargs)
since I read in FormEncode docs:
Validators use instance variables to store their customization
information. You can use either subclassing or normal instantiation to
set these.
and Schema is called in docs as a Compound Validator and is a subclass of FancyValidator so I guessed it's correct.
But this does not work: simply added phone_n are ignored and only name is required.
Update:
Also I tried both overriding __new__ and __classinit__ before asking with no success...
i had the same problem, i found a solution here:
http://markmail.org/message/m5ckyaml36eg2w3m
all the thing is to use the add_field method of schema in youre init method
class MySchema(Schema):
name = validators.String(not_empty=True)
def __init__(self, *args, **kwargs):
requested_phone_numbers = Session.query(...).scalar()
for n in xrange(requested_phone_numbers):
key = 'phone_{0}'.format(n)
self.add_field(key, validators.PhoneNumber(not_empty=True))
i don't think there's a need to call the parent init
I want use a model to save the system setting for a django app, So I want to limit the model can only have one record, how to do the limit?
Try this:
class MyModel(models.Model):
onefield = models.CharField('The field', max_length=100)
class MyModelAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
# if there's already an entry, do not allow adding
count = MyModel.objects.all().count()
if count == 0:
return True
return False
An easy way is to use the setting's name as the primary key in the settings table. There can't be more than one record with the same primary key, so that will allow both Django and the database to guarantee integrity.
William is right, but I guess this is the best practice
def has_add_permission(self, *args, **kwargs):
return not MyModel.objects.exists()
As reported in the official Django Documentation:
Note: Don’t use this if all you want to do is determine if at least one result exists.
It’s more efficient to use exists().
https://docs.djangoproject.com/en/dev/ref/models/querysets/#when-querysets-are-evaluated
Overwriting has_add_permission works, but in the given examples it breaks the permissions system in Django(staff without necessary permissions can add settings). Here's a one that doesn't break it:
class SettingAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
base_add_permission = super(SettingAdmin, self).has_add_permission(request)
if base_add_permission:
# if there's already an entry, do not allow adding
count = Setting.objects.all().count()
if count == 0:
return True
return False
A model with a single allowed row is nothing more than a perverted form of a "persisted object" -- maybe even a "persisted singleton"? Don't do that, that's not how models work.
Check out https://github.com/danielroseman/django-dbsettings
It looks like Ali Reza's answer but you can update the existed records and return the error message to any form that uses this model. I believe it is reliable and much easy to control.
class MyModel(models.Model):
...
def clean(self):
super().clean()
if not self.id and MyModel.objects.exists():
raise ValidationError('You cannot add more somethings.')
The following is a class I have created which can be used as a singleton class.
from django.db import models
class SingletonModel(models.Model):
class Meta:
abstract = True
def save(self, *args, **kwargs):
self.__class__.objects.exclude(id=self.id).delete()
super(SingletonModel, self).save(*args, **kwargs)
#classmethod
def load(cls):
try:
return cls.objects.get()
except cls.DoesNotExist:
return cls()
From the above SingletonModel we can create multiple models, all of which will be having only one record
class ProjectSettings(SingletonModel):
max_tickets = models.IntegerField(default=15)
min_tickets = models.IntegerField(default=2)
...
We can access the only object of the settings model as follows
ProjectSettings.load().max_tickets
It is also possible to register ProjectSettings to django admin
#admin.register(ProjectSettings)
class ProjectSettingsAdmin(admin.ModelAdmin):
list_display = [field.name for field in ProjectSettings._meta.get_fields()]
def has_delete_permission(self, request, obj=None):
# Nobody is allowed to delete
return False
You can rewrite the save method on your model. Whenever the user wants to register a new record, you can check the existence then, either save the record or, reject the request.
class MyModel(models.Model):
title = models.CharField(...)
body = models.TextField(...)
def save(self, *args, **kwargs):
if MyModel.objects.exists():
raise ValueError("This model has already its record.")
else:
super().save(*args, **kwargs)
You can also use validators. I prefer to use this method.
Hope you find it useful.