Ejemplo n.º 1
0
 def search(self, req):
     """
     Search the database. Currently just a keyword based search.
     """
     if not req.args.get('q'):
         return RedirectResponse('')
     return RedirectResponse('q/%s/' % req.args['q'])
Ejemplo n.º 2
0
 def do_login(self, req):
     """
     Display login form and do the login procedure.
     """
     if req.user is not None:
         return RedirectResponse('@admin/')
     login_failed = False
     if req.method == 'POST':
         if req.form.get('cancel'):
             return RedirectResponse('')
         username = req.form.get('username')
         password = req.form.get('password')
         if self.userdb.check_password(username, password):
             req.login(username)
             return RedirectResponse('@admin/')
         login_failed = True
     return Response(
         render_template(req, 'admin/login.html',
                         {'login_failed': login_failed}))
Ejemplo n.º 3
0
    def get_keyword_matches(self,
                            req,
                            term=None,
                            avoid_fuzzy=False,
                            is_error_page=False):
        """
        Find keyword matches. If there is an exact match, just redirect:
        http://docs.python.org/os.path.exists would automatically
        redirect to http://docs.python.org/library/os.path/#os.path.exists.
        Else, show a page with close matches.

        Module references are processed first so that "os.path" is handled as
        a module and not as member of os.
        """
        if term is None:
            term = req.path.strip('/')

        matches = self.env.find_keyword(term, avoid_fuzzy)

        # if avoid_fuzzy is False matches can be None
        if matches is None:
            return

        if isinstance(matches, tuple):
            url = get_target_uri(matches[1])
            if matches[0] != 'module':
                url += '#' + matches[2]
            return RedirectResponse(url)
        else:
            # get some close matches
            close_matches = []
            good_matches = 0
            for ratio, type, filename, anchorname, desc in matches:
                link = get_target_uri(filename)
                if type != 'module':
                    link += '#' + anchorname
                good_match = ratio > 0.75
                good_matches += good_match
                close_matches.append({
                    'href': relative_uri(req.path, link),
                    'title': anchorname,
                    'good_match': good_match,
                    'type': self.pretty_type.get(type, type),
                    'description': desc,
                })
            return Response(render_template(
                req, 'keyword_not_found.html', {
                    'close_matches': close_matches,
                    'good_matches_count': good_matches,
                    'keyword': term
                }, self.globalcontext),
                            status=404)
Ejemplo n.º 4
0
    def get_settings_page(self, req):
        """
        Handle the settings page.
        """
        referer = req.environ.get('HTTP_REFERER') or ''
        if referer:
            base = get_base_uri(req.environ)
            if not referer.startswith(base):
                referer = ''
            else:
                referer = referer[len(base):]
                referer = referer.split('?')[0] or referer

        if req.method == 'POST':
            if req.form.get('cancel'):
                if req.form.get('referer'):
                    return RedirectResponse(req.form['referer'])
                return RedirectResponse('')
            new_style = req.form.get('design')
            if new_style and new_style in known_designs:
                req.session['design'] = new_style
            new_comments = req.form.get('comments')
            if new_comments and new_comments in comments_methods:
                req.session['comments'] = new_comments
            if req.form.get('goback') and req.form.get('referer'):
                return RedirectResponse(req.form['referer'])
            # else display the same page again
            referer = ''

        context = {
            'known_designs': sorted(known_designs.iteritems()),
            'comments_methods': comments_methods.items(),
            'curdesign': req.session.get('design') or 'default',
            'curcomments': req.session.get('comments') or 'inline',
            'referer': referer,
        }

        return Response(
            render_template(req, 'settings.html', self.globalcontext, context))
