Ejemplo n.º 1
0
    def _getDebugInfo(self, request, start_response):
        app = get_app_for_server(self.appfactory)
        if not app.config.get('site/enable_debug_info', True):
            raise Forbidden("PieCrust debug info isn't enabled.")

        found = False
        page_path = request.args.get('page')
        try:
            req_page = get_requested_page(app, page_path)
            found = (req_page is not None)
        except (RouteNotFoundError, PageNotFoundError):
            pass
        if not found:
            raise NotFound("No such page: %s" % page_path)

        ctx = DataBuildingContext(req_page.page,
                                  sub_num=req_page.sub_num)
        data = build_page_data(ctx)

        var_path = request.args.getlist('var')
        if not var_path:
            var_path = None
        output = build_var_debug_info(data, var_path)

        response = Response(output, mimetype='text/html')
        return response(request.environ, start_response)
Ejemplo n.º 2
0
def edit_page(slug):
    site = g.site
    site_app = site.piecrust_app
    rp = get_requested_page(site_app, "/site/%s/%s" % (g.sites.current_site, slug))
    page = rp.qualified_page
    if page is None:
        abort(404)

    if request.method == "POST":
        page_text = request.form["page_text"]
        if request.form["is_dos_nl"] == "0":
            page_text = page_text.replace("\r\n", "\n")

        if "do_preview" in request.form or "do_save" in request.form or "do_save_and_commit" in request.form:
            logger.debug("Writing page: %s" % page.path)
            with open(page.path, "w", encoding="utf8") as fp:
                fp.write(page_text)
            flash("%s was saved." % os.path.relpath(page.path, site_app.root_dir))

        if "do_save_and_commit" in request.form:
            message = request.form.get("commit_msg")
            if not message:
                message = "Edit %s" % os.path.relpath(page.path, site_app.root_dir)
            site.scm.commit([page.path], message)

        if "do_preview" in request.form:
            return _preview_page(page)

        if "do_save" in request.form or "do_save_and_commit" in request.form:
            return _edit_page_form(page)

        abort(400)

    return _edit_page_form(page)
Ejemplo n.º 3
0
def upload_page_asset(url):
    if 'ft-asset-file' not in request.files:
        return redirect(url_for('.edit_page', url=url))

    asset_file = request.files['ft-asset-file']
    if asset_file.filename == '':
        return redirect(url_for('.edit_page', url=url))

    site = g.site
    site_app = site.piecrust_app
    rp = get_requested_page(site_app, site.make_url('/preview/%s' % url))
    page = rp.page
    if page is None:
        abort(404)

    filename = asset_file.filename
    if request.form['ft-asset-name']:
        _, ext = os.path.splitext(filename)
        filename = request.form['ft-asset-name'] + ext
    filename = secure_filename(filename)
    # TODO: this only works for FS sources.
    dirname, _ = os.path.splitext(page.content_spec)
    dirname += '-assets'
    if not os.path.exists(dirname):
        os.makedirs(dirname)

    asset_path = os.path.join(dirname, filename)
    logger.info("Uploading file to: %s" % asset_path)
    asset_file.save(asset_path)
    return redirect(url_for('.edit_page', url=url))
Ejemplo n.º 4
0
    def _getDebugInfo(self, request, start_response):
        app = get_app_for_server(self.root_dir, debug=self.debug,
                                 sub_cache_dir=self.sub_cache_dir)
        if not app.config.get('site/enable_debug_info'):
            return Forbidden()

        found = False
        page_path = request.args.get('page')
        try:
            req_page = get_requested_page(app, page_path)
            found = (req_page is not None)
        except (RouteNotFoundError, PageNotFoundError):
            pass
        if not found:
            return NotFound("No such page: %s" % page_path)

        ctx = DataBuildingContext(req_page.qualified_page,
                                  page_num=req_page.page_num)
        data = build_page_data(ctx)

        var_path = request.args.getlist('var')
        if not var_path:
            var_path = None
        output = build_var_debug_info(data, var_path)

        response = Response(output, mimetype='text/html')
        return response(request.environ, start_response)
