Example #1
0
    def test_topic_replying_and_answering(self):
        """Replies to topics and answering"""
        user = self.make_test_user()
        topic = models.Topic('en', 'This is a test topic', 'Foobar', user)
        session.commit()
        topic_id = topic.id
        self.assertEqual(topic.last_change, topic.question.created)
        self.assertEqual(topic.is_answered, False)
        self.assertEqual(len(topic.posts), 1)
        models.Post(topic, user, 'This is more text')
        topic.accept_answer(models.Post(topic, user, 'And this is another answer'))
        self.assertEqual(topic.answer.is_answer, True)
        self.assertEqual(topic.answer.is_question, False)
        session.commit()

        def internal_test():
            self.assertEqual(len(topic.posts), 3)
            self.assertEqual(topic.answer_date, topic.answer.created)
            self.assertEqual(topic.answer_author, topic.answer.author)
            self.assertEqual(topic.last_change, topic.answer.created)
            self.assertEqual(topic.is_answered, True)

        # do the test now
        internal_test()
        topic = None
        session.remove()

        # and a second time with the queried data from the database
        topic = models.Topic.query.get(topic_id)
        internal_test()

        self.assertEqual(topic.reply_count, 2)
Example #2
0
def restore_post(request, id):
    post, revision = _load_post_and_revision(request, id)

    # sanity checks
    if revision is None:
        if not request.user.is_moderator:
            raise Forbidden()
        elif not post.is_deleted:
            return redirect(url_for(post))
    elif not request.user.can_edit(post):
        raise Forbidden()

    form = EmptyForm()
    if request.method == 'POST' and form.validate():
        if 'yes' in request.form:
            if revision is None:
                request.flash(_(u'The post was restored'))
                post.restore()
            else:
                request.flash(_(u'The revision was restored'))
                revision.restore()
            session.commit()
        return form.redirect(post)

    return render_template('kb/restore_post.html',
                           form=form.as_widget(),
                           post=post,
                           revision=revision)
Example #3
0
def topic(request, id, slug=None):
    """Shows a topic."""
    topic = Topic.query.eagerposts().get(id)

    # if the topic id does not exist or the topic is from a different
    # language, we abort with 404 early
    if topic is None or topic.locale != request.view_lang:
        raise NotFound()

    # make sure the slug is okay, otherwise redirect to the real one
    # to ensure URLs are unique.
    if slug is None or topic.slug != slug:
        return redirect(url_for(topic))

    # deleted posts cannot be seen by people without privilegs
    if topic.is_deleted and not (request.user and request.user.is_moderator):
        raise Forbidden()

    # a form for the replies.
    form = ReplyForm(topic)

    if request.method == 'POST' and form.validate():
        reply = form.create_reply()
        session.commit()
        request.flash(_(u'Your reply was posted.'))
        return redirect(url_for(reply))

    # pull in the votes in a single query for all the posts related to the
    # topic so that we only have to fire the database once.
    if request.is_logged_in:
        request.user.pull_votes(topic.posts)

    return render_template('kb/topic.html',
                           topic=topic,
                           reply_form=form.as_widget())
Example #4
0
    def test_pages(self):
        """Make sure that all pages are valid HTML5"""
        settings.LANGUAGE_SECTIONS = ['en']
        user = models.User('user1', '*****@*****.**', 'default')
        user.active = True
        topic = models.Topic('en', 'This is a test topic', 'Foobar', user)
        post1 = models.Post(topic, user, 'meh1')
        post2 = models.Post(topic, user, 'meh2')
        topic.accept_answer(post1)
        session.commit()

        visited_links = set()
        def visit(url):
            url = urljoin(BASE_URL, url).split('#', 1)[0]
            if not url.startswith(BASE_URL) or url in visited_links:
                return
            visited_links.add(url)
            path = url.split('/', 3)[-1]
            response = self.client.get(path, follow_redirects=True)
            content_type = response.headers['Content-Type']
            if content_type.split(';')[0].strip() == 'text/html':
                self.doExternalValidation(url, response.data, content_type)
            for link in html_xpath(response.html, '//html:a[@href]'):
                visit(link.attrib['href'])

        self.login('user1', 'default')
        visit('/')
Example #5
0
    def test_reset_password(self):
        """Reset password."""
        settings.RECAPTCHA_ENABLE = False
        user = models.User('A_USER', '*****@*****.**', 'default')
        session.commit()

        self.submit_form('/_reset_password', {
            'username':         '******',
            'email':            ''
        })

        mails = self.get_mails()
        self.assert_(mails)

        for link in _link_re.findall(mails[0].get_payload()):
            if 'reset_password' in link:
                response = self.client.get('/' + link.split('/', 3)[-1])
                break
        else:
            self.assert_(False, 'Did not find password reset link')

        match = re.compile(r'password was reset to <code>(.*?)</code>') \
            .search(response.data)
        self.assert_(match)
        self.login('A_USER', match.group(1))
        response = self.client.get('/en/')
        self.assert_('A_USER' in response.data)
Example #6
0
File: kb.py Project: Plurk/Solace
def submit_comment(request, post):
    """Used by the form on `get_comments` to submit the form data to
    the database.  Returns partial data for the remote side.
    """
    if not request.is_xhr:
        raise BadRequest()
    post = Post.query.get(post)
    if post is None:
        raise NotFound()

    # not even moderators can submit comments for deleted posts.
    if post.is_deleted:
        message = _(u'You cannot submit comments for deleted posts')
        return json_response(success=False, form_errors=[message])

    form = _get_comment_form(post)
    if form.validate():
        comment = form.create_comment()
        session.commit()
        comment_box = get_macro('kb/_boxes.html', 'render_comment')
        comment_link = get_macro('kb/_boxes.html', 'render_comment_link')
        return json_response(html=comment_box(comment),
                             link=comment_link(post),
                             success=True)
    return json_response(success=False, form_errors=form.as_widget().all_errors)
Example #7
0
def submit_comment(request, post):
    """Used by the form on `get_comments` to submit the form data to
    the database.  Returns partial data for the remote side.
    """
    if not request.is_xhr:
        raise BadRequest()
    post = Post.query.get(post)
    if post is None:
        raise NotFound()

    # not even moderators can submit comments for deleted posts.
    if post.is_deleted:
        message = _(u'You cannot submit comments for deleted posts')
        return json_response(success=False, form_errors=[message])

    form = _get_comment_form(post)
    if form.validate():
        comment = form.create_comment()
        session.commit()
        comment_box = get_macro('kb/_boxes.html', 'render_comment')
        comment_link = get_macro('kb/_boxes.html', 'render_comment_link')
        return json_response(html=comment_box(comment),
                             link=comment_link(post),
                             success=True)
    return json_response(success=False,
                         form_errors=form.as_widget().all_errors)
Example #8
0
    def test_basic_reputation_changes(self):
        """Basic reputation changes"""
        user1 = self.make_test_user('user1')
        user2 = self.make_test_user('user2')
        user3 = self.make_test_user('user3')
        user4 = self.make_test_user('user4')
        topic = models.Topic('en', 'This is a test topic', 'Foobar', user1)
        session.commit()
        user2.upvote(topic)
        user3.upvote(topic)
        session.commit()
        self.assertEqual(user1.reputation, 2)

        user4.downvote(topic)
        session.commit()
        self.assertEqual(user1.reputation, 0)
        self.assertEqual(user4.reputation, -1)

        topic.accept_answer(models.Post(topic, user4, 'blafasel'))
        session.commit()
        self.assertEqual(user4.reputation, 49)

        topic.accept_answer(models.Post(topic, user1, 'another answer'))
        user1.upvote(topic.answer)
        session.commit()
        self.assertEqual(user4.reputation, -1)
        self.assertEqual(user1.reputation, 60)
Example #9
0
File: kb.py Project: Plurk/Solace
def restore_post(request, id):
    post, revision = _load_post_and_revision(request, id)

    # sanity checks
    if revision is None:
        if not request.user.is_moderator:
            raise Forbidden()
        elif not post.is_deleted:
            return redirect(url_for(post))
    elif not request.user.can_edit(post):
        raise Forbidden()

    form = EmptyForm()
    if request.method == 'POST' and form.validate():
        if 'yes' in request.form:
            if revision is None:
                request.flash(_(u'The post was restored'))
                post.restore()
            else:
                request.flash(_(u'The revision was restored'))
                revision.restore()
            session.commit()
        return form.redirect(post)

    return render_template('kb/restore_post.html', form=form.as_widget(),
                           post=post, revision=revision)
Example #10
0
File: kb.py Project: Plurk/Solace
def edit_post(request, id):
    post, revision = _load_post_and_revision(request, id)
    if not request.user.can_edit(post):
        raise Forbidden()

    if post.is_question:
        form = QuestionForm(post.topic, revision=revision)
    else:
        form = ReplyForm(post=post, revision=revision)

    if request.method == 'POST' and form.validate():
        form.save_changes()
        session.commit()
        request.flash(_('The post was edited.'))
        return redirect(url_for(post))

    def _format_entry(author, date, extra=u''):
        return _(u'%s (%s)') % (author, format_datetime(date)) + extra
    post_revisions = [(revision is None, '', _format_entry(
            (post.editor or post.author).display_name, post.updated,
            u' [%s]' % _(u'Current revision')))] + \
        [(revision == entry, entry.id, _format_entry(
            entry.editor.display_name, entry.date))
         for entry in post.revisions.order_by(PostRevision.date.desc())]

    return render_template('kb/edit_post.html', form=form.as_widget(),
                           post=post, all_revisions=post_revisions)
Example #11
0
File: kb.py Project: Plurk/Solace
def topic(request, id, slug=None):
    """Shows a topic."""
    topic = Topic.query.eagerposts().get(id)

    # if the topic id does not exist or the topic is from a different
    # language, we abort with 404 early
    if topic is None or topic.locale != request.view_lang:
        raise NotFound()

    # make sure the slug is okay, otherwise redirect to the real one
    # to ensure URLs are unique.
    if slug is None or topic.slug != slug:
        return redirect(url_for(topic))

    # deleted posts cannot be seen by people without privilegs
    if topic.is_deleted and not (request.user and request.user.is_moderator):
        raise Forbidden()

    # a form for the replies.
    form = ReplyForm(topic)

    if request.method == 'POST' and form.validate():
        reply = form.create_reply()
        session.commit()
        request.flash(_(u'Your reply was posted.'))
        return redirect(url_for(reply))

    # pull in the votes in a single query for all the posts related to the
    # topic so that we only have to fire the database once.
    if request.is_logged_in:
        request.user.pull_votes(topic.posts)

    return render_template('kb/topic.html', topic=topic,
                           reply_form=form.as_widget())
Example #12
0
    def test_reset_password(self):
        """Reset password."""
        settings.RECAPTCHA_ENABLE = False
        user = models.User('A_USER', '*****@*****.**', 'default')
        session.commit()

        self.submit_form('/_reset_password', {
            'username': '******',
            'email': ''
        })

        mails = self.get_mails()
        self.assert_(mails)

        for link in _link_re.findall(mails[0].get_payload()):
            if 'reset_password' in link:
                response = self.client.get('/' + link.split('/', 3)[-1])
                break
        else:
            self.assert_(False, 'Did not find password reset link')

        match = re.compile(r'password was reset to <code>(.*?)</code>') \
            .search(response.data)
        self.assert_(match)
        self.login('A_USER', match.group(1))
        response = self.client.get('/en/')
        self.assert_('A_USER' in response.data)
Example #13
0
def edit_post(request, id):
    post, revision = _load_post_and_revision(request, id)
    if not request.user.can_edit(post):
        raise Forbidden()

    if post.is_question:
        form = QuestionForm(post.topic, revision=revision)
    else:
        form = ReplyForm(post=post, revision=revision)

    if request.method == 'POST' and form.validate():
        form.save_changes()
        session.commit()
        request.flash(_('The post was edited.'))
        return redirect(url_for(post))

    def _format_entry(author, date, extra=u''):
        return _(u'%s (%s)') % (author, format_datetime(date)) + extra
    post_revisions = [(revision is None, '', _format_entry(
            (post.editor or post.author).display_name, post.updated,
            u' [%s]' % _(u'Current revision')))] + \
        [(revision == entry, entry.id, _format_entry(
            entry.editor.display_name, entry.date))
         for entry in post.revisions.order_by(PostRevision.date.desc())]

    return render_template('kb/edit_post.html',
                           form=form.as_widget(),
                           post=post,
                           all_revisions=post_revisions)
Example #14
0
def vote(request, post):
    """Votes on a post."""
    # TODO: this is currently also fired as GET if JavaScript is
    # not available.  Not very nice.
    post = Post.query.get(post)
    if post is None:
        raise NotFound()

    # you cannot cast votes on deleted shit
    if post.is_deleted:
        message = _(u"You cannot vote on deleted posts.")
        if request.is_xhr:
            return json_response(message=message, error=True)
        request.flash(message, error=True)
        return redirect(url_for(post))

    # otherwise
    val = request.args.get("val", 0, type=int)
    if val == 0:
        request.user.unvote(post)
    elif val == 1:
        # users cannot upvote on their own stuff
        if post.author == request.user:
            message = _(u"You cannot upvote your own post.")
            if request.is_xhr:
                return json_response(message=message, error=True)
            request.flash(message, error=True)
            return redirect(url_for(post))
        # also some reputation is needed
        if not request.user.is_admin and request.user.reputation < settings.REPUTATION_MAP["UPVOTE"]:
            message = _(u"In order to upvote you " u"need at least %d reputation") % settings.REPUTATION_MAP["UPVOTE"]
            if request.is_xhr:
                return json_response(message=message, error=True)
            request.flash(message, error=True)
            return redirect(url_for(post))
        request.user.upvote(post)
    elif val == -1:
        # users need some reputation to downvote.  Keep in mind that
        # you *can* downvote yourself.
        if not request.user.is_admin and request.user.reputation < settings.REPUTATION_MAP["DOWNVOTE"]:
            message = (
                _(u"In order to downvote you " u"need at least %d reputation") % settings.REPUTATION_MAP["DOWNVOTE"]
            )
            if request.is_xhr:
                return json_response(message=message, error=True)
            request.flash(message, error=True)
            return redirect(url_for(post))
        request.user.downvote(post)
    else:
        raise BadRequest()
    session.commit()

    # standard requests are answered with a redirect back
    if not request.is_xhr:
        return redirect(url_for(post))

    # others get a re-rendered vote box
    box = get_macro("kb/_boxes.html", "render_vote_box")
    return json_response(html=box(post, request.user))
Example #15
0
 def test_logout(self):
     """Logging a user out"""
     models.User('THE_USER', '*****@*****.**', 'default')
     session.commit()
     self.login('THE_USER', 'default')
     self.logout()
     response = self.client.get('/en/')
     self.assert_('THE_USER' not in response.data)
Example #16
0
 def test_logout(self):
     """Logging a user out"""
     models.User('THE_USER', '*****@*****.**', 'default')
     session.commit()
     self.login('THE_USER', 'default')
     self.logout()
     response = self.client.get('/en/')
     self.assert_('THE_USER' not in response.data)
Example #17
0
 def run(self):
     from solace.database import session
     users = self.create_users()
     tags = self.create_tags()
     topics = self.create_topics(tags, users)
     posts = self.answer_and_vote(topics, users)
     self.create_comments(posts, users)
     self.rebase_dates(topics)
     session.commit()
Example #18
0
 def run(self):
     from solace.database import session
     users = self.create_users()
     tags = self.create_tags()
     topics = self.create_topics(tags, users)
     posts = self.answer_and_vote(topics, users)
     self.create_comments(posts, users)
     self.rebase_dates(topics)
     session.commit()
Example #19
0
File: kb.py Project: Plurk/Solace
def new(request):
    """The new-question form."""
    form = QuestionForm()

    if request.method == 'POST' and form.validate():
        topic = form.create_topic()
        session.commit()
        request.flash(_(u'Your question was posted.'))
        return redirect(url_for(topic))

    return render_template('kb/new.html', form=form.as_widget())
Example #20
0
def new(request):
    """The new-question form."""
    form = QuestionForm()

    if request.method == 'POST' and form.validate():
        topic = form.create_topic()
        session.commit()
        request.flash(_(u'Your question was posted.'))
        return redirect(url_for(topic))

    return render_template('kb/new.html', form=form.as_widget())
Example #21
0
File: kb.py Project: Plurk/Solace
def accept(request, post):
    """Accept a post as an answer."""
    # TODO: this is currently also fired as GET if JavaScript is
    # not available.  Not very nice.
    post = Post.query.get(post)
    if post is None:
        raise NotFound()

    # just for sanity.  It makes no sense to accept the question
    # as answer.  The UI does not allow that, so the user must have
    # tampered with the data here.
    if post.is_question:
        raise BadRequest()

    # likewise you cannot accept a deleted post as answer
    if post.is_deleted:
        message = _(u'You cannot accept deleted posts as answers')
        if request.is_xhr:
            return json_response(message=message, error=True)
        request.flash(message, error=True)
        return redirect(url_for(post))

    topic = post.topic

    # if the post is already the accepted answer, we unaccept the
    # post as answer.
    if post.is_answer:
        if not request.user.can_unaccept_as_answer(post):
            message = _(u'You cannot unaccept this reply as an answer.')
            if request.is_xhr:
                return json_response(message=message, error=True)
            request.flash(message, error=True)
            return redirect(url_for(post))
        topic.accept_answer(None, request.user)
        session.commit()
        if request.is_xhr:
            return json_response(accepted=False)
        return redirect(url_for(post))

    # otherwise we try to accept the post as answer.
    if not request.user.can_accept_as_answer(post):
        message = _(u'You cannot accept this reply as answer.')
        if request.is_xhr:
            return json_response(message=message, error=True)
        request.flash(message, error=True)
        return redirect(url_for(post))
    topic.accept_answer(post, request.user)
    session.commit()
    if request.is_xhr:
        return json_response(accepted=True)
    return redirect(url_for(post))
Example #22
0
def accept(request, post):
    """Accept a post as an answer."""
    # TODO: this is currently also fired as GET if JavaScript is
    # not available.  Not very nice.
    post = Post.query.get(post)
    if post is None:
        raise NotFound()

    # just for sanity.  It makes no sense to accept the question
    # as answer.  The UI does not allow that, so the user must have
    # tampered with the data here.
    if post.is_question:
        raise BadRequest()

    # likewise you cannot accept a deleted post as answer
    if post.is_deleted:
        message = _(u'You cannot accept deleted posts as answers')
        if request.is_xhr:
            return json_response(message=message, error=True)
        request.flash(message, error=True)
        return redirect(url_for(post))

    topic = post.topic

    # if the post is already the accepted answer, we unaccept the
    # post as answer.
    if post.is_answer:
        if not request.user.can_unaccept_as_answer(post):
            message = _(u'You cannot unaccept this reply as an answer.')
            if request.is_xhr:
                return json_response(message=message, error=True)
            request.flash(message, error=True)
            return redirect(url_for(post))
        topic.accept_answer(None, request.user)
        session.commit()
        if request.is_xhr:
            return json_response(accepted=False)
        return redirect(url_for(post))

    # otherwise we try to accept the post as answer.
    if not request.user.can_accept_as_answer(post):
        message = _(u'You cannot accept this reply as answer.')
        if request.is_xhr:
            return json_response(message=message, error=True)
        request.flash(message, error=True)
        return redirect(url_for(post))
    topic.accept_answer(post, request.user)
    session.commit()
    if request.is_xhr:
        return json_response(accepted=True)
    return redirect(url_for(post))
Example #23
0
    def test_new_topic(self):
        """Creating new topics and replying"""
        # create the user
        models.User('user1', '*****@*****.**', 'default')
        session.commit()

        # login and submit
        self.login('user1', 'default')
        response = self.submit_form(
            '/en/new', {
                'title': 'Hello World',
                'text': 'This is just a small test\n\n**test**',
                'tags': 'foo, bar, baz'
            })

        # we will need the topic URL later for commit submission,
        # capture it!
        topic_url = '/' + response.headers['Location'].split('/', 3)[-1]
        response = self.client.get(topic_url)
        q = lambda x: html_xpath(response.html, x)

        # we have a headline
        self.assertEqual(q('//html:h1')[0].text, 'Hello World')

        # and all the tags
        tags = sorted(x.text for x in q('//html:p[@class="tags"]/html:a'))
        self.assertEqual(tags, ['bar', 'baz', 'foo'])

        # and the text is present and parsed
        pars = q('//html:div[@class="text"]/html:p')
        self.assertEqual(len(pars), 2)
        self.assertEqual(pars[0].text, 'This is just a small test')
        self.assertEqual(pars[1][0].tag,
                         '{http://www.w3.org/1999/xhtml}strong')
        self.assertEqual(pars[1][0].text, 'test')

        # now try to submit a reply
        response = self.submit_form(
            topic_url, {'text': 'This is a reply\n\nwith //text//'},
            follow_redirects=True)
        q = lambda x: html_xpath(response.html, x)

        # do we have the text?
        pars = q(
            '//html:div[@class="replies"]//html:div[@class="text"]/html:p')
        self.assertEqual(len(pars), 2)
        self.assertEqual(pars[0].text, 'This is a reply')
        self.assertEqual(pars[1].text, 'with ')
        self.assertEqual(pars[1][0].tag, '{http://www.w3.org/1999/xhtml}em')
        self.assertEqual(pars[1][0].text, 'text')
Example #24
0
def activate_user(request, email, key):
    """Activates the user."""
    # the email is not unique on the database, we try all matching users.
    # Most likely it's only one, otherwise we activate the first matching.
    user = User.query.filter_by(email=email, activation_key=key).first()
    if user is not None:
        user.is_active = True
        session.commit()
        request.flash(_(u'Your account was activated.  You can '
                        u'log in now.'))
        return redirect(url_for('core.login'))
    request.flash(_(u'User activation failed.  The user is either already '
                    u'activated or you followed a wrong link.'), error=True)
    return redirect(url_for('kb.overview'))