Ejemplo n.º 5
0
def handle_html_url(req, url):
    def inner():
        # global special pages
        if url.endswith('/contents.html'):
            return 'contents/'
        if url.endswith('/genindex.html'):
            return 'genindex/'
        if url.endswith('/about.html'):
            return 'about/'
        if url.endswith('/reporting-bugs.html'):
            return 'bugs/'
        if url == 'modindex.html' or url.endswith('/modindex.html'):
            return 'modindex/'
        if url == 'mac/using.html':
            return 'howto/pythonmac/'
        # library
        if url[:4] in ('lib/', 'mac/'):
            p = 'library/'
            m = _module_re.match(url[4:])
            if m:
                mn = m.group(1)
                return p + special_module_names.get(mn, mn)
            # module sub-pages
            m = _modsub_re.match(url[4:])
            if m and not _modobj_re.match(url[4:]):
                mn = m.group(1)
                return p + special_module_names.get(mn, mn)
        # XXX: handle all others
        # tutorial
        elif url[:4] == 'tut/':
            try:
                node = int(url[8:].split('.html')[0])
            except ValueError:
                pass
            else:
                if tutorial_nodes[node]:
                    return 'tutorial/' + tutorial_nodes[node]
        # installing: all in one (ATM)
        elif url[:5] == 'inst/':
            return 'install/'
        # no mapping for "documenting Python..."
        # nothing found
        raise NotFound()

    return RedirectResponse('%s?oldurl=1' % inner())
Ejemplo n.º 6
0
 def do_change_password(self, req):
     """
     Allows the user to change his password.
     """
     change_failed = change_successful = False
     if req.method == 'POST':
         if req.form.get('cancel'):
             return RedirectResponse('@admin/')
         pw = req.form.get('pw1')
         if pw and pw == req.form.get('pw2'):
             self.userdb.set_password(req.user, pw)
             self.userdb.save()
             change_successful = True
         else:
             change_failed = True
     return Response(
         render_template(
             req, 'admin/change_password.html', {
                 'change_failed': change_failed,
                 'change_successful': change_successful
             }))
Ejemplo n.º 7
0
    def dispatch(self, req, page):
        """
        Dispatch the requests for the current user in the admin panel.
        """
        is_logged_in = req.user is not None
        if is_logged_in:
            privileges = self.userdb.privileges[req.user]
            is_master_admin = 'master' in privileges
            can_change_password = '******' not in privileges
        else:
            privileges = set()
            can_change_password = is_master_admin = False

        # login and logout
        if page == 'login':
            return self.do_login(req)
        elif not is_logged_in:
            return RedirectResponse('@admin/login/')
        elif page == 'logout':
            return self.do_logout(req)

        # account maintance
        elif page == 'change_password' and can_change_password:
            return self.do_change_password(req)
        elif page == 'manage_users' and is_master_admin:
            return self.do_manage_users(req)

        # moderate comments
        elif page.split('/')[0] == 'moderate_comments':
            return self.do_moderate_comments(req, page[18:])

        # missing page
        elif page != '':
            raise NotFound()
        return Response(
            render_template(
                req, 'admin/index.html', {
                    'is_master_admin': is_master_admin,
                    'can_change_password': can_change_password
                }))
