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 flask_wtf import Form
 from wtforms import (StringField, TextAreaField, PasswordField, IntegerField,
 from wtforms import (StringField, TextAreaField, PasswordField, IntegerField,
-                     BooleanField, SelectField, DateField, SubmitField)
+                     BooleanField, SelectField, SubmitField)
 from wtforms.validators import (DataRequired, Optional, Email, regexp, Length,
 from wtforms.validators import (DataRequired, Optional, Email, regexp, Length,
                                 URL, ValidationError)
                                 URL, ValidationError)
 from wtforms.ext.sqlalchemy.fields import (QuerySelectField,
 from wtforms.ext.sqlalchemy.fields import (QuerySelectField,
                                            QuerySelectMultipleField)
                                            QuerySelectMultipleField)
 from flask_babelex import lazy_gettext as _
 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.extensions import db
 from flaskbb.forum.models import Forum, Category
 from flaskbb.forum.models import Forum, Category
 from flaskbb.user.models import User, Group
 from flaskbb.user.models import User, Group
@@ -51,9 +52,9 @@ class UserForm(Form):
     password = PasswordField("Password", validators=[
     password = PasswordField("Password", validators=[
         Optional()])
         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=[
     gender = SelectField(_("Gender"), default="None", choices=[
         ("None", ""),
         ("None", ""),

+ 30 - 3
flaskbb/templates/macros.html

@@ -76,7 +76,7 @@
 {%- endmacro -%}
 {%- endmacro -%}
 
 
 
 
-{%- macro render_select_field(field, div_class='') -%}
+{%- macro render_select_field(field, div_class='', select_class="form-control") -%}
 <div class="form-group">
 <div class="form-group">
     {% if div_class %}
     {% if div_class %}
     <div class="{{ div_class }}">
     <div class="{{ div_class }}">
@@ -85,9 +85,9 @@
     {% endif %}
     {% endif %}
         <label>{{ field.label.text }}</label>
         <label>{{ field.label.text }}</label>
         {% if field.type == 'QuerySelectMultipleField' or field.type == 'SelectMultipleField' %}
         {% if field.type == 'QuerySelectMultipleField' or field.type == 'SelectMultipleField' %}
-            {{ field(multiple=True, class="form-control") }}
+            {{ field(multiple=True, class=select_class) }}
         {% else %}
         {% else %}
-            {{ field(class="form-control") }}
+            {{ field(class=select_class) }}
         {%- endif -%}
         {%- endif -%}
 
 
         {{ field_description(field) }}
         {{ field_description(field) }}
@@ -188,6 +188,33 @@
     </div>
     </div>
 {%- endmacro -%}
 {%- 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='') -%}
 {%- macro horizontal_boolean_field(field, div_class='') -%}
 {%- if div_class %}
 {%- if div_class %}

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

@@ -3,7 +3,7 @@
 
 
 {% extends theme("management/management_layout.html") %}
 {% extends theme("management/management_layout.html") %}
 {% block management_content %}
 {% 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">
 <div class="col-md-3">
     <ul class="nav nav-pills nav-stacked">
     <ul class="nav nav-pills nav-stacked">
@@ -23,7 +23,7 @@
             {{ horizontal_field(form.username) }}
             {{ horizontal_field(form.username) }}
             {{ horizontal_field(form.email) }}
             {{ horizontal_field(form.email) }}
             {{ horizontal_field(form.password) }}
             {{ 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.gender) }}
             {{ horizontal_field(form.location) }}
             {{ horizontal_field(form.location) }}
             {{ horizontal_field(form.website) }}
             {{ horizontal_field(form.website) }}

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

@@ -1,11 +1,11 @@
 {% extends theme("user/settings_layout.html") %}
 {% 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 %}
 {% block settings_content %}
 <form class="form-horizontal" role="form" method="POST">
 <form class="form-horizontal" role="form" method="POST">
     <legend class="">{% trans %}Change User Details{% endtrans %}</legend>
     <legend class="">{% trans %}Change User Details{% endtrans %}</legend>
     {{ form.hidden_tag() }}
     {{ 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.gender) }}
     {{ horizontal_field(form.location) }}
     {{ horizontal_field(form.location) }}
     {{ horizontal_field(form.website) }}
     {{ horizontal_field(form.website) }}

+ 11 - 6
flaskbb/user/forms.py

@@ -10,15 +10,16 @@
 """
 """
 from flask_login import current_user
 from flask_login import current_user
 from flask_wtf import Form
 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,
 from wtforms.validators import (Length, DataRequired, InputRequired, Email,
                                 EqualTo, regexp, Optional, URL)
                                 EqualTo, regexp, Optional, URL)
 from flask_babelex import lazy_gettext as _
 from flask_babelex import lazy_gettext as _
 
 
 from flaskbb.user.models import User, PrivateMessage
 from flaskbb.user.models import User, PrivateMessage
 from flaskbb.extensions import db
 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)'
 IMG_RE = r'^[^/\\]\.(?:jpg|gif|png)'
@@ -82,9 +83,9 @@ class ChangePasswordForm(Form):
 
 
 
 
 class ChangeUserDetailsForm(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=[
     gender = SelectField(_("Gender"), default="None", choices=[
         ("None", ""),
         ("None", ""),
@@ -108,6 +109,10 @@ class ChangeUserDetailsForm(Form):
 
 
     submit = SubmitField(_("Save"))
     submit = SubmitField(_("Save"))
 
 
+    def validate_birthday(self, field):
+        if field.data is None:
+            return True
+
 
 
 class NewMessageForm(Form):
 class NewMessageForm(Form):
     to_user = StringField(_("To User"), validators=[
     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 -*-
 # -*- coding: utf-8 -*-
 """
 """
-    flaskbb.utils.wtforms
-    ~~~~~~~~~~~~~~~~~~~~
+    flaskbb.utils.widgets
+    ~~~~~~~~~~~~~~~~~~~~~
 
 
     Additional widgets for wtforms
     Additional widgets for wtforms
 
 
@@ -12,7 +12,7 @@ from datetime import datetime
 from wtforms.widgets.core import Select, HTMLString, html_params
 from wtforms.widgets.core import Select, HTMLString, html_params
 
 
 
 
-class SelectDateWidget(object):
+class SelectBirthdayWidget(object):
     """Renders a DateTime field with 3 selects.
     """Renders a DateTime field with 3 selects.
     For more information see: http://stackoverflow.com/a/14664504
     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)):
     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]
         self.FORMAT_CHOICES['%Y'] = [(x, str(x)) for x in years]
 
 
     def __call__(self, field, **kwargs):
     def __call__(self, field, **kwargs):
         field_id = kwargs.pop('id', field.id)
         field_id = kwargs.pop('id', field.id)
         html = []
         html = []
         allowed_format = ['%d', '%m', '%Y']
         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
                 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:
                 try:
                     del kwargs['placeholder']
                     del kwargs['placeholder']
-                except:
+                except KeyError:
                     pass
                     pass
 
 
+                if surrounded_div is not None:
+                    html.append("<div class='%s'>" % surrounded_div)
+
                 html.append('<select %s>' % html_params(name=field.name,
                 html.append('<select %s>' % html_params(name=field.name,
                                                         id=id_current,
                                                         id=id_current,
                                                         **kwargs))
                                                         **kwargs))
 
 
                 if field.data:
                 if field.data:
-                    current_value = int(field.data.strftime(format))
+                    current_value = int(field.data.strftime(date_format))
                 else:
                 else:
                     current_value = None
                     current_value = None
 
 
                 for value, label in choices:
                 for value, label in choices:
                     selected = (value == current_value)
                     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.render_option(value, label, selected))
+
                 html.append('</select>')
                 html.append('</select>')
+
+                if surrounded_div is not None:
+                    html.append("</div>")
             else:
             else:
-                html.append(format)
+                html.append(date_format)
                 html.append(
                 html.append(
                     """<input type="hidden" value="{}" {}></input>""".format(
                     """<input type="hidden" value="{}" {}></input>""".format(
                         html_params(name=field.name, id=id_current, **kwargs)))
                         html_params(name=field.name, id=id_current, **kwargs)))