Skip to content

Instantly share code, notes, and snippets.

@scabbiaza
Last active August 29, 2015 14:06
Show Gist options
  • Save scabbiaza/9d87c714fce1585f9972 to your computer and use it in GitHub Desktop.
Save scabbiaza/9d87c714fce1585f9972 to your computer and use it in GitHub Desktop.
Date interval form

Date interval form

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

Date interval form

Date interval form. Error example

Date interval form. Error example

Date interval form. Form complete

Date interval form. using current end date option

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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment