795764886b3c887a6e4988efe77eea8f7665d71a — Kridsada Thanabulpong a month ago
Allow banwords to be created with scope.
M fanboi2/forms.py => fanboi2/forms.py +14 -0
@@ 49,6 49,7 @@ to two objects, :attr:`title` to :class:`Topic` and :attr:`body` to
      :class:`Post`.
      """
+ 
      title = TextField("Title", validators=[Required(), Length(5, 200)])
      body = TextAreaField("Body", validators=[Required(), Length(5, 4000)])
  


@@ 57,18 58,21 @@ """A :class:`Form` for replying to a topic. The :attr:`body` field should
      be populated to :class:`Post`.
      """
+ 
      body = TextAreaField("Body", validators=[Required(), Length(5, 4000)])
      bumped = BooleanField("Bump this topic", default=True)
  
  
  class AdminLoginForm(Form):
      """A :class:`Form` for logging into a moderation system."""
+ 
      username = TextField("Username", validators=[Required()])
      password = PasswordField("Password", validators=[Required()])
  
  
  class AdminSetupForm(Form):
      """A :class:`Form` for creating an initial user."""
+ 
      username = TextField("Username", validators=[Required(), Length(2, 32)])
      password = PasswordField("Password", validators=[Required(), Length(8, 64)])
      password_confirm = PasswordField(


@@ 80,6 84,7 @@   class AdminSettingForm(Form):
      """A :class:`Form` for updating settings."""
+ 
      value = TextAreaField("Value", validators=[Required()])
  
      def validate_value(self, field):


@@ 92,6 97,7 @@   class AdminBanForm(Form):
      """A :class:`Form` for creating and updating bans."""
+ 
      ip_address = TextField("IP address", validators=[Required()])
      description = TextField("Description")
      duration = IntegerField("Duration", default=0)


@@ 108,8 114,10 @@   class AdminBanwordForm(Form):
      """A :class:`Form` for creating and updating banwords."""
+ 
      expr = TextField("Expression", validators=[Required()])
      description = TextField("Description")
+     scope = TextField("Scope")
      active = BooleanField("Active", default=True)
  
      def validate_expr(self, field):


@@ 122,6 130,7 @@   class AdminBoardForm(Form):
      """A :class:`Form` for updating a board."""
+ 
      title = TextField("Title", validators=[Required()])
      description = TextField("Description", validators=[Required()])
      status = SelectField(


@@ 148,26 157,31 @@   class AdminBoardNewForm(AdminBoardForm):
      """A :class:`Form` for creating a board."""
+ 
      slug = TextField("Slug", validators=[Required()])
  
  
  class AdminPageForm(Form):
      """A :class:`Form` for creating and updating pages."""
+ 
      body = TextAreaField("Body", validators=[Required()])
  
  
  class AdminPublicPageForm(AdminPageForm):
      """A :class:`Form` for updating public pages."""
+ 
      title = TextField("Title", validators=[Required()])
  
  
  class AdminPublicPageNewForm(AdminPublicPageForm):
      """A :class:`Form` for creating public pages."""
+ 
      slug = TextField("Slug", validators=[Required()])
  
  
  class AdminTopicForm(Form):
      """A :class:`Form` for updating topic."""
+ 
      status = SelectField(
          "Status",
          validators=[Required()],

M fanboi2/services/banword.py => fanboi2/services/banword.py +9 -3
@@ 11,20 11,26 @@ def __init__(self, dbsession):
          self.dbsession = dbsession
  
-     def create(self, expr, description=None, active=True):
+     def create(self, expr, description=None, scope=None, active=True):
          """Create a new banword.
  
          :param expr: A regular expression for the keyword to ban.
          :param description: A description for the banword.
