generate category
This commit is contained in:
parent
6d9660f44c
commit
37c3104bc5
15
README.md
15
README.md
|
@ -1,4 +1,4 @@
|
|||
# gawa
|
||||
# Gawa
|
||||
|
||||
Gawa is my personal website. I've personally written it using the Django framework.
|
||||
|
||||
|
@ -7,13 +7,13 @@ 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 |
|
||||
|--------------------------------------------------|----------------|
|
||||
| [`contact@cscherr.de`](mailto:contact@cscherr.de) | `hrCcDa0jBspG` |
|
||||
|
||||
## License
|
||||
|
||||
|
@ -22,6 +22,13 @@ 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
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
@ -34,7 +36,7 @@ class Category(models.Model):
|
|||
Maybe some day it would be cool if these were Searchable
|
||||
"""
|
||||
name = models.CharField(max_length=50)
|
||||
slug = models.SlugField()
|
||||
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,6 +230,12 @@ class BlogPost(Searchable):
|
|||
# parse file name
|
||||
try:
|
||||
matches = re.match(regex, file[0])
|
||||
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)
|
||||
|
@ -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")
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
{% extends 'admin/change_list.html' %}
|
||||
|
||||
{% block object-tools %}
|
||||
{% extends 'admin/change_list.html' %} {% block object-tools %}
|
||||
<form action="sync" method="POST">
|
||||
{% csrf_token %}
|
||||
<button type="submit">Sync with FS</button>
|
||||
<button type="submit" class="button" style="padding: 4px">Sync with FS</button>
|
||||
</form>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
{{ block.super }} {% endblock %}
|
||||
|
|
Loading…
Reference in New Issue