Ejemplo n.º 8
0
    def __call__(self, environ, start_response):
        """
        Dispatch requests.
        """
        set_connection(self.db_con)
        req = Request(environ)
        url = req.path.strip('/') or 'index'

        # check if the environment was updated
        new_mtime = path.getmtime(self.buildfile)
        if self.buildmtime != new_mtime:
            self.load_env(new_mtime)

        try:
            if req.path == '/favicon.ico':
                # TODO: change this to real favicon?
                resp = Response('404 Not Found', status=404)
            elif req.path == '/robots.txt':
                resp = Response(robots_txt, mimetype='text/plain')
            elif not req.path.endswith('/') and req.method == 'GET':
                # may be an old URL
                if url.endswith('.html'):
                    resp = handle_html_url(self, url)
                else:
                    # else, require a trailing slash on GET requests
                    # this ensures nice looking urls and working relative
                    # links for cached resources.
                    query = req.environ.get('QUERY_STRING', '')
                    resp = RedirectResponse(req.path + '/' +
                                            (query and '?' + query))
            # index page is special
            elif url == 'index':
                # presets for settings
                if req.args.get(
                        'design') and req.args['design'] in known_designs:
                    req.session['design'] = req.args['design']
                if req.args.get('comments'
                                ) and req.args['comments'] in comments_methods:
                    req.session['comments'] = req.args['comments']
                # alias for fuzzy search
                if 'q' in req.args:
                    resp = RedirectResponse('q/%s/' % req.args['q'])
                # stylesheet
                elif req.args.get('do') == 'stylesheet':
                    resp = self.get_user_stylesheet(req)
                else:
                    resp = self.get_special_page(req, 'index')
            # go to the search page
            # XXX: this is currently just a redirect to /q/ which is handled below
            elif url == 'search':
                resp = self.search(req)
            # settings page cannot be cached
            elif url == 'settings':
                resp = self.get_settings_page(req)
            # module index page is special
            elif url == 'modindex':
                resp = self.get_module_index(req)
            # genindex page is special too
            elif url == 'genindex':
                resp = self.get_special_page(req, 'genindex')
            # start the fuzzy search
            elif url[:2] == 'q/':
                resp = self.get_keyword_matches(req, url[2:])
            # special URLs -- don't forget to add them to robots.py
            elif url[0] == '@':
                # source view
                if url[:8] == '@source/':
                    resp = self.show_source(req, url[8:])
                # suggest changes view
                elif url[:6] == '@edit/':
                    resp = self.suggest_changes(req, url[6:])
                # suggest changes submit
                elif url[:8] == '@submit/':
                    resp = self.submit_changes(req, url[8:])
                # show that comment form
                elif url[:10] == '@comments/':
                    resp = self.show_comment_form(req, url[10:])
                # comments RSS feed
                elif url[:5] == '@rss/':
                    resp = self.comments_feed(req, url[5:])
                # dispatch requests to the admin panel
                elif url == '@admin' or url[:7] == '@admin/':
                    resp = self.admin_panel.dispatch(req, url[7:])
                else:
                    raise NotFound()
            # everything else is handled as page or fuzzy search
            # if a page does not exist.
            else:
                resp = self.get_page(req, url)
        # views can raise a NotFound exception to show an error page.
        # Either a real not found page or a similar matches page.
        except NotFound, e:
            if e.show_keyword_matches:
                resp = self.get_keyword_matches(req, is_error_page=True)
            else:
                resp = self.get_error_404(req)
Ejemplo n.º 9
0
    def show_comment_form(self, req, page):
        """
        Show the "new comment" form.
        """
        page_id = self.env.get_real_filename(page)[:-4]
        ajax_mode = req.args.get('mode') == 'ajax'
        target = req.args.get('target')
        page_comment_mode = not target

        form_error = preview = None
        title = req.form.get('title', '').strip()
        if 'author' in req.form:
            author = req.form['author']
        else:
            author = req.session.get('author', '')
        if 'author_mail' in req.form:
            author_mail = req.form['author_mail']
        else:
            author_mail = req.session.get('author_mail', '')
        comment_body = req.form.get('comment_body', '')
        fields = (title, author, author_mail, comment_body)

        if req.method == 'POST':
            if req.form.get('preview'):
                preview = Comment(page_id, target, title, author, author_mail,
                                  comment_body)
            # 'homepage' is a forbidden field to thwart bots
            elif req.form.get('homepage') or self.antispam.is_spam(fields):
                form_error = 'Your text contains blocked URLs or words.'
            else:
                if not all(fields):
                    form_error = 'You have to fill out all fields.'
                elif _mail_re.search(author_mail) is None:
                    form_error = 'You have to provide a valid e-mail address.'
                elif len(comment_body) < 20:
                    form_error = 'You comment is too short ' \
                                 '(must have at least 20 characters).'
                else:
                    # '|none' can stay since it doesn't include comments
                    self.cache.pop(page_id + '|inline', None)
                    self.cache.pop(page_id + '|bottom', None)
                    comment = Comment(page_id, target, title, author,
                                      author_mail, comment_body)
                    comment.save()
                    req.session['author'] = author
                    req.session['author_mail'] = author_mail
                    if ajax_mode:
                        return JSONResponse({
                            'posted': True,
                            'error': False,
                            'commentID': comment.comment_id
                        })
                    return RedirectResponse(comment.url)

        output = render_template(
            req, '_commentform.html', {
                'ajax_mode': ajax_mode,
                'preview': preview,
                'suggest_url': '@edit/%s/' % page,
                'comments_form': {
                    'target': target,
                    'title': title,
                    'author': author,
                    'author_mail': author_mail,
                    'comment_body': comment_body,
                    'error': form_error
                }
            })

        if ajax_mode:
            return JSONResponse({
                'body': output,
                'error': bool(form_error),
                'posted': False
            })
        return Response(
            render_template(req, 'commentform.html', {'form': output}))
