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)
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)
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))
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)
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)
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)
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
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)
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