Zine

open source content publishing system


source: zine/forms.py @ 1362:e378a3bb8ea3

Revision 1362:e378a3bb8ea3, 53.8 KB checked in by Mike Crute <mcrute@…>, 2 years ago (diff)

Merging polish translation changes for Ticket 237

Line 
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"""
11from copy import copy
12from datetime import datetime
13
14from zine.i18n import _, lazy_gettext, list_languages
15from zine.application import get_application, get_request, emit_event
16from zine.config import DEFAULT_VARS
17from zine.database import db, posts
18from 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
23from zine.parsers import render_preview
24from zine.privileges import bind_privileges
25from zine.notifications import send_notification_template, NEW_COMMENT, \
26     COMMENT_REQUIRES_MODERATION
27from zine.utils import forms, log, dump_json
28from zine.utils.http import redirect_to
29from zine.utils.validators import ValidationError, is_valid_email, \
30     is_valid_url, is_valid_slug, is_not_whitespace_only
31from zine.utils.redirects import register_redirect, change_url_prefix
32
33
34def 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
48class 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
67class 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
91class 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
208class 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
234class 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
242class 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
409class 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
421class PageForm(PostForm):
422    content_type = 'page'
423
424
425class 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
444class _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
458class 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
510class 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
518class 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
529class 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
546class 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
565class _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
580class 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
628class 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
640class 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
696class _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
711class 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
753class 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
795class _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
810class 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
893class 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
941class 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
1005class 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
1038class _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
1060class 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
1066class 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
1103class 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
1142class ThemeOptionsForm(_ConfigForm):
1143    """
1144    The form for theme changes.  This is mainly just a dummy,
1145    to get csrf protection working.
1146    """
1147
1148
1149class 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
1170class MaintenanceModeForm(forms.Form):
1171    """yet a dummy form, but could be extended later."""
1172
1173
1174class 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
1180class 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
1186class DeleteImportForm(forms.Form):
1187    """This form is used to delete a imported file."""
1188
1189
1190class ExportForm(forms.Form):
1191    """This form is used to implement the export dialog."""
1192
1193
1194def 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
1229def 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
1269def 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
1318def 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()})
Note: See TracBrowser for help on using the repository browser.