setting help_text for each choice in a RadioSelect - python

I can set the help_text attribute on any form field, but is it possible to set help_text on the choices used for a RadioSelect()?
I'd looking for a clean way to show some help information under each radio button.
Below is the code for the model and the form, I can render the name attribute in a template with the label, input element and help text. I'd also like to be able to render membership_type attribute with a label ('Membership Type'), radio buttons ('open membership' and 'closed membership'), and help text associated to each radio element ('anyone can join this group' and 'only select members can join this group').
class Group(models.Model):
MEMBERSHIP_CHOICES = (
('O', 'Open membership'),
('C', 'Closed membership'),
)
name = models.CharField(max_length=255)
membership_type = models.CharField(max_length=1, choices=MEMBERSHIP_CHOICES, default="O")
class GroupForm(forms.ModelForm):
name = forms.CharField(label="Group name", help_text="Enter a name for your new group")
class Meta:
model = Group
widgets = { "membership_type": forms.RadioSelect }

#Rishabh is correct but I'll elaborate further as, at first glance, it doesn't appear to be the solution, although it is; or, at least, it can be kludged to get a useful effect without having to dive too deep into django forms.
The second element of the tuple is presented inside the "label" tag - so any 'inline elements' are permissible; for example:
The desired result
Or something like it
<ul>
<li><label for="id_ticket_0">
<input type="radio" id="id_ticket_0" value="PARTTIME" name="ticket">
<em>Part Time</em> Valid on Friday Night and Saturday Only
</label></li>
<li><label for="id_ticket_1">
<input type="radio" id="id_ticket_1" value="DAYTIME" name="ticket">
<em>Casual</em> Valid on Saturday Only
</label></li>
<li><label for="id_ticket_2">
<input type="radio" id="id_ticket_2" value="EARLYBIRD" name="ticket">
<em>Early Bird</em> Valid on Friday, Saturday, and Sunday. $15 discount for booking before 1am January 3rd, 2011
</label></li>
</ul>
The simple example
The trick is to "mark_safe" the content of the description then stuff whatever you need into:
from django.utils.safestring import mark_safe
choices = (
('1', mark_safe(u'<em>One</em> | This is the first option. It is awesome')),
('2', mark_safe(u'<em>Two</em> | This is the second option. Good too.'))
)
The complex example
So in this example we:
assemble the choices into a list (any iterable structure will do)
pass the structure to the form's init to create our radio options on the fly
use a comprehension list to create an extended description for each radio option
The data structure:
Tickets are my own classes and they have attributes:
tickets.code - as in a ticket code
label - a pithy short description
help - a longer description
But more about that later. First lets create some instances:
from mymodule import ticket
# so lets create a few
fulltime = ticket('FULLTIME',160,'Full Time',
"Valid Monday to Friday inclusive")
parttime = ticket('PARTTIME',110,'Full Time',
"Valid outside of business hours only")
daytime = ticket('DAYTIME',70,'Day Time',
"Valid only on weekends and public holidays")
# and put them together in a list any way you like
available_tickets = [fulltime, parttime, daytime]
# now create the form
OrderForm(tickets=available_tickets)
That probably happened in your view code. Now to see what happens in the form
class OrderForm(ModelForm):
def __init__(self, *args, **kwargs):
self.tickets = kwargs.pop('tickets')
super(OrderForm, self).__init__(*args, **kwargs)
choices = [(t.code, mark_safe(u'<em>%s</em> %s' % (t.label, t.help)))
for t in self.tickets]
self.fields['ticket'] = forms.ChoiceField(
choices = choices,
widget = forms.RadioSelect()
)

For others coming across this 10+ years later, I was able to do this by creating a custom widget that overrides the default template for radio buttons, and passing custom attributes to it.
# Custom widget
class RadioSelectHelpTextWidget(forms.RadioSelect):
option_template_name = 'custom_templates/forms/radio_option_help_text.html'
# Form class that calls widgets, passed custom attributes to widget
class TemplateCreateForm(ModelForm):
created_by = forms.ModelChoiceField(required=False,queryset=User.objects.none())
class Meta:
model = Template
fields = ['name', 'type', 'created_by']
widgets = {
'name': forms.TextInput(attrs={'class': 'input'}),
'type': RadioSelectHelpTextWidget(
attrs={
'help_text': {
'custom': 'This is custom help text.',
'html': 'This is help text for html.'
}
}
)
}
Template for custom radio buttons (custom_templates/forms/radio_option_help_text.html)
{% load custom_template_filters %}
{% if widget.wrap_label %}
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>
{% endif %}
{% include "django/forms/widgets/input.html" %}
{% if widget.wrap_label %}
{{ widget.label }}
{% if widget.attrs.help_text %}
{% if widget.value in widget.attrs.help_text %}
<p class="is-size-7">
{{ widget.attrs.help_text|dict_get:widget.value }}
</p>
{% endif %}
{% endif %}
</label>
{% endif %}
The result:

