Dynamically change form labels in Django/oTree - python

I have a form label that I want to have variable content. I expose to my template a variable called outgroup which I want to be included in the formfield label. My current (incorrect) attempt looks like this:
{% formfield sent_amount label="How much do you want to send to a "+{{outgroup}} %}
But this obviously doesn't work. What is the correct way to get a variable into the label method?

Why couldn't you do something like this in the view?
def study(request, studyID):
if request.method == 'GET' and request.user.is_authenticated:
form = ContactForm()
form.fields['from_email'].label = "{}, what's your email?".format(.get_full_name())
Use form.fields["your_label_id"] to set it.

When the dynamic content to be inserted in the label is a string, instead of an integer, you only need to add () after referring to the dynamic variable (in the example below, after self.player.type)... Like this:
FIRST go to pages.py:
class Contribute(Page):
form_model = 'player'
form_fields = ['type']
def vars_for_template(self):
return dict(
contribution_label='How many {} do you want to contribute?'.format(self.player.type())
)
SECOND go to the relevant HTML page, Contribute.html
{% formfield player.contribution label=contribution_label %}

Related

Change value of a textarea using flask-wtforms

I am using flask-wtforms to create a Textarea.
body = TextAreaField('body')
I want to change the value of the textarea which you can do in html like this.
<textarea>other value then default</textarea>
How can I integrate this with flask-wtforms?
My template looks like this:
{{ form.body(rows="20") }} <!--texarea -->
With a simple input field I could do something like this:
{{ form.body(value="other value then default") }} <!-- input field -->
I need to set the default value in the template itself. Because it will have info over the article the page is about.
Weird that 2 years after this question was asked, there's still no standard way to implement this.
What I do is write it out in plain HTML:
<textarea id="body" name="body">{{ value }}</textarea>
This works fine and the textarea is validated normally
With TextArea you have to use the default value so it renders with that.
body = TextAreaField('body', default='Some other value')
You can create a widget that defines an entire alternative html behavior, like so:
in widgets.py
from markupsafe import Markup
from wtforms.widgets import html_params
class BodyWidget(object):
data_template = (
'<textarea %(text)s >other value then default</textarea>'
)
def __call__(self, field, **kwargs):
kwargs.setdefault("id", field.id)
kwargs.setdefault("name", field.name)
template = self.data_template
return Markup(
template % {"text": html_params(type="text", value=(field.data or ""), rows=20, cols=50, **kwargs)}
)
in forms.py
from widgets import BodyWidget
body = TextAreaField('body', widget=BodyWidget())
I used kind of a hack. Using javascript to set the value of the textarea.
<script>document.getElementById('body-input').value="other value then default";</script>
Although not optimal, it works.

Changing Id of django form in for loop

I'm stuck in my code. Need help.
This is my front end. I am rendering forms stored in "form_list".
The problem is that the forms stored are of same type and thus produce input fields with same "id" and same "name".
This is my view:-
#login_required
def VideoLinkView(request):
"""view to save the video links """
current_form_list = []
current_form = None
if request.method == 'GET':
vl = VideoLink.objects.filter(company=CompanyModel.objects.get(owner=request.user))
for link in vl:
current_form = VideoLinkForm(link.__dict__)
current_form_list.append(current_form)
return render(request, "premium/video_link.html", context={'form_list':current_form_list})
This is my html template :-
{% for form in form_list %}
<div class="form-group">
<label for="id_video_link">Video Link:</label>
{{ form.video_link }}
</div>
{% endfor %}
How can I create different "id" and different "name" in each iteration of for loop's input tag, automatically without having knowledge of no form stored in form_list.
I tried {{ forloop.counter}} it didn't worked, perhaps I made some mistake. Also, raw python don't work in template.
Thanks in Advance.
The way you are creating your forms is wrong in two ways. Firstly, the first positional argument is for the values submitted by the user; passing this arg triggers validation, among other things. If you are passing values for display to prepopulate the form, you must use the initial kwarg:
current_form = VideoLinkForm(initial={...dict_of_values...})
However, even that is not correct for your use case here. link is a model instance; you should use the instance kwarg:
current_form = VideoLinkForm(instance=link)
Now, to solve the problem you asked, you could just pass a prefix as well as I originally recommended:
for i, link in enumerate(vl):
current_form = VideoLinkForm(instance=link, prefix="link{}".format(i))
However, now that you have shown all the details, we can see that this is not the best approach. You have a queryset; so you should simply use a model formset.
from django.forms import modelformset_factory
VideoLinkFormSet = modelformset_factory(VideoLink, form=VideoLinkForm, queryset=vl)
current_form_list = VideoLinkFormSet()

