Compare commits
2 commits
f8c3577a54
...
9a4f535ca2
Author | SHA1 | Date | |
---|---|---|---|
9a4f535ca2 | |||
6d9660f44c |
13 changed files with 145 additions and 73 deletions
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.
|
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:
|
These are the Credentials for logging into the admin panel:
|
||||||
| Username | Password |
|
| Username | Password |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| root | root |
|
| `root` | `root` |
|
||||||
|
|
||||||
### Blog
|
### Blog
|
||||||
|
|
||||||
| Username | Password |
|
| Username | Password |
|
||||||
|--------------------|--------------|
|
|--------------------------------------------------|----------------|
|
||||||
| contact@cscherr.de | hrCcDa0jBspG |
|
| [`contact@cscherr.de`](mailto:contact@cscherr.de) | `hrCcDa0jBspG` |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -22,6 +22,13 @@ Django: BSD 3-Clause "New" or "Revised" License
|
||||||
|
|
||||||
__Gawa: MIT Licensed, see LICENSE__
|
__Gawa: MIT Licensed, see LICENSE__
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
| Description | Package (fedora) |
|
||||||
|
|----------------|------------------|
|
||||||
|
| Database stuff | `libpq-devel` |
|
||||||
|
| Database stuff | `mariadb` |
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
- [ ] Do something about the files in the blog dir
|
- [ ] 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.db import models
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from start.models import Searchable
|
from start.models import Searchable
|
||||||
|
@ -5,7 +10,6 @@ from start.models import Searchable
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
import markdown
|
|
||||||
EXTENSIONS = [
|
EXTENSIONS = [
|
||||||
"extra",
|
"extra",
|
||||||
"admonition",
|
"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):
|
class Category(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -34,7 +36,7 @@ class Category(models.Model):
|
||||||
Maybe some day it would be cool if these were Searchable
|
Maybe some day it would be cool if these were Searchable
|
||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
slug = models.SlugField()
|
slug = models.SlugField(unique=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Category")
|
verbose_name = _("Category")
|
||||||
|
@ -43,16 +45,19 @@ class Category(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{{<{self.__class__.__name__}>\"{self.name}\"}}"
|
return f"{{<{self.__class__.__name__}>\"{self.name}\"}}"
|
||||||
|
|
||||||
|
|
||||||
class BlogPost(Searchable):
|
class BlogPost(Searchable):
|
||||||
"""
|
"""
|
||||||
Should contain a blogpost
|
Should contain a blogpost
|
||||||
"""
|
"""
|
||||||
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, 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")
|
thumbnail = models.ImageField(blank=True, upload_to="img/thumbnails")
|
||||||
featured = models.BooleanField(default=False)
|
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()
|
slug = models.SlugField()
|
||||||
|
|
||||||
# TODO autodiscover new blog posts based on markdown files?
|
# TODO autodiscover new blog posts based on markdown files?
|
||||||
|
@ -88,21 +93,38 @@ class BlogPost(Searchable):
|
||||||
|
|
||||||
html_en: str = MD.convert(body_en)
|
html_en: str = MD.convert(body_en)
|
||||||
try:
|
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
|
meta_en = MD.Meta
|
||||||
self.title_en = meta_en["title"][0]
|
self.title_en = meta_en["title"][0]
|
||||||
self.subtitle_en = meta_en["subtitle"][0]
|
self.subtitle_en = meta_en["subtitle"][0]
|
||||||
self.desc_en = meta_en["desc"][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.featured = meta_en["featured"][0] == "True"
|
||||||
self.public = meta_en["public"][0] == "True"
|
self.public = meta_en["public"][0] == "True"
|
||||||
# self.thumbnail = meta_en["thumbnail"]
|
# self.thumbnail = meta_en["thumbnail"]
|
||||||
# TODO: parse keywords from markdown
|
# TODO: parse keywords from markdown
|
||||||
# TODO: parse category 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
|
# if keyword or category do not exist, create them
|
||||||
# I suppose
|
# I suppose
|
||||||
except Exception as e:
|
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 = ""
|
||||||
self.body_en = html_en
|
self.body_en = html_en
|
||||||
|
@ -110,7 +132,8 @@ class BlogPost(Searchable):
|
||||||
# TODO: mark as untranslated
|
# TODO: mark as untranslated
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
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:
|
try:
|
||||||
MD.reset()
|
MD.reset()
|
||||||
with open(f"{self.DATA_DIR}/de-{self.slug}.md") as f_de:
|
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)
|
html_de: str = MD.convert(body_de)
|
||||||
try:
|
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
|
meta_de = MD.Meta
|
||||||
self.title_de = meta_de["title"][0]
|
self.title_de = meta_de["title"][0]
|
||||||
self.subtitle_de = meta_de["subtitle"][0]
|
self.subtitle_de = meta_de["subtitle"][0]
|
||||||
|
@ -133,7 +163,8 @@ class BlogPost(Searchable):
|
||||||
# if keyword or category do not exist, create them
|
# if keyword or category do not exist, create them
|
||||||
# I suppose
|
# I suppose
|
||||||
except Exception as e:
|
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 = ""
|
||||||
self.body_de = html_de
|
self.body_de = html_de
|
||||||
|
@ -141,9 +172,10 @@ class BlogPost(Searchable):
|
||||||
# TODO: mark as untranslated
|
# TODO: mark as untranslated
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
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
|
get available languages
|
||||||
"""
|
"""
|
||||||
|
@ -152,7 +184,13 @@ class BlogPost(Searchable):
|
||||||
# SECURITY:
|
# SECURITY:
|
||||||
# If someone could inject the langs field, arbitrary python code might
|
# If someone could inject the langs field, arbitrary python code might
|
||||||
# run, Potentially ending in a critical RCE vulnerability
|
# 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]):
|
def set_langs(self, langs: dict[str, bool]):
|
||||||
"""
|
"""
|
||||||
|
@ -179,7 +217,8 @@ class BlogPost(Searchable):
|
||||||
if not data_dir.is_dir():
|
if not data_dir.is_dir():
|
||||||
logger.error(f"'{cls.DATA_DIR} is not a directory'")
|
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}")
|
logger.debug(f"discovered files: {files}")
|
||||||
|
|
||||||
# finding lang and title
|
# finding lang and title
|
||||||
|
@ -191,6 +230,12 @@ class BlogPost(Searchable):
|
||||||
# parse file name
|
# parse file name
|
||||||
try:
|
try:
|
||||||
matches = re.match(regex, file[0])
|
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)
|
current_lang = matches.group(1)
|
||||||
file[1][current_lang] = True
|
file[1][current_lang] = True
|
||||||
file[2] = matches.group(2)
|
file[2] = matches.group(2)
|
||||||
|
@ -215,12 +260,14 @@ class BlogPost(Searchable):
|
||||||
# only a single version of this file
|
# only a single version of this file
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
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:
|
try:
|
||||||
# deduplicate
|
# deduplicate
|
||||||
_files = []
|
_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:
|
if f not in _files:
|
||||||
_files.append(f)
|
_files.append(f)
|
||||||
files = _files
|
files = _files
|
||||||
|
@ -236,8 +283,6 @@ class BlogPost(Searchable):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Could not create BlogPost for '{file[1]}': {e}")
|
logger.error(f"Could not create BlogPost for '{file[1]}': {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("blog post")
|
verbose_name = _("blog post")
|
||||||
verbose_name_plural = _("blog posts")
|
verbose_name_plural = _("blog posts")
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
{% extends 'admin/change_list.html' %}
|
{% extends 'admin/change_list.html' %} {% block object-tools %}
|
||||||
|
|
||||||
{% block object-tools %}
|
|
||||||
<form action="sync" method="POST">
|
<form action="sync" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit">Sync with FS</button>
|
<button type="submit" class="button" style="padding: 4px">Sync with FS</button>
|
||||||
</form>
|
</form>
|
||||||
{{ block.super }}
|
{{ block.super }} {% endblock %}
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# for getting envvars
|
# for getting envvars
|
||||||
|
import logging
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# default django
|
# default django
|
||||||
|
@ -100,7 +102,6 @@ DATABASES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
@ -123,7 +124,6 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
LANGUAGES = [
|
LANGUAGES = [
|
||||||
("de", _("German")),
|
("de", _("German")),
|
||||||
|
@ -173,7 +173,6 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
# Logging configs
|
# Logging configs
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
myServerFormatter = ServerFormatter
|
myServerFormatter = ServerFormatter
|
||||||
myServerFormatter.default_time_format = "%Y-%M-%d %H:%M:%S"
|
myServerFormatter.default_time_format = "%Y-%M-%d %H:%M:%S"
|
||||||
|
|
|
@ -10,6 +10,7 @@ def regenerate(modeladmin, request, queryset):
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
obj.regenerate()
|
obj.regenerate()
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Keyword)
|
@admin.register(Keyword)
|
||||||
class KeywordAdmin(admin.ModelAdmin):
|
class KeywordAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
|
@ -17,21 +18,25 @@ class KeywordAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
list_display = ["text_en", "text_de"]
|
list_display = ["text_en", "text_de"]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(StaticSite)
|
@admin.register(StaticSite)
|
||||||
class StaticSiteAdmin(admin.ModelAdmin):
|
class StaticSiteAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Admin Interface for StaticSite
|
Admin Interface for StaticSite
|
||||||
"""
|
"""
|
||||||
list_display = ["title_en", "subtitle_en", "title_de", "subtitle_de", "suburl"]
|
list_display = ["title_en", "subtitle_en",
|
||||||
|
"title_de", "subtitle_de", "suburl"]
|
||||||
ordering = ['title_de', 'title_en']
|
ordering = ['title_de', 'title_en']
|
||||||
actions = [regenerate]
|
actions = [regenerate]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Searchable)
|
@admin.register(Searchable)
|
||||||
class SearchableAdmin(admin.ModelAdmin):
|
class SearchableAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Abstract Admin Interface for all Searchables
|
Abstract Admin Interface for all Searchables
|
||||||
"""
|
"""
|
||||||
list_display = ["title_en", "subtitle_en", "title_de", "subtitle_de", "suburl"]
|
list_display = ["title_en", "subtitle_en",
|
||||||
|
"title_de", "subtitle_de", "suburl"]
|
||||||
ordering = ['title_de', 'title_en']
|
ordering = ['title_de', 'title_en']
|
||||||
actions = [regenerate]
|
actions = [regenerate]
|
||||||
|
|
||||||
|
@ -41,6 +46,7 @@ class LinkAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Admin Interface for Links
|
Admin Interface for Links
|
||||||
"""
|
"""
|
||||||
list_display = ["title_en", "title_de", "url", "suburl", "favicon", "status", "personal"]
|
list_display = ["title_en", "title_de", "url",
|
||||||
|
"suburl", "favicon", "status", "personal"]
|
||||||
ordering = ['status', 'title_de', 'title_en']
|
ordering = ['status', 'title_de', 'title_en']
|
||||||
actions = [regenerate]
|
actions = [regenerate]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class StartConfig(AppConfig):
|
class StartConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'start'
|
name = 'start'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
|
||||||
class MainSearchForm(forms.Form):
|
class MainSearchForm(forms.Form):
|
||||||
search = forms.CharField(
|
search = forms.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
|
|
|
@ -15,7 +15,8 @@ class LangBasedOnUrlMiddleware(MiddlewareMixin):
|
||||||
def process_request(request):
|
def process_request(request):
|
||||||
|
|
||||||
if hasattr(request, 'session'):
|
if hasattr(request, 'session'):
|
||||||
active_session_lang = request.session.get(translation.LANGUAGE_SESSION_KEY)
|
active_session_lang = request.session.get(
|
||||||
|
translation.LANGUAGE_SESSION_KEY)
|
||||||
|
|
||||||
if active_session_lang == request.LANGUAGE_CODE:
|
if active_session_lang == request.LANGUAGE_CODE:
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import random
|
||||||
|
import favicon
|
||||||
|
import requests
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.options import override
|
from django.db.models.options import override
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
@ -8,9 +11,6 @@ from django.conf import settings
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
import requests
|
|
||||||
import favicon
|
|
||||||
import random
|
|
||||||
|
|
||||||
class Keyword(models.Model):
|
class Keyword(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -26,6 +26,7 @@ class Keyword(models.Model):
|
||||||
verbose_name = _("Keyword")
|
verbose_name = _("Keyword")
|
||||||
verbose_name_plural = _("keywords")
|
verbose_name_plural = _("keywords")
|
||||||
|
|
||||||
|
|
||||||
class Searchable(models.Model):
|
class Searchable(models.Model):
|
||||||
"""
|
"""
|
||||||
Abstract class for any model that should be searchable.
|
Abstract class for any model that should be searchable.
|
||||||
|
@ -37,8 +38,10 @@ class Searchable(models.Model):
|
||||||
title_en = models.CharField(max_length=50, default="title EN")
|
title_en = models.CharField(max_length=50, default="title EN")
|
||||||
subtitle_de = models.CharField(max_length=50, blank=True)
|
subtitle_de = models.CharField(max_length=50, blank=True)
|
||||||
subtitle_en = models.CharField(max_length=50, blank=True)
|
subtitle_en = models.CharField(max_length=50, blank=True)
|
||||||
desc_de = models.TextField(blank=True, max_length=250, unique=False, default="Beschreibung DE")
|
desc_de = models.TextField(
|
||||||
desc_en = models.TextField(blank=True, max_length=250, unique=False, default="Description EN")
|
blank=True, max_length=250, unique=False, default="Beschreibung DE")
|
||||||
|
desc_en = models.TextField(
|
||||||
|
blank=True, max_length=250, unique=False, default="Description EN")
|
||||||
# may be empty/blank for some entries
|
# may be empty/blank for some entries
|
||||||
date = models.DateField(blank=True, null=True)
|
date = models.DateField(blank=True, null=True)
|
||||||
keywords = models.ManyToManyField(Keyword)
|
keywords = models.ManyToManyField(Keyword)
|
||||||
|
@ -61,12 +64,14 @@ class Searchable(models.Model):
|
||||||
"""
|
"""
|
||||||
regenerate a object
|
regenerate a object
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError(f"{self.__class__.__name__} does not implement regenerate")
|
raise NotImplementedError(
|
||||||
|
f"{self.__class__.__name__} does not implement regenerate")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Searchable")
|
verbose_name = _("Searchable")
|
||||||
verbose_name_plural = _("Searchables")
|
verbose_name_plural = _("Searchables")
|
||||||
|
|
||||||
|
|
||||||
class StaticSite(Searchable):
|
class StaticSite(Searchable):
|
||||||
"""
|
"""
|
||||||
This model represents any static site, such as start:index,
|
This model represents any static site, such as start:index,
|
||||||
|
@ -89,6 +94,7 @@ class StaticSite(Searchable):
|
||||||
verbose_name = _("static site")
|
verbose_name = _("static site")
|
||||||
verbose_name_plural = _("static sites")
|
verbose_name_plural = _("static sites")
|
||||||
|
|
||||||
|
|
||||||
class Link(Searchable):
|
class Link(Searchable):
|
||||||
"""
|
"""
|
||||||
contains all my interesting links
|
contains all my interesting links
|
||||||
|
@ -121,11 +127,13 @@ class Link(Searchable):
|
||||||
icons = favicon.get(self.url, timeout=2)
|
icons = favicon.get(self.url, timeout=2)
|
||||||
except (ConnectionError) as ce:
|
except (ConnectionError) as ce:
|
||||||
# just keep whatever was stored if we cant get a new favicon
|
# just keep whatever was stored if we cant get a new favicon
|
||||||
logger.warn(f"unable to download favicon for {self}: {ce.with_traceback(None)}")
|
logger.warn(
|
||||||
|
f"unable to download favicon for {self}: {ce.with_traceback(None)}")
|
||||||
self.status = False
|
self.status = False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn(f"Unexpected Exception while downloading {self}: {e.with_traceback(None)}")
|
logger.warn(
|
||||||
|
f"Unexpected Exception while downloading {self}: {e.with_traceback(None)}")
|
||||||
self.status = False
|
self.status = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -146,7 +154,8 @@ class Link(Searchable):
|
||||||
self.favicon = None
|
self.favicon = None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn(f"Unexpected Exception while downloading {self}: {e.with_traceback(None)}")
|
logger.warn(
|
||||||
|
f"Unexpected Exception while downloading {self}: {e.with_traceback(None)}")
|
||||||
self.favicon = None
|
self.favicon = None
|
||||||
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
|
@ -36,4 +36,3 @@ def change_lang(context, lang="de", *args, **kwargs):
|
||||||
finally:
|
finally:
|
||||||
activate(lang)
|
activate(lang)
|
||||||
return "%s" % url
|
return "%s" % url
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,6 @@ urlpatterns = [
|
||||||
path("legal/", views.LegalInfo.as_view(), name="legal"),
|
path("legal/", views.LegalInfo.as_view(), name="legal"),
|
||||||
path("links/", views.Links.as_view(), name="links"),
|
path("links/", views.Links.as_view(), name="links"),
|
||||||
path("professional/", views.LegalInfo.as_view(), name="professional"),
|
path("professional/", views.LegalInfo.as_view(), name="professional"),
|
||||||
path('language/activate/<language_code>/', views.ActivateLanguage.as_view(), name='activate_language'),
|
path('language/activate/<language_code>/',
|
||||||
|
views.ActivateLanguage.as_view(), name='activate_language'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -18,6 +18,7 @@ from abc import ABC
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SearchableView(View, ABC):
|
class SearchableView(View, ABC):
|
||||||
"""
|
"""
|
||||||
This abstract view implements some traits of views that should show up
|
This abstract view implements some traits of views that should show up
|
||||||
|
@ -40,6 +41,7 @@ class Index(TemplateView, SearchableView):
|
||||||
|
|
||||||
template_name: str = "start/index.html"
|
template_name: str = "start/index.html"
|
||||||
|
|
||||||
|
|
||||||
class Professional(TemplateView, SearchableView):
|
class Professional(TemplateView, SearchableView):
|
||||||
"""
|
"""
|
||||||
Professional informations that might interest a professional employer
|
Professional informations that might interest a professional employer
|
||||||
|
@ -47,6 +49,7 @@ class Professional(TemplateView, SearchableView):
|
||||||
# TODO
|
# TODO
|
||||||
template_name: str = "start/legalinfo.html"
|
template_name: str = "start/legalinfo.html"
|
||||||
|
|
||||||
|
|
||||||
class LegalInfo(TemplateView, SearchableView):
|
class LegalInfo(TemplateView, SearchableView):
|
||||||
"""
|
"""
|
||||||
Legal info that the german authorities want.
|
Legal info that the german authorities want.
|
||||||
|
@ -54,6 +57,7 @@ class LegalInfo(TemplateView, SearchableView):
|
||||||
# TODO
|
# TODO
|
||||||
template_name: str = "start/legalinfo.html"
|
template_name: str = "start/legalinfo.html"
|
||||||
|
|
||||||
|
|
||||||
class ActivateLanguage(View):
|
class ActivateLanguage(View):
|
||||||
"""
|
"""
|
||||||
Set the language to whatever
|
Set the language to whatever
|
||||||
|
@ -68,6 +72,7 @@ class ActivateLanguage(View):
|
||||||
request.session[translation.LANGUAGE_SESSION_KEY] = self.language_code
|
request.session[translation.LANGUAGE_SESSION_KEY] = self.language_code
|
||||||
return redirect(self.redirect_to)
|
return redirect(self.redirect_to)
|
||||||
|
|
||||||
|
|
||||||
class MainSearch(ListView):
|
class MainSearch(ListView):
|
||||||
"""
|
"""
|
||||||
Search for anything.
|
Search for anything.
|
||||||
|
@ -94,6 +99,7 @@ class MainSearch(ListView):
|
||||||
return render(request, "errors/bad_request.html")
|
return render(request, "errors/bad_request.html")
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Links(ListView):
|
class Links(ListView):
|
||||||
"""
|
"""
|
||||||
This View contains links to various interesting sites.
|
This View contains links to various interesting sites.
|
||||||
|
|
Loading…
Add table
Reference in a new issue