diff --git a/docker/main/requirements.txt b/docker/main/requirements.txt index 082b81c..31fb00a 100644 --- a/docker/main/requirements.txt +++ b/docker/main/requirements.txt @@ -8,3 +8,4 @@ colorlog>=6.7.0 favicon>=0.7.0 markdown>=3.4.4 Pygments>=2.16.1 +toml>=0.10 diff --git a/gawa/blog/data/articles/de-test.md b/gawa/blog/data/articles/de-test.md index 7e8dbd5..1b84e4c 100644 --- a/gawa/blog/data/articles/de-test.md +++ b/gawa/blog/data/articles/de-test.md @@ -1,16 +1,3 @@ ---- -Title: Bash Arrays -Subtitle: sub -Desc: Brief intro to Bash Arrays -Date: 2023-09-29 -Keywords: bash - technology - test -Category: Test -Featured: True -Public: True ---- - **NOTE** This is a stolen article from [opensource.com](https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays) diff --git a/gawa/blog/data/articles/en-empty.md b/gawa/blog/data/articles/en-empty.md new file mode 100644 index 0000000..b05de61 --- /dev/null +++ b/gawa/blog/data/articles/en-empty.md @@ -0,0 +1,8 @@ +--- +Title: empty +Subtitle: empty +Desc: empty +Date: 2023-09-29 +Featured: True +Public: False +--- diff --git a/gawa/blog/data/articles/en-test.md b/gawa/blog/data/articles/en-test.md index b580c5e..1fa7e64 100644 --- a/gawa/blog/data/articles/en-test.md +++ b/gawa/blog/data/articles/en-test.md @@ -1,16 +1,3 @@ ---- -Title: Bash Arrays -Subtitle: sub -Desc: Brief intro to Bash Arrays -Date: 2023-09-29 -Thumbnail: img/thumbnails/bash.png -Keywords: bash - technology -Category: Test -Featured: True -Public: True ---- - **NOTE** This is a stolen article from [opensource.com](https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays) diff --git a/gawa/blog/data/articles/test.toml b/gawa/blog/data/articles/test.toml new file mode 100644 index 0000000..7f0d45e --- /dev/null +++ b/gawa/blog/data/articles/test.toml @@ -0,0 +1,24 @@ +date = "2023-09-29" +keywords = ["test", "bash"] +category = "Test" +featured = true +public = true +thumbnail = "img/thumbnails/bash.png" + +[lang.en] +title = "title" +subtitle = "subtitle" +desc = """ +long +multiline +desc +""" + +[lang.de] +title = "Titel" +subtitle = "Subtitel" +desc = """ +Lange, +mehrzeilige, +Beschreibung +""" diff --git a/gawa/blog/models.py b/gawa/blog/models.py index dfd409a..f768365 100644 --- a/gawa/blog/models.py +++ b/gawa/blog/models.py @@ -1,3 +1,4 @@ +import toml import ast import re import os @@ -45,6 +46,14 @@ class Category(models.Model): def __str__(self): return f"{{<{self.__class__.__name__}>\"{self.name}\"}}" + @staticmethod + def get_or_create_uncategorized(): + try: + return Category.objects.get(slug="uncategorized") + except Category.DoesNotExist: + return Category.objects.create( + slug="uncategorized", name="uncategorized") + class BlogPost(Searchable): """ @@ -53,7 +62,8 @@ class BlogPost(Searchable): body_en = models.TextField(blank=True, default="") body_de = models.TextField(blank=True, default="") category = models.ForeignKey( - Category, on_delete=models.SET_NULL, null=True) + Category, on_delete=models.SET_DEFAULT, null=False, + default=Category.get_or_create_uncategorized) thumbnail = models.ImageField( blank=True, upload_to="img/thumbnails", @@ -67,12 +77,14 @@ class BlogPost(Searchable): DATA_DIR = "/app/blog/data/articles" DEFAULT_LANGS = {'en': False, 'de': False} - - def has_keywords(self) -> bool: - """ - check if the post has keywords - """ - return self.keywords.first() is not None + META_TOP_KEYS = [ + "date", + "keywords", + "category", + "featured", + "public", + "lang"] + META_LANG_KEYS = ["title", "subtitle", "desc"] def regenerate(self): """ @@ -85,130 +97,80 @@ class BlogPost(Searchable): self.suburl = f"/blog/{self.category.name}/{self.slug}" # load from markdown - self.sync_file() + # self.sync_file() + # redundand vvvv self.save() def sync_file(self): """ generate an article fromm it's original markdown file """ - logger.info(f"regenerating article from markdown for: {self}") + logger = logging.getLogger(__name__) + logger.info(f"syncing article to markdown for: {self}") + # read metadata try: - MD.reset() - with open(f"{self.DATA_DIR}/en-{self.slug}.md") as f_en: - - body_en: str = f_en.read() - - html_en: str = MD.convert(body_en) - try: - # NOTE: MD.Meta is generated after MD.convert() by the meta - # extension. - # Meta not being shown is a reported issue: - # https://github.com/Python-Markdown/markdown/issues/1383 - if not hasattr(MD, 'Meta'): - logger.error("Metadata extension for markdown\ - not loaded") - raise ValueError("Metadata extension for markdown\ - not loaded") - meta_en = MD.Meta - self.title_en = meta_en["title"][0] - self.subtitle_en = meta_en["subtitle"][0] - self.desc_en = meta_en["desc"][0] - self.date = meta_en["date"][0] - self.featured = meta_en["featured"][0] == "True" - self.public = meta_en["public"][0] == "True" - if "thumbnail" in meta_en: - self.thumbnail = meta_en["thumbnail"][0] - - try: - category: Category = Category.objects.get( - slug=meta_en['category'][0]) - except Category.DoesNotExist: - category = Category.objects.create( - name=meta_en['category'], slug=meta_en['category']) - self.category = category - - # NOTE: we need to save before we can use the manytomany - # logic. - self.save() - for item in meta_en["keywords"]: - try: - self.keywords.add(Keyword.objects.get(slug=item)) - except Keyword.DoesNotExist: - self.keywords.create( - slug=item, text_en=item, text_de=item) - logger.debug(f"keywords of '{self}': {self.keywords}") - - except Exception as e: - logger.warning( - f"could not generate metadata {self.slug} from markdown: {e}") - - self.body_en = "" - self.body_en = html_en - except FileNotFoundError as e: - # TODO: mark as untranslated - pass + fmeta = open(f"{self.DATA_DIR}/{self.slug}.toml", "r") except Exception as e: - logger.warning( - f"could not generate article {self.slug} from markdown: {e}") - try: - MD.reset() - with open(f"{self.DATA_DIR}/de-{self.slug}.md") as f_de: - - body_de: str = f_de.read() - - html_de: str = MD.convert(body_de) - try: - # NOTE: MD.Meta is generated after MD.convert() by the meta - # extension. - # Meta not being shown is a reported issue: - # https://github.com/Python-Markdown/markdown/issues/1383 - if not hasattr(MD, 'Meta'): - logger.error("Metadata extension for markdown\ - not loaded") - raise ValueError("Metadata extension for markdown\ - not loaded") - meta_de = MD.Meta - self.title_de = meta_de["title"][0] - self.subtitle_de = meta_de["subtitle"][0] - self.desc_de = meta_de["desc"][0] - # TODO: parse date from markdown - self.featured = meta_de["featured"][0] == "True" - self.public = meta_de["public"][0] == "True" - if "thumbnail" in meta_de: - self.thumbnail = meta_de["thumbnail"][0] - - try: - category: Category = Category.objects.get( - slug=meta_de['category'][0]) - except Category.DoesNotExist: - category = Category.objects.create( - name=meta_de['category'], slug=meta_de['category']) - self.category = category - - # NOTE: we need to save before we can use the manytomany - # logic. - self.save() - for item in meta_de["keywords"]: - try: - self.keywords.add(Keyword.objects.get(slug=item)) - except Keyword.DoesNotExist: - self.keywords.create( - slug=item, text_en=item, text_de=item) - logger.debug(f"keywords of '{self}': {self.keywords}") - except Exception as e: - logger.warning( - f"could not generate metadata {self.slug} from markdown: {e}") - - self.body_de = "" - self.body_de = html_de - except FileNotFoundError as e: - # TODO: mark as untranslated - pass - except Exception as e: - logger.warning( - f"could not generate article {self.slug} from markdown: {e}") + logger.error(f"could not find meta file for '{self}'") + return + data = toml.load(fmeta) + for key in self.META_TOP_KEYS: + if key not in data: + logger.error(f"Key '{key}' missing in meta file for '{self}'") + raise ValueError( + f"Key '{key}' missing in meta file for '{self}'") + for lang in data['lang']: + for key in self.META_LANG_KEYS: + if key not in data['lang'][lang]: + logger.error( + f"Key '{key}' ('{lang}') missing in meta file for '{self}'") + raise ValueError( + f"Key '{key}' ('{lang}') missing in meta file for '{self}'") + with open(f"{self.DATA_DIR}/{lang}-{self.slug}.md") as f_en: + MD.reset() + body: str = f_en.read() + # FIXME: + # these fields are loaded but wrong? + # de is in en, de is empty + match lang: + case "en": + self.title_en = data['lang'][lang]["title"] + self.subtitle_en = data['lang'][lang]["subtitle"] + self.desc_en = data['lang'][lang]["desc"] + self.body_en = MD.convert(body) + case "de": + self.title_en = data['lang'][lang]["title"] + self.subtitle_en = data['lang'][lang]["subtitle"] + self.desc_en = data['lang'][lang]["desc"] + self.body_de = MD.convert(body) + case _: + logger.error( + f"unknown language '{lang}' in meta file for '{self}'") + self.date = data["date"] + self.featured = data["featured"] + self.public = data["public"] + # NOTE: thumbnail is optional + if "thumbnail" in data: + self.thumbnail = data["thumbnail"] + # NOTE: thumbnail is optional + if "category" in data: + try: + category: Category = Category.objects.get( + slug=data['category']) + except Category.DoesNotExist: + category = Category.objects.create( + name=data['category'], slug=data['category']) + self.category = category + self.save() + logger.debug("keywords next") + for keyword in data["keywords"]: + try: + self.keywords.add(Keyword.objects.get(slug=keyword)) + except Keyword.DoesNotExist: + self.keywords.create( + slug=keyword, text_en=keyword, text_de=keyword) + logger.debug(f"keywords of '{self}': {self.keywords}") self.save() def get_langs(self) -> dict[str, bool]: diff --git a/gawa/blog/templates/blog/featured.html b/gawa/blog/templates/blog/featured.html index 0a23c55..28cfd8b 100644 --- a/gawa/blog/templates/blog/featured.html +++ b/gawa/blog/templates/blog/featured.html @@ -7,48 +7,46 @@
- thumbnail -
- {% if LANGUAGE_CODE == "de" %} -
- {{ post.title_de }}{{ post.subtitle }} -
-