Example #25
0
 def test_post_revisions(self):
     """Internal revisions for posts"""
     creator = self.make_test_user('creator')
     editor = self.make_test_user('editor')
     topic = models.Topic('en', 'Topic with revisions', 'Original text.', creator)
     session.commit()
     self.assertEqual(topic.question.revisions.count(), 0)
     topic.question.edit('New text with default params.')
     session.commit()
     self.assertEqual(topic.question.text, 'New text with default params.')
     rev = topic.question.revisions.first()
     self.assertEqual(rev.editor, creator)
     self.assertEqual(rev.date, topic.date)
     self.assertEqual(rev.text, 'Original text.')
     d = datetime.datetime.utcnow()
     topic.question.edit('From the editor', editor, d)
     session.commit()
     self.assertEqual(topic.question.author, creator)
     self.assertEqual(topic.question.editor, editor)
     self.assertEqual(topic.question.updated, d)
     self.assertEqual(topic.last_change, d)
     rev.restore()
     session.commit()
     self.assertEqual(topic.question.editor, rev.editor)
     self.assertEqual(topic.question.updated, rev.date)
     self.assertEqual(topic.question.text, rev.text)
     self.assertEqual(topic.question.edits, 3)
Example #26
0
 def create_test_data(self, topics=20):
     # don't put ourselves into the query.  The user that logs in must not be
     # part of the generated content, otherwise we could end up with less
     # queries which would result in random failures
     me = models.User('me', '*****@*****.**', 'default')
     users = []
     for x in xrange(5):
         username = '******' % x
         users.append(models.User(username, username + '@example.com'))
     for x in xrange(topics):
         t = models.Topic('en', 'Topic %d' % x, 'test contents', choice(users))
         for x in xrange(4):
             models.Post(t, choice(users), 'test contents')
     session.commit()
Example #27
0
    def test_voting(self):
        """Voting from the web interface"""
        # create a bunch of users and let one of them create a topic
        users = [
            models.User('user_%d' % x, '*****@*****.**' % x, 'default')
            for x in xrange(5)
        ]
        for user in users:
            user.reputation = 50
        topic = models.Topic('en', 'Hello World', 'foo', users[0])
        session.commit()
        tquid = topic.question.id

        def get_vote_count(response):
            el = html_xpath(response.html,
                            '//html:div[@class="votebox"]/html:h4')
            return int(el[0].text)

        vote_url = '/_vote/%s?val=%%d&_xt=%s' % (tquid,
                                                 self.get_exchange_token())

        # the author should not be able to upvote
        self.login('user_0', 'default')
        response = self.client.get(vote_url % 1, follow_redirects=True)
        self.assert_('cannot upvote your own post' in response.data)

        # by default the user should not be able to downvote, because
        # he does not have enough reputation
        response = self.client.get(vote_url % -1, follow_redirects=True)
        self.assert_(
            'to downvote you need at least 100 reputation' in response.data)

        # so give him and all other users reputation
        for user in models.User.query.all():
            user.reputation = 10000
        session.commit()

        # and let him downvote
        response = self.client.get(vote_url % -1, follow_redirects=True)
        self.assertEqual(get_vote_count(response), -1)

        # and now let *all* users vote up, including the author, but his
        # request will fail.
        for num in xrange(5):
            self.logout()
            self.login('user_%d' % num, 'default')
            response = self.client.get(vote_url % 1, follow_redirects=True)

        # we should be at 4, author -1 the other four +1
        self.assertEqual(get_vote_count(response), 3)
Example #28
0
def reset_password(request, email=None, key=None):
    """Resets the password if possible."""
    auth = get_auth_system()
    if not auth.can_reset_password:
        raise NotFound()

    form = ResetPasswordForm()
    new_password = None

    # if the user is logged in, he goes straight back to the overview
    # page.  Why would a user that is logged in (and does not anywhere
    # see a link to that page) reset the password?  Of course that does
    # not give us anything security wise because he just has to logout.
    if request.is_logged_in:
        return redirect(url_for('kb.overview'))

    # we came back from the link in the mail, try to reset the password
    if email is not None:
        for user in User.query.filter_by(email=email).all():
            if user.password_reset_key == key:
                break
        else:
            request.flash(_(u'The password-reset key expired or the link '
                            u'was invalid.'),
                          error=True)
            return redirect(url_for('core.reset_password'))
        new_password = user.set_random_password()
        session.commit()

    # otherwise validate the form
    elif request.method == 'POST' and form.validate(request.form):
        user = form.user
        reset_url = url_for('core.reset_password',
                            email=user.email,
                            key=user.password_reset_key,
                            _external=True)
        send_email(
            _(u'Reset Password'),
            render_template('mails/reset_password.txt',
                            user=user,
                            reset_url=reset_url), user.email)
        request.flash(
            _(u'A mail with a link to reset the password '
              u'was sent to ā€œ%sā€') % user.email)
        return redirect(url_for('kb.overview'))

    return render_template('core/reset_password.html',
                           form=form.as_widget(),
                           new_password=new_password)
