Browse Source

Improved the birthday field.

Renamed the SelectDateWidget to SelectBirthdayWidget.
Added a custom field called BirthdayField - which allows to remove the
birthday if desired.
sh4nks 10 years ago
parent
commit
cae7881f7e

+ 6 - 5
flaskbb/management/forms.py

@@ -10,14 +10,15 @@
 """
 from flask_wtf import Form
 from wtforms import (StringField, TextAreaField, PasswordField, IntegerField,
-                     BooleanField, SelectField, DateField, SubmitField)
+                     BooleanField, SelectField, SubmitField)
 from wtforms.validators import (DataRequired, Optional, Email, regexp, Length,
                                 URL, ValidationError)
 from wtforms.ext.sqlalchemy.fields import (QuerySelectField,
                                            QuerySelectMultipleField)
 from flask_babelex import lazy_gettext as _
 
-from flaskbb.utils.widgets import SelectDateWidget
+from flaskbb.utils.fields import BirthdayField
+from flaskbb.utils.widgets import SelectBirthdayWidget
 from flaskbb.extensions import db
 from flaskbb.forum.models import Forum, Category
 from flaskbb.user.models import User, Group
@@ -51,9 +52,9 @@ class UserForm(Form):
     password = PasswordField("Password", validators=[
         Optional()])
 
-    birthday = DateField(_("Birthday"), format="%d %m %Y",
-                         widget=SelectDateWidget(),
-                         validators=[Optional()])
+    birthday = BirthdayField(_("Birthday"), format="%d %m %Y",
+                             widget=SelectBirthdayWidget(),
+                             validators=[Optional()])
 
     gender = SelectField(_("Gender"), default="None", choices=[
         ("None", ""),

+ 30 - 3
flaskbb/templates/macros.html

@@ -76,7 +76,7 @@
 {%- endmacro -%}
 
 
-{%- macro render_select_field(field, div_class='') -%}
+{%- macro render_select_field(field, div_class='', select_class="form-control") -%}
 <div class="form-group">
     {% if div_class %}
     <div class="{{ div_class }}">
@@ -85,9 +85,9 @@
     {% endif %}
         <label>{{ field.label.text }}</label>
         {% if field.type == 'QuerySelectMultipleField' or field.type == 'SelectMultipleField' %}
-            {{ field(multiple=True, class="form-control") }}
+            {{ field(multiple=True, class=select_class) }}
         {% else %}
-            {{ field(class="form-control") }}
+            {{ field(class=select_class) }}
         {%- endif -%}
 
         {{ field_description(field) }}
@@ -188,6 +188,33 @@
     </div>
 {%- endmacro -%}
 
+{%- macro horizontal_select_field(field, div_class='', label_class='', select_class="form-control", surrounded_div="col-sm-4") -%}
+<div class="form-group row {%- if field.errors %} has-error{%- endif %}">
+    {% if label_class %}
+        {{ field.label(class=label_class) }}
+    {% else %}
+        {{ field.label(class="col-sm-3 control-label") }}
+    {% endif %}
+
+    {% if div_class %}
+        <div class="{{ div_class }}">
+    {% else %}
+        <div class="col-sm-5">
+    {% endif %}
+
+        <div class="row">
+        {% if field.type == 'QuerySelectMultipleField' or field.type == 'SelectMultipleField' %}
+            {{ field(multiple=True, class=select_class, surrounded_div=surrounded_div) }}
+        {% else %}
+            {{ field(class=select_class, surrounded_div=surrounded_div) }}
+        {%- endif -%}
+        </div>
+        {{ field_description(field) }}
+        {{ field_errors(field) }}
+    </div>
+</div>
+{%- endmacro -%}
+
 
 {%- macro horizontal_boolean_field(field, div_class='') -%}
 {%- if div_class %}

+ 2 - 2
flaskbb/templates/management/user_form.html

@@ -3,7 +3,7 @@
 
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
-{% from theme("macros.html") import horizontal_field, navlink with context %}
+{% from theme("macros.html") import horizontal_field, horizontal_select_field, navlink with context %}
 
 <div class="col-md-3">
     <ul class="nav nav-pills nav-stacked">
@@ -23,7 +23,7 @@
             {{ horizontal_field(form.username) }}
             {{ horizontal_field(form.email) }}
             {{ horizontal_field(form.password) }}
-            {{ horizontal_field(form.birthday) }}
+            {{ horizontal_select_field(form.birthday, surrounded_div="col-sm-4") }}
             {{ horizontal_field(form.gender) }}
             {{ horizontal_field(form.location) }}
             {{ horizontal_field(form.website) }}

+ 2 - 2
flaskbb/templates/user/change_user_details.html

@@ -1,11 +1,11 @@
 {% extends theme("user/settings_layout.html") %}
-{% from theme("macros.html") import horizontal_field %}
+{% from theme("macros.html") import horizontal_field, horizontal_select_field %}
 
 {% block settings_content %}
 <form class="form-horizontal" role="form" method="POST">
     <legend class="">{% trans %}Change User Details{% endtrans %}</legend>
     {{ form.hidden_tag() }}
-    {{ horizontal_field(form.birthday) }}
+    {{ horizontal_select_field(form.birthday, select_class="form-control", surrounded_div="col-sm-4") }}
     {{ horizontal_field(form.gender) }}
     {{ horizontal_field(form.location) }}
     {{ horizontal_field(form.website) }}

+ 11 - 6
flaskbb/user/forms.py

@@ -10,15 +10,16 @@
 """
 from flask_login import current_user
 from flask_wtf import Form
