Flask-WTF and field name - flask

I'd like to change name attribure of SubmitField (which is "submit" by default). What have I tried:
from flask.ext.wtf import Form, SubmitField
class BaseForm(Form):
submit = SubmitField('Create', id='submit_button', name='submit_button') #1
submit = SubmitField('Create', id='submit_button', _name='submit_button') #2
def __init__(self, edit=None, *args, **kwargs):
self.submit.kwargs['name'] = 'submit_button' #5
self.submit.kwargs['_name'] = 'submit_button' #6
All of them failed with different error. If I removing name or _name parameter all working fine. I found that name attribute is passed by flask.ext.wtf.Form but I have no sense how to fix it.
NOTE: I am using not trivial import of my form: it is imported in run-time, inside of view's method, not at the top of file. I cannot and will not change it because of technical issues. I.e. if I copy-pasting my code in IDLE it is working ok. But if I importing this code inside port method of MethodView it is fails.

The simplest way to change the name is to change the name of the field:
class BaseForm(Form):
# This one's name will be submit_button
submit_button = SubmitField('Create')
# This one's name will be another_button
another_button = SubmitField('Do Stuff')

Have you looked at extending the SubmitField itself with a custom constructor. See an example here
Basically you would do something like:
class CustomSubmitField(SubmitField):
def __init__(self, label='', validators=None,_name='',**kwargs):
super(SubmitField, self).__init__(label, validators, **kwargs)
custom_name = "whatever"
self._name = custom_name

Related

How to access the parent model of a Django-CMS plugin