Example #29
0
def activate_user(request, email, key):
    """Activates the user."""
    # the email is not unique on the database, we try all matching users.
    # Most likely it's only one, otherwise we activate the first matching.
    user = User.query.filter_by(email=email, activation_key=key).first()
    if user is not None:
        user.is_active = True
        session.commit()
        request.flash(
            _(u'Your account was activated.  You can '
              u'log in now.'))
        return redirect(url_for('core.login'))
    request.flash(_(u'User activation failed.  The user is either already '
                    u'activated or you followed a wrong link.'),
                  error=True)
    return redirect(url_for('kb.overview'))
Example #30
0
 def test_topic_tagging(self):
     """Topic tagging"""
     user = self.make_test_user()
     en_topic = models.Topic('en', 'This is a test topic', 'text', user)
     en_topic.bind_tags(['foo', 'bar', 'baz'])
     de_topic = models.Topic('de', 'This is a test topic', 'text', user)
     de_topic.bind_tags(['foo'])
     session.commit()
     foo = models.Tag.query.filter_by(locale=Locale('de'), name='foo').first()
     self.assertEqual(foo.name, 'foo')
     self.assertEqual(foo.tagged, 1)
     self.assertEqual(foo.topics.first(), de_topic)
     models.Topic('de', 'Another topic', 'text', user) \
         .bind_tags(['foo', 'bar'])
     session.commit()
     self.assertEqual(foo.tagged, 2)
Example #31
0
    def test_new_topic(self):
        """Creating new topics and replying"""
        # create the user
        models.User('user1', '*****@*****.**', 'default')
        session.commit()

        # login and submit
        self.login('user1', 'default')
        response = self.submit_form('/en/new', {
            'title':    'Hello World',
            'text':     'This is just a small test\n\n**test**',
            'tags':     'foo, bar, baz'
        })

        # we will need the topic URL later for commit submission,
        # capture it!
        topic_url = '/' + response.headers['Location'].split('/', 3)[-1]
        response = self.client.get(topic_url)
        q = lambda x: html_xpath(response.html, x)

        # we have a headline
        self.assertEqual(q('//html:h1')[0].text, 'Hello World')

        # and all the tags
        tags = sorted(x.text for x in q('//html:p[@class="tags"]/html:a'))
        self.assertEqual(tags, ['bar', 'baz', 'foo'])

        # and the text is present and parsed
        pars = q('//html:div[@class="text"]/html:p')
        self.assertEqual(len(pars), 2)
        self.assertEqual(pars[0].text, 'This is just a small test')
        self.assertEqual(pars[1][0].tag, '{http://www.w3.org/1999/xhtml}strong')
        self.assertEqual(pars[1][0].text, 'test')

        # now try to submit a reply
        response = self.submit_form(topic_url, {
            'text':     'This is a reply\n\nwith //text//'
        }, follow_redirects=True)
        q = lambda x: html_xpath(response.html, x)

        # do we have the text?
        pars = q('//html:div[@class="replies"]//html:div[@class="text"]/html:p')
        self.assertEqual(len(pars), 2)
        self.assertEqual(pars[0].text, 'This is a reply')
        self.assertEqual(pars[1].text, 'with ')
        self.assertEqual(pars[1][0].tag, '{http://www.w3.org/1999/xhtml}em')
        self.assertEqual(pars[1][0].text, 'text')
Example #32
0
    def test_voting(self):
        """Voting from the web interface"""
        # create a bunch of users and let one of them create a topic
        users = [models.User('user_%d' % x, '*****@*****.**' % x,
                             'default') for x in xrange(5)]
        for user in users:
            user.reputation = 50
        topic = models.Topic('en', 'Hello World', 'foo', users[0])
        session.commit()
        tquid = topic.question.id

        def get_vote_count(response):
            el = html_xpath(response.html, '//html:div[@class="votebox"]/html:h4')
            return int(el[0].text)

        vote_url = '/_vote/%s?val=%%d&_xt=%s' % (tquid, self.get_exchange_token())

        # the author should not be able to upvote
        self.login('user_0', 'default')
        response = self.client.get(vote_url % 1, follow_redirects=True)
        self.assert_('cannot upvote your own post' in response.data)

        # by default the user should not be able to downvote, because
        # he does not have enough reputation
        response = self.client.get(vote_url % -1, follow_redirects=True)
        self.assert_('to downvote you need at least 100 reputation'
                     in response.data)

        # so give him and all other users reputation
        for user in models.User.query.all():
            user.reputation = 10000
        session.commit()

        # and let him downvote
        response = self.client.get(vote_url % -1, follow_redirects=True)
        self.assertEqual(get_vote_count(response), -1)

        # and now let *all* users vote up, including the author, but his
        # request will fail.
        for num in xrange(5):
            self.logout()
            self.login('user_%d' % num, 'default')
            response = self.client.get(vote_url % 1, follow_redirects=True)

        # we should be at 4, author -1 the other four +1
        self.assertEqual(get_vote_count(response), 3)
