Browse Source

Added dynamic form composition for the setting fields.

sh4nks 11 years ago
parent
commit
df006a5686
5 changed files with 165 additions and 99 deletions
  1. 4 0
      flaskbb/admin/forms.py
  2. 112 8
      flaskbb/admin/models.py
  3. 20 2
      flaskbb/admin/views.py
  4. 7 0
      flaskbb/app.py
  5. 22 89
      flaskbb/templates/admin/settings.html

+ 4 - 0
flaskbb/admin/forms.py

@@ -354,3 +354,7 @@ class CategoryForm(Form):
     def save(self):
     def save(self):
         category = Category(**self.data)
         category = Category(**self.data)
         return category.save()
         return category.save()
+
+
+class SettingsForm(Form):
+    pass

+ 112 - 8
flaskbb/admin/models.py

@@ -5,14 +5,16 @@ try:
 except ImportError:
 except ImportError:
     import pickle
     import pickle
 
 
+from wtforms import (TextField, IntegerField, BooleanField, SelectField,
+                     FloatField, validators)
+from flask.ext.wtf import Form
 from flaskbb.extensions import db, cache
 from flaskbb.extensions import db, cache
 
 
 
 
 def normalize_to(value, value_type, reverse=False):
 def normalize_to(value, value_type, reverse=False):
     """Converts a value to a specified value type.
     """Converts a value to a specified value type.
-    Available value types are: string, int, boolean, list.
+    Available value types are: string, integer, boolean and array.
     A boolean type is handled as 0 for false and 1 for true.
     A boolean type is handled as 0 for false and 1 for true.
-    Raises a exception if the value couldn't be converted
 
 
     :param value: The value which should be converted.
     :param value: The value which should be converted.
     :param value_type: The value_type.
     :param value_type: The value_type.
@@ -77,12 +79,14 @@ class Setting(db.Model):
     # Available types: string, integer, boolean, array, float
     # Available types: string, integer, boolean, array, float
     value_type = db.Column(db.String, nullable=False)
     value_type = db.Column(db.String, nullable=False)
 
 
-    # Available types: text, choice, yesno (used for the form creation process)
+    # Available types: text, number, choice, yesno
+    # They are used in the form creation process
     input_type = db.Column(db.String, nullable=False)
     input_type = db.Column(db.String, nullable=False)
 
 
     # Extra attributes like, validation things (min, max length...)
     # Extra attributes like, validation things (min, max length...)
     _extra = db.Column("extra", db.String)
     _extra = db.Column("extra", db.String)
 
 
+    # Properties
     @property
     @property
     def value(self):
     def value(self):
         return normalize_to(self._value, self.value_type)
         return normalize_to(self._value, self.value_type)
@@ -101,17 +105,117 @@ class Setting(db.Model):
             pickle.dumps((extra), pickle.HIGHEST_PROTOCOL)
             pickle.dumps((extra), pickle.HIGHEST_PROTOCOL)
         )
         )
 
 
-    def get_field(self):
-        pass
-
     @classmethod
     @classmethod
     @cache.memoize(timeout=sys.maxint)
     @cache.memoize(timeout=sys.maxint)
+    def config(self):
+        """Returns the configs as a dict (only self.key and self.value).
+        If a value/key has changed, you need to invalidate the cache."""
+        settings = {}
+        for setting in self.get_all():
+            settings[setting.key.upper()] = setting.value
+
+        return settings
+
+    @classmethod
     def get_all(cls):
     def get_all(cls):
-        pass
+        return cls.query.all()
+
+    @classmethod
+    def get_form(cls, group):
+        """Returns a Form for all settings found in :class:`SettingsGroup`.
+
+        :param group: The settingsgroup name. It is used to get the settings
+                      which are in the specified group. Aborts with 404 if the
+                      group is found.
+        """
+        settings = SettingsGroup.query.filter_by(key=group).first_or_404()
+
+        class SettingsForm(Form):
+            pass
+
+        # now parse that shit
+        for setting in settings.settings:
+            field_validators = []
+
+            # generate the validators
+            # TODO: Do this in another function
+            if "min" in setting.extra:
+                # Min number validator
+                if setting.value_type in ("integer", "float"):
+                    field_validators.append(
+                        validators.NumberRange(min=setting.extra["min"])
+                    )
+
+                # Min text length validator
+                elif setting.value_type in ("string", "array"):
+                    field_validators.append(
+                        validators.Length(min=setting.extra["min"])
+                    )
+
+            if "max" in setting.extra:
+                # Max number validator
+                if setting.value_type in ("integer", "float"):
+                    field_validators.append(
+                        validators.NumberRange(max=setting.extra["max"])
+                    )
+
+                # Max text length validator
+                elif setting.value_type in ("string", "array"):
+                    field_validators.append(
+                        validators.Length(max=setting.extra["max"])
+                    )
+
+            # Generate the fields based on input_type and value_type
+            if setting.input_type == "number":
+                # IntegerField
+                if setting.value_type == "integer":
+                    setattr(
+                        SettingsForm, setting.key,
+                        IntegerField(setting.name, field_validators)
+                    )
+                # FloatField
+                elif setting.value_type == "float":
+                    setattr(
+                        SettingsForm, setting.key,
+                        FloatField(setting.name, field_validators)
+                    )
+
+            # TextField
+            if setting.input_type == "text":
+                setattr(
+                    SettingsForm, setting.key,
+                    TextField(setting.name, field_validators)
+                )
+
+            # SelectField
+            if setting.input_type == "choice" and "choices" in setting.extra:
+                setattr(
+                    SettingsForm, setting.key,
+                    SelectField(setting.name, choices=setting.extra['choices'])
+                )
+
+            # BooleanField
+            if setting.input_type == "yesno":
+                setattr(
+                    SettingsForm, setting.key,
+                    BooleanField(setting.name)
+                )
+
+        return SettingsForm()
+
+    @classmethod
+    def update(self, app):
+        """Updates the config for the app
+
+        :param app: The application.
+        """
+        self.invalidate_cache()
+
+        app.config.update(self.config())
 
 
     def save(self):
     def save(self):
         db.session.add(self)
         db.session.add(self)
         db.session.commit()
         db.session.commit()
 
 
     def invalidate_cache(self):
     def invalidate_cache(self):
