Form could be useful for creating fields like Education interval or Work Experience interval. You could see such fields on facebook and linkedin.
Using: Flask, WTForms, SQLAlchemy, jQuery, Bootstrap
function toggleDateType($togglerDateType) { | |
var $choiceDateWrapper = $togglerDateType.closest('.date').find('.choice-date'); | |
var $currentDateWrapper = $togglerDateType.closest('.date').find('.current-date'); | |
if ($togglerDateType.is(':checked')) { | |
$choiceDateWrapper.addClass('hidden'); | |
$currentDateWrapper.removeClass('hidden'); | |
} | |
else { | |
$choiceDateWrapper.removeClass('hidden'); | |
$currentDateWrapper.addClass('hidden'); | |
} | |
} | |
function initMonthYearPeriod($wrapper) { | |
var $periodWrapper = $wrapper.find('.month-year-period'); | |
var $currentDateToogler = $periodWrapper.find('.current-date-toggler'); | |
if ($periodWrapper.length && $currentDateToogler) { | |
$.each($currentDateToogler, function () { | |
toggleDateType($(this)); | |
}); | |
} | |
} | |
$wrapper = $('form'); // put here you wrapper | |
initMonthYearPeriod($wrapper); | |
$wrapper.on('change', '.current-date-toggler', function (e) { | |
toggleDateType($(this)); | |
}); |
.month-year-period { | |
.clearfix; | |
.date { | |
float: left; | |
width: 46%; | |
} | |
.devider { | |
float: left; | |
width: 8%; | |
text-align: center; | |
font-size: 120%; | |
line-height: @input-height-base; | |
} | |
.choice-date { | |
.clearfix; | |
.month, .year { | |
float: left; | |
width: 49%; | |
} | |
.month { | |
margin-right: 2%; | |
} | |
} | |
.current-date { | |
line-height: @input-height-base; | |
} | |
} |
{% from 'macros.html' import form_field %} | |
<form method="post"> | |
<div class="month-year-period"> | |
<div class="date start-date"> | |
<div class="choice-date"> | |
<div class="month"> | |
{{ form_field(form, form['start_date_month'], class_='position-start-date-month') }} | |
</div> | |
<div class="year"> | |
{{ form_field(form, form['start_date_year'], has_label=false, class_='position-start-date-year', maxlength=4) }} | |
</div> | |
</div> | |
</div> | |
<div class="devider">–</div> | |
<div class="date end-date"> | |
<div class="choice-date"> | |
<div class="month"> | |
{{ form_field(form, form['end_date_month'], class_='position-end-date-month') }} | |
</div> | |
<div class="year"> | |
{{ form_field(form, form['end_date_year'], has_label=false, class_='position-end-date-year', maxlength=4) }} | |
</div> | |
</div> | |
<div class="form-group current-date hidden"> | |
Current date | |
</div> | |
<div class="pull-right"> | |
{{ form_field(form, form['end_date_is_current'], class_='position-end-date-is-current current-date-toggler') }} | |
</div> | |
</div> | |
</div> | |
</form> |
from flask.ext.wtf import Form | |
from wtforms import (StringField, TextAreaField, BooleanField, SelectField, IntegerField, SubmitField) | |
from wtforms.validators import DataRequired, Length, Optional | |
class EducationRecordForm(Form): | |
start_date_month = SelectField('', [Optional(), valid_month_year_period], choices=[(0, '')] + get_month_list_shortname(), coerce=int) | |
start_date_year = IntegerField('from', [valid_month_year_period]) | |
end_date_month = SelectField('', [Optional(), valid_month_year_period], choices=[(0, '')] + get_month_list_shortname(), coerce=int) | |
end_date_year = IntegerField('to', [valid_month_year_period]) | |
end_date_is_current = BooleanField('To the current date') | |
sudmit = SubmitField('Submit') | |
def get_month_list_shortname(): | |
return [(1, 'Jan'), (2, 'Feb'), (3, 'Mar'), (4, 'Apr'), | |
(5, 'May'), (6, 'Jun'), (7, 'Jul'), (8, 'Aug'), | |
(9, 'Sep'), (10, 'Oct'), (11, 'Nov'), (12, 'Dec')] | |
def valid_month_year_period(form, field, minyear=1900): | |
# Validation logic | |
# | |
# Form is optional if all fields are not set. | |
# Otherwise use next rules: | |
# | |
# start_date_year: | |
# - is required | |
# - should be number from minyear to current year | |
# | |
# start_date_month: | |
# - is not required | |
# - if it's set and start_date_year == current year, start_date_month should be not greater then current month | |
# | |
# end_date_is_current: | |
# - is not required | |
# - if it's set, end_date_year and end_date_month should be unset | |
# | |
# end_date_year: | |
# - if end_date_is_current is not set, end_date_year is required | |
# - if required, should be number from start_date_year to current year | |
# | |
# end_date_month: | |
# - is not required | |
# - if it's set and end_date_year == current year, end_date_month should be not greater then current month | |
# - if it's set and end_date_year == start_date_year, end_date_month should be bigger then start_date_month | |
if not form.start_date_month.data and not form.start_date_year.data and\ | |
not form.end_date_month.data and not form.end_date_year.data and\ | |
not form.end_date_is_current.data: | |
field.errors[:] = [] | |
raise StopValidation() | |
current_year = date.today().year | |
current_month = date.today().month | |
if 'start_date_year' in field.name: | |
if not field.data: | |
raise ValidationError(messages.start_date_year_required) | |
if field.data < minyear: | |
raise ValidationError(messages.start_date_year_min_value.format(min=minyear)) | |
if field.data > current_year: | |
raise ValidationError(messages.start_date_year_max_value.format(max=current_year)) | |
if 'start_date_month' in field.name: | |
if field.data: | |
if form.start_date_year.data == current_year and field.data > current_month: | |
raise ValidationError(messages.start_date_month_max_value.format(max=dict(get_month_list_shortname()).get(current_month))) | |
if form.end_date_is_current.data: | |
form.end_date_year.data = None | |
form.end_date_month.data = 0 | |
else: | |
if 'end_date_year' in field.name: | |
if not field.data: | |
raise ValidationError(messages.end_date_year_required) | |
if field.data < form.start_date_year.data: | |
raise ValidationError(messages.end_date_year_min_value.format(min=form.start_date_year.data)) | |
if field.data > current_year: | |
raise ValidationError(messages.end_date_year_max_value.format(max=current_year)) | |
if 'end_date_month' in field.name: | |
if field.data: | |
if form.end_date_year.data == current_year and field.data > current_month: | |
raise ValidationError(messages.end_date_month_max_value.format(max=dict(get_month_list_shortname()).get(current_month))) | |
if form.end_date_year.data == form.start_date_year.data and field.data < form.start_date_month.data: | |
raise ValidationError(messages.end_date_month_min_value.format(min=dict(get_month_list_shortname()).get(form.start_date_month.data))) | |
{% macro form_field(form, field, print_status=True) -%} | |
{% set has_label = kwargs.pop('has_label', True) %} | |
{% set placeholder = '' %} | |
{% if not has_label %} | |
{% set placeholder = field.label.text %} | |
{% endif %} | |
{% set field_status = '' %} | |
{% if form.errors and (form.submitted or (form.is_submitted() and form.submit.data)) %} | |
{# form.submit.data for support multiple forms on page #} | |
{# form.submitted - manual control for form without button (ajax) #} | |
{# If form has error, password field would be empty in repeat form | |
and user need to retype this value. | |
That is why we need to mark password field always as `error`, | |
to draw user attention | |
#} | |
{# Simulate error for password field (because if form has errors this fields is empty after reloading page) #} | |
{# Can reset this simulations with field.hide_error = True (need to sing in form) #} | |
{% if field.type == 'PasswordField' and not field.hide_error %} | |
{% set field_status = 'error' %} | |
{% set field_errors = ['Please retype password'] %} | |
{% else %} | |
{% if field.errors %} | |
{% set field_status = 'error' %} | |
{% else %} | |
{% set field_status = 'success' %} | |
{% endif %} | |
{% endif %} | |
{% endif %} | |
<div class="form-group {{ field_status }}"> | |
{% if has_label and field.label.text and 'BooleanField' not in field.type %} | |
<label for="{{ field.id }}" class="control-label"> | |
{{ field.label.text }}{% if field.flags.required %} <span class="required">*</span>{% endif %} | |
</label> | |
{% endif %} | |
<div class="controls"> | |
{% set class_ = kwargs.pop('class_', '') %} | |
{% if 'BooleanField' in field.type %} | |
<div class="checkbox"> | |
<label for="{{ field.id }}"> | |
{{ field(class_=class_, **kwargs) }} | |
{{ field.label.text | safe }} | |
</label> | |
</div> | |
{% elif 'RadioField' in field.type %} | |
{% for value, label, checked in field.iter_choices() %} | |
<div class="radio"> | |
{% if checked %} | |
<label> | |
<input type="radio" name="{{ field.id }}" id="{{ field.id }}" value="{{ value }}" | |
checked="True">{{ label }} | |
</label> | |
{% else %} | |
<label> | |
<input type="radio" name="{{ field.id }}" id="{{ field.id }}" value="{{ value }}">{{ label }} | |
</label> | |
{% endif %} | |
</div> | |
{% endfor %} | |
{% else %} | |
{% set class_ = class_ ~ ' form-control' %} | |
{% set prepend = kwargs.pop('prepend', None) %} | |
{% set append = kwargs.pop('append', None) %} | |
{% if append or prepend %} | |
{% if append and prepend %} | |
<div class="input-group input-group-prepend input-group-append"> | |
<span class="input-group-addon">{{ prepend | safe }}</span> | |
{{ field(class_=class_, placeholder=placeholder, **kwargs) }} | |
<span class="input-group-addon">{{ append | safe }}</span> | |
</div> | |
{% elif prepend %} | |
<div class="input-group input-group-prepend"> | |
<span class="input-group-addon">{{ prepend | safe }}</span> | |
{{ field(class_=class_, placeholder=placeholder, **kwargs) }} | |
</div> | |
{% elif append %} | |
<div class="input-group input-group-append"> | |
{{ field(class_=class_, placeholder=placeholder, **kwargs) }} | |
<span class="input-group-addon">{{ append | safe }}</span> | |
</div> | |
{% endif %} | |
{% else %} | |
{{ field(class_=class_, placeholder=placeholder, **kwargs) }} | |
{% endif %} | |
{% endif %} | |
{% if field.description %} | |
<p class="help-block">{{ field.description|safe }}</p> | |
{% endif %} | |
{% if print_status %} | |
{% if field_status == 'error' %} | |
<p class="error">{{ field.errors|join('<br/>') or field_errors|join('<br/>') }}</p> | |
{% elif field_status == 'success' %} | |
<p class="success">Field is ok</p> | |
{% endif %} | |
{% endif %} | |
</div> | |
</div> | |
{%- endmacro %} |
class EducationRecord(db.Model): | |
__tablename__ = 'education_records' | |
id = db.Column(db.Integer, primary_key=True) | |
start_date_month = db.Column(db.SmallInteger) | |
start_date_year = db.Column(db.SmallInteger) | |
end_date_month = db.Column(db.SmallInteger) | |
end_date_year = db.Column(db.SmallInteger) | |
end_date_is_current = db.Column(db.Boolean, default=False) |
@bp.route('/edit/<int:uid>', methods=['GET', 'POST']) | |
def edit(uid): | |
model = EducationRecord.query.get_or_404(uid) | |
form = EducationRecordForm(obj=model) | |
if form.validate_on_submit(): | |
form.populate_obj(model) | |
db.session.commit() | |
flash(messages.object_updated, 'success') | |
return render_template('education/form.html', model=model, form=form) |