+         :param scope: A scope for the banword.
          :param active: Boolean flag whether the banword should be active.
          """
          if not expr:
              expr = None
  
+         if not scope:
+             scope = None
+ 
          if not description:
              description = None
  
-         banword = Banword(expr=expr, description=description, active=bool(active))
+         banword = Banword(
+             expr=expr, description=description, scope=scope, active=bool(active)
+         )
          self.dbsession.add(banword)
          return banword
  


@@ 84,7 90,7 @@ if "active" in kwargs:
              banword.active = bool(kwargs["active"])
  
-         for key in ("expr", "description"):
+         for key in ("expr", "description", "scope"):
              if key in kwargs:
                  value = kwargs[key]
                  if not value:

M fanboi2/templates/admin/bans/edit.mako => fanboi2/templates/admin/bans/edit.mako +3 -3
@@ 15,21 15,21 @@ </div>
      <div class="form-item${' error' if form.description.errors else ''}">
          <label class="form-item-label" for="${form.description.id}">Description</label>
-         ${form.description(class_="input block font-large", rows=6)}
+         ${form.description(class_="input block font-large")}
          % if form.description.errors:
              <span class="form-item-error">${form.description.errors[0]}</span>
          % endif
      </div>
      <div class="form-item${' error' if form.duration.errors else ''}">
          <label class="form-item-label" for="${form.duration.id}">Duration</label>
-         ${form.duration(class_="input block font-large", rows=6)}
+         ${form.duration(class_="input block font-large")}
          % if form.duration.errors:
              <span class="form-item-error">${form.duration.errors[0]}</span>
          % endif
      </div>
      <div class="form-item${' error' if form.scope.errors else ''}">
          <label class="form-item-label" for="${form.scope.id}">Scope</label>
-         ${form.scope(class_="input block font-large", rows=6)}
+         ${form.scope(class_="input block font-large")}
          % if form.scope.errors:
              <span class="form-item-error">${form.scope.errors[0]}</span>
          % endif

M fanboi2/templates/admin/bans/new.mako => fanboi2/templates/admin/bans/new.mako +3 -3
@@ 15,21 15,21 @@ </div>
      <div class="form-item${' error' if form.description.errors else ''}">
          <label class="form-item-label" for="${form.description.id}">Description</label>
-         ${form.description(class_="input block font-large", rows=6)}
+         ${form.description(class_="input block font-large")}
          % if form.description.errors:
              <span class="form-item-error">${form.description.errors[0]}</span>
          % endif
      </div>
      <div class="form-item${' error' if form.duration.errors else ''}">
          <label class="form-item-label" for="${form.duration.id}">Duration</label>
-         ${form.duration(class_="input block font-large", rows=6)}
+         ${form.duration(class_="input block font-large")}
          % if form.duration.errors:
              <span class="form-item-error">${form.duration.errors[0]}</span>
          % endif
      </div>
      <div class="form-item${' error' if form.scope.errors else ''}">
          <label class="form-item-label" for="${form.scope.id}">Scope</label>
-         ${form.scope(class_="input block font-large", rows=6)}
+         ${form.scope(class_="input block font-large")}
          % if form.scope.errors:
              <span class="form-item-error">${form.scope.errors[0]}</span>
          % endif

M fanboi2/templates/admin/banwords/all.mako => fanboi2/templates/admin/banwords/all.mako +15 -7
@@ 10,6 10,7 @@ <thead class="admin-table-header">
              <tr class="admin-table-row">
                  <th class="admin-table-item title">Expression</th>
+                 <th class="admin-table-item title sublead">Scope</th>
                  <th class="admin-table-item title tail">Description</th>
              </tr>
          </thead>


@@ 18,14 19,21 @@ <tr class="admin-table-row">
                      <th class="admin-table-item title">
                          <code><a href="${request.route_path('admin_banword', banword=banword.id)}">${banword.expr}</a></code>
-                         <td class="admin-table-item">
-                             % if banword.description:
-                                 ${banword.description}
-                             % else:
-                                 <em>No description</em>
-                             % endif
-                         </td>
                      </th>
+                     <td class="admin-table-item">
+                         % if banword.scope:
+                             ${banword.scope}
+                         % else:
+                             <em>Global</em>
+                         % endif
+                     </td>
+                     <td class="admin-table-item">
+                         % if banword.description:
+                             ${banword.description}
+                         % else:
+                             <em>No description</em>
+                         % endif
+                     </td>
                  </tr>
              % endfor
          </tbody>

M fanboi2/templates/admin/banwords/edit.mako => fanboi2/templates/admin/banwords/edit.mako +8 -1
@@ 15,11 15,18 @@ </div>
      <div class="form-item${' error' if form.description.errors else ''}">
          <label class="form-item-label" for="${form.description.id}">Description</label>
-         ${form.description(class_="input block font-large", rows=6)}
+         ${form.description(class_="input block font-large")}
          % if form.description.errors:
              <span class="form-item-error">${form.description.errors[0]}</span>
          % endif
      </div>
+     <div class="form-item${' error' if form.scope.errors else ''}">
+         <label class="form-item-label" for="${form.scope.id}">Scope</label>
+         ${form.scope(class_="input block font-large")}
+         % if form.scope.errors:
+             <span class="form-item-error">${form.scope.errors[0]}</span>
+         % endif
+     </div>
      <div class="form-item">
          <button class="button brand" type="submit">Update Banword</button>
          <span class="form-item-inline">

M fanboi2/templates/admin/banwords/inactive.mako => fanboi2/templates/admin/banwords/inactive.mako +14 -7
@@ 18,14 18,21 @@ <tr class="admin-table-row">
                      <th class="admin-table-item title">
                          <code><a href="${request.route_path('admin_banword', banword=banword.id)}">${banword.expr}</a></code>
-                         <td class="admin-table-item">
-                             % if banword.description:
-                                 ${banword.description}
-                             % else:
-                                 <em>No description</em>
-                             % endif
-                         </td>
                      </th>
+                     <td class="admin-table-item">
+                         % if banword.description:
+                             ${banword.description}
+                         % else:
+                             <em>No description</em>
+                         % endif
+                     </td>
+                     <td class="admin-table-item">
+                         % if banword.description:
+                             ${banword.description}
+                         % else:
+                             <em>No description</em>
+                         % endif
+                     </td>
                  </tr>
              % endfor
          </tbody>

M fanboi2/templates/admin/banwords/new.mako => fanboi2/templates/admin/banwords/new.mako +8 -1
@@ 15,11 15,18 @@ </div>
      <div class="form-item${' error' if form.description.errors else ''}">
          <label class="form-item-label" for="${form.description.id}">Description</label>
-         ${form.description(class_="input block font-large", rows=6)}
+         ${form.description(class_="input block font-large")}
          % if form.description.errors:
              <span class="form-item-error">${form.description.errors[0]}</span>
          % endif
      </div>
+     <div class="form-item${' error' if form.scope.errors else ''}">
+         <label class="form-item-label" for="${form.scope.id}">Scope</label>
+         ${form.scope(class_="input block font-large")}
+         % if form.scope.errors:
+             <span class="form-item-error">${form.scope.errors[0]}</span>
+         % endif
+     </div>
      <div class="form-item">
          <button class="button green" type="submit">Create Banword</button>
          <span class="form-item-inline">

M fanboi2/templates/admin/banwords/show.mako => fanboi2/templates/admin/banwords/show.mako +10 -0
@@ 27,6 27,16 @@ % endif
                  </td>
              </tr>
+             <tr class="admin-table-row">
+                 <th class="admin-table-item title lead">Scope</th>
+                 <td class="admin-table-item">
+                 % if banword.scope:
+                     ${banword.scope}
+                 % else:
+                     <em>Global</em>
+                 % endif
+                 </td>
+             </tr>
          </tbody>
      </table>
      <a class="button brand" href="${request.route_path('admin_banword_edit', banword=banword.id)}">Edit Banword</a>

M fanboi2/tests/test_integrations_admin_banwords.py => fanboi2/tests/test_integrations_admin_banwords.py +16 -4
@@ 73,6 73,7 @@ request.POST = MultiDict({})
          request.POST["expr"] = "https?:\\/\\/bit\\.ly/"
          request.POST["description"] = "Violation of galactic law"
+         request.POST["scope"] = "board:foo"
          request.POST["active"] = "1"
          request.POST["csrf_token"] = request.session.get_csrf_token()
          self.config.add_route("admin_banword", "/admin/banwords/{banword}")


@@ 82,6 83,7 @@ self.assertEqual(self.dbsession.query(Banword).count(), 1)
          self.assertEqual(banword.expr, "https?:\\/\\/bit\\.ly/")
          self.assertEqual(banword.description, "Violation of galactic law")
+         self.assertEqual(banword.scope, "board:foo")
          self.assertTrue(banword.active)
  
      def test_banword_new_post_bad_csrf(self):


@@ 94,7 96,7 @@ self.request.POST["ip_address"] = "10.0.1.0/24"
          self.request.POST["description"] = "Violation of galactic law"
          self.request.POST["duration"] = 30
-         self.request.POST["scope"] = "galaxy_far_away"
+         self.request.POST["scope"] = "board:galaxy_far_away"
          self.request.POST["active"] = "1"
          with self.assertRaises(BadCSRFToken):
              banword_new_post(self.request)


@@ 114,6 116,7 @@ request.POST = MultiDict({})
          request.POST["expr"] = "(?y)"
          request.POST["description"] = "Violation of galactic law"
+         request.POST["scope"] = "board:foo"
          request.POST["active"] = "1"
          request.POST["csrf_token"] = request.session.get_csrf_token()
          response = banword_new_post(request)


@@ 207,7 210,10 @@           banword = self._make(
              Banword(
-                 expr="https?:\\/\\/bit\\.ly", description="no shortlinks", active=True
+                 expr="https?:\\/\\/bit\\.ly",
+                 description="no shortlinks",
+                 scope="board:foo",
+                 active=True,
              )
          )
          self.dbsession.commit()


@@ 226,6 232,7 @@ request.POST = MultiDict({})
          request.POST["expr"] = "https?:\\/\\/(bit\\.ly|goo\\.gl)"
          request.POST["description"] = "violation of galactic law"
+         request.POST["scope"] = "board:bar"
          request.POST["active"] = ""
          request.POST["csrf_token"] = request.session.get_csrf_token()
          self.config.add_route("admin_banword", "/admin/banwords/{banword}")


@@ 233,6 240,7 @@ self.assertEqual(response.location, "/admin/banwords/%s" % banword.id)
          self.assertEqual(banword.expr, "https?:\\/\\/(bit\\.ly|goo\\.gl)")
          self.assertEqual(banword.description, "violation of galactic law")
+         self.assertEqual(banword.scope, "board:bar")
          self.assertFalse(banword.active)
  
      def test_banword_edit_post_not_found(self):


@@ 266,11 274,12 @@ self.request.POST = MultiDict({})
          self.request.POST["expr"] = "https?:\\/\\/(bit\\.ly|goo\\.gl)"
          self.request.POST["description"] = "violation of galactic law"
+         self.request.POST["scope"] = "board:foo"
          self.request.POST["active"] = ""
          with self.assertRaises(BadCSRFToken):
              banword_edit_post(self.request)
  
-     def test_banword_edit_post_invalid_banword(self):
+     def test_banword_edit_post_invalid_expr(self):
          from ..models import Banword
          from ..interfaces import IBanwordQueryService, IBanwordUpdateService
          from ..services import BanwordQueryService, BanwordUpdateService, ScopeService


@@ 279,7 288,10 @@           banword = self._make(
              Banword(
-                 expr="https?:\\/\\/bit\\.ly", description="no shortlinks", active=True
+                 expr="https?:\\/\\/bit\\.ly",
+                 description="no shortlinks",
+                 scope="board:foo",
+                 active=True,
              )
          )
          self.dbsession.commit()

M fanboi2/tests/test_services_banword.py => fanboi2/tests/test_services_banword.py +26 -7
@@ 13,23 13,29 @@ def test_create(self):
          banword_create_svc = self._get_target_class()(self.dbsession)
          banword = banword_create_svc.create(
-             r"https?:\/\/bit\.ly", description="no shortlinks", active=True
+             r"https?:\/\/bit\.ly",
+             description="no shortlinks",
+             scope="board:foo",
+             active=True,
          )
          self.assertEqual(banword.expr, r"https?:\/\/bit\.ly")
          self.assertEqual(banword.description, "no shortlinks")
+         self.assertEqual(banword.scope, "board:foo")
          self.assertTrue(banword.active)
  
      def test_create_without_optional_fields(self):
          banword_create_svc = self._get_target_class()(self.dbsession)
          banword = banword_create_svc.create(r"https?:\/\/bit\.ly")
          self.assertIsNone(banword.description)
+         self.assertIsNone(banword.scope)
          self.assertTrue(banword.active)
  
      def test_create_with_empty_fields(self):
          banword_create_svc = self._get_target_class()(self.dbsession)
-         banword = banword_create_svc.create("", description="", active="")
+         banword = banword_create_svc.create("", description="", scope="", active="")
          self.assertIsNone(banword.expr)
          self.assertIsNone(banword.description)
+         self.assertIsNone(banword.scope)
          self.assertFalse(banword.active)
  
      def test_create_deactivated(self):


@@ 109,7 115,10 @@           banword = self._make(
              Banword(
-                 expr=r"https?:\/\/bit\.ly", description="no shortlinks", active=True
+                 expr=r"https?:\/\/bit\.ly",
+                 description="no shortlinks",
+                 scope="board:foo",
+                 active=True,
              )
          )
          self.dbsession.commit()


@@ 118,10 127,12 @@ banword.id,
              expr=r"https?:\/\/(bit\.ly|goo\.gl)",
              description="no any shortlinks",
+             scope="board:bar",
              active=False,
          )
          self.assertEqual(banword.expr, r"https?:\/\/(bit\.ly|goo\.gl)")
          self.assertEqual(banword.description, "no any shortlinks")
+         self.assertEqual(banword.scope, "board:bar")
          self.assertFalse(banword.active)
  
      def test_update_not_found(self):


@@ 136,16 147,20 @@           banword = self._make(
              Banword(
-                 expr=r"https?:\/\/bit\.ly", description="no shortlinks", active=True
+                 expr=r"https?:\/\/bit\.ly",
+                 description="no shortlinks",
+                 scope="board:foo",
+                 active=True,
              )
          )
          self.dbsession.commit()
          banword_update_svc = self._get_target_class()(self.dbsession)
          banword = banword_update_svc.update(
-             banword.id, expr=None, description=None, active=None
+             banword.id, expr=None, description=None, scope=None, active=None
          )
          self.assertIsNone(banword.expr)
          self.assertIsNone(banword.description)
+         self.assertIsNone(banword.scope)
          self.assertFalse(banword.active)
  
      def test_update_empty(self):


@@ 153,14 168,18 @@           banword = self._make(
              Banword(
-                 expr=r"https?:\/\/bit\.ly", description="no shortlinks", active=True
+                 expr=r"https?:\/\/bit\.ly",
+                 description="no shortlinks",
+                 scope="board:foo",
+                 active=True,
              )
          )
          self.dbsession.commit()
          banword_update_svc = self._get_target_class()(self.dbsession)
          banword = banword_update_svc.update(
-             banword.id, expr="", description="", active=""
+             banword.id, expr="", description="", scope="", active=""
          )
          self.assertIsNone(banword.expr)
          self.assertIsNone(banword.description)
+         self.assertIsNone(banword.scope)
          self.assertFalse(banword.active)

M fanboi2/views/admin.py => fanboi2/views/admin.py +5 -1
@@ 247,7 247,10 @@       banword_create_svc = request.find_service(IBanwordCreateService)
      banword = banword_create_svc.create(
-         form.expr.data, description=form.description.data, active=form.active.data
+         form.expr.data,
+         description=form.description.data,
+         scope=form.scope.data,
+         active=form.active.data,
      )
  
      # Explicitly flush so that ID is available.


@@ 288,6 291,7 @@ banword.id,
          expr=form.expr.data,
          description=form.description.data,
+         scope=form.scope.data,
          active=form.active.data,
      )
      return HTTPFound(