Assuming you're using RadioSelect as a widget for forms.ChoiceField, you can do something like:
choices = (('1', 'First help_text here'),
('2', 'Second help_text here'),
('3', 'Third help_text here'),
)
class MyForm(forms.Form):
...
choice = forms.ChoiceField(widget = RadioSelect, choices = choices)
This isn't a strict use of help_text but it should get the job done in most cases.

Related

Django template: force choices on field and print display value with get_FOO_display()

I want a model with 5 choices, but I cannot enforce them and display the display value in template. I am using CharField(choice=..) instead of ChoiceField or TypeChoiceField as in the docs. I tried the solutions here but they don't work for me (see below).
model.py:
class Language(models.Model):
language = models.CharField(max_length=20,blank=False)
ILR_scale = (
(5, 'Native'),
(4, 'Full professional proficiency'),
(3, 'Professional working proficiency'),
(2, 'Limited professional proficiency'),
(1, 'Elementary professional proficiency')
)
level = models.CharField(help_text='Choice between 1 and 5', default=5, max_length=25, choices=ILR_scale)
def level_verbose(self):
return dict(Language.ILR_scale)[self.level]
class Meta:
ordering = ['level','id']
def __unicode__(self):
return ''.join([self.language, '-', self.level])
view.py
..
def index(request):
language = Language.objects.all()
..
mytemplate.html
<div class="subheading strong-underlined mb-3 my-3">
Languages
</div>
{% regroup language|dictsortreversed:"level" by level as level_list %}
<ul>
{% for lan_list in level_list %}
<li>
{% for lan in lan_list.list %}
<strong>{{ lan.language }}</strong>: {{ lan.level_verbose }}{%if not forloop.last%},{%endif%}
{% endfor %}
</li>
{% endfor %}
</ul>
From shell:
python3 manage.py shell
from resume.models import Language
l1=Language.objects.create(language='English',level=4)
l1.save()
l1.get_level_display() #This is good
Out[20]: 'Full professional proficiency'
As soon as I create a Language instance from shell I cannot load the site. It fails at line 0 of the template with Exception Type: KeyError, Exception Value: '4', Exception Location: /models.py in level_verbose, line 175 (which is the return line of the level_verbose method).
Also, I was expecting a validation error here from shell:
l1.level='asdasd'
l1.save() #Why can I save this instance with this level?
And I can also save a shown above when using ChoiceField, meaning that I do not understand what that field is used for.
How to force instances to take field values within choices, and display the display value in templates?
Well this is the common issue even when I started with django. So first let's look at django's feature that you can do it like below (Note: your choice case's value are going to be store as integer so you should use models.IntegerField instead of models.CharField):
get_FOO_display() : you are very near about this solution.
As you can see in documentation FOO is the field name of your model. in your case it is level so when you want to access corresponding choice value in shell or view you can call method with model instance as below as you have already mentioned:
`l1.get_level_display()`
but when you want to access it in template file you need to write it like below:
{{ l1.get_level_display }}
Now let's look at your method level_verbose() if you see quite again your model is a class and level_verbose() is the method you have created you can access self.ILR_scale directly just as you have used self.level
the main catch in you create dictionary of ILR_scale it's keys are Integer values (i.e. 1, 2, 3, 4, 5) but you have used CharField() to store the level values which returns you string values (i.e. '1', '2', '3', '4' or '5') and in python dictionary key 1 and '1' are both different one is integer and other is string. So you may change your model field to models.IntegerField() or you can access the keys like
dict(self.ILR_scal)[int(self.level)]
You can also use models.CharField but you have to set field option choices to your tuples.
For exapmle:
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
LEVELS = (
(FRESHMAN, 'Freshman'),
(SOPHOMORE, 'Sophomore'),
(JUNIOR, 'Junior'),
(SENIOR, 'Senior'),
)
level = models.CharField(
max_length=2,
choices=LEVELS,
default=FRESHMAN,
)
Then in your template you can use get_FOO_display() for example:
{{l1.get_level_display}}
See more in docs

How do I map WTForms field for a db.Enum model on Flask-SQLAlchemy model?

Working with the following model:
class Recipe(db.Model):
__tablename__ = 'recipe'
__searchable__ = ['description']
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128), index=True, unique=True, nullable=False)
description = db.Column(db.String(128))
style = db.Column(db.Enum('fried', 'baked', 'roasted', 'mixed', name='cooking_style'))
type = db.Column(db.Enum('breakfast', 'lunch', 'dinner', 'snack', 'sauce', 'bread', 'dessert', name='recipe_type'))
And the following:
form = CreateRecipeForm()
return render_template('create_client_recipe.html', form=form, client=c, recipe=r)
How do I represent style & type (both db.Enum fields) as a select field in the WTForm?
Don't know if you need or not but I'm working with Flask-SQLAlchemy, I'm still a beginner, but I hope this can help you basically I created an Enum called state which has value 'Active' and 'Inactive', and I wanted to put this values in a form but I wanted to get the values from the database.
My model this one:
class StationhasBots(db.Model):
"Many to Many table one raio station will have many functions"
__tablename__ = 'station_has_bots'
fk_radio_station_id = db.Column(db.ForeignKey('radio_station.id'), primary_key=True)
fk_bot_functions_id = db.Column(db.ForeignKey('bot_functions.id'), primary_key=True)
#Function is active or not
state = db.Column(db.Enum('Active','Inactive',name='estado'),nullable=False)
#In which time it will run
run_frequency = db.Column(db.String(STRING_LEN),nullable=False)
next_run = db.Column(db.DateTime(timezone=True),nullable=False)
#Source to fetch information
source = db.Column(db.String,nullable=False)
#path to the file that will be executed to pull info.
path = db.Column(db.String,nullable=True)
function_of_bots = db.relationship("BotsFunctions", backref=db.backref('function_from_bots'))
This is my form:
class AddBotForm(Form):
station = QuerySelectField(query_factory=all_stations, allow_blank=False, blank_text='- select station-')
function = QuerySelectField(query_factory=all_bot_functions, allow_blank=False, blank_text='- select function-')
#state = SelectField(choices=[('active', 'Active'), ('inactive', 'Inactive')])
state = SelectField(choices=[(g, g)for g in StationhasBots.state.property.columns[0].type.enums]) #Get the state from Station_has_Bots Table.
next_run = DurationField(description=_("Duration , in HH:MM(:SS)"))
run_frequency = HiddenField()
source = StringField()
path = StringField()
submit = SubmitField(_('Save'))
In this form you can see that in the state field I run a query, this query will get the Enum called state that was created when I created the database.
To render the form I just did this in my views
#radio.route('/bots/add/', methods=['GET', 'POST'])
#login_required
def bot_function_add():
"""Renders the form"""
form = AddBotForm(request.form)
program = None
return render_template('radio/bot.html', program=program, form=form)
And then in templates did this
<h2>{{ _('Add') }} {{ _('Bot') }}</h2>
<form method="POST" action=""/>
{{ form.hidden_tag() }}
{{ render_field(form, form.station) }}
{{ render_field(form, form.function) }}
{{ render_field(form, form.next_run) }}
{{ render_field(form, form.state) }}
{{ render_field(form, form.source) }}
{{ render_field(form, form.path) }}
{{ render_field(form, form.submit) }}
</form>
I think in your case something like this may work for you.
Type = SelectField(choices=[(g, g)for g in Recipe.type.property.columns[0].type.enums])
First g -> Stays for value (in "HTML code")
Second g -> Is what will be presented to the user.
And then to render the form in your create_client_recipe.html file
you just need to do something like
{{ render_field(form, form.Type) }}
I know that six months have past since your post hope this can help some other people.