Ejemplo n.º 10
0
    def submit_changes(self, req, page):
        """
        Submit the suggested changes as a patch.
        """
        if req.method != 'POST':
            # only available via POST
            raise NotFound()
        if req.form.get('cancel'):
            # handle cancel requests directly
            return RedirectResponse(page)
        # raises NotFound if page doesn't exist
        page_id, orig_contents = self.get_page_source(page)
        author = req.form.get('name')
        email = req.form.get('email')
        summary = req.form.get('summary')
        contents = req.form.get('contents')
        fields = (author, email, summary, contents)

        form_error = None
        rendered = None

        if not all(fields):
            form_error = 'You have to fill out all fields.'
        elif not _mail_re.search(email):
            form_error = 'You have to provide a valid e-mail address.'
        elif req.form.get('homepage') or self.antispam.is_spam(fields):
            form_error = 'Your text contains blocked URLs or words.'
        else:
            if req.form.get('preview'):
                rendered = self._generate_preview(page_id, contents)

            else:
                asctime = time.asctime()
                contents = contents.splitlines()
                orig_contents = orig_contents.splitlines()
                diffname = 'suggestion on %s by %s <%s>' % (asctime, author,
                                                            email)
                diff = difflib.unified_diff(orig_contents,
                                            contents,
                                            n=3,
                                            fromfile=page_id,
                                            tofile=diffname,
                                            lineterm='')
                diff_text = '\n'.join(diff)
                try:
                    mail = Email(
                        self.config['patch_mail_from'],
                        'Python Documentation Patches',
                        self.config['patch_mail_to'],
                        '',
                        'Patch for %s by %s' % (page_id, author),
                        PATCH_MESSAGE % locals(),
                        self.config['patch_mail_smtp'],
                    )
                    mail.attachments.add_string('patch.diff', diff_text,
                                                'text/x-diff')
                    mail.send()
                except:
                    import traceback
                    traceback.print_exc()
                    # XXX: how to report?
                    pass
                return Response(
                    render_template(
                        req, 'submitted.html', self.globalcontext,
                        dict(backlink=relative_uri('/@submit/' + page +
                                                   '/', page + '/'))))

        return Response(
            render_template(
                req, 'edit.html', self.globalcontext,
                dict(
                    contents=contents,
                    author=author,
                    email=email,
                    summary=summary,
                    pagename=page,
                    form_error=form_error,
                    rendered=rendered,
                    submiturl=relative_uri('/@edit/' + page + '/',
                                           '/@submit/' + page),
                )))
Ejemplo n.º 11
0
 def do_logout(self, req):
     """
     Log the user out.
     """
     req.logout()
     return RedirectResponse('@admin/login/')