-        cache.delete_memoized(self.get_all, self)
+        cache.delete_memoized(self.config, self)

+ 20 - 2
flaskbb/admin/views.py

@@ -23,6 +23,7 @@ from flaskbb.utils.decorators import admin_required
 from flaskbb.extensions import db
 from flaskbb.extensions import db
 from flaskbb.user.models import User, Group
 from flaskbb.user.models import User, Group
 from flaskbb.forum.models import Post, Topic, Forum, Category, Report
 from flaskbb.forum.models import Post, Topic, Forum, Category, Report
+from flaskbb.admin.models import Setting, SettingsGroup
 from flaskbb.admin.forms import (AddUserForm, EditUserForm, AddGroupForm,
 from flaskbb.admin.forms import (AddUserForm, EditUserForm, AddGroupForm,
                                  EditGroupForm, EditForumForm, AddForumForm,
                                  EditGroupForm, EditForumForm, AddForumForm,
                                  CategoryForm)
                                  CategoryForm)
@@ -48,9 +49,26 @@ def overview():
 
 
 
 
 @admin.route("/settings", methods=["GET", "POST"])
 @admin.route("/settings", methods=["GET", "POST"])
+@admin.route("/settings/<path:slug>", methods=["GET", "POST"])
 @admin_required
 @admin_required
-def settings():
-    return render_template("admin/settings.html", themes=[])
+def settings(slug=None):
+    # Get all settinggroups so that we can build the settings navigation
+    settingsgroup = SettingsGroup.query.all()
+
+    if slug is not None:
+        form = Setting.get_form(slug)
+    else:
+        # or should we display an index with all available settingsgroups?
+        form = Setting.get_form("general")
+
+    if form.validate_on_submit():
+        # update the db rows
+        # invalidate the cache
+        # update the app config
+        pass
+
+    return render_template("admin/settings.html", form=form,
+                           settingsgroup=settingsgroup)
 
 
 
 
 @admin.route("/users", methods=['GET', 'POST'])
 @admin.route("/users", methods=['GET', 'POST'])

+ 7 - 0
flaskbb/app.py

@@ -22,6 +22,7 @@ from flaskbb.user.models import User, Guest, PrivateMessage
 from flaskbb.auth.views import auth
 from flaskbb.auth.views import auth
 # Import the admin blueprint
 # Import the admin blueprint
 from flaskbb.admin.views import admin
 from flaskbb.admin.views import admin
+from flaskbb.admin.models import Setting
 # Import the forum blueprint
 # Import the forum blueprint
 from flaskbb.forum.views import forum
 from flaskbb.forum.views import forum
 from flaskbb.forum.models import Post, Topic, Category, Forum
 from flaskbb.forum.models import Post, Topic, Category, Forum
@@ -59,6 +60,7 @@ def create_app(config=None):
     configure_before_handlers(app)
     configure_before_handlers(app)
     configure_errorhandlers(app)
     configure_errorhandlers(app)
     configure_logging(app)
     configure_logging(app)
+    update_settings_from_db(app)
 
 
     return app
     return app
 
 
@@ -131,6 +133,11 @@ def configure_extensions(app):
     login_manager.init_app(app)
     login_manager.init_app(app)
 
 
 
 
+def update_settings_from_db(app):
+    with app.app_context():
+        app.config.update(Setting.config())
+
+
 def configure_template_filters(app):
 def configure_template_filters(app):
     """
     """
     Configures the template filters
     Configures the template filters

+ 22 - 89
flaskbb/templates/admin/settings.html

@@ -1,100 +1,33 @@
 {% extends theme("admin/admin_layout.html") %}
 {% extends theme("admin/admin_layout.html") %}
 {% block admin_content %}
 {% block admin_content %}
-{% from theme('macros.html') import render_pagination %}
+{% from theme('macros.html') import render_boolean_field, render_select_field, render_field %}
 
 
 <legend>Settings</legend>
 <legend>Settings</legend>
 
 
 <form class="form-horizontal" role="form" method="post">
 <form class="form-horizontal" role="form" method="post">
 
 