wtforms field created through setattr() loses properties

I am trying to create a form in python / Flask that will add some dynamic slider inputs to a set of standard fields. I am struggling to get it to work properly, though.
Most of the web forms in my app are static, created through wtforms as in:
class CritiqueForm(Form):
rating = IntegerField('Rating')
comment = TextAreaField('Comments')
submit = SubmitField('Save Critique')
When I am explicit like that, I can get the expected results by using the CritiqueForm() in the view and passing the form object to render in the template.
However, I have a critique form that needs to dynamically include some sliders for rating criteria specific to a particular record. The number of sliders can vary form one record to the next, as will the text and IDs that come from the record's associated criteria.
When I looked for some ways to handle this, I found a possible solution from dezza (Dynamic forms from variable length elements: wtforms) by creating a class method in the form, which I could then call before instantiating the form I want to render. As in:
class CritiqueForm(Form):
rating = IntegerField('Rating')
comment = TextAreaField('Comments')
submit = SubmitField('Save Critique')
#classmethod
def append_slider(cls, name, label):
setattr(cls, name, IntegerField(label))
return cls
where 'append_slider' is always an IntegerField with a label I provide. This works enough to allow me to populate the criteria sliders in the view, as in:
#app.route('/critique/<url_id>/edit', methods=['GET', 'POST'])
def edit_critique(url_id):
from app.models import RecordModel
from app.models.forms import CritiqueForm
record = RecordModel.get_object_by_url_id(url_id)
if not record: abort(404)
# build editing form
ratings = list()
for i, criterium in enumerate(record.criteria):
CritiqueForm.append_slider('rating_' + str(i+1),criterium.name)
ratings.append('form.rating_' + str(i+1))
form = CritiqueForm(request.form)
# Process valid POST
if request.method=='POST' and form.validate():
# Process the submitted form and show updated read-only record
return render_template('critique.html')
# Display edit form
return render_template('edit_critique.html',
form=form,
ratings=ratings,
)
The ratings list is built to give the template an easy way to reference the dynamic fields:
{% for rating_field in ratings %}
{{ render_slider_field(rating_field, label_visible=True, default_value=0) }}
{% endfor %}
where render_slider_field is a macro to turn the IntegerField into a slider.
With form.rating—an integer field explicitly defined in CritiqueForm—there is no problem and the slider is generated with a label, as expected. With the dynamic integer fields, however, I cannot reference the label value in the integer field. The last part of the stack trace looks like:
File "/home/vagrant/msp/app/templates/edit_critique.html", line 41, in block "content"
{{ render_slider_field(rating_field, label_visible=True, default_value=0) }}
File "/home/vagrant/msp/app/templates/common/form_macros.html", line 49, in template
{% set label = kwargs.pop('label', field.label.text) %}
File "/home/vagrant/.virtualenvs/msp/lib/python2.7/site-packages/jinja2/environment.py", line 397, in getattr
return getattr(obj, attribute)
UndefinedError: 'str object' has no attribute 'label'
Through some debugging, I have confirmed that none of the expected field properties (e.g., name, short_name, id ...) are showing up. When the dust settles, I just want this:
CritiqueForm.append_slider('rating', 'Rating')
to be equivalent to this:
rating = IntegerField('Rating')
Is the setattr() technique inherently limiting in what information can be included in the form, or am I just initializing or referencing the field properties incorrectly?
EDIT:
Two changes allowed my immediate blockers to be removed.
1) I was improperly referencing the form field in the template. The field parameters (e.g., label) appeared where expected with this change:
{% for rating_field in ratings %}
{{ render_slider_field(form[rating_field], label_visible=True, default_value=0) }}
{% endfor %}
where I replace the string rating_field with form[rating_field].
2) To address the problem of dynamically changing a base class from the view, a new form class ThisForm() is created to extend my base CritiqueForm, and then the dynamic appending is done there:
class ThisForm(CritiqueForm):
pass
# build criteria form fields
ratings = list()
for i, criterium in enumerate(record.criteria):
setattr(ThisForm, 'rating_' + str(i+1), IntegerField(criterium.name))
ratings.append('rating_' + str(i+1))
form = ThisForm(request.form)
I don't know if this addresses the anticipated performance and data integrity problems noted in the comments, but it at least seems a step in the right direction.
setattr(obj, name, value) is the very exact equivalent of obj.name = value - both being syntactic sugar for obj.__setattr__(name, value) -, so your problem is not with "some limitation" of setattr() but first with how wtform.Form works. If you look at the source code, you can see there's much more to make fields and form work together than just having the fields declared as class attributes (metaclass magic involved...). IOW, you'll have to go thru the source code to find out how to dynamically add fields to a form.
Also, your code tries to set the new fields on the class itself. This is a big NO NO in a multiprocess / multithreaded / long-running process environnement with concurrent access - each request will modify the (shared at process level) form class, adding or overriding fields aphazardly. It might seem to work on a single-process single-threaded dev server with a single concurrent user but will break in production with the most unpredictable errors or (worse) wrong results.
So what you want to find out is really how to dynamically add fields to a form instance - or, as an alternative, how to dynamically build a new temporary form class (which is far from difficult really - remember that Python classes are objects too).