Django fill up ChoiceField - Select html options

I am facing problem filling my select options using django forms. Even though I define choices, I don't see any choices.
My form in .py file:
class SignupForm(forms.Form):
countrySignup = forms.ChoiceField( required = True,
widget = forms.Select( choices = ('United States', 'Jordan') ))
my html template has the following:
<p>
<label for="countrySignup" class="youcountry" data-icon="">Your Country </label> </br>
{{ signupForm.countrySignup }}
{{ signupForm.countrySignup.errors }}
</p>
and of course my view has the following code to pass the variable to template:
def myView(request):
#
# create the unbinded forms for registration
#
if request.method == 'GET':
signupForm = regForms.SignupForm()
return render(request,
"login.html",
{'loginForm': loginForm,
'signupForm' : signupForm })
What am I missing because I don't see any options in my select , it remains empty
I missed the documentation that choices needs to be a list of tuples with 2 elements each.
choices:
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this field. This argument accepts the same formats as the choices argument to a model field. See the model field reference documentation on choices for more details.**
this solved it:
countrySignup = forms.ChoiceField( choices = [('PS', 'Palestine'),
('JD', 'Jordan')] )

Saving the order of items

My model:
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
order = models.SmallIntegerField()
My template:
{% for c in categories %}
{{ c }} -- <input type="text" name="order[{{ c.id }}]" value="{{ c.order }}">
Submit Button
{% endfor %}
My view:
def category_order(request):
print request.POST
print request.POST.getlist('order[]')
Output:
<QueryDict: {u'order[3]': [u'1'], u'order[1]': [u'1'], u'order[5]': [u'2'], u'order[4]': [u'33'], u'order[2]': [u'2'], u'order[6]': [u'3'], u'csrfmiddlewaretoken': [u'4XjehEwMdNK032342JkYJvBJabunKB'], u'order-btn': [u'order']}>
[]
So my question is how can I loop over every order[N] and get the category ID and its new order value to save it to the database?
Using dict comprehension:
orders = {name.split('[')[1].split(']')[0]: request.POST[name]
for name in request.POST if name.startswith('order[')}
# `orders` is not a dictionary maps `id`s to `order`s.
# Do something with `orders`.
Or using simple for loop:
for name in request.POST:
if not name.startswith('order['): continue
id_ = name.split('[')[1].split(']')[0]
order = request.POST[name]
# Do something with `id_`, `order`
If you are looking at having ordered objects, there is a library, django-ordered-model , that can take care of that. That includes a good, drag-and-drop admin view. For example:
from django.db import models
from ordered_model.models import OrderedModel
class Category(OrderedModel):
name = models.CharField(max_length=50, unique=True)
class Meta(OrderedModel.Meta):
pass

