def page_content(lesson, page, solution=None, course=None, lesson_url=None, subpage_url=None, static_url=None, without_cache=False): variables = None if course is not None: variables = course.vars def content_creator(): """Return the content and all relative URLs used in it. Since the content is stored in cache and can be reused elsewhere, URLs must be stored as relative to the current page, so new absolute urls can be generated where the content is reused. """ with temporary_url_for_logger(app) as logger: with logger: content = page.render_html(solution=solution, static_url=static_url, lesson_url=lesson_url, subpage_url=subpage_url, vars=variables) absolute_urls = [ url_for(logged[0], **logged[1]) for logged in logger.logged_calls ] relative_urls = [ get_relative_url(request.path, x) for x in absolute_urls ] return {"content": content, "urls": relative_urls} # Only use the cache if there are no local changes # and not rendering in fork if without_cache or is_dirty(Repo(".")): return content_creator() # Since ARCA_IGNORE_CACHE_ERRORS is used, this won't fail in forks # even if the cache doesn't work. # This is only dangerous if the fork sets absolute path to cache and # CurrentEnvironmentBackend or VenvBackend are used locally. # FIXME? But I don't think there's a way to prevent writing # to a file in those backends content_key = page_content_cache_key(Repo("."), lesson.slug, page.slug, solution, variables) cached = arca.region.get_or_create(content_key, content_creator) # The urls are recorded twice when the content is created. # But it doesn't matter, duplicate URLs are skipped. for x in cached["urls"]: record_url(urljoin(request.path, x)) return cached
def course_page(course, lesson, page, solution=None): lesson_slug = lesson page_slug = page try: lesson = model.get_lesson(lesson_slug) canonical_url = url_for('lesson', lesson=lesson, _external=True) except LookupError: lesson = canonical_url = None kwargs = {} prev_link = session_link = next_link = session = None if course.is_link(): naucse.utils.views.forks_raise_if_disabled() fork_kwargs = {"request_url": request.path} try: # Checks if the rendered page content is in cache locally # to offer it to the fork. # ``course.vars`` calls ``course_info`` so it has to be in # the try block. # The function can also raise FileNotFoundError if the # lesson doesn't exist in repo. content_key = page_content_cache_key( arca.get_repo(course.repo, course.branch), lesson_slug, page, solution, course.vars) content_offer = arca.region.get(content_key) # We've got the fragment in cache, let's offer it to the fork. if content_offer: fork_kwargs["content_key"] = content_key data_from_fork = course.render_page(lesson_slug, page, solution, **fork_kwargs) record_content_urls(data_from_fork, f"/{course.slug}/") content = data_from_fork["content"] if content is None: # the offer was accepted content = content_offer["content"] for x in content_offer["urls"]: record_url(urljoin(request.path, x)) else: # the offer was rejected or the the fragment was not in cache arca.region.set(content_key, { "content": content, "urls": data_from_fork["content_urls"] }) for x in data_from_fork["content_urls"]: record_url(urljoin(request.path, x)) # compatibility page = process_page_data(data_from_fork.get("page")) course = process_course_data(data_from_fork.get("course"), slug=course.slug) session = process_session_data(data_from_fork.get("session")) kwargs["edit_info"] = links.process_edit_info( data_from_fork.get("edit_info")) prev_link, session_link, next_link = process_footer_data( data_from_fork.get("footer")) title = '{}: {}'.format(course["title"], page["title"]) except POSSIBLE_FORK_EXCEPTIONS as e: if raise_errors_from_forks(): raise rendered_replacement = False logger.error("There was an error rendering url %s for course '%s'", request.path, course.slug) if lesson is not None: try: logger.error( "Rendering the canonical version with a warning.") lesson_url, subpage_url, static_url = relative_url_functions( request.path, course, lesson) page = lesson.pages[page] content = page_content(lesson, page, solution, course, lesson_url=lesson_url, subpage_url=subpage_url, static_url=static_url)["content"] title = '{}: {}'.format(course.title, page.title) try: footer_links = course.get_footer_links( lesson.slug, page_slug, request_url=request.path, ) for link in footer_links: _prefix = f"/{course.slug}/" if link and link["url"].startswith(_prefix): record_url(link["url"]) prev_link, session_link, next_link = footer_links except POSSIBLE_FORK_EXCEPTIONS as e: if raise_errors_from_forks(): raise # The fork is failing spectacularly, so the footer # links aren't that important logger.error( "Could not retrieve even footer links from the fork at page %s", request.path) logger.exception(e) rendered_replacement = True kwargs["edit_info"] = get_edit_info(page.edit_path) kwargs["error_in_fork"] = True kwargs["travis_build_id"] = os.environ.get( "TRAVIS_BUILD_ID") except Exception as canonical_error: logger.error("Rendering the canonical version failed.") logger.exception(canonical_error) if not rendered_replacement: logger.exception(e) return render_template( "error_in_fork.html", malfunctioning_course=course, edit_info=get_edit_info(course.edit_path), faulty_page="lesson", lesson=lesson_slug, pg=page_slug, # avoid name conflict solution=solution, root_slug=model.meta.slug, travis_build_id=os.environ.get("TRAVIS_BUILD_ID"), ) else: if lesson is None: abort(404) lesson_url, subpage_url, static_url = relative_url_functions( request.path, course, lesson) page, session, prv, nxt = get_page(course, lesson, page) prev_link, session_link, next_link = get_footer_links( course, session, prv, nxt, lesson_url) content = page_content(lesson, page, solution, course=course, lesson_url=lesson_url, subpage_url=subpage_url, static_url=static_url) content = content["content"] allowed_elements_parser.reset_and_feed(content) title = '{}: {}'.format(course.title, page.title) kwargs["edit_info"] = get_edit_info(page.edit_path) if solution is not None: kwargs["solution_number"] = int(solution) return render_template("lesson.html", canonical_url=canonical_url, title=title, content=content, prev_link=prev_link, session_link=session_link, next_link=next_link, root_slug=model.meta.slug, course=course, lesson=lesson, page=page, solution=solution, session=session, **kwargs)
def record_content_urls(data_from_fork, prefix): # Freeze URLs generated by the code in fork, but only if # they start with the given prefix for url in data_from_fork.get("urls", ()): if url.startswith(prefix): record_url(url)