Django: custom template tag which takes 2 variables

I want to have a custom template tag which takes two variables as arguments. This is what I have in my template:
{% load accountSum %}
{% accountSum 'account_id' 'account_type' %}
I have read that you need to load the context of these variables but I have not found a working way. So my question is, how do I define the custom template tag in templatetags/accountSum.py?
This is what I have so far:
from django import template
register = template.Library()
def accountSum(context, account_id, account_type):
account_id = context[account_id]
account_type = context[account_type]
# do something with the data
# return the modified data
register.simple_tag(takes_context=True)(accountSum)
You have misunderstood the usage of template tags, I have read that you need to load the context of these variables... context is only required if you need to access/modify the existing context, not if you only need to return the calculated value from the provided arguments.
So, in your case, what you only need is this:
#register.simple_tag
def accountSum(account_id, account_type):
# your calculation here...
return # your return value here
Django document has a more detailed explanation and example that you can follow -- Simple tags
Or, if your intention is to take the context value account_id and account_type and return a modified value on each call, you can simply omit taking the arguments, and simply do this:
#register.simple_tag(take_context=True)
def accountSum(context):
account_id = context['account_id']
account_type = context['account_type']
# do your calculation here...
return # your modified value
Then you can simply call {% accountSum %} in your template.
Or, if you want to dynamically take context content as arguments:
#register.simple_tag(take_context=True)
def accountSum(context, arg1, arg2):
arg1 = context[arg1]
arg2 = context[arg2]
# calculation here...
return # modified value...
And passing arguments in template using string like:
{% accountSum 'account_id' 'account_type' %}
I hope this helps you understand how to use template tags in your case.
updated
What I meant is this (as you don't need to access the context, what you really need is taking arguments just like usual):
#register.simple_tag
def accountSum(arg1, arg2):
# your calculation here...
return # your return value here
and use this in your template:
{% accountSum account.account_id account.account_type %}

Passing variables to template based on db

I am trying to add a feature to my app that would allow me to enable/disable the "Call Me" button based on whether or not I am at [home|the office]. I created a model in the database called setting, it looks like this:
class setting(models.Model):
key = models.CharField(max_length=200)
value = models.CharField(max_length=200)
Pretty simple. There is currently one row, available, the value of it is the string True. I want to be able to transparently pass variables to the templates like this:
{% if available %}
<!-- Display button -->
{% else %}
<!-- Display grayed out button -->
{% endif %}
Now, I could add logic to every view that would check the database, and pass the variable to the template, but I am trying to stay DRY.
What is the best way to do this?
UPDATE
I created a context processor, and added it's path to the TEMPLATE_CONTEXT_PROCESSORS, but it is not being passed to the template
def available(request):
available = Setting.objects.get(key="available")
if open.value == "True":
return {"available":True}
else:
return {}
UPDATE TWO
If you are using the shortcut render_to_response, you need to pass an instance of RequestContext to the function.
from the django documentation:
If you're using Django's render_to_response() shortcut to populate a template with the contents of a dictionary, your template will be passed a Context instance by default (not a RequestContext). To use a RequestContext in your template rendering, pass an optional third argument to render_to_response(): a RequestContext instance. Your code might look like this:
def some_view(request):
# ...
return render_to_response('my_template.html',
my_data_dictionary,
context_instance=RequestContext(request))
Many thanks for all the help!
Write a custom context processor.

Categories