Esempio n. 1
0
 def get_or_create_tracker(self, user, forum):
     """
     Correctly create tracker in mysql db on default REPEATABLE READ transaction mode
     It's known problem when standrard get_or_create method return can raise exception
     with correct data in mysql database.
     See http://stackoverflow.com/questions/2235318/how-do-i-deal-with-this-race-condition-in-django/2235624
     """
     is_new = True
     sid = transaction.savepoint(using=self.db)
     try:
         with get_atomic_func()():
             obj = ForumReadTracker.objects.create(user=user, forum=forum)
         transaction.savepoint_commit(sid)
     except DatabaseError:
         transaction.savepoint_rollback(sid)
         is_new = False
         obj = ForumReadTracker.objects.get(user=user, forum=forum)
     return obj, is_new
Esempio n. 2
0
    def get_or_create_tracker(self, user, forum):
        """
        Correctly create tracker in mysql db on default REPEATABLE READ transaction mode

        It's known problem when standrard get_or_create method return can raise exception
        with correct data in mysql database.
        See http://stackoverflow.com/questions/2235318/how-do-i-deal-with-this-race-condition-in-django/2235624
        """
        is_new = True
        sid = transaction.savepoint(using=self.db)
        try:
            with get_atomic_func()():
                obj = ForumReadTracker.objects.create(user=user, forum=forum)
            transaction.savepoint_commit(sid)
        except DatabaseError:
            transaction.savepoint_rollback(sid)
            is_new = False
            obj = ForumReadTracker.objects.get(user=user, forum=forum)
        return obj, is_new
Esempio n. 3
0
class PostEditMixin(PybbFormsMixin):
    @method_decorator(get_atomic_func())
    def post(self, request, *args, **kwargs):
        return super(PostEditMixin, self).post(request, *args, **kwargs)

    def get_form_class(self):
        if defaults.PYBB_ENABLE_ADMIN_POST_FORM and \
                perms.may_post_as_admin(self.request.user):
            return self.get_admin_post_form_class()
        else:
            return self.get_post_form_class()

    def get_context_data(self, **kwargs):

        ctx = super(PostEditMixin, self).get_context_data(**kwargs)

        if perms.may_attach_files(
                self.request.user) and 'aformset' not in kwargs:
            ctx['aformset'] = self.get_attachment_formset_class()(
                instance=getattr(self, 'object', None))

        if perms.may_create_poll(
                self.request.user) and 'pollformset' not in kwargs:
            ctx['pollformset'] = self.get_poll_answer_formset_class()(
                instance=self.object.topic if getattr(self, 'object', None
                                                      ) else None)

        return ctx

    def form_valid(self, form):
        success = True
        save_attachments = False
        save_poll_answers = False
        self.object, topic = form.save(commit=False)

        if perms.may_attach_files(self.request.user):
            aformset = self.get_attachment_formset_class()(
                self.request.POST, self.request.FILES, instance=self.object)
            if aformset.is_valid():
                save_attachments = True
            else:
                success = False
        else:
            aformset = None

        if perms.may_create_poll(self.request.user):
            pollformset = self.get_poll_answer_formset_class()()
            if getattr(self, 'forum', None) or topic.head == self.object:
                if topic.poll_type != Topic.POLL_TYPE_NONE:
                    pollformset = self.get_poll_answer_formset_class()(
                        self.request.POST, instance=topic)
                    if pollformset.is_valid():
                        save_poll_answers = True
                    else:
                        success = False
                else:
                    topic.poll_question = None
                    topic.poll_answers.all().delete()
        else:
            pollformset = None

        if success:
            try:
                topic.save()
            except ValidationError as e:
                success = False
                errors = form._errors.setdefault('name', ErrorList())
                errors += e.error_list
            else:
                self.object.topic = topic
                self.object.save()
                if save_attachments:
                    aformset.save()
                    if self.object.attachments.count():
                        # re-parse the body to replace attachment's references by URLs
                        self.object.save()
                if save_poll_answers:
                    pollformset.save()
                return HttpResponseRedirect(self.get_success_url())
        return self.render_to_response(
            self.get_context_data(form=form,
                                  aformset=aformset,
                                  pollformset=pollformset))