Example #33
0
    def test_new_topic(self):
        """Creating new topics and replying"""
        # create the user
        models.User("user1", "*****@*****.**", "default")
        session.commit()

        # login and submit
        self.login("user1", "default")
        response = self.submit_form(
            "/en/new",
            {"title": "Hello World", "text": "This is just a small test\n\n**test**", "tags": "foo, bar, baz"},
        )

        # we will need the topic URL later for commit submission,
        # capture it!
        topic_url = "/" + response.headers["Location"].split("/", 3)[-1]
        response = self.client.get(topic_url)

        # was: q = response.html.xpath
        q = html.fromstring(response.data).xpath

        # we have a headline
        self.assertEqual(q("//h1")[0].text, "Hello World")

        # and all the tags
        tags = sorted(x.text for x in q('//p[@class="tags"]/a'))
        self.assertEqual(tags, ["bar", "baz", "foo"])

        # and the text is present and parsed
        pars = q('//div[@class="text"]/p')
        self.assertEqual(len(pars), 2)
        self.assertEqual(pars[0].text, "This is just a small test")
        self.assertEqual(pars[1][0].tag, "strong")
        self.assertEqual(pars[1][0].text, "test")

        # now try to submit a reply
        response = self.submit_form(topic_url, {"text": "This is a reply\n\nwith //text//"}, follow_redirects=True)
        q = html.fromstring(response.data).xpath

        # do we have the text?
        pars = q('//div[@class="replies"]//div[@class="text"]/p')
        self.assertEqual(len(pars), 2)
        self.assertEqual(pars[0].text, "This is a reply")
        self.assertEqual(pars[1].text, "with ")
        self.assertEqual(pars[1][0].tag, "em")
        self.assertEqual(pars[1][0].text, "text")
Example #34
0
def delete_post(request, id):
    post = Post.query.get(id)

    # sanity checks
    if not request.user.is_moderator:
        raise Forbidden()
    elif post.is_deleted:
        return redirect(url_for(post))

    form = EmptyForm()
    if request.method == "POST" and form.validate():
        if "yes" in request.form:
            post.delete()
            session.commit()
            request.flash(_("The post was deleted"))
        return redirect(url_for(post))

    return render_template("kb/delete_post.html", post=post, form=form.as_widget())
Example #35
0
def reset_password(request, email=None, key=None):
    """Resets the password if possible."""
    auth = get_auth_system()
    if not auth.can_reset_password:
        raise NotFound()

    form = ResetPasswordForm()
    new_password = None

    # if the user is logged in, he goes straight back to the overview
    # page.  Why would a user that is logged in (and does not anywhere
    # see a link to that page) reset the password?  Of course that does
    # not give us anything security wise because he just has to logout.
    if request.is_logged_in:
        return redirect(url_for('kb.overview'))

    # we came back from the link in the mail, try to reset the password
    if email is not None:
        for user in User.query.filter_by(email=email).all():
            if user.password_reset_key == key:
                break
        else:
            request.flash(_(u'The password-reset key expired or the link '
                            u'was invalid.'), error=True)
            return redirect(url_for('core.reset_password'))
        new_password = user.set_random_password()
        session.commit()

    # otherwise validate the form
    elif request.method == 'POST' and form.validate(request.form):
        user = form.user
        reset_url = url_for('core.reset_password', email=user.email,
                            key=user.password_reset_key, _external=True)
        send_email(_(u'Reset Password'),
                   render_template('mails/reset_password.txt', user=user,
                                   reset_url=reset_url), user.email)
        request.flash(_(u'A mail with a link to reset the password '
                        u'was sent to ā€œ%sā€') % user.email)
        return redirect(url_for('kb.overview'))

    return render_template('core/reset_password.html', form=form.as_widget(),
                           new_password=new_password)
Example #36
0
def delete_post(request, id):
    post = Post.query.get(id)

    # sanity checks
    if not request.user.is_moderator:
        raise Forbidden()
    elif post.is_deleted:
        return redirect(url_for(post))

    form = EmptyForm()
    if request.method == 'POST' and form.validate():
        if 'yes' in request.form:
            post.delete()
            session.commit()
            request.flash(_('The post was deleted'))
        return redirect(url_for(post))

    return render_template('kb/delete_post.html',
                           post=post,
                           form=form.as_widget())
Example #37
0
    def first_login(self, request):
        """Until the openid information is removed from the session, this view
        will be use to create the user account based on the openid url.
        """
        identity_url = request.session.get('openid')
        if identity_url is None:
            return redirect(url_for('core.login'))
        if request.is_logged_in:
            del request.session['openid']
            return redirect(request.next_url or url_for('kb.overview'))

        form = OpenIDRegistrationForm()
        if request.method == 'POST' and form.validate():
            user = User(form['username'], form['email'])
            user.openid_logins.add(identity_url)
            self.after_register(request, user)
            session.commit()
            del request.session['openid']
            self.set_user_checked(request, user)
            return self.redirect_back(request)

        return render_template('core/register_openid.html', form=form.as_widget(),
                               identity_url=identity_url)
Example #38
0
    def test_only_valid_links(self):
        """Make sure that all links are valid"""
        settings.LANGUAGE_SECTIONS = ['en']
        user = models.User('user1', '*****@*****.**', 'default')
        user.is_admin = True
        banned_user = models.User('user2', '*****@*****.**', 'default')
        banned_user.is_banned = True
        topic = models.Topic('en', 'This is a test topic', 'Foobar', user)
        post1 = models.Post(topic, user, 'meh1')
        post2 = models.Post(topic, user, 'meh2')
        topic.accept_answer(post1)
        session.commit()

        visited_links = set()

        def visit(url):
            url = urljoin(BASE_URL, url).split('#', 1)[0]
            if not url.startswith(BASE_URL) or url in visited_links:
                return
            visited_links.add(url)
            path = '/' + url.split('/', 3)[-1]
            if path.startswith('/logout?'):
                return
            response = self.client.get(path, follow_redirects=True)
            self.assertEqual(response.status_code, 200)
            for link in html_xpath(response.html, '//html:a[@href]'):
                visit(link.attrib['href'])

        # logged out
        visit('/')
        self.assert_(len(visited_links) > MIN_VISITED)

        # logged in
        visited_links.clear()
        self.login('user1', 'default')
        visit('/')
        self.assert_(len(visited_links) > MIN_VISITED)