Ejemplo n.º 5
0
def edit_page(url):
    site = g.site
    site_app = site.piecrust_app
    rp = get_requested_page(site_app, site.make_url('/preview/%s' % url))
    page = rp.page
    if page is None:
        abort(404)

    if request.method == 'POST':
        page_text = request.form['page_text']
        if request.form['is_dos_nl'] == '0':
            page_text = page_text.replace('\r\n', '\n')

        page_spec = page.content_spec

        if 'do_save' in request.form or 'do_save_and_commit' in request.form:
            logger.debug("Writing page: %s" % page_spec)
            with page.source.openItem(page.content_item, 'w',
                                      encoding='utf8', newline='') as fp:
                fp.write(page_text)
            flash("%s was saved." % page_spec)

        if 'do_save_and_commit' in request.form:
            message = request.form.get('commit_msg')
            if not message:
                message = "Edit %s" % page_spec
            if site.scm:
                commit_paths = [page_spec]
                assets_dir = os.path.splitext(page_spec)[0] + '-assets'
                if os.path.isdir(assets_dir):
                    commit_paths += list(os.listdir(assets_dir))
                site.scm.commit(commit_paths, message)

        if 'do_save' in request.form or 'do_save_and_commit' in request.form:
            return _edit_page_form(page, url)

        abort(400)

    return _edit_page_form(page, url)
Ejemplo n.º 6
0
def edit_page(slug):
    site = g.site
    site_app = site.piecrust_app
    rp = get_requested_page(site_app,
                            '/site/%s/%s' % (g.sites.current_site, slug))
    page = rp.qualified_page
    if page is None:
        abort(404)

    if request.method == 'POST':
        page_text = request.form['page_text']
        if request.form['is_dos_nl'] == '0':
            page_text = page_text.replace('\r\n', '\n')

        if 'do_preview' in request.form or 'do_save' in request.form or \
                'do_save_and_commit' in request.form:
            logger.debug("Writing page: %s" % page.path)
            with open(page.path, 'w', encoding='utf8', newline='') as fp:
                fp.write(page_text)
            flash("%s was saved." % os.path.relpath(
                    page.path, site_app.root_dir))

        if 'do_save_and_commit' in request.form:
            message = request.form.get('commit_msg')
            if not message:
                message = "Edit %s" % os.path.relpath(
                    page.path, site_app.root_dir)
            if site.scm:
                site.scm.commit([page.path], message)

        if 'do_preview' in request.form:
            return _preview_page(page)

        if 'do_save' in request.form or 'do_save_and_commit' in request.form:
            return _edit_page_form(page)

        abort(400)

    return _edit_page_form(page)