I created 2 django-cms plugins, a parent "Container" that can contain multiple child "Content" plugins.
When I save the child plugin I would like to access the model of the parent plugin.
from cms.plugin_pool import plugin_pool
from cms.plugin_base import CMSPluginBase
from .models import Container, Content
class ContainerPlugin(CMSPluginBase):
model = Container
name = "Foo Container"
render_template = "my_package/container.html"
allow_children = True
child_classes = ["ContentPlugin"]
class ContentPlugin(CMSPluginBase):
model = content
name = "Bar Content"
render_template = "my_package/content.html"
require_parent = True
parent_classes = ["ContainerPlugin"]
allow_children = True
def save_model(self, request, obj, form, change):
response = super(ContentPlugin, self).save_model(
request, obj, form, change
)
# here I want to access the parent's (container) model, but how?
return response
plugin_pool.register_plugin(ContainerPlugin)
plugin_pool.register_plugin(ContentPlugin)
obj is the current plugin instance, so I can get all the properties of this model, but I can't figure out how to access the parent's plugin model. There is obj.parent, but it's not the plugin instance as far as I can tell. Also tried playing around with self.cms_plugin_instance and obj.parent.get_plugin_instance() but with no success.
Any advice?
Given a plugin instance,instance.get_plugin_instance() method returns a tuple containing:
instance - The plugin instance
plugin - the associated plugin class instance
get_plugin_instance
so something like this to get the parent object:
instance, plugin_class = object.parent.get_plugin_instance()
Option 1
Looking at the source code of CMSPluginBase, you might be able to use the implementation of get_child_classes. Unfortunately, that method really only returns the class names, so you cannot use it directly. But I think it actually does iterate over the child instances to get the class names:
def get_child_classes(self, slot, page):
from cms.utils.placeholder import get_placeholder_conf
template = page and page.get_template() or None
# config overrides..
ph_conf = get_placeholder_conf('child_classes', slot, template, default={})
child_classes = ph_conf.get(self.__class__.__name__, self.child_classes)
if child_classes:
return child_classes
from cms.plugin_pool import plugin_pool
installed_plugins = plugin_pool.get_all_plugins(slot, page)
return [cls.__name__ for cls in installed_plugins]
What you'd be interested in would be these two lines:
from cms.plugin_pool import plugin_pool
installed_plugins = plugin_pool.get_all_plugins(slot, page)
Option 2
Another way (the one I am using in my code) is to use signals, though this also requires finding the correct objects. The code is not very readable imho (see my lingering inline comments), but it works. It was written a while ago but I am still using it with django-cms 3.2.3.
The placeholder names really are the names that you have configured for your placeholders. It's certainly preferable to move that into the settings or somewhere. I'm not sure why I haven't done that, though.
I'd be interested in your solution!
# signals.py
import itertools
import logging
from cms.models import CMSPlugin
from cms.plugin_pool import plugin_pool
from django.db import ProgrammingError
from django.db.models.signals import post_save
logger = logging.getLogger(__name__)
_registered_plugins = [CMSPlugin.__name__]
def on_placeholder_saved(sender, instance, created, raw, using, update_fields, **kwargs):
"""
:param sender: Placeholder
:param instance: instance of Placeholder
"""
logger.debug("Placeholder SAVED: %s by sender %s", instance, sender)
# TODO this is totally ugly - is there no generic way to find out the related names?
placeholder_names = [
'topicintro_abstracts',
'topicintro_contents',
'topicintro_links',
'glossaryentry_explanations',
]
fetch_phs = lambda ph_name: _fetch_qs_as_list(instance, ph_name)
container = list(itertools.chain.from_iterable(map(fetch_phs, placeholder_names)))
logger.debug("Modified Placeholder Containers %s (%s)", container, placeholder_names)
if container:
if len(container) > 1:
raise ProgrammingError("Several Containers use the same placeholder.")
else:
# TODO change modified_by (if possible?)
container[0].save()
def _fetch_qs_as_list(instance, field):
"""
:param instance: a model
:param field: optional field (might not exist on model)
:return: the field values as list (not as RelatedManager)
"""
qs = getattr(instance, field)
fields = qs.all() if qs else []
return fields
def on_cmsplugin_saved(sender, instance, created, raw, using, update_fields, **kwargs):
"""
:param sender: CMSPlugin or subclass
:param instance: instance of CMSPlugin
"""
plugin_class = instance.get_plugin_class()
logger.debug("CMSPlugin SAVED: %s; plugin class: %s", instance, plugin_class)
if not plugin_class.name in _registered_plugins:
post_save.connect(on_cmsplugin_saved, sender=plugin_class)
_registered_plugins.append(plugin_class.name)
logger.info("Registered post_save listener with %s", plugin_class.name)
on_placeholder_saved(sender, instance.placeholder, created, raw, using, update_fields)
def connect_existing_plugins():
plugin_types = CMSPlugin.objects.order_by('plugin_type').values_list('plugin_type').distinct()
for plugin_type in plugin_types:
plugin_type = plugin_type[0]
if not plugin_type in _registered_plugins:
plugin_class = plugin_pool.get_plugin(plugin_type)
post_save.connect(on_cmsplugin_saved, sender=plugin_class)
post_save.connect(on_cmsplugin_saved, sender=plugin_class.model)
_registered_plugins.append(plugin_type)
_registered_plugins.append(plugin_class.model.__name__)
logger.debug("INIT registered plugins: %s", _registered_plugins)
post_save.connect(on_cmsplugin_saved, sender=CMSPlugin)
You have to setup these signals somewhere. I'm doing this in my urls.py, though the app config might be the suitable location for it? (I'm trying to avoid app configs.)
# This code has to run at server startup (and not during migrate if avoidable)
try:
signals.connect_existing_plugins()
except db.utils.ProgrammingError:
logger.warn('Failed to setup signals (if your DB is not setup (not tables), you can savely ignore this error.')
By the fact that children plugins always inherit the context. In the parent template you be able to do:
{% with something=instance.some_parent_field %}
{% for plugin in instance.child_plugin_instances %}
{% render_plugin plugin %}
{% endfor %}
{% endwith %}
And use something in your child template.

Passing a variable defined in previous form to another form

