| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | """ |
|---|
| 3 | zine.forms |
|---|
| 4 | ~~~~~~~~~~ |
|---|
| 5 | |
|---|
| 6 | The form classes the zine core uses. |
|---|
| 7 | |
|---|
| 8 | :copyright: (c) 2010 by the Zine Team, see AUTHORS for more details. |
|---|
| 9 | :license: BSD, see LICENSE for more details. |
|---|
| 10 | """ |
|---|
| 11 | from copy import copy |
|---|
| 12 | from datetime import datetime |
|---|
| 13 | |
|---|
| 14 | from zine.i18n import _, lazy_gettext, list_languages |
|---|
| 15 | from zine.application import get_application, get_request, emit_event |
|---|
| 16 | from zine.config import DEFAULT_VARS |
|---|
| 17 | from zine.database import db, posts |
|---|
| 18 | from zine.models import User, Group, Comment, Post, Category, Tag, \ |
|---|
| 19 | NotificationSubscription, STATUS_DRAFT, STATUS_PUBLISHED, \ |
|---|
| 20 | STATUS_PROTECTED, STATUS_PRIVATE, \ |
|---|
| 21 | COMMENT_UNMODERATED, COMMENT_MODERATED, \ |
|---|
| 22 | COMMENT_BLOCKED_USER, COMMENT_BLOCKED_SPAM, COMMENT_DELETED |
|---|
| 23 | from zine.parsers import render_preview |
|---|
| 24 | from zine.privileges import bind_privileges |
|---|
| 25 | from zine.notifications import send_notification_template, NEW_COMMENT, \ |
|---|
| 26 | COMMENT_REQUIRES_MODERATION |
|---|
| 27 | from zine.utils import forms, log, dump_json |
|---|
| 28 | from zine.utils.http import redirect_to |
|---|
| 29 | from zine.utils.validators import ValidationError, is_valid_email, \ |
|---|
| 30 | is_valid_url, is_valid_slug, is_not_whitespace_only |
|---|
| 31 | from zine.utils.redirects import register_redirect, change_url_prefix |
|---|
| 32 | |
|---|
| 33 | |
|---|
| 34 | def config_field(cfgvar, label=None, **kwargs): |
|---|
| 35 | """Helper function for fetching fields from the config.""" |
|---|
| 36 | if isinstance(cfgvar, forms.Field): |
|---|
| 37 | field = copy(cfgvar) |
|---|
| 38 | else: |
|---|
| 39 | field = copy(DEFAULT_VARS[cfgvar]) |
|---|
| 40 | field._position_hint = forms._next_position_hint() |
|---|
| 41 | if label is not None: |
|---|
| 42 | field.label = label |
|---|
| 43 | for name, value in kwargs.iteritems(): |
|---|
| 44 | setattr(field, name, value) |
|---|
| 45 | return field |
|---|
| 46 | |
|---|
| 47 | |
|---|
| 48 | class LoginForm(forms.Form): |
|---|
| 49 | """The form for the login page.""" |
|---|
| 50 | user = forms.ModelField(User, 'username', required=True, messages=dict( |
|---|
| 51 | not_found=lazy_gettext(u'User “%(value)s” does not exist.'), |
|---|
| 52 | required=lazy_gettext(u'You have to enter a username.') |
|---|
| 53 | ), on_not_found=lambda user: |
|---|
| 54 | log.warning(_(u'Failed login attempt, user “%s” does not exist') |
|---|
| 55 | % user, 'auth') |
|---|
| 56 | ) |
|---|
| 57 | password = forms.TextField(widget=forms.PasswordInput) |
|---|
| 58 | permanent = forms.BooleanField() |
|---|
| 59 | |
|---|
| 60 | def context_validate(self, data): |
|---|
| 61 | if not data['user'].check_password(data['password']): |
|---|
| 62 | log.warning(_(u'Failed login attempt from “%s”, invalid password') |
|---|
| 63 | % data['user'].username, 'auth') |
|---|
| 64 | raise ValidationError(_('Incorrect password.')) |
|---|
| 65 | |
|---|
| 66 | |
|---|
| 67 | class ChangePasswordForm(forms.Form): |
|---|
| 68 | """The form used on the password-change dialog in the admin panel.""" |
|---|
| 69 | old_password = forms.TextField(lazy_gettext(u'Old password'), required=True, |
|---|
| 70 | widget=forms.PasswordInput) |
|---|
| 71 | new_password = forms.TextField(lazy_gettext(u'New password'), required=True, |
|---|
| 72 | widget=forms.PasswordInput) |
|---|
| 73 | check_password = forms.TextField(lazy_gettext(u'Repeat password'), |
|---|
| 74 | required=True, |
|---|
| 75 | widget=forms.PasswordInput) |
|---|
| 76 | |
|---|
| 77 | def __init__(self, user, initial=None): |
|---|
| 78 | forms.Form.__init__(self, initial) |
|---|
| 79 | self.user = user |
|---|
| 80 | |
|---|
| 81 | def validate_old_password(self, value): |
|---|
| 82 | if not self.user.check_password(value): |
|---|
| 83 | raise ValidationError(_('The old password you\'ve ' |
|---|
| 84 | 'entered is wrong.')) |
|---|
| 85 | |
|---|
| 86 | def context_validate(self, data): |
|---|
| 87 | if data['new_password'] != data['check_password']: |
|---|
| 88 | raise ValidationError(_('The two passwords don\'t match.')) |
|---|
| 89 | |
|---|
| 90 | |
|---|
| 91 | class NewCommentForm(forms.Form): |
|---|
| 92 | """New comment form for authors.""" |
|---|
| 93 | # implementation detail: the maximum length of the column in the |
|---|
| 94 | # database is longer than that. However we don't want users to |
|---|
| 95 | # insert too long names there. The long column is reserved for |
|---|
| 96 | # pingbacks and such. |
|---|
| 97 | author = forms.TextField(lazy_gettext(u'Name*'), required=True, |
|---|
| 98 | max_length=100, messages=dict( |
|---|
| 99 | too_long=lazy_gettext(u'Your name is too long.'), |
|---|
| 100 | required=lazy_gettext(u'You have to enter your name.') |
|---|
| 101 | )) |
|---|
| 102 | email = forms.TextField(lazy_gettext(u'Mail* (not published)'), |
|---|
| 103 | required=True, validators=[is_valid_email()], |
|---|
| 104 | messages=dict( |
|---|
| 105 | required=lazy_gettext(u'You have to enter a valid e-mail address.') |
|---|
| 106 | )) |
|---|
| 107 | www = forms.TextField(lazy_gettext(u'Website'), validators=[is_valid_url( |
|---|
| 108 | message=lazy_gettext(u'You have to enter a valid URL or omit the field.') |
|---|
| 109 | )]) |
|---|
| 110 | body = forms.TextField(lazy_gettext(u'Text'), min_length=2, max_length=6000, |
|---|
| 111 | required=True, messages=dict( |
|---|
| 112 | too_short=lazy_gettext(u'Your comment is too short.'), |
|---|
| 113 | too_long=lazy_gettext(u'Your comment is too long.'), |
|---|
| 114 | required=lazy_gettext(u'You have to enter a comment.') |
|---|
| 115 | ), widget=forms.Textarea) |
|---|
| 116 | parent = forms.HiddenModelField(Comment) |
|---|
| 117 | |
|---|
| 118 | def __init__(self, post, user, initial=None): |
|---|
| 119 | forms.Form.__init__(self, initial) |
|---|
| 120 | self.req = get_request() |
|---|
| 121 | self.post = post |
|---|
| 122 | self.user = user |
|---|
| 123 | |
|---|
| 124 | # if the user is logged in the form is a bit smaller |
|---|
| 125 | if user.is_somebody: |
|---|
| 126 | del self.fields['author'], self.fields['email'], self.fields['www'] |
|---|
| 127 | |
|---|
| 128 | def as_widget(self): |
|---|
| 129 | widget = forms.Form.as_widget(self) |
|---|
| 130 | widget.small_form = self.user.is_somebody |
|---|
| 131 | return widget |
|---|
| 132 | |
|---|
| 133 | def validate_parent(self, value): |
|---|
| 134 | if value.post != self.post: |
|---|
| 135 | #_ this message is only displayed if the user tempered with |
|---|
| 136 | #_ the form data |
|---|
| 137 | raise ValidationError(_('Invalid object referenced.')) |
|---|
| 138 | |
|---|
| 139 | def context_validate(self, data): |
|---|
| 140 | if not self.post.comments_enabled: |
|---|
| 141 | raise ValidationError(_('Post is closed for commenting.')) |
|---|
| 142 | if self.post.comments_closed: |
|---|
| 143 | raise ValidationError(_('Commenting is no longer possible.')) |
|---|
| 144 | |
|---|
| 145 | def make_comment(self): |
|---|
| 146 | """A handy helper to create a comment from the validated form.""" |
|---|
| 147 | ip = self.req and self.req.remote_addr or '0.0.0.0' |
|---|
| 148 | if self.user.is_somebody: |
|---|
| 149 | author = self.user |
|---|
| 150 | email = www = None |
|---|
| 151 | else: |
|---|
| 152 | author = self['author'] |
|---|
| 153 | email = self['email'] |
|---|
| 154 | www = self['www'] |
|---|
| 155 | return Comment(self.post, author, self['body'], email, www, |
|---|
| 156 | self['parent'], submitter_ip=ip) |
|---|
| 157 | |
|---|
| 158 | def create_if_valid(self, req): |
|---|
| 159 | """The one-trick pony for commenting. Passed a req it tries to |
|---|
| 160 | use the req data to submit a comment to the post. If the req |
|---|
| 161 | is not a post req or the form is invalid the return value is None, |
|---|
| 162 | otherwise a redirect response to the new comment. |
|---|
| 163 | """ |
|---|
| 164 | if req.method != 'POST' or not self.validate(req.form): |
|---|
| 165 | return |
|---|
| 166 | |
|---|
| 167 | # if we don't have errors let's save it and emit an |
|---|
| 168 | # `before-comment-saved` event so that plugins can do |
|---|
| 169 | # block comments so that administrators have to approve it |
|---|
| 170 | comment = self.make_comment() |
|---|
| 171 | |
|---|
| 172 | #! use this event to block comments before they are saved. This |
|---|
| 173 | #! is useful for antispam and other ways of moderation. |
|---|
| 174 | emit_event('before-comment-saved', req, comment) |
|---|
| 175 | |
|---|
| 176 | # Moderate Comment? Now that the spam check any everything |
|---|
| 177 | # went through the processing we explicitly set it to |
|---|
| 178 | # unmodereated if the blog configuration demands that |
|---|
| 179 | if not comment.blocked and comment.requires_moderation: |
|---|
| 180 | comment.status = COMMENT_UNMODERATED |
|---|
| 181 | comment.blocked_msg = _(u'Comment waiting for approval') |
|---|
| 182 | |
|---|
| 183 | #! this is sent directly after the comment was saved. Useful if |
|---|
| 184 | #! you want to send mail notifications or whatever. |
|---|
| 185 | emit_event('after-comment-saved', req, comment) |
|---|
| 186 | |
|---|
| 187 | # Commit so that make_visible_for_request can access the comment id. |
|---|
| 188 | db.commit() |
|---|
| 189 | |
|---|
| 190 | # send out a notification if the comment is not spam. Nobody is |
|---|
| 191 | # interested in notifications on spam... |
|---|
| 192 | if not comment.is_spam: |
|---|
| 193 | if comment.blocked: |
|---|
| 194 | notification_type = COMMENT_REQUIRES_MODERATION |
|---|
| 195 | else: |
|---|
| 196 | notification_type = NEW_COMMENT |
|---|
| 197 | send_notification_template(notification_type, |
|---|
| 198 | 'notifications/on_new_comment.zeml', |
|---|
| 199 | user=req.user, comment=comment) |
|---|
| 200 | |
|---|
| 201 | # Still allow the user to see his comment if it's blocked |
|---|
| 202 | if comment.blocked: |
|---|
| 203 | comment.make_visible_for_request(req) |
|---|
| 204 | |
|---|
| 205 | return redirect_to(self.post) |
|---|
| 206 | |
|---|
| 207 | |
|---|
| 208 | class PluginForm(forms.Form): |
|---|
| 209 | """The form for plugin activation and deactivation.""" |
|---|
| 210 | active_plugins = forms.MultiChoiceField(widget=forms.CheckboxGroup) |
|---|
| 211 | disable_guard = forms.BooleanField(lazy_gettext(u'Disable plugin guard'), |
|---|
| 212 | help_text=lazy_gettext(u'If the plugin guard is disabled errors ' |
|---|
| 213 | u'on plugin setup are not caught.')) |
|---|
| 214 | |
|---|
| 215 | def __init__(self, initial=None): |
|---|
| 216 | self.app = app = get_application() |
|---|
| 217 | self.active_plugins.choices = sorted([(x.name, x.display_name) |
|---|
| 218 | for x in app.plugins.values()], |
|---|
| 219 | key=lambda x: x[1].lower()) |
|---|
| 220 | if initial is None: |
|---|
| 221 | initial = dict( |
|---|
| 222 | active_plugins=[x.name for x in app.plugins.itervalues() |
|---|
| 223 | if x.active] |
|---|
| 224 | ) |
|---|
| 225 | forms.Form.__init__(self, initial) |
|---|
| 226 | |
|---|
| 227 | def apply(self): |
|---|
| 228 | """Apply the changes.""" |
|---|
| 229 | t = self.app.cfg.edit() |
|---|
| 230 | t['plugins'] = u', '.join(sorted(self.data['active_plugins'])) |
|---|
| 231 | t.commit() |
|---|
| 232 | |
|---|
| 233 | |
|---|
| 234 | class RemovePluginForm(forms.Form): |
|---|
| 235 | """Dummy form for plugin removing.""" |
|---|
| 236 | |
|---|
| 237 | def __init__(self, plugin): |
|---|
| 238 | forms.Form.__init__(self) |
|---|
| 239 | self.plugin = plugin |
|---|
| 240 | |
|---|
| 241 | |
|---|
| 242 | class PostForm(forms.Form): |
|---|
| 243 | """This is the baseclass for all forms that deal with posts. There are |
|---|
| 244 | two builtin subclasses for the builtin content types 'entry' and 'page'. |
|---|
| 245 | """ |
|---|
| 246 | title = forms.TextField(lazy_gettext(u'Title'), max_length=150, |
|---|
| 247 | validators=[is_not_whitespace_only()], |
|---|
| 248 | required=False) |
|---|
| 249 | text = forms.TextField(lazy_gettext(u'Text'), max_length=65000, |
|---|
| 250 | widget=forms.Textarea) |
|---|
| 251 | status = forms.ChoiceField(lazy_gettext(u'Publication status'), choices=[ |
|---|
| 252 | (STATUS_DRAFT, lazy_gettext(u'Draft')), |
|---|
| 253 | (STATUS_PUBLISHED, lazy_gettext(u'Published')), |
|---|
| 254 | (STATUS_PROTECTED, lazy_gettext(u'Protected')), |
|---|
| 255 | (STATUS_PRIVATE, lazy_gettext(u'Private'))]) |
|---|
| 256 | pub_date = forms.DateTimeField(lazy_gettext(u'Publication date'), |
|---|
| 257 | help_text=lazy_gettext(u'Clear this field to update to current time')) |
|---|
| 258 | slug = forms.TextField(lazy_gettext(u'Slug'), validators=[is_valid_slug()], |
|---|
| 259 | help_text=lazy_gettext(u'Clear this field to autogenerate a new slug')) |
|---|
| 260 | author = forms.ModelField(User, 'username', lazy_gettext('Author'), |
|---|
| 261 | widget=forms.SelectBox) |
|---|
| 262 | tags = forms.CommaSeparated(forms.TextField(), lazy_gettext(u'Tags')) |
|---|
| 263 | categories = forms.Multiple(forms.ModelField(Category, 'id'), |
|---|
| 264 | lazy_gettext(u'Categories'), |
|---|
| 265 | widget=forms.CheckboxGroup) |
|---|
| 266 | parser = forms.ChoiceField(lazy_gettext(u'Parser')) |
|---|
| 267 | comments_enabled = forms.BooleanField(lazy_gettext(u'Enable comments')) |
|---|
| 268 | pings_enabled = forms.BooleanField(lazy_gettext(u'Enable pingbacks')) |
|---|
| 269 | ping_links = forms.BooleanField(lazy_gettext(u'Ping links')) |
|---|
| 270 | |
|---|
| 271 | #: the content type for this field. |
|---|
| 272 | content_type = None |
|---|
| 273 | |
|---|
| 274 | def __init__(self, post=None, initial=None): |
|---|
| 275 | self.app = get_application() |
|---|
| 276 | self.post = post |
|---|
| 277 | self.preview = False |
|---|
| 278 | |
|---|
| 279 | if post is not None: |
|---|
| 280 | initial = forms.fill_dict(initial, |
|---|
| 281 | title=post.title, |
|---|
| 282 | text=post.text, |
|---|
| 283 | status=post.status, |
|---|
| 284 | pub_date=post.pub_date, |
|---|
| 285 | slug=post.slug, |
|---|
| 286 | author=post.author, |
|---|
| 287 | tags=[x.name for x in post.tags], |
|---|
| 288 | categories=[x.id for x in post.categories], |
|---|
| 289 | parser=post.parser, |
|---|
| 290 | comments_enabled=post.comments_enabled, |
|---|
| 291 | pings_enabled=post.pings_enabled, |
|---|
| 292 | ping_links=not post.parser_missing |
|---|
| 293 | ) |
|---|
| 294 | else: |
|---|
| 295 | initial = forms.fill_dict(initial, status=STATUS_DRAFT) |
|---|
| 296 | |
|---|
| 297 | # if we have a request, we can use the current user as a default |
|---|
| 298 | req = get_request() |
|---|
| 299 | if req and req.user: |
|---|
| 300 | initial['author'] = req.user |
|---|
| 301 | |
|---|
| 302 | initial.setdefault('parser', self.app.cfg['default_parser']) |
|---|
| 303 | |
|---|
| 304 | self.author.choices = [x.username for x in User.query.all()] |
|---|
| 305 | self.parser.choices = self.app.list_parsers() |
|---|
| 306 | self.parser_missing = post and post.parser_missing |
|---|
| 307 | if self.parser_missing: |
|---|
| 308 | self.parser.choices.append((post.parser, _('%s (missing)') % |
|---|
| 309 | post.parser.title())) |
|---|
| 310 | |
|---|
| 311 | self.categories.choices = [(c.id, c.name) for c in |
|---|
| 312 | Category.query.all()] |
|---|
| 313 | |
|---|
| 314 | forms.Form.__init__(self, initial) |
|---|
| 315 | |
|---|
| 316 | # if we have have an old post and the parser is not missing and |
|---|
| 317 | # it was published when the form was created we collect the old |
|---|
| 318 | # posts so that we don't have to ping them another time. |
|---|
| 319 | self._old_links = set() |
|---|
| 320 | if self.post is not None and not self.post.parser_missing and \ |
|---|
| 321 | self.post.is_published: |
|---|
| 322 | self._old_links.update(self.post.find_urls()) |
|---|
| 323 | |
|---|
| 324 | def validate(self, data): |
|---|
| 325 | """We only validate if we're not in preview mode.""" |
|---|
| 326 | self.preview = 'preview' in data |
|---|
| 327 | return forms.Form.validate(self, data) and not self.preview |
|---|
| 328 | |
|---|
| 329 | def find_new_links(self): |
|---|
| 330 | """Return a list of all new links.""" |
|---|
| 331 | for link in self.post.find_urls(): |
|---|
| 332 | if not link in self._old_links: |
|---|
| 333 | yield link |
|---|
| 334 | |
|---|
| 335 | def validate_slug(self, value): |
|---|
| 336 | """Make sure the slug is unique.""" |
|---|
| 337 | query = Post.query.filter_by(slug=value) |
|---|
| 338 | if self.post is not None: |
|---|
| 339 | query = query.filter(Post.id != self.post.id) |
|---|
| 340 | existing = query.first() |
|---|
| 341 | if existing is not None: |
|---|
| 342 | raise ValidationError(_('This slug is already in use.')) |
|---|
| 343 | |
|---|
| 344 | def validate_parser(self, value): |
|---|
| 345 | """Make sure the missing parser is not selected.""" |
|---|
| 346 | if self.parser_missing and value == self.post.parser: |
|---|
| 347 | raise ValidationError(_('Selected parser is no longer ' |
|---|
| 348 | 'available on the system.')) |
|---|
| 349 | |
|---|
| 350 | def render_preview(self): |
|---|
| 351 | """Renders the preview for the post.""" |
|---|
| 352 | return render_preview(self.data['text'], self.data['parser']) |
|---|
| 353 | |
|---|
| 354 | def as_widget(self): |
|---|
| 355 | widget = forms.Form.as_widget(self) |
|---|
| 356 | widget.new = self.post is None |
|---|
| 357 | widget.post = self.post |
|---|
| 358 | widget.preview = self.preview |
|---|
| 359 | widget.render_preview = self.render_preview |
|---|
| 360 | widget.parser_missing = self.parser_missing |
|---|
| 361 | return widget |
|---|
| 362 | |
|---|
| 363 | def make_post(self): |
|---|
| 364 | """A helper function that creates a post object from the data.""" |
|---|
| 365 | data = self.data |
|---|
| 366 | post = Post(data['title'], data['author'], data['text'], data['slug'], |
|---|
| 367 | parser=data['parser'], content_type=self.content_type, |
|---|
| 368 | pub_date=data['pub_date']) |
|---|
| 369 | post.bind_categories(data['categories']) |
|---|
| 370 | post.bind_tags(data['tags']) |
|---|
| 371 | self._set_common_attributes(post) |
|---|
| 372 | self.post = post |
|---|
| 373 | return post |
|---|
| 374 | |
|---|
| 375 | def save_changes(self): |
|---|
| 376 | """Save the changes back to the database. This also adds a redirect |
|---|
| 377 | if the slug changes. |
|---|
| 378 | """ |
|---|
| 379 | if not self.data['pub_date']: |
|---|
| 380 | # If user deleted publication timestamp, make a new one. |
|---|
| 381 | self.data['pub_date'] = datetime.utcnow() |
|---|
| 382 | old_slug = self.post.slug |
|---|
| 383 | old_parser = self.post.parser |
|---|
| 384 | forms.set_fields(self.post, self.data, 'title', 'author', 'parser') |
|---|
| 385 | if (self.data['text'] != self.post.text |
|---|
| 386 | or self.data['parser'] != old_parser): |
|---|
| 387 | self.post.text = self.data['text'] |
|---|
| 388 | add_redirect = self.post.is_published and old_slug != self.post.slug |
|---|
| 389 | |
|---|
| 390 | self.post.touch_times(self.data['pub_date']) |
|---|
| 391 | self.post.bind_slug(self.data['slug']) |
|---|
| 392 | |
|---|
| 393 | self._set_common_attributes(self.post) |
|---|
| 394 | if add_redirect: |
|---|
| 395 | register_redirect(old_slug, self.post.slug) |
|---|
| 396 | |
|---|
| 397 | def _set_common_attributes(self, post): |
|---|
| 398 | forms.set_fields(post, self.data, 'comments_enabled', 'pings_enabled', |
|---|
| 399 | 'status') |
|---|
| 400 | post.bind_categories(self.data['categories']) |
|---|
| 401 | post.bind_tags(self.data['tags']) |
|---|
| 402 | |
|---|
| 403 | def taglist(self): |
|---|
| 404 | """Return all available tags as a JSON-encoded list.""" |
|---|
| 405 | tags = [t.name for t in Tag.query.all()] |
|---|
| 406 | return dump_json(tags) |
|---|
| 407 | |
|---|
| 408 | |
|---|
| 409 | class EntryForm(PostForm): |
|---|
| 410 | content_type = 'entry' |
|---|
| 411 | |
|---|
| 412 | def __init__(self, post=None, initial=None): |
|---|
| 413 | app = get_application() |
|---|
| 414 | PostForm.__init__(self, post, forms.fill_dict(initial, |
|---|
| 415 | comments_enabled=app.cfg['comments_enabled'], |
|---|
| 416 | pings_enabled=app.cfg['pings_enabled'], |
|---|
| 417 | ping_links=True |
|---|
| 418 | )) |
|---|
| 419 | |
|---|
| 420 | |
|---|
| 421 | class PageForm(PostForm): |
|---|
| 422 | content_type = 'page' |
|---|
| 423 | |
|---|
| 424 | |
|---|
| 425 | class PostDeleteForm(forms.Form): |
|---|
| 426 | """Baseclass for deletion forms of posts.""" |
|---|
| 427 | |
|---|
| 428 | def __init__(self, post=None, initial=None): |
|---|
| 429 | self.app = get_application() |
|---|
| 430 | self.post = post |
|---|
| 431 | forms.Form.__init__(self, initial) |
|---|
| 432 | |
|---|
| 433 | def as_widget(self): |
|---|
| 434 | widget = forms.Form.as_widget(self) |
|---|
| 435 | widget.post = self.post |
|---|
| 436 | return widget |
|---|
| 437 | |
|---|
| 438 | def delete_post(self): |
|---|
| 439 | """Deletes the post from the db.""" |
|---|
| 440 | emit_event('before-post-deleted', self.post) |
|---|
| 441 | db.delete(self.post) |
|---|
| 442 | |
|---|
| 443 | |
|---|
| 444 | class _CommentBoundForm(forms.Form): |
|---|
| 445 | """Internal baseclass for comment bound forms.""" |
|---|
| 446 | |
|---|
| 447 | def __init__(self, comment, initial=None): |
|---|
| 448 | self.app = get_application() |
|---|
| 449 | self.comment = comment |
|---|
| 450 | forms.Form.__init__(self, initial) |
|---|
| 451 | |
|---|
| 452 | def as_widget(self): |
|---|
| 453 | widget = forms.Form.as_widget(self) |
|---|
| 454 | widget.comment = self.comment |
|---|
| 455 | return widget |
|---|
| 456 | |
|---|
| 457 | |
|---|
| 458 | class EditCommentForm(_CommentBoundForm): |
|---|
| 459 | """Form for comment editing in admin.""" |
|---|
| 460 | author = forms.TextField(lazy_gettext(u'Author'), required=True) |
|---|
| 461 | email = forms.TextField(lazy_gettext(u'Email'), |
|---|
| 462 | validators=[is_valid_email()]) |
|---|
| 463 | www = forms.TextField(lazy_gettext(u'Website'), |
|---|
| 464 | validators=[is_valid_url()]) |
|---|
| 465 | text = forms.TextField(lazy_gettext(u'Text'), widget=forms.Textarea) |
|---|
| 466 | pub_date = forms.DateTimeField(lazy_gettext(u'Date'), required=True) |
|---|
| 467 | parser = forms.ChoiceField(lazy_gettext(u'Parser'), required=True) |
|---|
| 468 | blocked = forms.BooleanField(lazy_gettext(u'Block Comment')) |
|---|
| 469 | blocked_msg = forms.TextField(lazy_gettext(u'Reason')) |
|---|
| 470 | |
|---|
| 471 | def __init__(self, comment, initial=None): |
|---|
| 472 | _CommentBoundForm.__init__(self, comment, forms.fill_dict(initial, |
|---|
| 473 | author=comment.author, |
|---|
| 474 | email=comment.email, |
|---|
| 475 | www=comment.www, |
|---|
| 476 | text=comment.text, |
|---|
| 477 | pub_date=comment.pub_date, |
|---|
| 478 | parser=comment.parser, |
|---|
| 479 | blocked=comment.blocked, |
|---|
| 480 | blocked_msg=comment.blocked_msg |
|---|
| 481 | )) |
|---|
| 482 | self.parser.choices = self.app.list_parsers() |
|---|
| 483 | self.parser_missing = comment.parser_missing |
|---|
| 484 | if self.parser_missing and comment.parser is not None: |
|---|
| 485 | self.parser.choices.append((comment.parser, _('%s (missing)') % |
|---|
| 486 | comment.parser.title())) |
|---|
| 487 | |
|---|
| 488 | def save_changes(self): |
|---|
| 489 | """Save the changes back to the database.""" |
|---|
| 490 | old_parser = self.comment.parser |
|---|
| 491 | forms.set_fields(self.comment, self.data, 'pub_date', 'parser', |
|---|
| 492 | 'blocked_msg') |
|---|
| 493 | |
|---|
| 494 | if (self.data['text'] != self.comment.text |
|---|
| 495 | or self.data['parser'] != old_parser): |
|---|
| 496 | self.comment.text = self.data['text'] |
|---|
| 497 | |
|---|
| 498 | # update status |
|---|
| 499 | if self.data['blocked']: |
|---|
| 500 | if not self.comment.blocked: |
|---|
| 501 | self.comment.status = COMMENT_BLOCKED_USER |
|---|
| 502 | else: |
|---|
| 503 | self.comment.status = COMMENT_MODERATED |
|---|
| 504 | |
|---|
| 505 | # only apply these if the comment is not anonymous |
|---|
| 506 | if self.comment.anonymous: |
|---|
| 507 | forms.set_fields(self.comment, self.data, 'author', 'email', 'www') |
|---|
| 508 | |
|---|
| 509 | |
|---|
| 510 | class DeleteCommentForm(_CommentBoundForm): |
|---|
| 511 | """Helper form that is used to delete comments.""" |
|---|
| 512 | |
|---|
| 513 | def delete_comment(self): |
|---|
| 514 | """Deletes the comment from the db.""" |
|---|
| 515 | delete_comment(self.comment) |
|---|
| 516 | |
|---|
| 517 | |
|---|
| 518 | class ApproveCommentForm(_CommentBoundForm): |
|---|
| 519 | """Helper form for comment approvement.""" |
|---|
| 520 | |
|---|
| 521 | def approve_comment(self): |
|---|
| 522 | """Approve the comment.""" |
|---|
| 523 | #! plugins can use this to react to comment approvals. |
|---|
| 524 | emit_event('before-comment-approved', self.comment) |
|---|
| 525 | self.comment.status = COMMENT_MODERATED |
|---|
| 526 | self.comment.blocked_msg = u'' |
|---|
| 527 | |
|---|
| 528 | |
|---|
| 529 | class BlockCommentForm(_CommentBoundForm): |
|---|
| 530 | """Form used to block comments.""" |
|---|
| 531 | |
|---|
| 532 | message = forms.TextField(lazy_gettext(u'Reason')) |
|---|
| 533 | |
|---|
| 534 | def __init__(self, comment, initial=None): |
|---|
| 535 | self.req = get_request() |
|---|
| 536 | _CommentBoundForm.__init__(self, comment, initial) |
|---|
| 537 | |
|---|
| 538 | def block_comment(self): |
|---|
| 539 | msg = self.data['message'] |
|---|
| 540 | if not msg and self.req: |
|---|
| 541 | msg = _(u'blocked by %s') % self.req.user.display_name |
|---|
| 542 | self.comment.status = COMMENT_BLOCKED_USER |
|---|
| 543 | self.comment.bocked_msg = msg |
|---|
| 544 | |
|---|
| 545 | |
|---|
| 546 | class MarkCommentForm(_CommentBoundForm): |
|---|
| 547 | """Form used to block comments.""" |
|---|
| 548 | |
|---|
| 549 | def __init__(self, comment, initial=None): |
|---|
| 550 | self.req = get_request() |
|---|
| 551 | _CommentBoundForm.__init__(self, comment, initial) |
|---|
| 552 | |
|---|
| 553 | def mark_as_spam(self): |
|---|
| 554 | emit_event('before-comment-mark-spam', self.comment) |
|---|
| 555 | self.comment.status = COMMENT_BLOCKED_SPAM |
|---|
| 556 | self.comment.blocked_msg = _("Comment reported as spam by %s" % |
|---|
| 557 | get_request().user.display_name) |
|---|
| 558 | def mark_as_ham(self): |
|---|
| 559 | emit_event('before-comment-mark-ham', self.comment) |
|---|
| 560 | emit_event('before-comment-approved', self.comment) |
|---|
| 561 | self.comment.status = COMMENT_MODERATED |
|---|
| 562 | self.comment.blocked_msg = u'' |
|---|
| 563 | |
|---|
| 564 | |
|---|
| 565 | class _CategoryBoundForm(forms.Form): |
|---|
| 566 | """Internal baseclass for category bound forms.""" |
|---|
| 567 | |
|---|
| 568 | def __init__(self, category, initial=None): |
|---|
| 569 | self.app = get_application() |
|---|
| 570 | self.category = category |
|---|
| 571 | forms.Form.__init__(self, initial) |
|---|
| 572 | |
|---|
| 573 | def as_widget(self): |
|---|
| 574 | widget = forms.Form.as_widget(self) |
|---|
| 575 | widget.category = self.category |
|---|
| 576 | widget.new = self.category is None |
|---|
| 577 | return widget |
|---|
| 578 | |
|---|
| 579 | |
|---|
| 580 | class EditCategoryForm(_CategoryBoundForm): |
|---|
| 581 | """Form that is used to edit or create a category.""" |
|---|
| 582 | |
|---|
| 583 | slug = forms.TextField(lazy_gettext(u'Slug'), validators=[is_valid_slug()]) |
|---|
| 584 | name = forms.TextField(lazy_gettext(u'Name'), max_length=50, required=True, |
|---|
| 585 | validators=[is_not_whitespace_only()]) |
|---|
| 586 | description = forms.TextField(lazy_gettext(u'Description'), |
|---|
| 587 | max_length=5000, widget=forms.Textarea) |
|---|
| 588 | |
|---|
| 589 | def __init__(self, category=None, initial=None): |
|---|
| 590 | if category is not None: |
|---|
| 591 | initial = forms.fill_dict(initial, |
|---|
| 592 | slug=category.slug, |
|---|
| 593 | name=category.name, |
|---|
| 594 | description=category.description |
|---|
| 595 | ) |
|---|
| 596 | _CategoryBoundForm.__init__(self, category, initial) |
|---|
| 597 | |
|---|
| 598 | def validate_slug(self, value): |
|---|
| 599 | """Make sure the slug is unique.""" |
|---|
| 600 | query = Category.query.filter_by(slug=value) |
|---|
| 601 | if self.category is not None: |
|---|
| 602 | query = query.filter(Category.id != self.category.id) |
|---|
| 603 | existing = query.first() |
|---|
| 604 | if existing is not None: |
|---|
| 605 | raise ValidationError(_('This slug is already in use')) |
|---|
| 606 | |
|---|
| 607 | def make_category(self): |
|---|
| 608 | """A helper function that creates a category object from the data.""" |
|---|
| 609 | category = Category(self.data['name'], self.data['description'], |
|---|
| 610 | self.data['slug'] or None) |
|---|
| 611 | self.category = category |
|---|
| 612 | return category |
|---|
| 613 | |
|---|
| 614 | def save_changes(self): |
|---|
| 615 | """Save the changes back to the database. This also adds a redirect |
|---|
| 616 | if the slug changes. |
|---|
| 617 | """ |
|---|
| 618 | old_slug = self.category.slug |
|---|
| 619 | forms.set_fields(self.category, self.data, 'name', 'description') |
|---|
| 620 | if self.data['slug']: |
|---|
| 621 | self.category.slug = self.data['slug'] |
|---|
| 622 | elif not self.category.slug: |
|---|
| 623 | self.category.set_auto_slug() |
|---|
| 624 | if old_slug != self.category.slug: |
|---|
| 625 | register_redirect(old_slug, self.category.slug) |
|---|
| 626 | |
|---|
| 627 | |
|---|
| 628 | class DeleteCategoryForm(_CategoryBoundForm): |
|---|
| 629 | """Used for deleting categories.""" |
|---|
| 630 | |
|---|
| 631 | def delete_category(self): |
|---|
| 632 | """Delete the category from the database.""" |
|---|
| 633 | #! plugins can use this to react to category deletes. They can't stop |
|---|
| 634 | #! the deleting of the category but they can delete information in |
|---|
| 635 | #! their own tables so that the database is consistent afterwards. |
|---|
| 636 | emit_event('before-category-deleted', self.category) |
|---|
| 637 | db.delete(self.category) |
|---|
| 638 | |
|---|
| 639 | |
|---|
| 640 | class CommentMassModerateForm(forms.Form): |
|---|
| 641 | """This form is used for comment mass moderation.""" |
|---|
| 642 | selected_comments = forms.MultiChoiceField(widget=forms.CheckboxGroup) |
|---|
| 643 | per_page = forms.ChoiceField(choices=[20, 40, 60, 80, 100], |
|---|
| 644 | label=lazy_gettext('Comments Per Page:')) |
|---|
| 645 | |
|---|
| 646 | def __init__(self, comments, initial=None): |
|---|
| 647 | self.comments = comments |
|---|
| 648 | self.selected_comments.choices = [c.id for c in self.comments] |
|---|
| 649 | forms.Form.__init__(self, initial) |
|---|
| 650 | |
|---|
| 651 | def as_widget(self): |
|---|
| 652 | widget = forms.Form.as_widget(self) |
|---|
| 653 | widget.comments = self.comments |
|---|
| 654 | return widget |
|---|
| 655 | |
|---|
| 656 | def iter_selection(self): |
|---|
| 657 | selection = set(self.data['selected_comments']) |
|---|
| 658 | for comment in self.comments: |
|---|
| 659 | if comment.id in selection: |
|---|
| 660 | yield comment |
|---|
| 661 | |
|---|
| 662 | def delete_selection(self): |
|---|
| 663 | for comment in self.iter_selection(): |
|---|
| 664 | delete_comment(comment) |
|---|
| 665 | |
|---|
| 666 | def approve_selection(self, comment=None): |
|---|
| 667 | if comment: |
|---|
| 668 | emit_event('before-comment-approved', comment) |
|---|
| 669 | comment.status = COMMENT_MODERATED |
|---|
| 670 | comment.blocked_msg = u'' |
|---|
| 671 | else: |
|---|
| 672 | for comment in self.iter_selection(): |
|---|
| 673 | emit_event('before-comment-approved', comment) |
|---|
| 674 | comment.status = COMMENT_MODERATED |
|---|
| 675 | comment.blocked_msg = u'' |
|---|
| 676 | |
|---|
| 677 | def block_selection(self): |
|---|
| 678 | for comment in self.iter_selection(): |
|---|
| 679 | emit_event('before-comment-blocked', comment) |
|---|
| 680 | comment.status = COMMENT_BLOCKED_USER |
|---|
| 681 | comment.blocked_msg = _("Comment blocked by %s" % |
|---|
| 682 | get_request().user.display_name) |
|---|
| 683 | |
|---|
| 684 | def mark_selection_as_spam(self): |
|---|
| 685 | for comment in self.iter_selection(): |
|---|
| 686 | emit_event('before-comment-mark-spam', comment) |
|---|
| 687 | comment.status = COMMENT_BLOCKED_SPAM |
|---|
| 688 | comment.blocked_msg = _("Comment marked as spam by %s" % |
|---|
| 689 | get_request().user.display_name) |
|---|
| 690 | def mark_selection_as_ham(self): |
|---|
| 691 | for comment in self.iter_selection(): |
|---|
| 692 | emit_event('before-comment-mark-ham', comment) |
|---|
| 693 | self.approve_selection(comment) |
|---|
| 694 | |
|---|
| 695 | |
|---|
| 696 | class _GroupBoundForm(forms.Form): |
|---|
| 697 | """Internal baseclass for group bound forms.""" |
|---|
| 698 | |
|---|
| 699 | def __init__(self, group, initial=None): |
|---|
| 700 | forms.Form.__init__(self, initial) |
|---|
| 701 | self.app = get_application() |
|---|
| 702 | self.group = group |
|---|
| 703 | |
|---|
| 704 | def as_widget(self): |
|---|
| 705 | widget = forms.Form.as_widget(self) |
|---|
| 706 | widget.group = self.group |
|---|
| 707 | widget.new = self.group is None |
|---|
| 708 | return widget |
|---|
| 709 | |
|---|
| 710 | |
|---|
| 711 | class EditGroupForm(_GroupBoundForm): |
|---|
| 712 | """Edit or create a group.""" |
|---|
| 713 | |
|---|
| 714 | groupname = forms.TextField(lazy_gettext(u'Groupname'), max_length=30, |
|---|
| 715 | validators=[is_not_whitespace_only()], |
|---|
| 716 | required=True) |
|---|
| 717 | privileges = forms.MultiChoiceField(lazy_gettext(u'Privileges'), |
|---|
| 718 | widget=forms.CheckboxGroup) |
|---|
| 719 | |
|---|
| 720 | def __init__(self, group=None, initial=None): |
|---|
| 721 | if group is not None: |
|---|
| 722 | initial = forms.fill_dict(initial, |
|---|
| 723 | groupname=group.name, |
|---|
| 724 | privileges=[x.name for x in group.privileges] |
|---|
| 725 | ) |
|---|
| 726 | _GroupBoundForm.__init__(self, group, initial) |
|---|
| 727 | self.privileges.choices = self.app.list_privileges() |
|---|
| 728 | |
|---|
| 729 | def validate_groupname(self, value): |
|---|
| 730 | query = Group.query.filter_by(name=value) |
|---|
| 731 | if self.group is not None: |
|---|
| 732 | query = query.filter(Group.id != self.group.id) |
|---|
| 733 | if query.first() is not None: |
|---|
| 734 | raise ValidationError(_('This groupname is already in use')) |
|---|
| 735 | |
|---|
| 736 | def _set_common_attributes(self, group): |
|---|
| 737 | forms.set_fields(group, self.data) |
|---|
| 738 | bind_privileges(group.privileges, self.data['privileges']) |
|---|
| 739 | |
|---|
| 740 | def make_group(self): |
|---|
| 741 | """A helper function that creates a new group object.""" |
|---|
| 742 | group = Group(self.data['groupname']) |
|---|
| 743 | self._set_common_attributes(group) |
|---|
| 744 | self.group = group |
|---|
| 745 | return group |
|---|
| 746 | |
|---|
| 747 | def save_changes(self): |
|---|
| 748 | """Apply the changes.""" |
|---|
| 749 | self.group.name = self.data['groupname'] |
|---|
| 750 | self._set_common_attributes(self.group) |
|---|
| 751 | |
|---|
| 752 | |
|---|
| 753 | class DeleteGroupForm(_GroupBoundForm): |
|---|
| 754 | """Used to delete a group from the admin panel.""" |
|---|
| 755 | |
|---|
| 756 | action = forms.ChoiceField(lazy_gettext(u'What should Zine do with users ' |
|---|
| 757 | u'assigned to this group?'), |
|---|
| 758 | choices=[ |
|---|
| 759 | ('delete_membership', lazy_gettext(u'Do nothing, just detach the membership')), |
|---|
| 760 | ('relocate', lazy_gettext(u'Move the users to another group')) |
|---|
| 761 | ], widget=forms.RadioButtonGroup) |
|---|
| 762 | relocate_to = forms.ModelField(Group, 'id', lazy_gettext(u'Relocate users to'), |
|---|
| 763 | widget=forms.SelectBox) |
|---|
| 764 | |
|---|
| 765 | def __init__(self, group, initial=None): |
|---|
| 766 | self.relocate_to.choices = [('', u'')] + [ |
|---|
| 767 | (g.id, g.name) for g in Group.query.filter(Group.id != group.id) |
|---|
| 768 | ] |
|---|
| 769 | |
|---|
| 770 | _GroupBoundForm.__init__(self, group, forms.fill_dict(initial, |
|---|
| 771 | action='delete_membership')) |
|---|
| 772 | |
|---|
| 773 | def context_validate(self, data): |
|---|
| 774 | if data['action'] == 'relocate' and not data['relocate_to']: |
|---|
| 775 | raise ValidationError(_('You have to select a group that ' |
|---|
| 776 | 'gets the users assigned.')) |
|---|
| 777 | |
|---|
| 778 | def delete_group(self): |
|---|
| 779 | """Deletes a group.""" |
|---|
| 780 | if self.data['action'] == 'relocate': |
|---|
| 781 | new_group = Group.query.filter_by(self.data['reassign_to'].id).first() |
|---|
| 782 | for user in self.group.users: |
|---|
| 783 | if not new_group in user.groups: |
|---|
| 784 | user.groups.append(new_group) |
|---|
| 785 | db.commit() |
|---|
| 786 | |
|---|
| 787 | #! plugins can use this to react to user deletes. They can't stop |
|---|
| 788 | #! the deleting of the group but they can delete information in |
|---|
| 789 | #! their own tables so that the database is consistent afterwards. |
|---|
| 790 | #! Additional to the group object the form data is submitted. |
|---|
| 791 | emit_event('before-group-deleted', self.group, self.data) |
|---|
| 792 | db.delete(self.group) |
|---|
| 793 | |
|---|
| 794 | |
|---|
| 795 | class _UserBoundForm(forms.Form): |
|---|
| 796 | """Internal baseclass for user bound forms.""" |
|---|
| 797 | |
|---|
| 798 | def __init__(self, user, initial=None): |
|---|
| 799 | forms.Form.__init__(self, initial) |
|---|
| 800 | self.app = get_application() |
|---|
| 801 | self.user = user |
|---|
| 802 | |
|---|
| 803 | def as_widget(self): |
|---|
| 804 | widget = forms.Form.as_widget(self) |
|---|
| 805 | widget.user = self.user |
|---|
| 806 | widget.new = self.user is None |
|---|
| 807 | return widget |
|---|
| 808 | |
|---|
| 809 | |
|---|
| 810 | class EditUserForm(_UserBoundForm): |
|---|
| 811 | """Edit or create a user.""" |
|---|
| 812 | |
|---|
| 813 | username = forms.TextField(lazy_gettext(u'Username'), max_length=30, |
|---|
| 814 | validators=[is_not_whitespace_only()], |
|---|
| 815 | required=True) |
|---|
| 816 | real_name = forms.TextField(lazy_gettext(u'Realname'), max_length=180) |
|---|
| 817 | display_name = forms.ChoiceField(lazy_gettext(u'Display name')) |
|---|
| 818 | description = forms.TextField(lazy_gettext(u'Description'), |
|---|
| 819 | max_length=5000, widget=forms.Textarea) |
|---|
| 820 | email = forms.TextField(lazy_gettext(u'Email'), required=True, |
|---|
| 821 | validators=[is_valid_email()]) |
|---|
| 822 | www = forms.TextField(lazy_gettext(u'Website'), |
|---|
| 823 | validators=[is_valid_url()]) |
|---|
| 824 | password = forms.TextField(lazy_gettext(u'Password'), |
|---|
| 825 | widget=forms.PasswordInput) |
|---|
| 826 | privileges = forms.MultiChoiceField(lazy_gettext(u'Privileges'), |
|---|
| 827 | widget=forms.CheckboxGroup) |
|---|
| 828 | groups = forms.MultiChoiceField(lazy_gettext(u'Groups'), |
|---|
| 829 | widget=forms.CheckboxGroup) |
|---|
| 830 | is_author = forms.BooleanField(lazy_gettext(u'List as author'), |
|---|
| 831 | help_text=lazy_gettext(u'This user is listed as author')) |
|---|
| 832 | |
|---|
| 833 | def __init__(self, user=None, initial=None): |
|---|
| 834 | if user is not None: |
|---|
| 835 | initial = forms.fill_dict(initial, |
|---|
| 836 | username=user.username, |
|---|
| 837 | real_name=user.real_name, |
|---|
| 838 | display_name=user._display_name, |
|---|
| 839 | description=user.description, |
|---|
| 840 | email=user.email, |
|---|
| 841 | www=user.www, |
|---|
| 842 | privileges=[x.name for x in user.own_privileges], |
|---|
| 843 | groups=[g.name for g in user.groups], |
|---|
| 844 | is_author=user.is_author |
|---|
| 845 | ) |
|---|
| 846 | _UserBoundForm.__init__(self, user, initial) |
|---|
| 847 | self.display_name.choices = [ |
|---|
| 848 | (u'$username', user and user.username or _('Username')), |
|---|
| 849 | (u'$real_name', user and user.real_name or _('Realname')) |
|---|
| 850 | ] |
|---|
| 851 | self.privileges.choices = self.app.list_privileges() |
|---|
| 852 | self.groups.choices = [g.name for g in Group.query.all()] |
|---|
| 853 | self.password.required = user is None |
|---|
| 854 | |
|---|
| 855 | def validate_username(self, value): |
|---|
| 856 | query = User.query.filter_by(username=value) |
|---|
| 857 | if self.user is not None: |
|---|
| 858 | query = query.filter(User.id != self.user.id) |
|---|
| 859 | if query.first() is not None: |
|---|
| 860 | raise ValidationError(_('This username is already in use')) |
|---|
| 861 | |
|---|
| 862 | def _set_common_attributes(self, user): |
|---|
| 863 | forms.set_fields(user, self.data, 'www', 'real_name', 'description', |
|---|
| 864 | 'display_name', 'is_author') |
|---|
| 865 | bind_privileges(user.own_privileges, self.data['privileges'], user) |
|---|
| 866 | bound_groups = set(g.name for g in user.groups) |
|---|
| 867 | choosen_groups = set(self.data['groups']) |
|---|
| 868 | group_mapping = dict((g.name, g) for g in Group.query.all()) |
|---|
| 869 | # delete groups |
|---|
| 870 | for group in (bound_groups - choosen_groups): |
|---|
| 871 | user.groups.remove(group_mapping[group]) |
|---|
| 872 | # and add new groups |
|---|
| 873 | for group in (choosen_groups - bound_groups): |
|---|
| 874 | user.groups.append(group_mapping[group]) |
|---|
| 875 | |
|---|
| 876 | def make_user(self): |
|---|
| 877 | """A helper function that creates a new user object.""" |
|---|
| 878 | user = User(self.data['username'], self.data['password'], |
|---|
| 879 | self.data['email']) |
|---|
| 880 | self._set_common_attributes(user) |
|---|
| 881 | self.user = user |
|---|
| 882 | return user |
|---|
| 883 | |
|---|
| 884 | def save_changes(self): |
|---|
| 885 | """Apply the changes.""" |
|---|
| 886 | self.user.username = self.data['username'] |
|---|
| 887 | if self.data['password']: |
|---|
| 888 | self.user.set_password(self.data['password']) |
|---|
| 889 | self.user.email = self.data['email'] |
|---|
| 890 | self._set_common_attributes(self.user) |
|---|
| 891 | |
|---|
| 892 | |
|---|
| 893 | class DeleteUserForm(_UserBoundForm): |
|---|
| 894 | """Used to delete a user from the admin panel.""" |
|---|
| 895 | |
|---|
| 896 | action = forms.ChoiceField(lazy_gettext(u'What should Zine do with posts ' |
|---|
| 897 | u'written by this user?'), choices=[ |
|---|
| 898 | ('delete', lazy_gettext(u'Delete them permanently')), |
|---|
| 899 | ('reassign', lazy_gettext(u'Reassign posts')) |
|---|
| 900 | ], widget=forms.RadioButtonGroup) |
|---|
| 901 | reassign_to = forms.ModelField(User, 'id', |
|---|
| 902 | lazy_gettext(u'Reassign posts to'), |
|---|
| 903 | widget=forms.SelectBox) |
|---|
| 904 | |
|---|
| 905 | def __init__(self, user, initial=None): |
|---|
| 906 | self.reassign_to.choices = [('', u'')] + [ |
|---|
| 907 | (u.id, u.username) |
|---|
| 908 | for u in User.query.filter(User.id != user.id) |
|---|
| 909 | ] |
|---|
| 910 | _UserBoundForm.__init__(self, user, forms.fill_dict(initial, |
|---|
| 911 | action='reassign' |
|---|
| 912 | )) |
|---|
| 913 | |
|---|
| 914 | def context_validate(self, data): |
|---|
| 915 | if self.user.posts.count() is 0: |
|---|
| 916 | data['action'] = None |
|---|
| 917 | if data['action'] == 'reassign' and not data['reassign_to']: |
|---|
| 918 | raise ValidationError(_('You have to select a user to reassign ' |
|---|
| 919 | 'the posts to.')) |
|---|
| 920 | |
|---|
| 921 | def delete_user(self): |
|---|
| 922 | """Deletes the user.""" |
|---|
| 923 | if self.data['action'] == 'reassign': |
|---|
| 924 | db.execute(posts.update(posts.c.author_id == self.user.id), dict( |
|---|
| 925 | author_id=self.data['reassign_to'].id |
|---|
| 926 | )) |
|---|
| 927 | |
|---|
| 928 | # find all the comments by this author and make them comments that |
|---|
| 929 | # are no longer linked to the author. |
|---|
| 930 | for comment in self.user.comments.all(): |
|---|
| 931 | comment.unbind_user() |
|---|
| 932 | |
|---|
| 933 | #! plugins can use this to react to user deletes. They can't stop |
|---|
| 934 | #! the deleting of the user but they can delete information in |
|---|
| 935 | #! their own tables so that the database is consistent afterwards. |
|---|
| 936 | #! Additional to the user object the form data is submitted. |
|---|
| 937 | emit_event('before-user-deleted', self.user, self.data) |
|---|
| 938 | db.delete(self.user) |
|---|
| 939 | |
|---|
| 940 | |
|---|
| 941 | class EditProfileForm(_UserBoundForm): |
|---|
| 942 | """Edit or create a user's profile.""" |
|---|
| 943 | |
|---|
| 944 | username = forms.TextField(lazy_gettext(u'Username'), max_length=30, |
|---|
| 945 | validators=[is_not_whitespace_only()], |
|---|
| 946 | required=True) |
|---|
| 947 | real_name = forms.TextField(lazy_gettext(u'Realname'), max_length=180) |
|---|
| 948 | display_name = forms.ChoiceField(lazy_gettext(u'Display name')) |
|---|
| 949 | description = forms.TextField(lazy_gettext(u'Description'), |
|---|
| 950 | max_length=5000, widget=forms.Textarea) |
|---|
| 951 | email = forms.TextField(lazy_gettext(u'Email'), required=True, |
|---|
| 952 | validators=[is_valid_email()]) |
|---|
| 953 | www = forms.TextField(lazy_gettext(u'Website'), |
|---|
| 954 | validators=[is_valid_url()]) |
|---|
| 955 | password = forms.TextField(lazy_gettext(u'Password'), |
|---|
| 956 | widget=forms.PasswordInput) |
|---|
| 957 | password_confirm = forms.TextField(lazy_gettext(u'Confirm password'), |
|---|
| 958 | widget=forms.PasswordInput, |
|---|
| 959 | help_text=lazy_gettext(u'Confirm password')) |
|---|
| 960 | |
|---|
| 961 | def __init__(self, user=None, initial=None): |
|---|
| 962 | if user is not None: |
|---|
| 963 | initial = forms.fill_dict(initial, |
|---|
| 964 | username=user.username, |
|---|
| 965 | real_name=user.real_name, |
|---|
| 966 | display_name=user._display_name, |
|---|
| 967 | description=user.description, |
|---|
| 968 | email=user.email, |
|---|
| 969 | www=user.www |
|---|
| 970 | ) |
|---|
| 971 | _UserBoundForm.__init__(self, user, initial) |
|---|
| 972 | self.display_name.choices = [ |
|---|
| 973 | (u'$username', user and user.username or _('Username')), |
|---|
| 974 | (u'$real_name', user and user.real_name or _('Realname')) |
|---|
| 975 | ] |
|---|
| 976 | |
|---|
| 977 | def validate_email(self, value): |
|---|
| 978 | query = User.query.filter_by(email=value) |
|---|
| 979 | if self.user is not None: |
|---|
| 980 | query = query.filter(User.id != self.user.id) |
|---|
| 981 | if query.first() is not None: |
|---|
| 982 | raise ValidationError(_('This email address is already in use')) |
|---|
| 983 | |
|---|
| 984 | def validate_password(self, value): |
|---|
| 985 | if 'password_confirm' in self.data: |
|---|
| 986 | password_confirm = self.data['password_confirm'] |
|---|
| 987 | else: |
|---|
| 988 | password_confirm = self.request.values.get('password_confirm', '') |
|---|
| 989 | if ((not value == password_confirm) or (value and not password_confirm) |
|---|
| 990 | or (password_confirm and not value)): |
|---|
| 991 | raise ValidationError(_('Passwords do not match')) |
|---|
| 992 | |
|---|
| 993 | |
|---|
| 994 | def save_changes(self): |
|---|
| 995 | """Apply the changes.""" |
|---|
| 996 | if self.data['password']: |
|---|
| 997 | self.user.set_password(self.data['password']) |
|---|
| 998 | self.user.real_name = self.data['real_name'] |
|---|
| 999 | self.user.display_name = self.data['display_name'] |
|---|
| 1000 | self.user.description = self.data['description'] |
|---|
| 1001 | self.user.email = self.data['email'] |
|---|
| 1002 | self.user.www = self.data['www'] |
|---|
| 1003 | |
|---|
| 1004 | |
|---|
| 1005 | class DeleteAccountForm(_UserBoundForm): |
|---|
| 1006 | """Used for a user to delete a his own account.""" |
|---|
| 1007 | |
|---|
| 1008 | password = forms.TextField( |
|---|
| 1009 | lazy_gettext(u"Your password is required to delete your account:"), |
|---|
| 1010 | required=True, widget=forms.PasswordInput, |
|---|
| 1011 | messages = dict(required=lazy_gettext(u'Your password is required!')) |
|---|
| 1012 | ) |
|---|
| 1013 | |
|---|
| 1014 | def __init__(self, user, initial=None): |
|---|
| 1015 | _UserBoundForm.__init__(self, user, forms.fill_dict(initial, |
|---|
| 1016 | action='delete' |
|---|
| 1017 | )) |
|---|
| 1018 | |
|---|
| 1019 | def validate_password(self, value): |
|---|
| 1020 | if not self.user.check_password(value): |
|---|
| 1021 | raise ValidationError(_(u'Invalid password')) |
|---|
| 1022 | |
|---|
| 1023 | def delete_user(self): |
|---|
| 1024 | """Deletes the user's account.""" |
|---|
| 1025 | # find all the comments by this author and make them comments that |
|---|
| 1026 | # are no longer linked to the author. |
|---|
| 1027 | for comment in self.user.comments.all(): |
|---|
| 1028 | comment.unbind_user() |
|---|
| 1029 | |
|---|
| 1030 | #! plugins can use this to react to user deletes. They can't stop |
|---|
| 1031 | #! the deleting of the user but they can delete information in |
|---|
| 1032 | #! their own tables so that the database is consistent afterwards. |
|---|
| 1033 | #! Additional to the user object the form data is submitted. |
|---|
| 1034 | emit_event('before-user-deleted', self.user, self.data) |
|---|
| 1035 | db.delete(self.user) |
|---|
| 1036 | |
|---|
| 1037 | |
|---|
| 1038 | class _ConfigForm(forms.Form): |
|---|
| 1039 | """Internal baseclass for forms that operate on config values.""" |
|---|
| 1040 | |
|---|
| 1041 | def __init__(self, initial=None): |
|---|
| 1042 | self.app = get_application() |
|---|
| 1043 | if initial is None: |
|---|
| 1044 | initial = {} |
|---|
| 1045 | for name in self.fields: |
|---|
| 1046 | initial[name] = self.app.cfg[name] |
|---|
| 1047 | forms.Form.__init__(self, initial) |
|---|
| 1048 | |
|---|
| 1049 | def _apply(self, t, skip): |
|---|
| 1050 | for key, value in self.data.iteritems(): |
|---|
| 1051 | if key not in skip: |
|---|
| 1052 | t[key] = value |
|---|
| 1053 | |
|---|
| 1054 | def apply(self): |
|---|
| 1055 | t = self.app.cfg.edit() |
|---|
| 1056 | self._apply(t, set()) |
|---|
| 1057 | t.commit() |
|---|
| 1058 | |
|---|
| 1059 | |
|---|
| 1060 | class LogOptionsForm(_ConfigForm): |
|---|
| 1061 | """A form for the logfiles.""" |
|---|
| 1062 | log_file = config_field('log_file', lazy_gettext(u'Filename')) |
|---|
| 1063 | log_level = config_field('log_level', lazy_gettext(u'Log Level')) |
|---|
| 1064 | |
|---|
| 1065 | |
|---|
| 1066 | class BasicOptionsForm(_ConfigForm): |
|---|
| 1067 | """The form where the basic options are changed.""" |
|---|
| 1068 | blog_title = config_field('blog_title', lazy_gettext(u'Blog title')) |
|---|
| 1069 | blog_tagline = config_field('blog_tagline', lazy_gettext(u'Blog tagline')) |
|---|
| 1070 | blog_email = config_field('blog_email', lazy_gettext(u'Blog email')) |
|---|
| 1071 | language = config_field('language', lazy_gettext(u'Language')) |
|---|
| 1072 | timezone = config_field('timezone', lazy_gettext(u'Timezone')) |
|---|
| 1073 | session_cookie_name = config_field('session_cookie_name', |
|---|
| 1074 | lazy_gettext(u'Cookie Name')) |
|---|
| 1075 | comments_enabled = config_field('comments_enabled', |
|---|
| 1076 | label=lazy_gettext(u'Comments enabled'), |
|---|
| 1077 | help_text=lazy_gettext(u'enable comments per default')) |
|---|
| 1078 | moderate_comments = config_field('moderate_comments', |
|---|
| 1079 | lazy_gettext(u'Comment Moderation'), |
|---|
| 1080 | widget=forms.RadioButtonGroup) |
|---|
| 1081 | comments_open_for = config_field('comments_open_for', |
|---|
| 1082 | label=lazy_gettext(u'Comments Open Period')) |
|---|
| 1083 | pings_enabled = config_field('pings_enabled', |
|---|
| 1084 | lazy_gettext(u'Pingbacks enabled'), |
|---|
| 1085 | help_text=lazy_gettext(u'enable pingbacks per default')) |
|---|
| 1086 | use_flat_comments = config_field('use_flat_comments', |
|---|
| 1087 | lazy_gettext(u'Use flat comments'), |
|---|
| 1088 | help_text=lazy_gettext(u'All comments are posted top-level')) |
|---|
| 1089 | default_parser = config_field('default_parser', |
|---|
| 1090 | lazy_gettext(u'Default parser')) |
|---|
| 1091 | comment_parser = config_field('comment_parser', |
|---|
| 1092 | lazy_gettext(u'Comment parser')) |
|---|
| 1093 | posts_per_page = config_field('posts_per_page', |
|---|
| 1094 | lazy_gettext(u'Posts per page')) |
|---|
| 1095 | |
|---|
| 1096 | def __init__(self, initial=None): |
|---|
| 1097 | _ConfigForm.__init__(self, initial) |
|---|
| 1098 | self.language.choices = list_languages() |
|---|
| 1099 | self.default_parser.choices = self.comment_parser.choices = \ |
|---|
| 1100 | self.app.list_parsers() |
|---|
| 1101 | |
|---|
| 1102 | |
|---|
| 1103 | class URLOptionsForm(_ConfigForm): |
|---|
| 1104 | """The form for url changes. This form sends database queries, even |
|---|
| 1105 | though seems to only operate on the config. Make sure to commit. |
|---|
| 1106 | """ |
|---|
| 1107 | |
|---|
| 1108 | blog_url_prefix = config_field('blog_url_prefix', |
|---|
| 1109 | lazy_gettext(u'Blog URL prefix')) |
|---|
| 1110 | admin_url_prefix = config_field('admin_url_prefix', |
|---|
| 1111 | lazy_gettext(u'Admin URL prefix')) |
|---|
| 1112 | category_url_prefix = config_field('category_url_prefix', |
|---|
| 1113 | lazy_gettext(u'Category URL prefix')) |
|---|
| 1114 | tags_url_prefix = config_field('tags_url_prefix', |
|---|
| 1115 | lazy_gettext(u'Tag URL prefix')) |
|---|
| 1116 | profiles_url_prefix = config_field('profiles_url_prefix', |
|---|
| 1117 | lazy_gettext(u'Author Profiles URL prefix')) |
|---|
| 1118 | post_url_format = config_field('post_url_format', |
|---|
| 1119 | lazy_gettext(u'Post permalink URL format')) |
|---|
| 1120 | ascii_slugs = config_field('ascii_slugs', |
|---|
| 1121 | lazy_gettext(u'Limit slugs to ASCII')) |
|---|
| 1122 | fixed_url_date_digits = config_field('fixed_url_date_digits', |
|---|
| 1123 | lazy_gettext(u'Use zero-padded dates')) |
|---|
| 1124 | force_https = config_field('force_https', lazy_gettext(u'Force HTTPS')) |
|---|
| 1125 | |
|---|
| 1126 | def _apply(self, t, skip): |
|---|
| 1127 | for key, value in self.data.iteritems(): |
|---|
| 1128 | if key not in skip: |
|---|
| 1129 | old = t[key] |
|---|
| 1130 | if old != value: |
|---|
| 1131 | if key == 'blog_url_prefix': |
|---|
| 1132 | change_url_prefix(old, value) |
|---|
| 1133 | t[key] = value |
|---|
| 1134 | |
|---|
| 1135 | # update the blog_url based on the force_https flag. |
|---|
| 1136 | blog_url = (t['force_https'] and 'https' or 'http') + \ |
|---|
| 1137 | ':' + t['blog_url'].split(':', 1)[1] |
|---|
| 1138 | if blog_url != t['blog_url']: |
|---|
| 1139 | t['blog_url'] = blog_url |
|---|
| 1140 | |
|---|
| 1141 | |
|---|
| 1142 | class ThemeOptionsForm(_ConfigForm): |
|---|
| 1143 | """ |
|---|
| 1144 | The form for theme changes. This is mainly just a dummy, |
|---|
| 1145 | to get csrf protection working. |
|---|
| 1146 | """ |
|---|
| 1147 | |
|---|
| 1148 | |
|---|
| 1149 | class CacheOptionsForm(_ConfigForm): |
|---|
| 1150 | cache_system = config_field('cache_system', lazy_gettext(u'Cache system')) |
|---|
| 1151 | cache_timeout = config_field('cache_timeout', |
|---|
| 1152 | lazy_gettext(u'Default cache timeout')) |
|---|
| 1153 | enable_eager_caching = config_field('enable_eager_caching', |
|---|
| 1154 | lazy_gettext(u'Enable eager caching'), |
|---|
| 1155 | help_text=lazy_gettext(u'Enable')) |
|---|
| 1156 | memcached_servers = config_field('memcached_servers') |
|---|
| 1157 | filesystem_cache_path = config_field('filesystem_cache_path') |
|---|
| 1158 | |
|---|
| 1159 | def context_validate(self, data): |
|---|
| 1160 | if data['cache_system'] == 'memcached': |
|---|
| 1161 | if not data['memcached_servers']: |
|---|
| 1162 | raise ValidationError(_(u'You have to provide at least one ' |
|---|
| 1163 | u'server to use memcached.')) |
|---|
| 1164 | elif data['cache_system'] == 'filesystem': |
|---|
| 1165 | if not data['filesystem_cache_path']: |
|---|
| 1166 | raise ValidationError(_(u'You have to provide cache folder to ' |
|---|
| 1167 | u'use filesystem cache.')) |
|---|
| 1168 | |
|---|
| 1169 | |
|---|
| 1170 | class MaintenanceModeForm(forms.Form): |
|---|
| 1171 | """yet a dummy form, but could be extended later.""" |
|---|
| 1172 | |
|---|
| 1173 | |
|---|
| 1174 | class WordPressImportForm(forms.Form): |
|---|
| 1175 | """This form is used in the WordPress importer.""" |
|---|
| 1176 | download_url = forms.TextField(lazy_gettext(u'Dump Download URL'), |
|---|
| 1177 | validators=[is_valid_url()]) |
|---|
| 1178 | |
|---|
| 1179 | |
|---|
| 1180 | class FeedImportForm(forms.Form): |
|---|
| 1181 | """This form is used in the feed importer.""" |
|---|
| 1182 | download_url = forms.TextField(lazy_gettext(u'Feed Download URL'), |
|---|
| 1183 | validators=[is_valid_url()]) |
|---|
| 1184 | |
|---|
| 1185 | |
|---|
| 1186 | class DeleteImportForm(forms.Form): |
|---|
| 1187 | """This form is used to delete a imported file.""" |
|---|
| 1188 | |
|---|
| 1189 | |
|---|
| 1190 | class ExportForm(forms.Form): |
|---|
| 1191 | """This form is used to implement the export dialog.""" |
|---|
| 1192 | |
|---|
| 1193 | |
|---|
| 1194 | def delete_comment(comment): |
|---|
| 1195 | """ |
|---|
| 1196 | Deletes or marks for deletion the specified comment, depending on the |
|---|
| 1197 | comment's position in the comment thread. Comments are not pruned from |
|---|
| 1198 | the database until all their children are. |
|---|
| 1199 | """ |
|---|
| 1200 | if comment.children: |
|---|
| 1201 | # We don't have to check if the children are also marked deleted or not |
|---|
| 1202 | # because if they still exist, it means somewhere down the tree is a |
|---|
| 1203 | # comment that is not deleted. |
|---|
| 1204 | comment.status = COMMENT_DELETED |
|---|
| 1205 | comment.text = u'' |
|---|
| 1206 | comment.user = None |
|---|
| 1207 | comment._author = comment._email = comment._www = None |
|---|
| 1208 | else: |
|---|
| 1209 | parent = comment.parent |
|---|
| 1210 | #! plugins can use this to react to comment deletes. They can't |
|---|
| 1211 | #! stop the deleting of the comment but they can delete information |
|---|
| 1212 | #! in their own tables so that the database is consistent |
|---|
| 1213 | #! afterwards. |
|---|
| 1214 | emit_event('before-comment-deleted', comment) |
|---|
| 1215 | db.delete(comment) |
|---|
| 1216 | while parent is not None and parent.is_deleted: |
|---|
| 1217 | if not parent.children: |
|---|
| 1218 | newparent = parent.parent |
|---|
| 1219 | emit_event('before-comment-deleted', parent) |
|---|
| 1220 | db.delete(parent) |
|---|
| 1221 | parent = newparent |
|---|
| 1222 | else: |
|---|
| 1223 | parent = None |
|---|
| 1224 | # XXX: one could probably optimize this by tracking the amount |
|---|
| 1225 | # of deleted comments |
|---|
| 1226 | comment.post.sync_comment_count() |
|---|
| 1227 | |
|---|
| 1228 | |
|---|
| 1229 | def make_config_form(): |
|---|
| 1230 | """Returns the form for the configuration editor.""" |
|---|
| 1231 | app = get_application() |
|---|
| 1232 | fields = {} |
|---|
| 1233 | values = {} |
|---|
| 1234 | use_default_label = lazy_gettext(u'Use default value') |
|---|
| 1235 | |
|---|
| 1236 | for category in app.cfg.get_detail_list(): |
|---|
| 1237 | items = {} |
|---|
| 1238 | values[category['name']] = category_values = {} |
|---|
| 1239 | for item in category['items']: |
|---|
| 1240 | items[item['name']] = forms.Mapping( |
|---|
| 1241 | value=item['field'], |
|---|
| 1242 | use_default=forms.BooleanField(use_default_label) |
|---|
| 1243 | ) |
|---|
| 1244 | category_values[item['name']] = { |
|---|
| 1245 | 'value': item['value'], |
|---|
| 1246 | 'use_default': False |
|---|
| 1247 | } |
|---|
| 1248 | fields[category['name']] = forms.Mapping(**items) |
|---|
| 1249 | |
|---|
| 1250 | class _ConfigForm(forms.Form): |
|---|
| 1251 | values = forms.Mapping(**fields) |
|---|
| 1252 | cfg = app.cfg |
|---|
| 1253 | |
|---|
| 1254 | def apply(self): |
|---|
| 1255 | t = self.cfg.edit() |
|---|
| 1256 | for category, items in self.data['values'].iteritems(): |
|---|
| 1257 | for key, d in items.iteritems(): |
|---|
| 1258 | if category != 'zine': |
|---|
| 1259 | key = '%s/%s' % (category, key) |
|---|
| 1260 | if d['use_default']: |
|---|
| 1261 | t.revert_to_default(key) |
|---|
| 1262 | else: |
|---|
| 1263 | t[key] = d['value'] |
|---|
| 1264 | t.commit() |
|---|
| 1265 | |
|---|
| 1266 | return _ConfigForm({'values': values}) |
|---|
| 1267 | |
|---|
| 1268 | |
|---|
| 1269 | def make_notification_form(user): |
|---|
| 1270 | """Creates a notification form.""" |
|---|
| 1271 | app = get_application() |
|---|
| 1272 | fields = {} |
|---|
| 1273 | subscriptions = {} |
|---|
| 1274 | |
|---|
| 1275 | systems = [(s.key, s.name) for s in |
|---|
| 1276 | sorted(app.notification_manager.systems.values(), |
|---|
| 1277 | key=lambda x: x.name.lower())] |
|---|
| 1278 | |
|---|
| 1279 | for obj in app.notification_manager.types(user): |
|---|
| 1280 | fields[obj.name] = forms.MultiChoiceField(choices=systems, |
|---|
| 1281 | label=obj.description, |
|---|
| 1282 | widget=forms.CheckboxGroup) |
|---|
| 1283 | |
|---|
| 1284 | for ns in user.notification_subscriptions: |
|---|
| 1285 | subscriptions.setdefault(ns.notification_id, []) \ |
|---|
| 1286 | .append(ns.notification_system) |
|---|
| 1287 | |
|---|
| 1288 | class _NotificationForm(forms.Form): |
|---|
| 1289 | subscriptions = forms.Mapping(**fields) |
|---|
| 1290 | system_choices = systems |
|---|
| 1291 | |
|---|
| 1292 | def apply(self): |
|---|
| 1293 | user_subscriptions = {} |
|---|
| 1294 | for subscription in user.notification_subscriptions: |
|---|
| 1295 | user_subscriptions.setdefault(subscription.notification_id, |
|---|
| 1296 | set()).add(subscription.notification_system) |
|---|
| 1297 | |
|---|
| 1298 | for key, active in self['subscriptions'].iteritems(): |
|---|
| 1299 | currently_set = user_subscriptions.get(key, set()) |
|---|
| 1300 | active = set(active) |
|---|
| 1301 | |
|---|
| 1302 | # remove outdated |
|---|
| 1303 | for system in currently_set.difference(active): |
|---|
| 1304 | for subscription in user.notification_subscriptions \ |
|---|
| 1305 | .filter_by(notification_id=key, |
|---|
| 1306 | notification_system=system): |
|---|
| 1307 | db.session.delete(subscription) |
|---|
| 1308 | |
|---|
| 1309 | # add new |
|---|
| 1310 | for system in active.difference(currently_set): |
|---|
| 1311 | user.notification_subscriptions.append( |
|---|
| 1312 | NotificationSubscription(user=user, notification_id=key, |
|---|
| 1313 | notification_system=system)) |
|---|
| 1314 | |
|---|
| 1315 | return _NotificationForm({'subscriptions': subscriptions}) |
|---|
| 1316 | |
|---|
| 1317 | |
|---|
| 1318 | def make_import_form(blog): |
|---|
| 1319 | user_choices = [('__zine_create_user', _(u'Create new user'))] + [ |
|---|
| 1320 | (user.id, user.username) |
|---|
| 1321 | for user in User.query.order_by('username').all() |
|---|
| 1322 | ] |
|---|
| 1323 | |
|---|
| 1324 | _authors = dict((author.id, forms.ChoiceField(author.username, |
|---|
| 1325 | choices=user_choices)) |
|---|
| 1326 | for author in blog.authors) |
|---|
| 1327 | _posts = dict((post.id, forms.BooleanField(help_text=post.title)) for post |
|---|
| 1328 | in blog.posts) |
|---|
| 1329 | _comments = dict((post.id, forms.BooleanField()) for post |
|---|
| 1330 | in blog.posts) |
|---|
| 1331 | |
|---|
| 1332 | class _ImportForm(forms.Form): |
|---|
| 1333 | title = forms.BooleanField(lazy_gettext(u'Blog title'), |
|---|
| 1334 | help_text=blog.title) |
|---|
| 1335 | description = forms.BooleanField(lazy_gettext(u'Blog description'), |
|---|
| 1336 | help_text=blog.description) |
|---|
| 1337 | authors = forms.Mapping(_authors) |
|---|
| 1338 | posts = forms.Mapping(_posts) |
|---|
| 1339 | comments = forms.Mapping(_comments) |
|---|
| 1340 | load_config = forms.BooleanField(lazy_gettext(u'Load config values'), |
|---|
| 1341 | help_text=lazy_gettext( |
|---|
| 1342 | u'Load the configuration values ' |
|---|
| 1343 | u'from the import.')) |
|---|
| 1344 | |
|---|
| 1345 | def perform_import(self): |
|---|
| 1346 | from zine.importers import perform_import |
|---|
| 1347 | return perform_import(get_application(), blog, self.data, |
|---|
| 1348 | stream=True) |
|---|
| 1349 | |
|---|
| 1350 | _all_true = dict((x.id, True) for x in blog.posts) |
|---|
| 1351 | return _ImportForm({'posts': _all_true.copy(), |
|---|
| 1352 | 'comments': _all_true.copy()}) |
|---|