Ejemplo n.º 7
0
    def _try_serve_page(self, app, environ, request):
        # Find a matching page.
        req_page = get_requested_page(app, request.path)

        # If we haven't found any good match, report all the places we didn't
        # find it at.
        qp = req_page.qualified_page
        if qp is None:
            msg = "Can't find path for '%s':" % request.path
            raise MultipleNotFound(msg, req_page.not_found_errors)

        # We have a page, let's try to render it.
        render_ctx = PageRenderingContext(qp,
                                          page_num=req_page.page_num,
                                          force_render=True,
                                          is_from_request=True)
        if qp.route.is_generator_route:
            qp.route.generator.prepareRenderContext(render_ctx)

        # See if this page is known to use sources. If that's the case,
        # just don't use cached rendered segments for that page (but still
        # use them for pages that are included in it).
        uri = qp.getUri()
        entry = self._page_record.getEntry(uri, req_page.page_num)
        if (qp.route.is_generator_route or entry is None or
                entry.used_source_names):
            cache_key = '%s:%s' % (uri, req_page.page_num)
            app.env.rendered_segments_repository.invalidate(cache_key)

        # Render the page.
        rendered_page = render_page(render_ctx)

        # Remember stuff for next time.
        if entry is None:
            entry = ServeRecordPageEntry(req_page.req_path, req_page.page_num)
            self._page_record.addEntry(entry)
        for pinfo in render_ctx.render_passes:
            entry.used_source_names |= pinfo.used_source_names

        # Start doing stuff.
        page = rendered_page.page
        rp_content = rendered_page.content

        # Profiling.
        if app.config.get('site/show_debug_info'):
            now_time = time.perf_counter()
            timing_info = (
                    '%8.1f ms' %
                    ((now_time - app.env.start_time) * 1000.0))
            rp_content = rp_content.replace(
                    '__PIECRUST_TIMING_INFORMATION__', timing_info)

        # Build the response.
        response = Response()

        etag = hashlib.md5(rp_content.encode('utf8')).hexdigest()
        if not app.debug and etag in request.if_none_match:
            response.status_code = 304
            return response

        response.set_etag(etag)
        response.content_md5 = etag

        cache_control = response.cache_control
        if app.debug:
            cache_control.no_cache = True
            cache_control.must_revalidate = True
        else:
            cache_time = (page.config.get('cache_time') or
                          app.config.get('site/cache_time'))
            if cache_time:
                cache_control.public = True
                cache_control.max_age = cache_time

        content_type = page.config.get('content_type')
        if content_type and '/' not in content_type:
            mimetype = content_type_map.get(content_type, content_type)
        else:
            mimetype = content_type
        if mimetype:
            response.mimetype = mimetype

        if ('gzip' in request.accept_encodings and
                app.config.get('site/enable_gzip')):
            try:
                with io.BytesIO() as gzip_buffer:
                    with gzip.open(gzip_buffer, mode='wt',
                                   encoding='utf8') as gzip_file:
                        gzip_file.write(rp_content)
                    rp_content = gzip_buffer.getvalue()
                    response.content_encoding = 'gzip'
            except Exception:
                logger.error("Error compressing response, "
                             "falling back to uncompressed.")
        response.set_data(rp_content)

        return response
Ejemplo n.º 8
0
    def runTask(self, data, ctx):
        import json
        import requests
        from bs4 import BeautifulSoup
        from piecrust.app import PieCrustFactory
        from piecrust.serving.util import get_requested_page

        src_url = data['source']
        tgt_url = data['target']

        # Find if we have a page at the target URL.  To do that we need to spin
        # up a PieCrust app that knows how the website works. Because the
        # website might have been baked with custom settings (usually the site
        # root URL) there's a good chance we need to apply some variants, which
        # the user can specify in the config.
        pcappfac = PieCrustFactory(self.app.root_dir,
                                   cache_key='webmention')
        wmcfg = self.app.config.get('webmention')
        if wmcfg.get('config_variant'):
            pcappfac.config_variants = [wmcfg.get('config_variant')]
        if wmcfg.get('config_variants'):
            pcappfac.config_variants = list(wmcfg.get('config_variants'))
        if wmcfg.get('config_values'):
            pcappfac.config_values = list(wmcfg.get('config_values').items())
        pcapp = pcappfac.create()
        logger.debug("Locating page: %s" % tgt_url)
        try:
            req_page = get_requested_page(pcapp, tgt_url)
            if req_page.page is None:
                raise InvalidMentionTargetError()
        except Exception as ex:
            logger.error("Can't check webmention target page: %s" % tgt_url)
            logger.exception(ex)
            raise InvalidMentionTargetError()

        # Grab the source URL's contents and see if anything references the
        # target (ours) URL.
        logger.debug("Fetching mention source: %s" % src_url)
        src_t = requests.get(src_url)
        src_html = BeautifulSoup(src_t.text, 'html.parser')
        for link in src_html.find_all('a'):
            href = link.get('href')
            if href == tgt_url:
                break
        else:
            logger.error("Source '%s' doesn't link to target: %s" %
                         (src_url, tgt_url))
            raise SourceDoesntLinkToTargetError()

        # Load the previous mentions and find any pre-existing mention from the
        # source URL.
        mention_path, mention_data = _load_page_mentions(req_page.page)
        for m in mention_data['mentions']:
            if m['source'] == src_url:
                logger.error("Duplicate mention found from: %s" % src_url)
                raise DuplicateMentionError()

        # Make the new mention.
        new_mention = {'source': src_url}

        # Parse the microformats on the page, see if there's anything
        # interesting we can use.
        mf2_info = _get_mention_info_from_mf2(src_url, src_html)
        if mf2_info:
            new_mention.update(mf2_info)

        # Add the new mention.
        mention_data['mentions'].append(new_mention)

        with open(mention_path, 'w', encoding='utf-8') as fp:
            json.dump(mention_data, fp)
        logger.info("Received webmention from: %s" % src_url)
