forms.py 13 KB

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