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())
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))
def topic_feed(request, id, slug=None): """A feed for the answers to a question.""" 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, action="feed")) # deleted posts cannot be seen by people without privilegs if topic.is_deleted and not (request.user and request.user.is_moderator): raise Forbidden() feed = AtomFeed( u"%s — %s" % (topic.title, settings.WEBSITE_TITLE), subtitle=settings.WEBSITE_TAGLINE, feed_url=request.url, url=request.url_root, ) feed.add( topic.title, topic.question.rendered_text, content_type="html", author=topic.question.author.display_name, url=url_for(topic, _external=True), id=topic.guid, updated=topic.question.updated, published=topic.question.created, ) for reply in topic.replies: if reply.is_deleted and not (request.user and request.user.is_moderator): continue title = _(u"Answer by %s") % reply.author.display_name if reply.is_deleted: title += u" " + _("(deleted)") feed.add( title, reply.rendered_text, content_type="html", author=reply.author.display_name, url=url_for(reply, _external=True), id=reply.guid, updated=reply.updated, created=reply.created, ) return feed.get_response()
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))
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'))
def topic_feed(request, id, slug=None): """A feed for the answers to a question.""" 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, action='feed')) # deleted posts cannot be seen by people without privilegs if topic.is_deleted and not (request.user and request.user.is_moderator): raise Forbidden() feed = AtomFeed(u'%s — %s' % (topic.title, settings.WEBSITE_TITLE), subtitle=settings.WEBSITE_TAGLINE, feed_url=request.url, url=request.url_root) feed.add(topic.title, topic.question.rendered_text, content_type='html', author=topic.question.author.display_name, url=url_for(topic, _external=True), id=topic.guid, updated=topic.question.updated, published=topic.question.created) for reply in topic.replies: if reply.is_deleted and not (request.user and request.user.is_moderator): continue title = _(u'Answer by %s') % reply.author.display_name if reply.is_deleted: title += u' ' + _('(deleted)') feed.add(title, reply.rendered_text, content_type='html', author=reply.author.display_name, url=url_for(reply, _external=True), id=reply.guid, updated=reply.updated, created=reply.created) return feed.get_response()
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)
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)
def _topic_feed(request, title, query, order_by): # non moderators cannot see deleted posts, so we filter them out first # for moderators we mark the posts up as deleted so that # they can be kept apart from non-deleted ones. if not request.user or not request.user.is_moderator: query = query.filter_by(is_deleted=False) query = query.order_by(_topic_order[order_by]) query = query.options(eagerload('author'), eagerload('question')) query = query.limit(max(0, min(50, request.args.get('num', 10, type=int)))) feed = AtomFeed(u'%s — %s' % (title, settings.WEBSITE_TITLE), subtitle=settings.WEBSITE_TAGLINE, feed_url=request.url, url=request.url_root) for topic in query.all(): title = topic.title if topic.is_deleted: title += u' ' + _(u'(deleted)') feed.add(title, topic.question.rendered_text, content_type='html', author=topic.author.display_name, url=url_for(topic, _external=True), id=topic.guid, updated=topic.last_change, published=topic.date) return feed.get_response()
def sections(request): """Shows a page where all sections are listed for the user to select one. """ if len(settings.LANGUAGE_SECTIONS) == 1: return redirect(url_for("kb.overview", lang_code=settings.LANGUAGE_SECTIONS[0])) return render_template("kb/sections.html", languages=list_sections())
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'))
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)
def sections(request): """Shows a page where all sections are listed for the user to select one. """ if len(settings.LANGUAGE_SECTIONS) == 1: return redirect( url_for('kb.overview', lang_code=settings.LANGUAGE_SECTIONS[0])) return render_template('kb/sections.html', languages=list_sections())
def logout(request): """Logs the user out.""" if request.is_logged_in: rv = get_auth_system().logout(request) if rv is not None: return rv request.flash(_(u'You were logged out.')) return redirect(request.next_url or url_for('kb.overview'))
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())
def complete_login(self, request): consumer = Consumer(request.session, SolaceOpenIDStore()) openid_response = consumer.complete( request.args.to_dict(), url_for('core.login', _external=True)) if openid_response.status == SUCCESS: return self.create_or_login(request, openid_response.identity_url) elif openid_response.status == CANCEL: raise LoginUnsucessful(_(u'The request was cancelled')) else: raise LoginUnsucessful(_(u'OpenID authentication error'))
def edit_users(request): """Edit a user.""" pagination = Pagination(request, User.query, request.args.get('page', type=int)) form = EditUserRedirectForm() if request.method == 'POST' and form.validate(): return redirect(url_for('admin.edit_user', user=form.user.username)) return render_template('admin/edit_users.html', pagination=pagination, users=pagination.get_objects(), form=form.as_widget())
def complete_login(self, request): consumer = Consumer(request.session, SolaceOpenIDStore()) openid_response = consumer.complete(request.args.to_dict(), url_for('core.login', _external=True)) if openid_response.status == SUCCESS: return self.create_or_login(request, openid_response.identity_url) elif openid_response.status == CANCEL: raise LoginUnsucessful(_(u'The request was cancelled')) else: raise LoginUnsucessful(_(u'OpenID authentication error'))
def perform_login(self, request, openid_identifier): try: consumer = Consumer(request.session, SolaceOpenIDStore()) auth_request = consumer.begin(openid_identifier) except discover.DiscoveryFailure: raise LoginUnsucessful(_(u'The OpenID was invalid')) trust_root = request.host_url redirect_to = url_for('core.login', openid_complete='yes', next=request.next_url, _external=True) return redirect(auth_request.redirectURL(trust_root, redirect_to))
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)
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())
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())
def create_or_login(self, request, identity_url): user = User.query.by_openid_login(identity_url).first() # we don't have a user for this openid yet. What we want to do # now is to remember the openid in the session until we have the # user. We're using the session because it is signed. if user is None: request.session['openid'] = identity_url return redirect(url_for('core.login', firstlogin='******', next=request.next_url)) self.set_user_checked(request, user) return self.redirect_back(request)
def create_or_login(self, request, identity_url): user = User.query.by_openid_login(identity_url).first() # we don't have a user for this openid yet. What we want to do # now is to remember the openid in the session until we have the # user. We're using the session because it is signed. if user is None: request.session['openid'] = identity_url return redirect( url_for('core.login', firstlogin='******', next=request.next_url)) self.set_user_checked(request, user) return self.redirect_back(request)
def reset_password(self, request, user): if settings.REGISTRATION_REQUIRES_ACTIVATION: user.is_active = False confirmation_url = url_for('core.activate_user', email=user.email, key=user.activation_key, _external=True) send_email(_(u'Registration Confirmation'), render_template('mails/activate_user.txt', user=user, confirmation_url=confirmation_url), user.email) request.flash(_(u'A mail was sent to %s with a link to finish the ' u'registration.') % user.email) else: request.flash(_(u'You\'re registered. You can login now.'))
def unban_user(request, user): """Unbans a given user.""" user = User.query.filter_by(username=user).first() if user is None: raise NotFound() next = request.next_url or url_for('admin.bans') if not user.is_banned: request.flash(_(u'The user is not banned.')) return redirect(next) admin_utils.unban_user(user) request.flash(_(u'The user “%s” was successfully unbanned and notified.') % user.username) return redirect(next)
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)
def set_language(request, locale): """Sets the new locale.""" try: locale = Locale.parse(locale) if not has_section(locale): raise UnknownLocaleError(str(locale)) except UnknownLocaleError: raise NotFound() next_url = request.get_localized_next_url(locale) request.locale = locale request.flash(_('The interface language was set to %s. You were also ' 'forwarded to the help section of that language.') % locale.display_name) return redirect(next_url or url_for('kb.overview', lang_code=locale))
def set_language(request, locale): """Sets the new locale.""" try: locale = Locale.parse(locale) if not has_section(locale): raise UnknownLocaleError(str(locale)) except UnknownLocaleError: raise NotFound() next_url = request.get_localized_next_url(locale) request.locale = locale request.flash( _('The interface language was set to %s. You were also ' 'forwarded to the help section of that language.') % locale.display_name) return redirect(next_url or url_for('kb.overview', lang_code=locale))
def ban_user(request, user): """Bans a given user.""" user = User.query.filter_by(username=user).first() if user is None: raise NotFound() next = request.next_url or url_for('admin.bans') if user.is_banned: request.flash(_(u'The user is already banned.')) return redirect(next) if user == request.user: request.flash(_(u'You cannot ban yourself.'), error=True) return redirect(next) admin_utils.ban_user(user) request.flash(_(u'The user “%s” was successfully banned and notified.') % user.username) return redirect(next)
def reset_password(self, request, user): if settings.REGISTRATION_REQUIRES_ACTIVATION: user.is_active = False confirmation_url = url_for('core.activate_user', email=user.email, key=user.activation_key, _external=True) send_email( _(u'Registration Confirmation'), render_template('mails/activate_user.txt', user=user, confirmation_url=confirmation_url), user.email) request.flash( _(u'A mail was sent to %s with a link to finish the ' u'registration.') % user.email) else: request.flash(_(u'You\'re registered. You can login now.'))
def unban_user(user): """Unbans the user. What this actually does is sending the user an email with a link to reactivate his account. For reactivation he has to give himself a new password. """ if not user.is_banned: return if settings.REQUIRE_NEW_PASSWORD_ON_UNBAN: user.is_active = False user.is_banned = False reset_url = url_for('core.reset_password', email=user.email, key=user.password_reset_key, _external=True) send_email(_(u'Your ban was lifted'), render_template('mails/user_unbanned.txt', user=user, reset_url=reset_url), user.email) session.commit()
def userlist(request, locale=None): """Displays list of users. Optionally a locale identifier can be passed in that replaces the default "all users" query. This is used by the userlist form the knowledge base that forwards the call here. """ query = User.query if locale is not None: # if we just have one language, we ignore that there is such a thing # as being active in a section of the webpage and redirect to the # general user list. if len(settings.LANGUAGE_SECTIONS) == 1: return redirect(url_for('users.userlist')) locale = Locale.parse(locale) query = query.active_in(locale) query = query.order_by(User.reputation.desc()) pagination = Pagination(request, query, request.args.get('page', type=int)) return render_template('users/userlist.html', pagination=pagination, users=pagination.get_objects(), locale=locale, sections=list_sections())
def _get_comment_form(post): return CommentForm(post, action=url_for('kb.submit_comment', post=post.id))
def get_link(self, filename, ext=None): return url_for('themes.get_resource', theme=self.id, file=filename)
def overview(request): """Currently just a redirect.""" return redirect(url_for('admin.status'))
def login(request): """Shows the login page.""" next_url = request.next_url or url_for('kb.overview') if request.is_logged_in: return redirect(next_url) return get_auth_system().login(request)
def redirect_back(self, request): return redirect( request.get_redirect_target( [url_for('core.login'), url_for('core.register')]) or url_for('kb.overview'))
def default_link_func(fn, ext): from solace.application import url_for return url_for('static', file=fn)
def default_redirect(request): return redirect(url_for('api.help'))
def register(request): """Register a new user.""" if request.is_logged_in: return redirect(request.next_url or url_for('kb.overview')) return get_auth_system().register(request)
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))
def language_redirect(request): """Redirects to the index page of the requested language. Thanks to the magic in the `url_for` function there is very few code here. """ return redirect(url_for('kb.overview'))
def redirect_back(self, request): return redirect(request.get_redirect_target([ url_for('core.login'), url_for('core.register') ]) or url_for('kb.overview'))