Exemple #1
0
class License(Model):
    def __str__(self):
        return self.path.parts[-1]

    info = YamlProperty()

    title = DataProperty(info)
    url = DataProperty(info)
Exemple #2
0
class Lesson(Model):
    """An individual lesson stored on naucse"""
    def __str__(self):
        return '{} - {}'.format(self.slug, self.title)

    info = YamlProperty()

    title = DataProperty(info)

    @reify
    def slug(self):
        return '/'.join(self.path.parts[-2:])

    @reify
    def pages(self):
        pages = dict(self.info.get('subpages', {}))
        pages.setdefault('index', {})
        return {
            slug: Page(self, slug, self.info, p)
            for slug, p in pages.items()
        }

    @reify
    def index_page(self):
        return self.pages['index']
Exemple #3
0
class CourseLink(CourseMixin, Model):
    """ A link to a course from a separate git repo.
    """

    link = YamlProperty()
    repo: str = DataProperty(link)
    branch: str = DataProperty(link, default="master")

    info = ForkProperty(repo,
                        branch,
                        entry_point="naucse.utils.forks:course_info",
                        args=lambda instance: [instance.slug])
    title = DataProperty(info)
    description = DataProperty(info)
    start_date = DataProperty(info,
                              default=None,
                              convert=optional_convert_date)
    end_date = DataProperty(info, default=None, convert=optional_convert_date)
    subtitle = DataProperty(info, default=None)
    derives = DataProperty(info, default=None)
    vars = DataProperty(info, default=None)
    canonical = DataProperty(info, default=False)
    default_start_time = DataProperty(info,
                                      default=None,
                                      convert=optional_convert_time)
    default_end_time = DataProperty(info,
                                    default=None,
                                    convert=optional_convert_time)

    data_filename = "link.yml"  # for MultipleModelDirProperty

    def __str__(self):
        return 'CourseLink: {} ({})'.format(self.repo, self.branch)

    @reify
    def base_course(self):
        name = self.derives
        if name is None:
            return None
        try:
            return self.root.courses[name]
        except LookupError:
            return None

    def render(self, page_type, *args, **kwargs):
        """ Renders a page in the fork, checks the content and registers urls to freeze.
        """
        naucse.utils.routes.forks_raise_if_disabled()

        task = Task(
            "naucse.utils.forks:render",
            args=[page_type, self.slug] + list(args),
            kwargs=kwargs,
        )
        result = arca.run(self.repo,
                          self.branch,
                          task,
                          reference=Path("."),
                          depth=None)

        if page_type != "calendar_ics" and result.output["content"] is not None:
            allowed_elements_parser.reset_and_feed(result.output["content"])

        if "urls" in result.output:
            # freeze urls generated by the code in fork, but only if they start with the slug of the course
            absolute_urls_to_freeze.extend([
                url for url in result.output["urls"]
                if url.startswith(f"/{self.slug}/")
            ])

        return result.output

    def render_course(self, **kwargs):
        return self.render("course", **kwargs)

    def render_calendar(self, **kwargs):
        return self.render("calendar", **kwargs)

    def render_calendar_ics(self, **kwargs):
        return self.render("calendar_ics", **kwargs)

    def render_page(self,
                    lesson_slug,
                    page,
                    solution,
                    content_key=None,
                    **kwargs):
        return self.render("course_page",
                           lesson_slug,
                           page,
                           solution,
                           content_key=content_key,
                           **kwargs)

    def render_session_coverpage(self, session, coverpage, **kwargs):
        return self.render("session_coverpage", session, coverpage, **kwargs)

    def lesson_static(self, lesson_slug, path):
        filename = arca.static_filename(self.repo,
                                        self.branch,
                                        Path("lessons") / lesson_slug /
                                        "static" / path,
                                        reference=Path("."),
                                        depth=None).resolve()

        return filename.parent, filename.name

    def get_footer_links(self, lesson_slug, page, **kwargs):
        """ Returns links to previous page, to current session and to the next page. Each link
            is either a dict with url and title keys or ``None``.

            If :meth:`render_page` fails and a canonical versions is in the base repo, it's used instead
            with a warning. This method provides the correct footer links for the page, since ``sessions``
            is not included in the info provided by forks.
        """
        naucse.utils.routes.forks_raise_if_disabled()

        task = Task("naucse.utils.forks:get_footer_links",
                    args=[self.slug, lesson_slug, page],
                    kwargs=kwargs)

        result = arca.run(self.repo,
                          self.branch,
                          task,
                          reference=Path("."),
                          depth=None)

        to_return = []

        from naucse.routes import logger
        logger.debug(result.output)

        if not isinstance(result.output, dict):
            return None, None, None

        def validate_link(link, key):
            return key in link and isinstance(link[key], str)

        for link_type in "prev_link", "session_link", "next_link":
            link = result.output.get(link_type)

            if isinstance(link, dict) and validate_link(
                    link, "url") and validate_link(link, "title"):
                if link["url"].startswith(f"/{self.slug}/"):
                    absolute_urls_to_freeze.append(link["url"])
                to_return.append(link)
            else:
                to_return.append(None)

        logger.debug(to_return)

        return to_return

    @reify
    def edit_path(self):
        return self.path.relative_to(self.root.path) / "link.yml"
