forms.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.management.forms
  4. ~~~~~~~~~~~~~~~~~~~~~~~~
  5. It provides the forms that are needed for the management views.
  6. :copyright: (c) 2014 by the FlaskBB Team.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. from flask_wtf import Form
  10. from wtforms import (
  11. BooleanField, HiddenField, IntegerField, PasswordField,
  12. SelectField, StringField, SubmitField, TextAreaField,
  13. )
  14. from wtforms.validators import (
  15. DataRequired, Optional, Email, regexp, Length, URL, ValidationError
  16. )
  17. from wtforms.ext.sqlalchemy.fields import (
  18. QuerySelectField, QuerySelectMultipleField
  19. )
  20. from sqlalchemy.orm.session import make_transient, make_transient_to_detached
  21. from flask_babelplus import lazy_gettext as _
  22. from flaskbb.utils.fields import BirthdayField
  23. from flaskbb.utils.widgets import SelectBirthdayWidget
  24. from flaskbb.extensions import db
  25. from flaskbb.forum.models import Forum, Category
  26. from flaskbb.user.models import User, Group
  27. from flaskbb.utils.requirements import IsAtleastModerator
  28. from flask_allows import Permission
  29. USERNAME_RE = r'^[\w.+-]+$'
  30. is_username = regexp(USERNAME_RE,
  31. message=_("You can only use letters, numbers or dashes."))
  32. def selectable_forums():
  33. return Forum.query.order_by(Forum.position)
  34. def selectable_categories():
  35. return Category.query.order_by(Category.position)
  36. def selectable_groups():
  37. return Group.query.order_by(Group.id.asc()).all()
  38. def select_primary_group():
  39. return Group.query.filter(Group.guest != True).order_by(Group.id)
  40. class UserForm(Form):
  41. username = StringField(_("Username"), validators=[
  42. DataRequired(message=_("A Username is required.")),
  43. is_username])
  44. email = StringField(_("E-Mail Address"), validators=[
  45. DataRequired(message=_("A E-Mail Address is required.")),
  46. Email(message=_("Invalid E-Mail Address."))])
  47. password = PasswordField("Password", validators=[
  48. Optional()])
  49. birthday = BirthdayField(_("Birthday"), format="%d %m %Y",
  50. widget=SelectBirthdayWidget(),
  51. validators=[Optional()])
  52. gender = SelectField(_("Gender"), default="None", choices=[
  53. ("None", ""),
  54. ("Male", _("Male")),
  55. ("Female", _("Female"))])
  56. location = StringField(_("Location"), validators=[
  57. Optional()])
  58. website = StringField(_("Website"), validators=[
  59. Optional(), URL()])
  60. avatar = StringField(_("Avatar"), validators=[
  61. Optional(), URL()])
  62. signature = TextAreaField(_("Forum Signature"), validators=[
  63. Optional(), Length(min=0, max=250)])
  64. notes = TextAreaField(_("Notes"), validators=[
  65. Optional(), Length(min=0, max=5000)])
  66. activated = BooleanField(_("Is active?"), validators=[
  67. Optional()])
  68. primary_group = QuerySelectField(
  69. _("Primary Group"),
  70. query_factory=select_primary_group,
  71. get_label="name")
  72. secondary_groups = QuerySelectMultipleField(
  73. _("Secondary Groups"),
  74. # TODO: Template rendering errors "NoneType is not callable"
  75. # without this, figure out why.
  76. query_factory=select_primary_group,
  77. get_label="name")
  78. submit = SubmitField(_("Save"))
  79. def validate_username(self, field):
  80. if hasattr(self, "user"):
  81. user = User.query.filter(
  82. db.and_(
  83. User.username.like(field.data),
  84. db.not_(User.id == self.user.id)
  85. )
  86. ).first()
  87. else:
  88. user = User.query.filter(User.username.like(field.data)).first()
  89. if user:
  90. raise ValidationError(_("This Username is already taken."))
  91. def validate_email(self, field):
  92. if hasattr(self, "user"):
  93. user = User.query.filter(
  94. db.and_(
  95. User.email.like(field.data),
  96. db.not_(User.id == self.user.id)
  97. )
  98. ).first()
  99. else:
  100. user = User.query.filter(User.email.like(field.data)).first()
  101. if user:
  102. raise ValidationError(_("This E-Mail Address is already taken."))
  103. def save(self):
  104. data = self.data
  105. data.pop('submit', None)
  106. user = User(**data)
  107. return user.save()
  108. class AddUserForm(UserForm):
  109. pass
  110. class EditUserForm(UserForm):
  111. def __init__(self, user, *args, **kwargs):
  112. self.user = user
  113. kwargs['obj'] = self.user
  114. UserForm.__init__(self, *args, **kwargs)
  115. class GroupForm(Form):
  116. name = StringField(_("Group Name"), validators=[
  117. DataRequired(message=_("A Group name is required."))])
  118. description = TextAreaField(_("Description"), validators=[
  119. Optional()])
  120. admin = BooleanField(
  121. _("Is Admin Group?"),
  122. description=_("With this option the group has access to "
  123. "the admin panel.")
  124. )
  125. super_mod = BooleanField(
  126. _("Is Super Moderator Group?"),
  127. description=_("Check this if the users in this group are allowed to "
  128. "moderate every forum.")
  129. )
  130. mod = BooleanField(
  131. _("Is Moderator Group?"),
  132. description=_("Check this if the users in this group are allowed to "
  133. "moderate specified forums.")
  134. )
  135. banned = BooleanField(
  136. _("Is Banned Group?"),
  137. description=_("Only one Banned group is allowed.")
  138. )
  139. guest = BooleanField(
  140. _("Is Guest Group?"),
  141. description=_("Only one Guest group is allowed.")
  142. )
  143. editpost = BooleanField(
  144. _("Can edit posts"),
  145. description=_("Check this if the users in this group can edit posts.")
  146. )
  147. deletepost = BooleanField(
  148. _("Can delete posts"),
  149. description=_("Check this is the users in this group can delete posts.")
  150. )
  151. deletetopic = BooleanField(
  152. _("Can delete topics"),
  153. description=_("Check this is the users in this group can delete "
  154. "topics.")
  155. )
  156. posttopic = BooleanField(
  157. _("Can create topics"),
  158. description=_("Check this is the users in this group can create "
  159. "topics.")
  160. )
  161. postreply = BooleanField(
  162. _("Can post replies"),
  163. description=_("Check this is the users in this group can post replies.")
  164. )
  165. mod_edituser = BooleanField(
  166. _("Moderators can edit user profiles"),
  167. description=_("Allow moderators to edit a another users profile "
  168. "including password and email changes.")
  169. )
  170. mod_banuser = BooleanField(
  171. _("Moderators can ban users"),
  172. description=_("Allow moderators to ban other users.")
  173. )
  174. submit = SubmitField(_("Save"))
  175. def validate_name(self, field):
  176. if hasattr(self, "group"):
  177. group = Group.query.filter(
  178. db.and_(
  179. Group.name.like(field.data),
  180. db.not_(Group.id == self.group.id)
  181. )
  182. ).first()
  183. else:
  184. group = Group.query.filter(Group.name.like(field.data)).first()
  185. if group:
  186. raise ValidationError(_("This Group name is already taken."))
  187. def validate_banned(self, field):
  188. if hasattr(self, "group"):
  189. group = Group.query.filter(
  190. db.and_(
  191. Group.banned,
  192. db.not_(Group.id == self.group.id)
  193. )
  194. ).count()
  195. else:
  196. group = Group.query.filter_by(banned=True).count()
  197. if field.data and group > 0:
  198. raise ValidationError(_("There is already a Banned group."))
  199. def validate_guest(self, field):
  200. if hasattr(self, "group"):
  201. group = Group.query.filter(
  202. db.and_(
  203. Group.guest,
  204. db.not_(Group.id == self.group.id)
  205. )
  206. ).count()
  207. else:
  208. group = Group.query.filter_by(guest=True).count()
  209. if field.data and group > 0:
  210. raise ValidationError(_("There is already a Guest group."))
  211. def save(self):
  212. data = self.data
  213. data.pop('submit', None)
  214. group = Group(**data)
  215. return group.save()
  216. class EditGroupForm(GroupForm):
  217. def __init__(self, group, *args, **kwargs):
  218. self.group = group
  219. kwargs['obj'] = self.group
  220. GroupForm.__init__(self, *args, **kwargs)
  221. class AddGroupForm(GroupForm):
  222. pass
  223. class ForumForm(Form):
  224. title = StringField(
  225. _("Forum Title"),
  226. validators=[DataRequired(message=_("A Forum Title is required."))]
  227. )
  228. description = TextAreaField(
  229. _("Description"),
  230. validators=[Optional()],
  231. description=_("You can format your description with BBCode.")
  232. )
  233. position = IntegerField(
  234. _("Position"),
  235. default=1,
  236. validators=[DataRequired(message=_("The Forum Position is required."))]
  237. )
  238. category = QuerySelectField(
  239. _("Category"),
  240. query_factory=selectable_categories,
  241. allow_blank=False,
  242. get_label="title",
  243. description=_("The category that contains this forum.")
  244. )
  245. external = StringField(
  246. _("External Link"),
  247. validators=[Optional(), URL()],
  248. description=_("A link to a website i.e. 'http://flaskbb.org'.")
  249. )
  250. moderators = StringField(
  251. _("Moderators"),
  252. description=_("Comma seperated usernames. Leave it blank if you do not "
  253. "want to set any moderators.")
  254. )
  255. show_moderators = BooleanField(
  256. _("Show Moderators"),
  257. description=_("Do you want show the moderators on the index page?")
  258. )
  259. locked = BooleanField(
  260. _("Locked?"),
  261. description=_("Disable new posts and topics in this forum.")
  262. )
  263. groups = QuerySelectMultipleField(
  264. _("Group Access to Forum"),
  265. query_factory=selectable_groups,
  266. get_label="name",
  267. description=_("Select user groups that can access this forum.")
  268. )
  269. submit = SubmitField(_("Save"))
  270. def validate_external(self, field):
  271. if hasattr(self, "forum"):
  272. if self.forum.topics.count() > 0:
  273. raise ValidationError(_("You cannot convert a forum that "
  274. "contain topics in a external link."))
  275. def validate_show_moderators(self, field):
  276. if field.data and not self.moderators.data:
  277. raise ValidationError(_("You also need to specify some "
  278. "moderators."))
  279. def validate_moderators(self, field):
  280. approved_moderators = []
  281. if field.data:
  282. moderators = [mod.strip() for mod in field.data.split(',')]
  283. users = User.query.filter(User.username.in_(moderators))
  284. for user in users:
  285. if not Permission(IsAtleastModerator, identity=user):
  286. raise ValidationError(
  287. _("%(user)s is not in a moderators group.",
  288. user=user.username)
  289. )
  290. else:
  291. approved_moderators.append(user)
  292. field.data = approved_moderators
  293. def save(self):
  294. data = self.data
  295. # remove the button
  296. data.pop('submit', None)
  297. forum = Forum(**data)
  298. return forum.save()
  299. class EditForumForm(ForumForm):
  300. id = HiddenField()
  301. def __init__(self, forum, *args, **kwargs):
  302. self.forum = forum
  303. kwargs['obj'] = self.forum
  304. ForumForm.__init__(self, *args, **kwargs)
  305. def save(self):
  306. data = self.data
  307. # remove the button
  308. data.pop('submit', None)
  309. forum = Forum(**data)
  310. # flush SQLA info from created instance so that it can be merged
  311. make_transient(forum)
  312. make_transient_to_detached(forum)
  313. return forum.save()
  314. class AddForumForm(ForumForm):
  315. pass
  316. class CategoryForm(Form):
  317. title = StringField(_("Category Title"), validators=[
  318. DataRequired(message=_("A Category Title is required."))])
  319. description = TextAreaField(
  320. _("Description"),
  321. validators=[Optional()],
  322. description=_("You can format your description with BBCode.")
  323. )
  324. position = IntegerField(
  325. _("Position"),
  326. default=1,
  327. validators=[DataRequired(message=_("The Category Position is "
  328. "required."))]
  329. )
  330. submit = SubmitField(_("Save"))
  331. def save(self):
  332. data = self.data
  333. data.pop('submit', None)
  334. category = Category(**data)
  335. return category.save()