{{ post.desc_de }}

- {% elif LANGUAGE_CODE == "en" %} -
- {{ post.title_en }}{{ post.subtitle }} -
-

{{ post.desc_en }}

- {% else %} -
- {{ post.title_en }}{{ post.subtitle }} -
-

{{ post.desc_en }}

- {% endif %} -
-
-
    -
  • - {% translate "category" %}: {{ post.category.name }} -
  • - {% if post.has_keywords %} - {% for keyword in post.keywords.all %} - {% if LANGUAGE_CODE == "de" %} -
  • {{ keyword.text_de }}
  • - {% elif LANGUAGE_CODE == "en" %} -
  • {{ keyword.text_en }}
  • - {% else %} -
  • {{ keyword.text_en }}
  • - {% endif %} - {% endfor %} - {% endif %} -
-
-
-
- {% endfor %} - + thumbnail +
+ {% if LANGUAGE_CODE == "de" %} +
+ {{ post.title_de }}{{ post.subtitle }} +
+

{{ post.desc_de }}

+ {% elif LANGUAGE_CODE == "en" %} +
+ {{ post.title_en }}{{ post.subtitle }} +
+

{{ post.desc_en }}

+ {% else %} +
+ {{ post.title_en }}{{ post.subtitle }} +
+

{{ post.desc_en }}

+ {% endif %} +
+
+ +
+ + + {% endfor %} +