Esempio n. 4
0
class TopicView(RedirectToLoginMixin, PaginatorMixin, PybbFormsMixin,
                generic.ListView):
    paginate_by = defaults.PYBB_TOPIC_PAGE_SIZE
    template_object_name = 'post_list'
    template_name = 'pybb/topic.html'

    def get(self, request, *args, **kwargs):
        if defaults.PYBB_NICE_URL and 'pk' in kwargs:
            return redirect(
                self.topic,
                permanent=defaults.PYBB_NICE_URL_PERMANENT_REDIRECT)
        response = super(TopicView, self).get(request, *args, **kwargs)
        self.mark_read()
        return response

    def get_login_redirect_url(self):
        return self.topic.get_absolute_url()

    @method_decorator(csrf_protect)
    def dispatch(self, request, *args, **kwargs):
        self.topic = self.get_topic(**kwargs)

        if request.GET.get('first-unread'):
            if request.user.is_authenticated():
                read_dates = []
                try:
                    read_dates.append(
                        TopicReadTracker.objects.get(
                            user=request.user, topic=self.topic).time_stamp)
                except TopicReadTracker.DoesNotExist:
                    pass
                try:
                    read_dates.append(
                        ForumReadTracker.objects.get(
                            user=request.user,
                            forum=self.topic.forum).time_stamp)
                except ForumReadTracker.DoesNotExist:
                    pass

                read_date = read_dates and max(read_dates)
                if read_date:
                    try:
                        first_unread_topic = self.topic.posts.filter(
                            created__gt=read_date).order_by('created', 'id')[0]
                    except IndexError:
                        first_unread_topic = self.topic.last_post
                else:
                    first_unread_topic = self.topic.head
                return HttpResponseRedirect(
                    reverse('pybb:post', kwargs={'pk': first_unread_topic.id}))

        return super(TopicView, self).dispatch(request, *args, **kwargs)

    def get_queryset(self):
        if not perms.may_view_topic(self.request.user, self.topic):
            raise PermissionDenied
        if self.request.user.is_authenticated(
        ) or not defaults.PYBB_ANONYMOUS_VIEWS_CACHE_BUFFER:
            Topic.objects.filter(id=self.topic.id).update(views=F('views') + 1)
        else:
            cache_key = util.build_cache_key('anonymous_topic_views',
                                             topic_id=self.topic.id)
            cache.add(cache_key, 0)
            if cache.incr(cache_key
                          ) % defaults.PYBB_ANONYMOUS_VIEWS_CACHE_BUFFER == 0:
                Topic.objects.filter(id=self.topic.id).update(
                    views=F('views') +
                    defaults.PYBB_ANONYMOUS_VIEWS_CACHE_BUFFER)
                cache.set(cache_key, 0)
        qs = self.topic.posts.all().select_related('user')
        if defaults.PYBB_PROFILE_RELATED_NAME:
            qs = qs.select_related('user__%s' %
                                   defaults.PYBB_PROFILE_RELATED_NAME)
        if not perms.may_moderate_topic(self.request.user, self.topic):
            qs = perms.filter_posts(self.request.user, qs)
        return qs

    def get_context_data(self, **kwargs):
        ctx = super(TopicView, self).get_context_data(**kwargs)

        if self.request.user.is_authenticated():
            self.request.user.is_moderator = perms.may_moderate_topic(
                self.request.user, self.topic)
            self.request.user.is_subscribed = self.request.user in self.topic.subscribers.all(
            )
            if defaults.PYBB_ENABLE_ADMIN_POST_FORM and \
                    perms.may_post_as_admin(self.request.user):
                ctx['form'] = self.get_admin_post_form_class()(
                    initial={
                        'login': getattr(self.request.user, username_field)
                    },
                    topic=self.topic)
            else:
                ctx['form'] = self.get_post_form_class()(topic=self.topic)
        elif defaults.PYBB_ENABLE_ANONYMOUS_POST:
            ctx['form'] = self.get_post_form_class()(topic=self.topic)
        else:
            ctx['form'] = None
            ctx['next'] = self.get_login_redirect_url()
        if perms.may_attach_files(self.request.user):
            aformset = self.get_attachment_formset_class()()
            ctx['aformset'] = aformset
            ctx['attachment_max_size'] = defaults.PYBB_ATTACHMENT_SIZE_LIMIT
        if defaults.PYBB_FREEZE_FIRST_POST:
            ctx['first_post'] = self.topic.head
        else:
            ctx['first_post'] = None
        ctx['topic'] = self.topic

        if perms.may_vote_in_topic(self.request.user, self.topic) and \
                pybb_topic_poll_not_voted(self.topic, self.request.user):
            ctx['poll_form'] = self.get_poll_form_class()(self.topic)

        return ctx

    @method_decorator(get_atomic_func())
    def mark_read(self):
        if not self.request.user.is_authenticated():
            return
        try:
            forum_mark = ForumReadTracker.objects.get(forum=self.topic.forum,
                                                      user=self.request.user)
        except ForumReadTracker.DoesNotExist:
            forum_mark = None
        if (forum_mark is None) or (forum_mark.time_stamp <
                                    self.topic.updated):
            topic_mark, new = TopicReadTracker.objects.get_or_create_tracker(
                topic=self.topic, user=self.request.user)
            if not new and topic_mark.time_stamp > self.topic.updated:
                # Bail early if we already read this thread.
                return

            # Check, if there are any unread topics in forum
            readed_trackers = TopicReadTracker.objects.filter(
                user=self.request.user,
                topic__forum=self.topic.forum,
                time_stamp__gte=F('topic__updated'))
            unread = self.topic.forum.topics.exclude(
                topicreadtracker__in=readed_trackers)
            if forum_mark is not None:
                unread = unread.filter(updated__gte=forum_mark.time_stamp)

            if not unread.exists():
                # Clear all topic marks for this forum, mark forum as read
                TopicReadTracker.objects.filter(
                    user=self.request.user,
                    topic__forum=self.topic.forum).delete()
                forum_mark, new = ForumReadTracker.objects.get_or_create_tracker(
                    forum=self.topic.forum, user=self.request.user)
                forum_mark.save()

    def get_topic(self, **kwargs):
        if 'pk' in kwargs:
            topic = get_object_or_404(Topic, pk=kwargs['pk'], post_count__gt=0)
        elif ('slug' and 'forum_slug' and 'category_slug') in kwargs:
            topic = get_object_or_404(
                Topic,
                slug=kwargs['slug'],
                forum__slug=kwargs['forum_slug'],
                forum__category__slug=kwargs['category_slug'],
                post_count__gt=0)
        else:
            raise Http404(_('This topic does not exists'))
        return topic
