From 37c3104bc5285f69cf70b40498d0be28480efaac Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sun, 1 Oct 2023 18:06:07 +0200 Subject: [PATCH] generate category --- README.md | 21 ++++-- gawa/blog/models.py | 94 ++++++++++++++++++------- gawa/blog/templates/admin/blogpost.html | 15 ++-- 3 files changed, 90 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index db492b0..8621ad7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# gawa +# Gawa Gawa is my personal website. I've personally written it using the Django framework. @@ -7,21 +7,28 @@ Gawa is my personal website. I've personally written it using the Django framewo These are the Credentials for logging into the admin panel: | Username | Password | |----------|----------| -| root | root | +| `root` | `root` | ### Blog -| Username | Password | -|--------------------|--------------| -| contact@cscherr.de | hrCcDa0jBspG | +| Username | Password | +|--------------------------------------------------|----------------| +| [`contact@cscherr.de`](mailto:contact@cscherr.de) | `hrCcDa0jBspG` | ## License -Bootstrap: MIT Licensed -Django: BSD 3-Clause "New" or "Revised" License +Bootstrap: MIT Licensed +Django: BSD 3-Clause "New" or "Revised" License __Gawa: MIT Licensed, see LICENSE__ +## Dependencies + +| Description | Package (fedora) | +|----------------|------------------| +| Database stuff | `libpq-devel` | +| Database stuff | `mariadb` | + ## Security - [ ] Do something about the files in the blog dir diff --git a/gawa/blog/models.py b/gawa/blog/models.py index 736d3be..c5e9041 100644 --- a/gawa/blog/models.py +++ b/gawa/blog/models.py @@ -1,3 +1,8 @@ +import ast +import re +import os +import pathlib +import markdown from django.db import models from django.utils.translation import gettext as _ from start.models import Searchable @@ -5,7 +10,6 @@ from start.models import Searchable import logging logger = logging.getLogger(__name__) -import markdown EXTENSIONS = [ "extra", "admonition", @@ -20,11 +24,9 @@ EXTENSION_CONFIGS = { }, } -MD = markdown.Markdown(extensions=EXTENSIONS, extension_configs=EXTENSION_CONFIGS) +MD = markdown.Markdown(extensions=EXTENSIONS, + extension_configs=EXTENSION_CONFIGS) -import pathlib -import os -import re class Category(models.Model): """ @@ -33,8 +35,8 @@ class Category(models.Model): Name not translated because it would make i18n in urls and Searchables specifically a pain. Maybe some day it would be cool if these were Searchable """ - name= models.CharField(max_length=50) - slug = models.SlugField() + name = models.CharField(max_length=50) + slug = models.SlugField(unique=True) class Meta: verbose_name = _("Category") @@ -43,16 +45,19 @@ class Category(models.Model): def __str__(self): return f"{{<{self.__class__.__name__}>\"{self.name}\"}}" + class BlogPost(Searchable): """ Should contain a blogpost """ 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 = models.ForeignKey( + Category, on_delete=models.SET_NULL, null=True) thumbnail = models.ImageField(blank=True, upload_to="img/thumbnails") featured = models.BooleanField(default=False) - langs = models.CharField(default="['en': False, 'de': False]", max_length=64) + langs = models.CharField( + default="['en': False, 'de': False]", max_length=64) slug = models.SlugField() # TODO autodiscover new blog posts based on markdown files? @@ -88,21 +93,38 @@ class BlogPost(Searchable): html_en: str = MD.convert(body_en) try: + # NOTE: MD.Meta is generated after MD.convert() by the meta + # extension. + 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] - # TODO: parse date from markdown + self.date = meta_en["date"][0] self.featured = meta_en["featured"][0] == "True" self.public = meta_en["public"][0] == "True" # self.thumbnail = meta_en["thumbnail"] # TODO: parse keywords from markdown # TODO: parse category from markdown + 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']) + logger.debug(f"category of {self}: {category}") + self.category = category + # if keyword or category do not exist, create them # I suppose except Exception as e: - logger.warning(f"could not generate metadata {self.slug} from markdown: {e}") + logger.warning( + f"could not generate metadata {self.slug} from markdown: {e}") self.body_en = "" self.body_en = html_en @@ -110,7 +132,8 @@ class BlogPost(Searchable): # TODO: mark as untranslated pass except Exception as e: - logger.warning(f"could not generate article {self.slug} from markdown: {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: @@ -119,6 +142,13 @@ class BlogPost(Searchable): html_de: str = MD.convert(body_de) try: + # NOTE: MD.Meta is generated after MD.convert() by the meta + # extension. + 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] @@ -133,7 +163,8 @@ class BlogPost(Searchable): # if keyword or category do not exist, create them # I suppose except Exception as e: - logger.warning(f"could not generate metadata {self.slug} from markdown: {e}") + logger.warning( + f"could not generate metadata {self.slug} from markdown: {e}") self.body_de = "" self.body_de = html_de @@ -141,9 +172,10 @@ class BlogPost(Searchable): # TODO: mark as untranslated pass except Exception as e: - logger.warning(f"could not generate article {self.slug} from markdown: {e}") + logger.warning( + f"could not generate article {self.slug} from markdown: {e}") - def get_langs(self) -> dict[str, bool]: + def get_langs(self) -> dict[str, bool] | None: """ get available languages """ @@ -152,7 +184,13 @@ class BlogPost(Searchable): # SECURITY: # If someone could inject the langs field, arbitrary python code might # run, Potentially ending in a critical RCE vulnerability - return eval(str(self.langs)) + try: + langs = ast.literal_eval(str(self.langs)) + return langs + except ValueError as e: + logger.error( + f"could not safely evaluate 'langs' for '{self}': {e}") + return None def set_langs(self, langs: dict[str, bool]): """ @@ -179,7 +217,8 @@ class BlogPost(Searchable): if not data_dir.is_dir(): logger.error(f"'{cls.DATA_DIR} is not a directory'") - files = [f for f in os.listdir(data_dir) if (data_dir.joinpath(f)).is_file()] + files = [f for f in os.listdir(data_dir) if ( + data_dir.joinpath(f)).is_file()] logger.debug(f"discovered files: {files}") # finding lang and title @@ -191,9 +230,15 @@ class BlogPost(Searchable): # parse file name try: matches = re.match(regex, file[0]) - current_lang = matches.group(1) - file[1][current_lang] = True - file[2] = matches.group(2) + if matches is None: + logger.warning( + f"Data file '{file[0]}' does not fit to the filename\ + regex") + files.remove(file) + else: + current_lang = matches.group(1) + file[1][current_lang] = True + file[2] = matches.group(2) except Exception as e: logger.error(e) files.remove(file) @@ -215,12 +260,14 @@ class BlogPost(Searchable): # only a single version of this file continue except Exception as e: - logger.error(f"Could not combine BlogPosts for '{file[0]}': {e}") + logger.error( + f"Could not combine BlogPosts for '{file[0]}': {e}") try: # deduplicate _files = [] - for f in [[_f[1],_f[2]] for _f in files]: # dont care about fname + for f in [[_f[1], _f[2]] + for _f in files]: # dont care about fname if f not in _files: _files.append(f) files = _files @@ -232,12 +279,11 @@ class BlogPost(Searchable): try: obj = BlogPost(langs=file[0], slug=file[1]) obj.sync_file() + obj.regenerate() obj.save() except Exception as e: logger.error(f"Could not create BlogPost for '{file[1]}': {e}") - - class Meta: verbose_name = _("blog post") verbose_name_plural = _("blog posts") diff --git a/gawa/blog/templates/admin/blogpost.html b/gawa/blog/templates/admin/blogpost.html index e1f2936..750d46d 100644 --- a/gawa/blog/templates/admin/blogpost.html +++ b/gawa/blog/templates/admin/blogpost.html @@ -1,9 +1,6 @@ -{% extends 'admin/change_list.html' %} - -{% block object-tools %} -
- {% csrf_token %} - -
- {{ block.super }} -{% endblock %} +{% extends 'admin/change_list.html' %} {% block object-tools %} +
+ {% csrf_token %} + +
+{{ block.super }} {% endblock %}