So I have this flask app I'm making and I need some help with a variable access.
Most of the time, when you define a form in flask, you'll do the following :
class MyForm(Form):
my_field = StringField('I'm a field')
my_submit = SubmitField('Go!')
And when the time comes where you need the form, you'll declare an instance of that class with form = MyForm()
Up to here, it's all good, However :
If you want say, a SelectField (Dropdown) where the choices depend on the answers of a previous form, you need to be able to give the new form those choices. This is what I'm trying to achieve, but I can't get a variable to keep its contents.
Here is my Form code (Above the page code):
class DataMappingForm(Form):
dm_choices = #I need this array !
DMpatient_id = SelectField(u'Select Patient ID Column',
choices=dm_choices, validators=[Required()])
...
Here is my Page code :
#app.route('/upload', methods=['GET','POST'])
def upload():
uform = SomeOtherForm()
if uform.is_submitted() and uform.data['Usubmit']:
#Do stuff from previous form
# and declare array_of_choices
dmform = DataMappingForm() #Needs array_of_choices to work
...
What I've tried so far :
session['dm_choices'] gives me a working outside of request context error
global variables, get reset for some reason
overloading the __init__ of Form by adding the array but i can't access it in the parts above the __init__ function.
I should mention, this all needs to be on the same page.
Is there a way to pass this array_of_choices to my DataMappingForm class ?
EDIT This is what it looked like when I trid the __init__ overload:
class DataMappingForm(Form):
def __init__(self, dm_choices, *args, **kwargs):
self.dm_choices = dm_choices
Form.__init__(self, *args, **kwargs)
DMpatient_id = SelectField(u'Select Patient ID Column',
choices=dm_choices, validators=[Required()])
#I've tried putting it above or below, I get 'dm_choices is not defined'
I've Got it ! Thanks to #synonym for pointing me in the right direction with your last link.
All you need to do is declare a function in which the class is defined. You then pass the variable to the function, and it will be accessible within the class.
Finally, make the function return the form object.
Example :
def makeMyForm(myArray):
def class MyForm(Form):
my_select_field = SelectField(u'I'm a select field', choices=myArray)
my_submit = SubmitField(u'Go!')
return MyForm()
And to make the form, you use :
form = makeMyForm(theArrayYouWant)
And VoilĂ  !
Note : As I've had the problem before, I'll mention that the Array is composed of tuples :
myArray = [('value','What you see'),('value2','What you see again')]
If you want to dynamically change the choices of a SelectField the following should work:
class DataMappingForm(Form):
def __init__(self, choices)
self.DMpatient_id.choices = choices
DMpatient_id = SelectField(u'Select Patient ID Column') #note that choices is absent
If you want fully dynamic fields you can create the class dynamically in a function. From the WTForms Documentation:
def my_view():
class F(MyBaseForm):
pass
F.username = StringField('username')
for name in iterate_some_model_dynamically():
setattr(F, name, StringField(name.title()))
form = F(request.POST, ...)
# do view stuff
In that case you can customize the form as much as you want. Of course in the case you only want to customize the choices the first approach should be enough.

TypeError: unbound method make_valid_name() must be called with User instance as first argument (got unicode instance instead)

I was migrating my old code from
flask sqlalchemy
from flask.ext.sqlalchemy import SQLAlchemy
to
sqlalchemy.orm - session maker
from sqlalchemy.orm import sessionmaker
Model
#staticmethod
class User(Base):
__tablename__ = 'user'
id = Column(Integer , primary_key=True)
name = Column(String(20) , unique=True, nullable=False)
.
.
.
def make_valid_name(name):
return re.sub('[^a-zA-Z0-9_\.]', '', name)
Form
def validate(self):
.
.
.
if self.name.data != User.make_valid_name(self.name.data):
self.name.errors.append('Please use letters, numbers, dots and underscores only.')
return False
While making a call to method self.validate() throws following error
TypeError: unbound method make_valid_name() must be called with User instance as first argument (got unicode instance instead)
I am not sure what needs to be modified, how can i validate "self.name.data" From field data
Any help on this will be great.
That error means User.make_valid_name is an instance method, not a static method as you expected. The solution, if you want to keep your existing usage, is to decorate the method with staticmethod, not the class:
class User(Base):
...
#staticmethod
def make_valid_name(name):
return ...
Alternatively, you can make it a classmethod instead:
class User(Base):
...
#classmethod
def make_valid_name(cls, name):
return ...
Only option was to create User object again and initialize attributes then pass that as parameter instead of form object name directly
user = User(
name = self.name.data
)
if self.name.data != User.make_valid_name(user):
and in model accessing same by
user.name
:# have to rewrite everything.
Any other optimal solution will also be appreciated. If someone else faces similar issue in future.

django form: Passing parameter from view.py to forms gives out error

Newbie question:
I need to accept a parameter in a form from a method in views.py but it gave me troubles. In the view I created a method with following snippet:
def scan_page(request):
myClient = request.user.get_profile().client
form = WirelessScanForm(client = myClient) # pass parameter to the form
and in the forms.py I defined the following form:
class WirelessScanForm(forms.ModelForm):
time = forms.DateTimeField(label="Schedule Time", widget=AdminSplitDateTime())
def __init__(self,*args,**kwargs):
myClient = kwargs.pop("client") # client is the parameter passed from views.py
super(WirelessScanForm, self).__init__(*args,**kwargs)
prob = forms.ChoiceField(label="Sniffer", choices=[ x.sniffer.plug_ip for x in Sniffer.objects.filter(client = myClient) ])
But django keeps giving me error saying: TemplateSyntaxError: Caught NameError while rendering: name 'myClient' is not defined(This error happens in the query)
I'm afraid it would be something stupid missing here, but I cannot really figure out why. Please help, thanks.
Assuming I've corrected your formatting properly, you have an indentation issue: prob is outside __init__, so doesn't have access to the local myClient variable.
However if you bring it inside the method, it still won't work, as there are two other issues: first, simply assigning a field to a variable won't set it on the form; and second, the choices attribute needs a list of 2-tuples, not just a flat list. What you need is this:
def __init__(self,*args,**kwargs):
myClient = kwargs.pop("client") # client is the parameter passed from views.py
super(WirelessScanForm, self).__init__(*args,**kwargs)
self.fields['prob'] = forms.ChoiceField(label="Sniffer", choices=[(x.plug_ip, x.MY_DESCRIPTIVE_FIELD) for x in Sniffer.objects.filter(client = myClient)])
Obviously replace MY_DESCRIPTIVE_FIELD with the actual field you want displayed in the choices.

Django + WSGI: Force object initialization?

I have a form class that looks something like this:
class RegisterForm(Form):
username = Field(model_field='username', filters=validators.minlength(3))
You'll notice that username is a class variable. I believe this means that Field will be constructed once the first time the RegisterForm is used (after apache is restarted). It will not be re-constructed between page reloads (unless a 2nd WSGI instance (?) is spawned, but we won't get into that). I noticed this because some of the values I've set in Field.__init__ are not being reset.
However, Form.__init__ does seem to be called each page reload. I guess that's because of the way I'm using it? I'm actually constructing it like form = RegisterForm(request) at each page request.
So... supposing I don't want the [class variables in] RegisterForm to be "cached" and have the Fields re-initialized at each request... how would I do that? (without modifying the syntax of RegisterForm; you can do whatever inside the base class, Form)
You could update the class variable each instantiation:
class ContactForm(forms.Form):
username = Field(model_field='username', filters=validators.minlength(3))
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
ContactForm.username = Field(model_field='username', filters=validators.minlength(3))
You could define the class within a function. That way it gets constructed each time the function is called.
def gotclass(data):
class InnerClass(object):
someattr = DoSomethingWith(data)
return InnerClass
MyNewClass = gotclass(42)
someobj = MyNewClass()