Esempio n. 5
0
class MovePostForm(forms.Form):
    def __init__(self, instance, user, *args, **kwargs):
        super(MovePostForm, self).__init__(*args, **kwargs)
        self.instance = instance
        self.user = user
        self.post = self.instance
        self.category, self.forum, self.topic = self.post.get_parents()

        if not self.post.is_topic_head:
            # we do not move an entire topic but a part of it's posts. Let's select those posts.
            self.posts_to_move = Post.objects.filter(
                created__gte=self.post.created,
                topic=self.topic).order_by('created', 'pk')
            # if multiple posts exists with the same created datetime, it's important to keep the
            # same order and do not move some posts which could be "before" our post.
            # We can not just filter by adding `pk__gt=self.post.pk` because we could exclude
            # some posts if for some reasons, a lesser pk has a greater "created" datetime
            # Most of the time, we just do one extra request to be sure the first post is
            # the wanted one
            first_pk = self.posts_to_move.values_list('pk', flat=True)[0]
            while first_pk != self.post.pk:
                self.posts_to_move = self.posts_to_move.exclude(pk=first_pk)
                first_pk = self.posts_to_move.values_list('pk', flat=True)[0]

            i = 0
            choices = []
            for post in self.posts_to_move[1:]:  # all except the current one
                i += 1
                bvars = {
                    'author':
                    util.get_pybb_profile(post.user).get_display_name(),
                    'abstract': Truncator(post.body_text).words(8),
                    'i': i
                }
                label = _('%(i)d (%(author)s: "%(abstract)s")') % bvars
                choices.append((i, label))
            choices.insert(0, (0, _('None')))
            choices.insert(0, (-1, _('All')))
            self.fields['number'] = forms.TypedChoiceField(
                label=ugettext_lazy('Number of following posts to move with'),
                choices=choices,
                required=True,
                coerce=int,
            )
            # we move the entire topic, so we want to change it's forum.
            # So, let's exclude the current one

        # get all forum where we can move this post (and the others)
        move_to_forums = permissions.perms.filter_forums(
            self.user, Forum.objects.all())
        if self.post.is_topic_head:
            # we move the entire topic, so we want to change it's forum.
            # So, let's exclude the current one
            move_to_forums = move_to_forums.exclude(pk=self.forum.pk)
        last_cat_pk = None
        choices = []
        for forum in move_to_forums.order_by('category__position', 'position',
                                             'name'):
            if not permissions.perms.may_create_topic(self.user, forum):
                continue
            if last_cat_pk != forum.category.pk:
                last_cat_pk = forum.category.pk
                choices.append(('%s' % forum.category, []))
            if self.forum.pk == forum.pk:
                name = '%(forum)s (forum of the current post)' % {
                    'forum': self.forum
                }
            else:
                name = '%s' % forum
            choices[-1][1].append((forum.pk, name))

        self.fields['move_to'] = forms.ChoiceField(
            label=ugettext_lazy('Move to forum'),
            initial=self.forum.pk,
            choices=choices,
            required=True,
        )
        self.fields['name'] = forms.CharField(label=_('New subject'),
                                              initial=self.topic.name,
                                              max_length=255,
                                              required=True)
        if permissions.perms.may_edit_topic_slug(self.user):
            self.fields['slug'] = forms.CharField(label=_('New topic slug'),
                                                  initial=self.topic.slug,
                                                  max_length=255,
                                                  required=False)

    def get_new_topic(self):
        if hasattr(self, '_new_topic'):
            return self._new_topic
        if self.post.is_topic_head:
            topic = self.topic
        else:
            topic = Topic(user=self.post.user)

        if topic.name != self.cleaned_data['name']:
            topic.name = self.cleaned_data['name']
            # force slug auto-rebuild if slug is not speficied and topic is renamed
            topic.slug = self.cleaned_data.get('slug', None)
        elif self.cleaned_data.get('slug', None):
            topic.slug = self.cleaned_data['slug']

        topic.forum = Forum.objects.get(pk=self.cleaned_data['move_to'])
        topic.slug = create_or_check_slug(topic, Topic, forum=topic.forum)
        topic.save()
        return topic

    @method_decorator(compat.get_atomic_func())
    def save(self):
        data = self.cleaned_data
        topic = self.get_new_topic()

        if not self.post.is_topic_head:
            # we move some posts
            posts = self.posts_to_move
            if data['number'] != -1:
                number = data[
                    'number'] + 1  # we want to move at least the current post ;-)
                posts = posts[0:number]
            # update posts
            # we can not update with subqueries on same table with mysql 5.5
            # it raises: You can't specify target table 'pybb_post' for update in FROM clause
            # so we need to get all pks... It's bad for perfs, but posts are not often splited...
            posts_pks = [p.pk for p in posts]
            Post.objects.filter(pk__in=posts_pks).update(topic_id=topic.pk)

        topic.update_counters()
        topic.forum.update_counters()

        if topic.pk != self.topic.pk:
            # we just created a new topic. let's update the counters
            self.topic.update_counters()
        if self.forum.pk != topic.forum.pk:
            self.forum.update_counters()
        return Post.objects.get(pk=self.post.pk)