Exemple #4
0
class Course(CourseMixin, Model):
    """A course – ordered collection of sessions"""
    def __str__(self):
        return '{} - {}'.format(self.slug, self.title)

    info = YamlProperty()
    title = DataProperty(info)
    description = DataProperty(info)
    long_description = DataProperty(info)

    vars = DataProperty(info)
    subtitle = DataProperty(info, default=None)
    time = DataProperty(info, default=None)
    place = DataProperty(info, default=None)

    canonical = DataProperty(info, default=False)

    data_filename = "info.yml"  # for MultipleModelDirProperty

    # These two class attributes define what the function ``naucse.utils.forks:course_info`` returns from forks,
    # meaning, the function in the fork looks at these lists that are in the fork and returns those.
    # If you're adding an attribute to these lists, you have to make sure that you provide a default in
    # the CourseLink attribute since the forks already forked will not be returning the value.
    COURSE_INFO = ["title", "description", "vars", "canonical"]
    RUN_INFO = [
        "title", "description", "start_date", "end_date", "canonical",
        "subtitle", "derives", "vars", "default_start_time", "default_end_time"
    ]

    @property
    def derives(self):
        return self.info.get("derives")

    @reify
    def base_course(self):
        name = self.info.get('derives')
        if name is None:
            return None
        return self.root.courses[name]

    @reify
    def sessions(self):
        return _get_sessions(self, self.info['plan'])

    @reify
    def edit_path(self):
        return self.path.relative_to(self.root.path) / "info.yml"

    @reify
    def start_date(self):
        dates = [s.date for s in self.sessions.values() if s.date is not None]
        if not dates:
            return None
        return min(dates)

    @reify
    def end_date(self):
        dates = [s.date for s in self.sessions.values() if s.date is not None]
        if not dates:
            return None
        return max(dates)

    def _default_time(self, key):
        default_time = self.info.get('default_time')
        if default_time:
            return time_from_string(default_time[key])
        return None

    @reify
    def default_start_time(self):
        return self._default_time('start')

    @reify
    def default_end_time(self):
        return self._default_time('end')
Exemple #5
0
class Session(Model):
    """An ordered collection of materials"""
    def __init__(self, root, path, base_course, info, index, course=None):
        super().__init__(root, path)
        base_name = info.get('base')
        self.index = index
        self.course = course
        if base_name is None:
            self.info = info
        else:
            base = base_course.sessions[base_name].info
            self.info = merge_dict(base, info)
        # self.prev and self.next are set later

    def __str__(self):
        return self.title

    info = YamlProperty()

    title = DataProperty(info)
    slug = DataProperty(info)
    date = DataProperty(info, default=None)
    description = DataProperty(info, default=None)

    def _time(self, time):
        if self.date and time:
            return datetime.datetime.combine(self.date, time)
        return None

    def _session_time(self, key):
        sesion_time = self.info.get('time')
        if sesion_time:
            return time_from_string(sesion_time[key])
        return None

    @reify
    def has_irregular_time(self):
        """True iff the session has its own start or end time, the course has
        a default start or end time, and either of those does not match."""

        irregular_start = self.course.default_start_time is not None \
            and self._time(self.course.default_start_time) != self.start_time
        irregular_end = self.course.default_end_time is not None \
            and self._time(self.course.default_end_time) != self.end_time
        return irregular_start or irregular_end

    @reify
    def start_time(self):
        session_time = self._session_time('start')
        if session_time:
            return self._time(session_time)
        if self.course:
            return self._time(self.course.default_start_time)
        return None

    @reify
    def end_time(self):
        session_time = self._session_time('end')
        if session_time:
            return self._time(session_time)
        if self.course:
            return self._time(self.course.default_end_time)
        return None

    @reify
    def materials(self):
        materials = [
            material(self.root, self.path, s) for s in self.info['materials']
        ]
        materials_with_nav = [mat for mat in materials if mat.has_navigation]
        for prev, current, next in zip([None] + materials_with_nav,
                                       materials_with_nav,
                                       materials_with_nav[1:] + [None]):
            current.set_prev_next(prev, next)

        return materials

    def get_edit_path(self, run, coverpage):
        coverpage_path = self.path / "sessions" / self.slug / (coverpage +
                                                               ".md")
        if coverpage_path.exists():
            return coverpage_path.relative_to(self.root.path)

        return run.edit_path

    def get_coverpage_content(self, run, coverpage, app):
        coverpage += ".md"
        q = self.path / 'sessions' / self.slug / coverpage

        try:
            with q.open() as f:
                md_content = f.read()
        except FileNotFoundError:
            return ""

        html_content = convert_markdown(md_content)
        return html_content