from django.db import models import markdown from django.utils.functional import cached_property from django.template import Template, Context from django.core.files.storage import storages from django.core.files import File from pathlib import Path import os.path class Tag(models.Model): __yamdl__ = True slug = models.SlugField(max_length=128, primary_key=True) file_path = models.CharField(max_length=255) content = models.TextField() def get_page_upload_to(instance, filename): return f"{instance._meta.verbose_name}/{instance.slug}/{os.path.basename(filename)}" class Page(models.Model): __yamdl__ = True _template_cache = {} title = models.CharField(max_length=255) raw_content = models.TextField() runtime_render = models.BooleanField(default=False) content = models.TextField() toc = models.JSONField() slug = models.CharField(max_length=128, unique=True, db_index=True, default=None, null=True) file_path = models.CharField(max_length=255) media = models.FileField(null=True, default=None, storage=storages["yamdl"], upload_to=get_page_upload_to) tags = models.ManyToManyField(Tag) @classmethod def from_yaml(cls, **data): tags = data.pop("tags", None) md = markdown.Markdown(extensions=["toc"]) content = data.pop("content") data["raw_content"] = content data["content"] = md.convert(content) data["toc"] = { "html": md.toc, "tokens": md.toc_tokens } if data.get("media"): source_dir = Path(data["file_path"]).parent data["media"] = File((source_dir / data["media"]).open("rb")) instance = cls(**data) if not instance.runtime_render: instance.content = instance.render_content() instance.save() if tags: instance.tags.set(tags) return instance @cached_property def content_template(self): if self.runtime_render: return Template(self.content, name=self.slug) if (cached_template := self._template_cache.get(self.slug)) is None: cached_template = self._template_cache[self.slug] = Template(self.content, name=self.slug) return cached_template def get_context(self): return { "page": self } def render_content(self, extra_context=None): if extra_context is None: extra_context = {} return self.content_template.render(Context({**self.get_context(), **extra_context}))