models.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.management.models
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~
  5. This module contains all management related models.
  6. :copyright: (c) 2014 by the FlaskBB Team.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. import logging
  10. from wtforms import (TextField, IntegerField, FloatField, BooleanField,
  11. SelectField, SelectMultipleField, validators)
  12. from flask_wtf import FlaskForm
  13. from flaskbb._compat import text_type, iteritems
  14. from flaskbb.extensions import db, cache
  15. from flaskbb.utils.database import CRUDMixin
  16. logger = logging.getLogger(__name__)
  17. class SettingsGroup(db.Model, CRUDMixin):
  18. __tablename__ = "settingsgroup"
  19. key = db.Column(db.String(255), primary_key=True)
  20. name = db.Column(db.String(255), nullable=False)
  21. description = db.Column(db.Text, nullable=False)
  22. settings = db.relationship("Setting", lazy="dynamic", backref="group",
  23. cascade="all, delete-orphan")
  24. def __repr__(self):
  25. return "<{} {}>".format(self.__class__.__name__, self.key)
  26. class Setting(db.Model, CRUDMixin):
  27. __tablename__ = "settings"
  28. key = db.Column(db.String(255), primary_key=True)
  29. value = db.Column(db.PickleType, nullable=False)
  30. settingsgroup = db.Column(db.String(255),
  31. db.ForeignKey('settingsgroup.key',
  32. use_alter=True,
  33. name="fk_settingsgroup"),
  34. nullable=False)
  35. # The name (displayed in the form)
  36. name = db.Column(db.String(200), nullable=False)
  37. # The description (displayed in the form)
  38. description = db.Column(db.Text, nullable=False)
  39. # Available types: string, integer, float, boolean, select, selectmultiple
  40. value_type = db.Column(db.String(20), nullable=False)
  41. # Extra attributes like, validation things (min, max length...)
  42. # For Select*Fields required: choices
  43. extra = db.Column(db.PickleType)
  44. @classmethod
  45. def get_form(cls, group):
  46. """Returns a Form for all settings found in :class:`SettingsGroup`.
  47. :param group: The settingsgroup name. It is used to get the settings
  48. which are in the specified group.
  49. """
  50. class SettingsForm(FlaskForm):
  51. pass
  52. # now parse the settings in this group
  53. for setting in group.settings:
  54. field_validators = []
  55. if setting.value_type in ("integer", "float"):
  56. validator_class = validators.NumberRange
  57. elif setting.value_type == "string":
  58. validator_class = validators.Length
  59. # generate the validators
  60. if "min" in setting.extra:
  61. # Min number validator
  62. field_validators.append(
  63. validator_class(min=setting.extra["min"])
  64. )
  65. if "max" in setting.extra:
  66. # Max number validator
  67. field_validators.append(
  68. validator_class(max=setting.extra["max"])
  69. )
  70. # Generate the fields based on value_type
  71. # IntegerField
  72. if setting.value_type == "integer":
  73. setattr(
  74. SettingsForm, setting.key,
  75. IntegerField(setting.name, validators=field_validators,
  76. description=setting.description)
  77. )
  78. # FloatField
  79. elif setting.value_type == "float":
  80. setattr(
  81. SettingsForm, setting.key,
  82. FloatField(setting.name, validators=field_validators,
  83. description=setting.description)
  84. )
  85. # TextField
  86. elif setting.value_type == "string":
  87. setattr(
  88. SettingsForm, setting.key,
  89. TextField(setting.name, validators=field_validators,
  90. description=setting.description)
  91. )
  92. # SelectMultipleField
  93. elif setting.value_type == "selectmultiple":
  94. # if no coerce is found, it will fallback to unicode
  95. if "coerce" in setting.extra:
  96. coerce_to = setting.extra['coerce']
  97. else:
  98. coerce_to = text_type
  99. setattr(
  100. SettingsForm, setting.key,
  101. SelectMultipleField(
  102. setting.name,
  103. choices=setting.extra['choices'](),
  104. coerce=coerce_to,
  105. description=setting.description
  106. )
  107. )
  108. # SelectField
  109. elif setting.value_type == "select":
  110. # if no coerce is found, it will fallback to unicode
  111. if "coerce" in setting.extra:
  112. coerce_to = setting.extra['coerce']
  113. else:
  114. coerce_to = text_type
  115. setattr(
  116. SettingsForm, setting.key,
  117. SelectField(
  118. setting.name,
  119. coerce=coerce_to,
  120. choices=setting.extra['choices'](),
  121. description=setting.description)
  122. )
  123. # BooleanField
  124. elif setting.value_type == "boolean":
  125. setattr(
  126. SettingsForm, setting.key,
  127. BooleanField(setting.name, description=setting.description)
  128. )
  129. return SettingsForm
  130. @classmethod
  131. def get_all(cls):
  132. return cls.query.all()
  133. @classmethod
  134. def update(cls, settings, app=None):
  135. """Updates the cache and stores the changes in the
  136. database.
  137. :param settings: A dictionary with setting items.
  138. """
  139. # update the database
  140. for key, value in iteritems(settings):
  141. setting = cls.query.filter(Setting.key == key.lower()).first()
  142. setting.value = value
  143. db.session.add(setting)
  144. db.session.commit()
  145. cls.invalidate_cache()
  146. @classmethod
  147. def get_settings(cls, from_group=None):
  148. """This will return all settings with the key as the key for the dict
  149. and the values are packed again in a dict which contains
  150. the remaining attributes.
  151. :param from_group: Optionally - Returns only the settings from a group.
  152. """
  153. result = None
  154. if from_group is not None:
  155. result = from_group.settings
  156. else:
  157. result = cls.query.all()
  158. settings = {}
  159. for setting in result:
  160. settings[setting.key] = {
  161. 'name': setting.name,
  162. 'description': setting.description,
  163. 'value': setting.value,
  164. 'value_type': setting.value_type,
  165. 'extra': setting.extra
  166. }
  167. return settings
  168. @classmethod
  169. @cache.cached(key_prefix="settings")
  170. def as_dict(cls, from_group=None, upper=True):
  171. """Returns all settings as a dict. This method is cached. If you want
  172. to invalidate the cache, simply execute ``self.invalidate_cache()``.
  173. :param from_group: Returns only the settings from the group as a dict.
  174. :param upper: If upper is ``True``, the key will use upper-case
  175. letters. Defaults to ``False``.
  176. """
  177. settings = {}
  178. result = None
  179. if from_group is not None:
  180. result = SettingsGroup.query.filter_by(key=from_group).\
  181. first_or_404()
  182. result = result.settings
  183. else:
  184. result = cls.query.all()
  185. for setting in result:
  186. if upper:
  187. setting_key = setting.key.upper()
  188. else:
  189. setting_key = setting.key
  190. settings[setting_key] = setting.value
  191. return settings
  192. @classmethod
  193. def invalidate_cache(cls):
  194. """Invalidates this objects cached metadata."""
  195. cache.delete_memoized(cls.as_dict, cls)