formencode Schema add fields dynamically - python

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

Related

Override select method on peewee

I want to implement soft delete mechanism on peewee.
I tried overriding select method on my BaseModel like save method.
class BaseModel(Model):
id = BigAutoField()
created = DateTimeField(default=datetime.datetime.now)
modified = DateTimeField()
deleted = BooleanField(default=False)
def select(self, *args, **kwargs):
super(BaseModel, self).select(*args, **kwargs).where(BaseModel.deleted!=True)
def save(self, *args, **kwargs):
self.modified = datetime.datetime.now()
super(BaseModel, self).save(*args, **kwargs)
class Meta:
database = db
When I try to override select method on my base model it gives following error.
TypeError: select() missing 1 required positional argument: 'self'
Is there any other way to implement soft delete mechanism? What am I missing here?
Don't do this!
Don't ever do this!
Instead just add a new classmethod to your model class and use that instead of .select():
class BaseModel(Model):
#classmethod
def public(cls):
return cls.select().where(cls.deleted != True)
Peewee uses select() internally, and plus, if you choose to override .select() you now have no real way of issuing a query that does not include this filter.

How to use current_user in a flask-wtforms Validator

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)

Get Current User in django-filter Method

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)

Understanding django Form initialisation

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.

django form custom widget for a "ListField" and displaying a list [duplicate]

I'm experimenting with django-nonrel on appengine and trying to use a djangotoolbox.fields.ListField to implement a many-to-many relation. As I read in the documentation a ListField is something that you can use to make a workaround for djamgo-nonrel not supporting many-to-many relations.
This is an excerpt from my model:
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
So if I am getting this right I am creating a list of foreign keys to another class to show a relationship with multiple instances of another class
With this approach everything works fine ... No Exceptions. I can create `MyClass' objects in code and views. But when I try to use the admin interface I get the following error
No form field implemented for <class 'djangotoolbox.fields.ListField'>
So I though I would try something that I haven't done before. Create my own field. Well actually my own form for editing MyClass instances in the admin interface. Here is what I did:
class MyClassForm(ModelForm):
field = fields.MultipleChoiceField(choices=AnotherClass.objects.all(), widget=FilteredSelectMultiple("verbose_name", is_stacked=False))
class Meta:
model = MyClass
then I pass MyClassForm as the form to use to the admin interface
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
admin.site.register(MyClass, MyClassAdmin)
I though that this would work but It doesn't. When I go to the admin interface I get the same error as before. Can anyone tell what I am doing wrong here ... or if you have any other suggestions or success stories of using the ListField, SetField, etc. from djangotoolbox.fields in the admin interface it would be very much appreciated.
OK, here is what I did to get this all working ...
I'll start from the beginning
This is what what my model looked like
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
I wanted to be able to use the admin interface to create/edit instances of this model using a multiple select widget for the list field. Therefore, I created some custom classes as follows
class ModelListField(ListField):
def formfield(self, **kwargs):
return FormListField(**kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
"""
This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
"""
widget = ListFieldWidget
def clean(self, value):
#TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
return value
These classes allow the listfield to be used in the admin. Then I created a form to use in the admin site
class MyClassForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyClasstForm,self).__init__(*args, **kwargs)
self.fields['field'].widget.choices = [(i.pk, i) for i in AnotherClass.objects.all()]
if self.instance.pk:
self.fields['field'].initial = self.instance.field
class Meta:
model = MyClass
After having done this I created a admin model and registered it with the admin site
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
def __init__(self, model, admin_site):
super(MyClassAdmin,self).__init__(model, admin_site)
admin.site.register(MyClass, MyClassAdmin)
This is now working in my code. Keep in mind that this approach might not at all be well suited for google_appengine as I am not very adept at how it works and it might create inefficient queries an such.
As far as I understand, you're trying to have a M2M relationship in django-nonrel, which is not an out-of-the-box functionality. For starters, if you want a quick hack, you can go with this simple class and use a CharField to enter foreign keys manually:
class ListFormField(forms.Field):
""" A form field for being able to display a djangotoolbox.fields.ListField. """
widget = ListWidget
def clean(self, value):
return [v.strip() for v in value.split(',') if len(v.strip()) > 0]
But if you want to have a multiple selection from a list of models normally you'd have to use ModelMultipleChoiceField, which is also not functional in django-nonrel. Here's what I've done to emulate a M2M relationship using a MultipleSelectField:
Let's say you have a M2M relationship between 2 classes, SomeClass and AnotherClass respectively. You want to select the relationship on the form for SomeClass. Also I assume you want to hold the references as a ListField in SomeClass. (Naturally you want to create M2M relationships as they're explained here, to prevent exploding indexes if you're working on App Engine).
So you have your models like:
class SomeClass(models.Model):
another_class_ids = ListField(models.PositiveIntegerField(), null=True, blank=True)
#fields go here
class AnotherClass(models.Model):
#fields go here
And in your form:
class SomeClassForm(forms.ModelForm):
#Empty field, will be populated after form is initialized
#Otherwise selection list is not refreshed after new entities are created.
another_class = forms.MultipleChoiceField(required=False)
def __init__(self, *args, **kwargs):
super(SomeClassForm,self).__init__(*args, **kwargs)
self.fields['another_class'].choices = [(item.pk,item) for item in AnotherClass.objects.all()]
if self.instance.pk: #If class is saved, highlight the instances that are related
self.fields['another_class'].initial = self.instance.another_class_ids
def save(self, *args, **kwargs):
self.instance.another_class_ids = self.cleaned_data['another_class']
return super(SomeClassForm, self).save()
class Meta:
model = SomeClass
Hopefully this should get you going for the start, I implemented this functionality for normal forms, adjust it for admin panel shouldn't be that hard.
This could be unrelated but for the admin interface, be sure you have djangotoolbox listed after django.contrib.admin in the settings.. INSTALLED_APPS
You could avoid a custom form class for such usage by inquiring for the model object
class ModelListField(ListField):
def __init__(self, embedded_model=None, *args, **kwargs):
super(ModelListField, self).__init__(*args, **kwargs)
self._model = embedded_model.embedded_model
def formfield(self, **kwargs):
return FormListField(model=self._model, **kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
widget = ListFieldWidget
def __init__(self, model=None, *args, **kwargs):
self._model = model
super(FormListField, self).__init__(*args, **kwargs)
self.widget.choices = [(unicode(i.pk), i) for i in self._model.objects.all()]
def to_python(self, value):
return [self._model.objects.get(pk=key) for key in value]
def clean(self, value):
return value

Categories

Resources