-from wtforms import (StringField, PasswordField, DateField, TextAreaField,
-                     SelectField, ValidationError, SubmitField)
+from wtforms import (StringField, PasswordField, TextAreaField, SelectField,
+                     ValidationError, SubmitField)
 from wtforms.validators import (Length, DataRequired, InputRequired, Email,
                                 EqualTo, regexp, Optional, URL)
 from flask_babelex import lazy_gettext as _
 
 from flaskbb.user.models import User, PrivateMessage
 from flaskbb.extensions import db
-from flaskbb.utils.widgets import SelectDateWidget
+from flaskbb.utils.widgets import SelectBirthdayWidget
+from flaskbb.utils.fields import BirthdayField
 
 
 IMG_RE = r'^[^/\\]\.(?:jpg|gif|png)'
@@ -82,9 +83,9 @@ class ChangePasswordForm(Form):
 
 
 class ChangeUserDetailsForm(Form):
-    # TODO: Better birthday field
-    birthday = DateField(_("Your Birthday"), format="%d %m %Y",
-                         widget=SelectDateWidget(), validators=[Optional()])
+    birthday = BirthdayField(_("Birthday"), format="%d %m %Y",
+                             validators=[Optional()],
+                             widget=SelectBirthdayWidget())
 
     gender = SelectField(_("Gender"), default="None", choices=[
         ("None", ""),
@@ -108,6 +109,10 @@ class ChangeUserDetailsForm(Form):
 
     submit = SubmitField(_("Save"))
 
+    def validate_birthday(self, field):
+        if field.data is None:
+            return True
+
 
 class NewMessageForm(Form):
     to_user = StringField(_("To User"), validators=[

+ 33 - 0
flaskbb/utils/fields.py

@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+"""
+    flaskbb.utils.fields
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Additional fields for wtforms
+
+    :copyright: (c) 2014 by the FlaskBB Team.
+    :license: BSD, see LICENSE for more details.
+"""
+from datetime import datetime
+from wtforms.fields import DateField
+
+
+class BirthdayField(DateField):
+    """Same as DateField, except it allows ``None`` values in case a user
+    wants to delete his birthday.
+    """
+    def __init__(self, label=None, validators=None, format='%Y-%m-%d', **kwargs):
+        super(DateField, self).__init__(label, validators, format, **kwargs)
+
+    def process_formdata(self, valuelist):
+        if valuelist:
+            date_str = ' '.join(valuelist)
+            try:
+                self.data = datetime.strptime(date_str, self.format).date()
+            except ValueError:
+                self.data = None
+
+                # Only except the None value if all values are None.
+                # A bit dirty though
+                if valuelist != ["None", "None", "None"]:
+                    raise ValueError("Not a valid date value")

+ 33 - 12
flaskbb/utils/widgets.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 """
-    flaskbb.utils.wtforms
-    ~~~~~~~~~~~~~~~~~~~~
+    flaskbb.utils.widgets
+    ~~~~~~~~~~~~~~~~~~~~~
 
     Additional widgets for wtforms
 
@@ -12,7 +12,7 @@ from datetime import datetime
 from wtforms.widgets.core import Select, HTMLString, html_params
 
 
-class SelectDateWidget(object):
+class SelectBirthdayWidget(object):
     """Renders a DateTime field with 3 selects.
     For more information see: http://stackoverflow.com/a/14664504
     """
@@ -28,41 +28,62 @@ class SelectDateWidget(object):
     }
 
     def __init__(self, years=range(1930, datetime.utcnow().year + 1)):
-        super(SelectDateWidget, self).__init__()
+        """Initialzes the widget.
+
+        :param years: The min year which should be chooseable.
+                      Defatuls to ``1930``.
+        """
+        super(SelectBirthdayWidget, self).__init__()
         self.FORMAT_CHOICES['%Y'] = [(x, str(x)) for x in years]
 
     def __call__(self, field, **kwargs):
         field_id = kwargs.pop('id', field.id)
         html = []
         allowed_format = ['%d', '%m', '%Y']
+        surrounded_div = kwargs.get('surrounded_div', None)
 
-        for format in field.format.split():
-            if (format in allowed_format):
-                choices = self.FORMAT_CHOICES[format]
-                id_suffix = format.replace('%', '-')
+        for date_format in field.format.split():
+            if (date_format in allowed_format):
+                choices = self.FORMAT_CHOICES[date_format]
+                id_suffix = date_format.replace('%', '-')
                 id_current = field_id + id_suffix
 
-                kwargs['class'] = self.FORMAT_CLASSES[format]
+                select_class = "{} {}".format(self.FORMAT_CLASSES[date_format],
+                                              kwargs['class'])
+                kwargs['class'] = select_class
+
                 try:
                     del kwargs['placeholder']
-                except:
+                except KeyError:
                     pass
 
+                if surrounded_div is not None:
+                    html.append("<div class='%s'>" % surrounded_div)
+
                 html.append('<select %s>' % html_params(name=field.name,
                                                         id=id_current,
                                                         **kwargs))
 
                 if field.data:
-                    current_value = int(field.data.strftime(format))
+                    current_value = int(field.data.strftime(date_format))
                 else:
                     current_value = None
 
                 for value, label in choices:
                     selected = (value == current_value)
+
+                    # Defaults to blank
+                    if value == 1 or value == 1930:
+                        html.append(Select.render_option("None", " ", selected))
+
                     html.append(Select.render_option(value, label, selected))
+
                 html.append('</select>')
+
+                if surrounded_div is not None:
+                    html.append("</div>")
             else:
-                html.append(format)
+                html.append(date_format)
                 html.append(
                     """<input type="hidden" value="{}" {}></input>""".format(
                         html_params(name=field.name, id=id_current, **kwargs)))