Django - Rating System View & Template

I have a content that I'd like to rate on multiple criteria.
Imagine this kind of model:
class Content(models.Model):
name = models.CharField(max_length=50)
class Criterion(models.Model):
name = models.CharField(max_length=50)
content = models.ForeignKey(Content)
class ContRate(models.Model):
user = models.ForeignKey(User, help_text="Who rated ?")
crit = models.ForeignKey(Criterion)
rate = models.DecimalField()
The user has a page displaying the content.
From this page, he can also rate the content on the criteria set
The rating will be done with Ajax.
Now I'm trying to implement the view & the template
view.py
#...
def viewcont(request, content_id):
"""The user can view a content & rate it"""
content = get_object_or_404(Content, pk=content_id)
RateFormSet = modelformset_factory(ContRate)
formset = RateFormSet(queryset=ContRate.objects.filter(content=content, user=request.user))
objs = {
'content': content,
'forms': formset,
}
return render_to_response('content/content_detail.html', objs
, context_instance=RequestContext(request)
)
#...
content_detail.html
<!-- ... -->
<div id="rating">
<ul>
{% for crit in content.crit_set.all %}
<li>
{{ crit }}
<div class="rateit"
data-rateit-value="the_actual_rating_if_already_there"
data-rateit-ispreset="true"
crit-id="{{ crit.id }}"></div>
</li>
{% endfor %}
</ul>
</div>
<!-- ... -->
Now how can I use the forms formset to display the actual rates ?
And how can I draw an empty form to be posted by Ajax from any clicked star ?
(I know the javascript/jQuery part)
Not sure what the point of the formset is here. The rates are all available via the criteria object, using the reverse foreign key to ContRate in exactly the same way as you've done from Criteria to Content.
To make this as efficient as possible, you probably want to get the relevant ratings in the view and bring them together into a single datastructure:
content = get_object_or_404(Content, pk=content_id)
criteria = content.criteria_set.all()
user_ratings = ContRate.objects.filter(content=content, user=request.user)
ratings_dict = dict((c.crit_id, c.rate) for c in user_ratings)
for crit in criteria:
crit.user_rating = ratings_dict.get(crit.id)
Now you can pass criteria directly to your template, and there you can iterate through it to show the user_rating for each one.
(Final point: "criteria" is plural, the singular is "criterion". :-)

Categories

Resources