-  <div class="form-group">
-    <label for="project_title" class="col-sm-2 control-label">Project title</label>
-    <div class="col-sm-10">
-      <input type="text" class="form-control" name="project_title", id="project_title", value="{{ config['PROJECT_TITLE'] }}">
-      <span class="help-block">The name of your project</span>
+    {% for field in form %}
+        {% if field.type in ["HiddenField", "CSRFTokenField"] %}
+            {{ field() }}
+        {% endif %}
+        {% if field.type not in ["TextField", "IntegerField"] %}
+            {% if field.type == "BooleanField" %}
+                {{ render_boolean_field }}
+            {% endif %}
+
+            {% if field.type in ["SelectField", "SelectMultipleField"] %}
+                {{ render_select_field(field) }}
+            {% endif %}
+        {% else %}
+            {{ render_field(field) }}
+        {% endif %}
+    {%  endfor %}
+
+    <div class="form-group">
+        <div class="col-sm-offset-2 col-sm-10">
+            <button type="submit" class="btn btn-default">Save</button>
+        </div>
     </div>
     </div>
-  </div>
-
-  <div class="form-group">
-    <label for="project_subtitle" class="col-sm-2 control-label">Project subtitle</label>
-    <div class="col-sm-10">
-      <input type="text" class="form-control" name="project_subtitle", id="project_subtitle", value="{{ config['PROJECT_SUBTITLE'] }}">
-      <span class="help-block">The subtitle of your project (if any).</span>
-    </div>
-  </div>
-
-  <div class="form-group">
-    <label class="col-sm-2 control-label" for="default_theme">Default theme</label>
-    <div class="col-sm-10">
-      <select class="form-control" id="default_theme" name="default_theme">
-        <option selected value="{{ config['DEFAULT_THEME'].identifier }}">{{ config['DEFAULT_THEME'].name }}</option>
-        {% for theme in themes %}
-          <option value="{{ theme.identifier }}">{{ theme.name }}</option>
-        {% endfor %}
-      </select>
-      <span class="help-block">The default theme for FlaskBB.</span>
-    </div>
-  </div>
-
-  <div class="form-group">
-    <label class="col-sm-2 control-label" for="tracker_length">Tracker length</label>
-    <div class="col-sm-10">
-    <input type="text" class="form-control" name="tracker_length" id="tracker_length", value="{{ config['TRACKER_LENGTH'] }}">
-      <span class="help-block">The tracker length defines how long the topics will stay as unread. <b>0</b> to disable it.</span>
-    </div>
-  </div>
-
-  <div class="form-group">
-    <label class="col-sm-2 control-label" for="users_per_page">Users Per Page</label>
-    <div class="col-sm-10">
-    <input type="text" class="form-control" name="users_per_page" id="users_per_page", value="{{ config['USERS_PER_PAGE'] }}">
-      <span class="help-block">How many users per page are displayed.</span>
-    </div>
-  </div>
-
-  <div class="form-group">
-    <label class="col-sm-2 control-label" for="topics_per_page">Topics Per Page</label>
-    <div class="col-sm-10">
-    <input type="text" class="form-control" name="topics_per_page" id="topics_per_page", value="{{ config['TOPICS_PER_PAGE'] }}">
-      <span class="help-block">How many topics per page are displayed.</span>
-    </div>
-  </div>
-
-  <div class="form-group">
-    <label class="col-sm-2 control-label" for="posts_per_page">Posts Per Page</label>
-    <div class="col-sm-10">
-    <input type="text" class="form-control" name="posts_per_page" id="posts_per_page", value="{{ config['POSTS_PER_PAGE'] }}">
-      <span class="help-block">How many posts per page are displayed.</span>
-    </div>
-  </div>
-
-  <div class="form-group">
-    <label class="col-sm-2 control-label" for="online_last_minutes">Online Last Minutes</label>
-    <div class="col-sm-10">
-    <input type="text" class="form-control" name="online_last_minutes" id="online_last_minutes", value="{{ config['ONLINE_LAST_MINUTES'] }}">
-      <span class="help-block">How long the use can be inactive before he is marked as offline.</span>
-    </div>
-  </div>
-
-  <!--
-  <div class="form-group">
-    <label for="optionsRadio" class="col-sm-2 control-label">Simple yes/no</label>
-    <div class="col-sm-10">
-      <label class="radio-inline">
-        <input type="radio" name="optionsRadios" id="optionsRadios1" value="option1" checked> Yes
-      </label>
-      <label class="radio-inline">
-        <input type="radio" name="optionsRadios" id="optionsRadios2" value="option2"> No
-      </label>
-      <span class="help-block">A block of help text that breaks onto a new line and may extend beyond one line.</span>
-    </div>
-  </div>
-  -->
-
-  <div class="form-group">
-    <div class="col-sm-offset-2 col-sm-10">
-      <button type="submit" class="btn btn-default">Save</button>
-    </div>
-  </div>
 
 
 </form>
 </form>