Ejemplo n.º 9
0
    def _try_serve_page(self, app, environ, request):
        # Find a matching page.
        req_page = get_requested_page(app, request.path)

        # If we haven't found any good match, report all the places we didn't
        # find it at.
        qp = req_page.qualified_page
        if qp is None:
            msg = "Can't find path for '%s':" % request.path
            raise MultipleNotFound(msg, req_page.not_found_errors)

        # We have a page, let's try to render it.
        render_ctx = PageRenderingContext(qp,
                                          page_num=req_page.page_num,
                                          force_render=True)
        if qp.route.taxonomy_name is not None:
            taxonomy = app.getTaxonomy(qp.route.taxonomy_name)
            tax_terms = qp.route.getTaxonomyTerms(qp.route_metadata)
            render_ctx.setTaxonomyFilter(tax_terms, needs_slugifier=True)

        # See if this page is known to use sources. If that's the case,
        # just don't use cached rendered segments for that page (but still
        # use them for pages that are included in it).
        uri = qp.getUri()
        entry = self._page_record.getEntry(uri, req_page.page_num)
        if (qp.route.taxonomy_name is not None or entry is None
                or entry.used_source_names):
            cache_key = '%s:%s' % (uri, req_page.page_num)
            app.env.rendered_segments_repository.invalidate(cache_key)

        # Render the page.
        rendered_page = render_page(render_ctx)

        # Check if this page is a taxonomy page that actually doesn't match
        # anything.
        if qp.route.taxonomy_name is not None:
            paginator = rendered_page.data.get('pagination')
            if (paginator and paginator.is_loaded
                    and len(paginator.items) == 0):
                taxonomy = app.getTaxonomy(qp.route.taxonomy_name)
                message = ("This URL matched a route for taxonomy '%s' but "
                           "no pages have been found to have it. This page "
                           "won't be generated by a bake." % taxonomy.name)
                raise NotFound(message)

        # Remember stuff for next time.
        if entry is None:
            entry = ServeRecordPageEntry(req_page.req_path, req_page.page_num)
            self._page_record.addEntry(entry)
        for p, pinfo in render_ctx.render_passes.items():
            entry.used_source_names |= pinfo.used_source_names

        # Start doing stuff.
        page = rendered_page.page
        rp_content = rendered_page.content

        # Profiling.
        if app.config.get('site/show_debug_info'):
            now_time = time.perf_counter()
            timing_info = ('%8.1f ms' %
                           ((now_time - app.env.start_time) * 1000.0))
            rp_content = rp_content.replace('__PIECRUST_TIMING_INFORMATION__',
                                            timing_info)

        # Build the response.
        response = Response()

        etag = hashlib.md5(rp_content.encode('utf8')).hexdigest()
        if not app.debug and etag in request.if_none_match:
            response.status_code = 304
            return response

        response.set_etag(etag)
        response.content_md5 = etag

        cache_control = response.cache_control
        if app.debug:
            cache_control.no_cache = True
            cache_control.must_revalidate = True
        else:
            cache_time = (page.config.get('cache_time')
                          or app.config.get('site/cache_time'))
            if cache_time:
                cache_control.public = True
                cache_control.max_age = cache_time

        content_type = page.config.get('content_type')
        if content_type and '/' not in content_type:
            mimetype = content_type_map.get(content_type, content_type)
        else:
            mimetype = content_type
        if mimetype:
            response.mimetype = mimetype

        if ('gzip' in request.accept_encodings
                and app.config.get('site/enable_gzip')):
            try:
                with io.BytesIO() as gzip_buffer:
                    with gzip.open(gzip_buffer, mode='wt',
                                   encoding='utf8') as gzip_file:
                        gzip_file.write(rp_content)
                    rp_content = gzip_buffer.getvalue()
                    response.content_encoding = 'gzip'
            except Exception:
                logger.error("Error compressing response, "
                             "falling back to uncompressed.")
        response.set_data(rp_content)

        return response