Example #39
0
    def first_login(self, request):
        """Until the openid information is removed from the session, this view
        will be use to create the user account based on the openid url.
        """
        identity_url = request.session.get('openid')
        if identity_url is None:
            return redirect(url_for('core.login'))
        if request.is_logged_in:
            del request.session['openid']
            return redirect(request.next_url or url_for('kb.overview'))

        form = OpenIDRegistrationForm()
        if request.method == 'POST' and form.validate():
            user = User(form['username'], form['email'])
            user.openid_logins.add(identity_url)
            self.after_register(request, user)
            session.commit()
            del request.session['openid']
            self.set_user_checked(request, user)
            return self.redirect_back(request)

        return render_template('core/register_openid.html',
                               form=form.as_widget(),
                               identity_url=identity_url)
Example #40
0
    def test_only_valid_links(self):
        """Make sure that all links are valid"""
        settings.LANGUAGE_SECTIONS = ['en']
        user = models.User('user1', '*****@*****.**', 'default')
        user.is_admin = True
        banned_user = models.User('user2', '*****@*****.**', 'default')
        banned_user.is_banned = True
        topic = models.Topic('en', 'This is a test topic', 'Foobar', user)
        post1 = models.Post(topic, user, 'meh1')
        post2 = models.Post(topic, user, 'meh2')
        topic.accept_answer(post1)
        session.commit()

        visited_links = set()
        def visit(url):
            url = urljoin(BASE_URL, url).split('#', 1)[0]
            if not url.startswith(BASE_URL) or url in visited_links:
                return
            visited_links.add(url)
            path = '/' + url.split('/', 3)[-1]
            if path.startswith('/logout?'):
                return
            response = self.client.get(path, follow_redirects=True)
            self.assertEqual(response.status_code, 200)
            for link in response.html.xpath('//a[@href]'):
                visit(link.attrib['href'])

        # logged out
        visit('/')
        self.assert_(len(visited_links) > MIN_VISITED)

        # logged in
        visited_links.clear()
        self.login('user1', 'default')
        visit('/')
        self.assert_(len(visited_links) > MIN_VISITED)
Example #41
0
 def test_post_commenting(self):
     """Post commenting"""
     user = self.make_test_user()
     topic = models.Topic('en', 'This is a test topic', 'text', user)
     session.commit()
     self.assertEqual(topic.question.comment_count, 0)
     a = models.Comment(topic.question, user, 'Blafasel')
     session.commit()
     self.assertEqual(topic.question.comment_count, 1)
     b = models.Comment(topic.question, user, 'woooza')
     session.commit()
     self.assertEqual(topic.question.comment_count, 2)
     self.assertEqual(topic.question.comments, [a, b])
Example #42
0
 def test_topic_voting(self):
     """Voting on topics"""
     user1 = self.make_test_user('user1')
     user2 = self.make_test_user('user2')
     topic = models.Topic('en', 'This is a test topic', 'Foobar', user1)
     session.commit()
     user1.upvote(topic)
     user2.upvote(topic)
     user2.upvote(topic)
     session.commit()
     self.assertEqual(topic.votes, 2)
     user2.downvote(topic.question)
     self.assertEqual(topic.votes, 0)
     user1.unvote(topic.question)
     self.assertEqual(topic.votes, -1)
     session.commit()
Example #43
0
 def make_test_user(self, username='******'):
     user = models.User(username, "*****@*****.**")
     session.commit()
     return user
Example #44
0
def vote(request, post):
    """Votes on a post."""
    # TODO: this is currently also fired as GET if JavaScript is
    # not available.  Not very nice.
    post = Post.query.get(post)
    if post is None:
        raise NotFound()

    # you cannot cast votes on deleted shit
    if post.is_deleted:
        message = _(u'You cannot vote on deleted posts.')
        if request.is_xhr:
            return json_response(message=message, error=True)
        request.flash(message, error=True)
        return redirect(url_for(post))

    # otherwise
    val = request.args.get('val', 0, type=int)
    if val == 0:
        request.user.unvote(post)
    elif val == 1:
        # users cannot upvote on their own stuff
        if post.author == request.user:
            message = _(u'You cannot upvote your own post.')
            if request.is_xhr:
                return json_response(message=message, error=True)
            request.flash(message, error=True)
            return redirect(url_for(post))
        # also some reputation is needed
        if not request.user.is_admin and \
           request.user.reputation < settings.REPUTATION_MAP['UPVOTE']:
            message = _(u'In order to upvote you '
                        u'need at least %d reputation') % \
                settings.REPUTATION_MAP['UPVOTE']
            if request.is_xhr:
                return json_response(message=message, error=True)
            request.flash(message, error=True)
            return redirect(url_for(post))
        request.user.upvote(post)
    elif val == -1:
        # users need some reputation to downvote.  Keep in mind that
        # you *can* downvote yourself.
        if not request.user.is_admin and \
           request.user.reputation < settings.REPUTATION_MAP['DOWNVOTE']:
            message = _(u'In order to downvote you '
                        u'need at least %d reputation') % \
                settings.REPUTATION_MAP['DOWNVOTE']
            if request.is_xhr:
                return json_response(message=message, error=True)
            request.flash(message, error=True)
            return redirect(url_for(post))
        request.user.downvote(post)
    else:
        raise BadRequest()
    session.commit()

    # standard requests are answered with a redirect back
    if not request.is_xhr:
        return redirect(url_for(post))

    # others get a re-rendered vote box
    box = get_macro('kb/_boxes.html', 'render_vote_box')
    return json_response(html=box(post, request.user))