blog-markdown #31
|
@ -8,3 +8,4 @@ colorlog>=6.7.0
|
||||||
favicon>=0.7.0
|
favicon>=0.7.0
|
||||||
markdown>=3.4.4
|
markdown>=3.4.4
|
||||||
Pygments>=2.16.1
|
Pygments>=2.16.1
|
||||||
|
toml>=0.10
|
||||||
|
|
|
@ -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**
|
**NOTE**
|
||||||
|
|
||||||
This is a stolen article from [opensource.com](https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays)
|
This is a stolen article from [opensource.com](https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays)
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
Title: empty
|
||||||
|
Subtitle: empty
|
||||||
|
Desc: empty
|
||||||
|
Date: 2023-09-29
|
||||||
|
Featured: True
|
||||||
|
Public: False
|
||||||
|
---
|
|
@ -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**
|
**NOTE**
|
||||||
|
|
||||||
This is a stolen article from [opensource.com](https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays)
|
This is a stolen article from [opensource.com](https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays)
|
||||||
|
|
|
@ -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
|
||||||
|
"""
|
|
@ -1,3 +1,4 @@
|
||||||
|
import toml
|
||||||
import ast
|
import ast
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
@ -45,6 +46,14 @@ class Category(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{{<{self.__class__.__name__}>\"{self.name}\"}}"
|
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):
|
class BlogPost(Searchable):
|
||||||
"""
|
"""
|
||||||
|
@ -53,7 +62,8 @@ class BlogPost(Searchable):
|
||||||
body_en = models.TextField(blank=True, default="")
|
body_en = models.TextField(blank=True, default="")
|
||||||
body_de = models.TextField(blank=True, default="")
|
body_de = models.TextField(blank=True, default="")
|
||||||
category = models.ForeignKey(
|
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(
|
thumbnail = models.ImageField(
|
||||||
blank=True,
|
blank=True,
|
||||||
upload_to="img/thumbnails",
|
upload_to="img/thumbnails",
|
||||||
|
@ -67,12 +77,14 @@ class BlogPost(Searchable):
|
||||||
|
|
||||||
DATA_DIR = "/app/blog/data/articles"
|
DATA_DIR = "/app/blog/data/articles"
|
||||||
DEFAULT_LANGS = {'en': False, 'de': False}
|
DEFAULT_LANGS = {'en': False, 'de': False}
|
||||||
|
META_TOP_KEYS = [
|
||||||
def has_keywords(self) -> bool:
|
"date",
|
||||||
"""
|
"keywords",
|
||||||
check if the post has keywords
|
"category",
|
||||||
"""
|
"featured",
|
||||||
return self.keywords.first() is not None
|
"public",
|
||||||
|
"lang"]
|
||||||
|
META_LANG_KEYS = ["title", "subtitle", "desc"]
|
||||||
|
|
||||||
def regenerate(self):
|
def regenerate(self):
|
||||||
"""
|
"""
|
||||||
|
@ -85,130 +97,80 @@ class BlogPost(Searchable):
|
||||||
self.suburl = f"/blog/{self.category.name}/{self.slug}"
|
self.suburl = f"/blog/{self.category.name}/{self.slug}"
|
||||||
|
|
||||||
# load from markdown
|
# load from markdown
|
||||||
self.sync_file()
|
# self.sync_file()
|
||||||
|
|
||||||
|
# redundand vvvv
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def sync_file(self):
|
def sync_file(self):
|
||||||
"""
|
"""
|
||||||
generate an article fromm it's original markdown file
|
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:
|
try:
|
||||||
|
fmeta = open(f"{self.DATA_DIR}/{self.slug}.toml", "r")
|
||||||
|
except Exception as 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()
|
MD.reset()
|
||||||
with open(f"{self.DATA_DIR}/en-{self.slug}.md") as f_en:
|
body: str = f_en.read()
|
||||||
|
# FIXME:
|
||||||
body_en: str = f_en.read()
|
# these fields are loaded but wrong?
|
||||||
|
# de is in en, de is empty
|
||||||
html_en: str = MD.convert(body_en)
|
match lang:
|
||||||
try:
|
case "en":
|
||||||
# NOTE: MD.Meta is generated after MD.convert() by the meta
|
self.title_en = data['lang'][lang]["title"]
|
||||||
# extension.
|
self.subtitle_en = data['lang'][lang]["subtitle"]
|
||||||
# Meta not being shown is a reported issue:
|
self.desc_en = data['lang'][lang]["desc"]
|
||||||
# https://github.com/Python-Markdown/markdown/issues/1383
|
self.body_en = MD.convert(body)
|
||||||
if not hasattr(MD, 'Meta'):
|
case "de":
|
||||||
logger.error("Metadata extension for markdown\
|
self.title_en = data['lang'][lang]["title"]
|
||||||
not loaded")
|
self.subtitle_en = data['lang'][lang]["subtitle"]
|
||||||
raise ValueError("Metadata extension for markdown\
|
self.desc_en = data['lang'][lang]["desc"]
|
||||||
not loaded")
|
self.body_de = MD.convert(body)
|
||||||
meta_en = MD.Meta
|
case _:
|
||||||
self.title_en = meta_en["title"][0]
|
logger.error(
|
||||||
self.subtitle_en = meta_en["subtitle"][0]
|
f"unknown language '{lang}' in meta file for '{self}'")
|
||||||
self.desc_en = meta_en["desc"][0]
|
self.date = data["date"]
|
||||||
self.date = meta_en["date"][0]
|
self.featured = data["featured"]
|
||||||
self.featured = meta_en["featured"][0] == "True"
|
self.public = data["public"]
|
||||||
self.public = meta_en["public"][0] == "True"
|
# NOTE: thumbnail is optional
|
||||||
if "thumbnail" in meta_en:
|
if "thumbnail" in data:
|
||||||
self.thumbnail = meta_en["thumbnail"][0]
|
self.thumbnail = data["thumbnail"]
|
||||||
|
# NOTE: thumbnail is optional
|
||||||
|
if "category" in data:
|
||||||
try:
|
try:
|
||||||
category: Category = Category.objects.get(
|
category: Category = Category.objects.get(
|
||||||
slug=meta_en['category'][0])
|
slug=data['category'])
|
||||||
except Category.DoesNotExist:
|
except Category.DoesNotExist:
|
||||||
category = Category.objects.create(
|
category = Category.objects.create(
|
||||||
name=meta_en['category'], slug=meta_en['category'])
|
name=data['category'], slug=data['category'])
|
||||||
self.category = category
|
self.category = category
|
||||||
|
|
||||||
# NOTE: we need to save before we can use the manytomany
|
|
||||||
# logic.
|
|
||||||
self.save()
|
self.save()
|
||||||
for item in meta_en["keywords"]:
|
logger.debug("keywords next")
|
||||||
|
for keyword in data["keywords"]:
|
||||||
try:
|
try:
|
||||||
self.keywords.add(Keyword.objects.get(slug=item))
|
self.keywords.add(Keyword.objects.get(slug=keyword))
|
||||||
except Keyword.DoesNotExist:
|
except Keyword.DoesNotExist:
|
||||||
self.keywords.create(
|
self.keywords.create(
|
||||||
slug=item, text_en=item, text_de=item)
|
slug=keyword, text_en=keyword, text_de=keyword)
|
||||||
logger.debug(f"keywords of '{self}': {self.keywords}")
|
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
|
|
||||||
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}")
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def get_langs(self) -> dict[str, bool]:
|
def get_langs(self) -> dict[str, bool]:
|
||||||
|
|
|
@ -34,7 +34,6 @@
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
{% translate "category" %}: <b>{{ post.category.name }}</b>
|
{% translate "category" %}: <b>{{ post.category.name }}</b>
|
||||||
</li>
|
</li>
|
||||||
{% if post.has_keywords %}
|
|
||||||
{% for keyword in post.keywords.all %}
|
{% for keyword in post.keywords.all %}
|
||||||
{% if LANGUAGE_CODE == "de" %}
|
{% if LANGUAGE_CODE == "de" %}
|
||||||
<li class="list-group-item">{{ keyword.text_de }}</li>
|
<li class="list-group-item">{{ keyword.text_de }}</li>
|
||||||
|
@ -44,7 +43,6 @@
|
||||||
<li class="list-group-item">{{ keyword.text_en }}</li>
|
<li class="list-group-item">{{ keyword.text_en }}</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
Loading…
Reference in New Issue