Ejemplo n.º 12
0
    def do_moderate_comments(self, req, url):
        """
        Comment moderation panel.
        """
        if url == 'recent_comments':
            details_for = None
            recent_comments = Comment.get_recent(20)
        else:
            details_for = url and self.env.get_real_filename(url) or None
            recent_comments = None
        to_delete = set()
        edit_detail = None

        if 'edit' in req.args:
            try:
                edit_detail = Comment.get(int(req.args['edit']))
            except ValueError:
                pass

        if req.method == 'POST':
            for item in req.form.getlist('delete'):
                try:
                    to_delete.add(int(item))
                except ValueError:
                    pass
            if req.form.get('cancel'):
                return RedirectResponse('@admin/')
            elif req.form.get('confirmed'):
                for comment_id in to_delete:
                    try:
                        Comment.get(comment_id).delete()
                    except ValueError:
                        pass
                return RedirectResponse(req.path)
            elif req.form.get('aborted'):
                return RedirectResponse(req.path)
            elif req.form.get('edit') and not to_delete:
                if 'delete_this' in req.form:
                    try:
                        to_delete.add(req.form['delete_this'])
                    except ValueError:
                        pass
                else:
                    try:
                        edit_detail = c = Comment.get(int(req.args['edit']))
                    except ValueError:
                        pass
                    else:
                        if req.form.get('view'):
                            return RedirectResponse(c.url)
                        c.author = req.form.get('author', '')
                        c.author_mail = req.form.get('author_mail', '')
                        c.title = req.form.get('title', '')
                        c.comment_body = req.form.get('comment_body', '')
                        c.save()
                        self.app.cache.pop(edit_detail.associated_page, None)
                    return RedirectResponse(req.path)

        return Response(
            render_template(
                req,
                'admin/moderate_comments.html',
                {
                    'pages_with_comments': [
                        {
                            'page_id': page_id,
                            'title': page_id,  #XXX: get title somehow
                            'has_details': details_for == page_id,
                            'comments': comments
                        } for page_id, comments in Comment.get_overview(
                            details_for)
                    ],
                    'recent_comments':
                    recent_comments,
                    'to_delete':
                    to_delete,
                    'ask_confirmation':
                    req.method == 'POST' and to_delete,
                    'edit_detail':
                    edit_detail
                }))
Ejemplo n.º 13
0
    def do_manage_users(self, req):
        """
        Manage other user accounts. Requires master privileges.
        """
        add_user_mode = False
        user_privileges = {}
        users = sorted((user, []) for user in self.userdb.users)
        to_delete = set()
        generated_user = generated_password = None
        user_exists = False

        if req.method == 'POST':
            for item in req.form.getlist('delete'):
                try:
                    to_delete.add(item)
                except ValueError:
                    pass
            for name, item in req.form.iteritems():
                if name.startswith('privileges-'):
                    user_privileges[name[11:]] = [
                        x.strip() for x in item.split(',')
                    ]
            if req.form.get('cancel'):
                return RedirectResponse('@admin/')
            elif req.form.get('add_user'):
                username = req.form.get('username')
                if username:
                    if username in self.userdb.users:
                        user_exists = username
                    else:
                        generated_password = self.userdb.add_user(username)
                        self.userdb.save()
                        generated_user = username
                else:
                    add_user_mode = True
            elif req.form.get('aborted'):
                return RedirectResponse('@admin/manage_users/')

        users = {}
        for user in self.userdb.users:
            if user not in user_privileges:
                users[user] = sorted(self.userdb.privileges[user])
            else:
                users[user] = user_privileges[user]

        new_users = users.copy()
        for user in to_delete:
            new_users.pop(user, None)

        self_destruction = req.user not in new_users or \
                           'master' not in new_users[req.user]

        if req.method == 'POST' and (not to_delete or
           (to_delete and req.form.get('confirmed'))) and \
           req.form.get('update'):
            old_users = self.userdb.users.copy()
            for user in old_users:
                if user not in new_users:
                    del self.userdb.users[user]
                else:
                    self.userdb.privileges[user].clear()
                    self.userdb.privileges[user].update(new_users[user])
            self.userdb.save()
            return RedirectResponse('@admin/manage_users/')

        return Response(render_template(req, 'admin/manage_users.html', {
            'users':                users,
            'add_user_mode':        add_user_mode,
            'to_delete':            to_delete,
            'ask_confirmation':     req.method == 'POST' and to_delete \
                                    and not self_destruction,
            'generated_user':       generated_user,
            'generated_password':   generated_password,
            'self_destruction':     self_destruction,
            'user_exists':          user_exists
        }))