Esempio n. 6
0
class PostEditMixin(PybbFormsMixin):
    @method_decorator(get_atomic_func())
    def post(self, request, *args, **kwargs):
        return super(PostEditMixin, self).post(request, *args, **kwargs)

    def get_form_class(self):
        if perms.may_post_as_admin(self.request.user):
            return self.get_admin_post_form_class()
        else:
            return self.get_post_form_class()

    def get_context_data(self, **kwargs):

        ctx = super(PostEditMixin, self).get_context_data(**kwargs)

        if perms.may_attach_files(
                self.request.user) and (not 'aformset' in kwargs):
            ctx['aformset'] = self.get_attachment_formset_class()(
                instance=self.object if getattr(self, 'object') else None)

        if perms.may_create_poll(self.request.user) and ('pollformset'
                                                         not in kwargs):
            ctx['pollformset'] = self.get_poll_answer_formset_class(
            )(instance=self.object.topic if getattr(self, 'object') else None)

        return ctx

    def form_valid(self, form):
        success = True
        save_attachments = False
        save_poll_answers = False
        self.object = form.save(commit=False)

        if perms.may_attach_files(self.request.user):
            aformset = self.get_attachment_formset_class()(
                self.request.POST, self.request.FILES, instance=self.object)
            if aformset.is_valid():
                save_attachments = True
            else:
                success = False
        else:
            aformset = None

        if perms.may_create_poll(self.request.user):
            pollformset = self.get_poll_answer_formset_class()()
            if getattr(self, 'forum',
                       None) or self.object.topic.head == self.object:
                if self.object.topic.poll_type != Topic.POLL_TYPE_NONE:
                    pollformset = self.get_poll_answer_formset_class()(
                        self.request.POST, instance=self.object.topic)
                    if pollformset.is_valid():
                        save_poll_answers = True
                    else:
                        success = False
                else:
                    self.object.topic.poll_question = None
                    self.object.topic.poll_answers.all().delete()
        else:
            pollformset = None

        if success:
            self.object.topic.save()
            self.object.topic = self.object.topic
            self.object.save()
            if save_attachments:
                aformset.save()
            if save_poll_answers:
                pollformset.save()
            return HttpResponseRedirect(self.get_success_url())
        else:
            return self.render_to_response(
                self.get_context_data(form=form,
                                      aformset=aformset,
                                      pollformset=pollformset))