2025.12.23 17:30:33 (cachyos.cmoser.eu)
This commit is contained in:
BIN
.media/tinywiki/img/fabian-bachli-jQAe44MEIXU-unsplash.jpg
Normal file
BIN
.media/tinywiki/img/fabian-bachli-jQAe44MEIXU-unsplash.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 MiB |
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -1,3 +1,5 @@
|
||||
{
|
||||
"python-envs.pythonProjects": []
|
||||
"python-envs.pythonProjects": [],
|
||||
"python-envs.defaultEnvManager": "ms-python.python:poetry",
|
||||
"python-envs.defaultPackageManager": "ms-python.python:poetry"
|
||||
}
|
||||
@@ -122,6 +122,7 @@ TEMPLATES = [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'tinywiki.context_processors.sidebar',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -202,6 +203,12 @@ else:
|
||||
print("Email backend not known falling back to console!",file=sys.stderr)
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
|
||||
CSP_DEFAULT_SRC = [
|
||||
"'self'",
|
||||
"https://youtube.com",
|
||||
]
|
||||
|
||||
_secret_key = LOCAL_DIR / "secret_key.py"
|
||||
if _secret_key.is_file():
|
||||
from .local import secret_key
|
||||
|
||||
1064
poetry.lock
generated
1064
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ dependencies = [
|
||||
"pillow (>=11.3.0,<12.0.0)",
|
||||
"bbcode (>=1.1.0,<2.0.0)",
|
||||
"markdown (>=3.9,<4.0)",
|
||||
"django-csp (>=4.0,<5.0)",
|
||||
]
|
||||
|
||||
[tool.poetry]
|
||||
|
||||
@@ -1,19 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
: ${UWSGI_BUFFER_SIZE:=32768}
|
||||
: ${UWSGI_SOCKET_NAME:=uwsgi.sock}
|
||||
: ${UWSGI_SOCKET:=/data/run/${UWSGI_SOCKET_NAME}}
|
||||
: ${UWSGI_PIDFILE_NAME:=uwsgi.pid}
|
||||
: ${UWSGI_PIDFILE:=/data/run/${UWSGI_SOCKET_NAME}}
|
||||
: ${UWSGI_PROCESSES:=4}
|
||||
: ${HTTP_PORT:=8000}
|
||||
: ${HTTP_ADDRESS:=0.0.0.0}
|
||||
: ${HTTP_SOCKET:=${HTTP_ADDRESS}:${HTTP_PORT}}
|
||||
: ${UWSGI_MAX_REQUESTS:=4000}
|
||||
|
||||
migrate_db() {
|
||||
echo "Migrating database ..."
|
||||
poetry run python manage.py migrate
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Unable to migrate database!" >&2
|
||||
exit 5
|
||||
fi
|
||||
}
|
||||
|
||||
collectstatic() {
|
||||
echo "Running collectstatic ..."
|
||||
yes yes | poetry run python manage.py collectstatic
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Unable to collect static files!" >&2
|
||||
exit 5
|
||||
fi
|
||||
}
|
||||
|
||||
createsuperuser() {
|
||||
poetry run python manage.py createsuperuser "$@"
|
||||
}
|
||||
|
||||
if [ $# -eq 0 -o "$1" = "start" ]; then
|
||||
if [ -z "$DEBUG" -o $DEBUG != "1" ]; then
|
||||
echo "Starting UWSGI server ..."
|
||||
venv="$(poetry env info | head -n 6 | grep Path | awk '{print $2}')"
|
||||
@@ -21,15 +41,15 @@ if [ -z "$DEBUG" -o $DEBUG != "1" ]; then
|
||||
--chdir /app \
|
||||
--module django_project.wsgi:application \
|
||||
--master --pidfile /data/run/uwsgi.pid \
|
||||
--http-socket '0.0.0.0:8000' \
|
||||
--socket /data/run/uwsgi.sock \
|
||||
--processes 5 \
|
||||
--http-socket "$HTTP_SOCKET" \
|
||||
--socket "$UWSGI_SOCKET" \
|
||||
--processes $UWSGI_PROCESSES \
|
||||
--harakiri 60 \
|
||||
--max-requests 5000 \
|
||||
--max-requests $UWSGI_MAX_REQUESTS \
|
||||
--buffer-size $UWSGI_BUFFER_SIZE \
|
||||
--vacuum \
|
||||
--venv "$venv" \
|
||||
--home "$venv"
|
||||
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "UWSGI Server not started" >&2
|
||||
@@ -37,10 +57,28 @@ if [ -z "$DEBUG" -o $DEBUG != "1" ]; then
|
||||
fi
|
||||
else
|
||||
echo "Starting development server ..."
|
||||
poetry run python manage.py runserver 0.0.0.0:8000
|
||||
poetry run python manage.py runserver $HTTP_SOCKET
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "Developemnt server was not started!" >&2
|
||||
exit $rc
|
||||
fi
|
||||
fi
|
||||
elif [ "$1" == "init" -o "$1" == "update" ]; then
|
||||
migrate_db
|
||||
collectstatic
|
||||
elif [ "$1" == "suinit" ]; then
|
||||
shift
|
||||
migrate_db
|
||||
collectstatic
|
||||
createsuperuser "$@"
|
||||
elif [ "$1" == "migrate" ]; then
|
||||
migrate_db
|
||||
elif [ "$1" == "collectstatic" ]; then
|
||||
collectstatic
|
||||
elif [ "$1" == "createsuperuser" ]; then
|
||||
shift
|
||||
createsuperuser "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
||||
|
||||
@@ -66,23 +66,9 @@
|
||||
</div>
|
||||
<div class="row h-100 mb-4">
|
||||
<div class="col-lg-3 pt-6 px-4 d-none d-lg-block" >
|
||||
<ul class="list-group mt-2">
|
||||
<li class="list-group-item bg-primary text-white">
|
||||
<span class="list-group-badge">TinyWiki Pages</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a href="{% url "tinywiki:page" slug="tw-bbcode" %}">BBCode Guide</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a href="{% url "tinywiki:page" slug="tw-markdown" %}">Markdown Guide</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a href="{% url "tinywiki:page" slug="tw-license" %}">TinyWiki License</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a href="{% url "tinywiki:page" slug="tw-bootstrap-license" %}">Bootstrap License</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% block left_sidebar %}
|
||||
{{ tinywiki_sidebar }}
|
||||
{% enblock left_sidebar %}
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<main>
|
||||
|
||||
10
tinywiki/context_processors
Normal file
10
tinywiki/context_processors
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.http import HttpRequest
|
||||
from django.utils.safestring import mark_safe
|
||||
from .models import SidebarSection
|
||||
|
||||
def sidebar(request: HttpRequest):
|
||||
sections = [
|
||||
section.widget
|
||||
for section in SidebarSection.objects.filter(is_visible=True).order_by('-priority'):
|
||||
]
|
||||
return {'tinywiki_sidebar': mark_safe("\n".join(sections))}
|
||||
@@ -7,10 +7,12 @@ class Migration(migrations.Migration):
|
||||
('tinywiki', '0001_initial'),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def init_tinywiki_user(apps, schema_editor):
|
||||
from django.contrib.auth import get_user_model
|
||||
user = get_user_model().objects.create_user(**settings.TINYWIKI_USER_CONFIG)
|
||||
get_user_model().objects.create_user(**settings.TINYWIKI_USER_CONFIG)
|
||||
|
||||
@staticmethod
|
||||
def init_tinywiki_groups_and_permissions(apps, schema_editor):
|
||||
from ..models import Page
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
@@ -128,4 +130,3 @@ class Migration(migrations.Migration):
|
||||
migrations.RunPython(init_builtin_images),
|
||||
migrations.RunPython(init_builtin_pages),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Generated by Django 5.2.9 on 2025-12-23 15:57
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tinywiki', '0002_initial_data'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SidebarSection',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=255, verbose_name='title')),
|
||||
('priority', models.PositiveIntegerField(verbose_name='priority')),
|
||||
('is_visible', models.BooleanField(default=True, verbose_name='is visible')),
|
||||
],
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='page',
|
||||
name='status_data',
|
||||
field=models.CharField(choices=[('in_progress', 'in progress'), ('draft', 'draft'), ('published', 'published')], default='in_progress', max_length=15, verbose_name='status'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SidebarEntry',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=255, verbose_name='Title')),
|
||||
('is_visible', models.BooleanField(default=True, verbose_name='is visible')),
|
||||
('priority', models.PositiveIntegerField(verbose_name='Priority')),
|
||||
('wiki_slug', models.CharField(blank=True, max_length=255, null=True, verbose_name='Wiki slug')),
|
||||
('url', models.CharField(blank=True, max_length=512, null=True, verbose_name='Link URL')),
|
||||
('widget', models.TextField(blank=True, null=True, verbose_name='Widget')),
|
||||
('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='tinywiki.sidebarsection', verbose_name='Section')),
|
||||
],
|
||||
),
|
||||
]
|
||||
92
tinywiki/migrations/0004_sidebar.py
Normal file
92
tinywiki/migrations/0004_sidebar.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# Generated by Django 5.2.9 on 2025-12-23 15:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
@staticmethod
|
||||
def create_tinywiki_sections(apps, schema_editor):
|
||||
from tinywiki.models import SidebarSection, SidebarEntry
|
||||
|
||||
data = [
|
||||
{
|
||||
"title": "TinyWiki Pages",
|
||||
"is_visible": True,
|
||||
"priority": 1000,
|
||||
"items": [
|
||||
{
|
||||
"title": "BBCode Guide",
|
||||
"is_visible": True,
|
||||
"priority": 100,
|
||||
"wiki_slug": "tw-bbcode",
|
||||
},
|
||||
{
|
||||
"title": "Markdown Guide",
|
||||
"is_visible": True,
|
||||
"priority": 90,
|
||||
"wiki_slug": "tw-markdown"
|
||||
},
|
||||
{
|
||||
"title": "TinyWiki License",
|
||||
"is_visible": True,
|
||||
"priority": 80,
|
||||
"wiki_slug": "tw-license"
|
||||
},
|
||||
{
|
||||
"title": "Bootstrap License",
|
||||
"is_visible": True,
|
||||
"priority": 70,
|
||||
"wiki_slug": "tw-bootstrap-license"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "TinyWiki Seiten",
|
||||
"is_visible": False,
|
||||
"priority": 1000,
|
||||
"items": [
|
||||
{
|
||||
"title": "BBCode Anleitung",
|
||||
"is_visible": True,
|
||||
"priority": 100,
|
||||
"wiki_slug": "tw-de-bbcode",
|
||||
},
|
||||
{
|
||||
"title": "Markdown Anleitung",
|
||||
"is_visible": True,
|
||||
"priority": 90,
|
||||
"wiki_slug": "tw-de-markdown"
|
||||
},
|
||||
{
|
||||
"title": "TinyWiki Lizenz (EN)",
|
||||
"is_visible": True,
|
||||
"priority": 80,
|
||||
"wiki_slug": "tw-license"
|
||||
},
|
||||
{
|
||||
"title": "Bootstrap License (EN)",
|
||||
"is_visible": True,
|
||||
"priority": 70,
|
||||
"wiki_slug": "tw-bootstrap-license"
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
for sect_spec in data:
|
||||
if 'items' in sect_spec:
|
||||
items = sect_spec.pop('items')
|
||||
else:
|
||||
items = []
|
||||
|
||||
section = SidebarSection.objects.create(**sect_spec)
|
||||
for item_spec in items:
|
||||
SidebarEntry.objects.create(section=section, **item_spec)
|
||||
|
||||
dependencies = [
|
||||
('tinywiki', '0003_sidebarsection_alter_page_status_data_sidebarentry'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
@@ -1,12 +1,17 @@
|
||||
from tabnanny import verbose
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils.safestring import mark_safe, SafeText
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from tinywiki.enums import WIKI_CONTENT_TYPES, WIKI_PAGE_STATUS, WikiContentType, WikiPageStatus
|
||||
from django.utils.html import escape
|
||||
from django.urls import reverse
|
||||
from tinywiki.enums import (
|
||||
WIKI_CONTENT_TYPES,
|
||||
WIKI_PAGE_STATUS,
|
||||
WikiContentType,
|
||||
WikiPageStatus,
|
||||
)
|
||||
import markdown
|
||||
|
||||
import bbcode as _bbcode
|
||||
@@ -16,6 +21,7 @@ from .parser import parse_bbcode
|
||||
from . import settings
|
||||
import tinywiki
|
||||
|
||||
|
||||
def get_tinywiki_default_user():
|
||||
UserModel = get_user_model()
|
||||
try:
|
||||
@@ -24,6 +30,7 @@ def get_tinywiki_default_user():
|
||||
user = UserModel.objects.filter(is_superuser=True).order_by('pk')[0]
|
||||
return user
|
||||
|
||||
|
||||
class Page(models.Model):
|
||||
slug = models.SlugField(_("slug"),
|
||||
max_length=255,
|
||||
@@ -134,3 +141,77 @@ class Image(models.Model):
|
||||
def description_html_safe(self) -> SafeText:
|
||||
return mark_safe(self.description_html)
|
||||
|
||||
|
||||
class SidebarSection(models.Model):
|
||||
title = models.CharField(_("title"),
|
||||
max_length=255,
|
||||
null=False,
|
||||
blank=False)
|
||||
priority = models.PositiveIntegerField(_("priority"))
|
||||
is_visible = models.BooleanField(_("is visible"),
|
||||
default=True)
|
||||
|
||||
@property
|
||||
def widget(self) -> SafeText:
|
||||
if settings.USE_BOOTSTRAP:
|
||||
lines = [
|
||||
"<ul class=\"list-group mt-2\">",
|
||||
f"<li class=\"list-group-item bg-primary text-white\">{escape(self.title)}</li>", # noqa: E501
|
||||
*[item.item for item in self.items.filter(is_visible=True).order_by('-priority')], # noqa: E501
|
||||
"</ul>"
|
||||
]
|
||||
else:
|
||||
lines = [
|
||||
"<ul class=\"sidebar-section\">",
|
||||
f"<li class=\"sidebar-title\">{escape(self.title)}</li>",
|
||||
*[item.item for item in self.items.filter(is_visible=True).order_by('-priority')], # noqa: E501
|
||||
"</ul>"
|
||||
]
|
||||
return mark_safe("\n".join(lines))
|
||||
|
||||
|
||||
class SidebarEntry(models.Model):
|
||||
section = models.ForeignKey(SidebarSection,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("Section"),
|
||||
related_name="items")
|
||||
title = models.CharField(_("Title"),
|
||||
max_length=255,
|
||||
null=False,
|
||||
blank=False)
|
||||
is_visible = models.BooleanField(_("is visible"),
|
||||
default=True)
|
||||
priority = models.PositiveIntegerField(_("Priority"))
|
||||
wiki_slug = models.CharField(_("Wiki slug"),
|
||||
max_length=255,
|
||||
null=True,
|
||||
blank=True)
|
||||
url = models.CharField(_("Link URL"),
|
||||
max_length=512,
|
||||
null=True,
|
||||
blank=True)
|
||||
|
||||
widget = models.TextField(_("Widget"),
|
||||
blank=True,
|
||||
null=True)
|
||||
|
||||
@property
|
||||
def link(self) -> SafeText:
|
||||
if self.wiki_slug:
|
||||
return mark_safe(f"<a href=\"{reverse('tinywiki:page', kwargs={'slug': self.wiki_slug})}\">{escape(self.title)}</a>") # noqa: E501
|
||||
elif self.url:
|
||||
return mark_safe(f"<a href=\"{self.url}\">{escape(self.title)}</a>") # noqa E501
|
||||
return mark_safe(f"<a href=\"#\">{escape(self.title)}</a>") # noqa E501
|
||||
|
||||
@property
|
||||
def item(self) -> SafeText:
|
||||
if settings.USE_BOOTSTRAP:
|
||||
if self.widget:
|
||||
return mark_safe(f"<li class=\"list-group-item\">{self.widget}</li>") # noqa: E501
|
||||
else:
|
||||
return mark_safe(f"<li class=\"list-group-item\">{self.link}</li>") # noqa: E501
|
||||
|
||||
if self.widget:
|
||||
return mark_safe(f"<li class=\"sidebar-item\">{self.widget}</li>")
|
||||
else:
|
||||
return mark_safe(f"<li class=\"sidebar-item\">{self.link}</li>")
|
||||
|
||||
@@ -334,6 +334,7 @@ def render_table_data(tag_name:str,value,options,parent,context):
|
||||
class_attr = f"class=\"{" ".join(classes)}\"" if classes else ""
|
||||
return f"<td {class_attr} {" ".join(extra_attributes)}>{value}</td>"
|
||||
|
||||
|
||||
def render_youtube_video(tag_name: str, value, options, parent, context):
|
||||
if tag_name not in options:
|
||||
return ""
|
||||
@@ -345,8 +346,11 @@ def render_youtube_video(tag_name: str, value, options, parent, context):
|
||||
|
||||
if settings.USE_BOOTSTRAP:
|
||||
styles = []
|
||||
classes = ["w-100"]
|
||||
div_classes = ["my-1"]
|
||||
classes = ["embed-responsive-item", "w-100"]
|
||||
div_classes = [
|
||||
"embed-responsive",
|
||||
"my-1",
|
||||
]
|
||||
div_styles = []
|
||||
else:
|
||||
styles = ["max-width:100%;"]
|
||||
@@ -425,6 +429,8 @@ def render_youtube_video(tag_name: str, value, options, parent, context):
|
||||
else:
|
||||
div_style = ""
|
||||
if settings.USE_BOOTSTRAP:
|
||||
return f'<div class=""{' '.join(div_classes)}" {div_style}><iframe class={" ".join(classes)}" src="https://www.youtube.com/embed/{options[tag_name]}" allowfullscreen></iframe></div>'
|
||||
return f"""<div class=""{' '.join(div_classes)}" {div_style}>
|
||||
<iframe class={" ".join(classes)}" src="https://www.youtube.com/embed/{options[tag_name]}?rel=0" allowfullscreen></iframe>
|
||||
</div>"""
|
||||
else:
|
||||
return f'<div {div_style}><iframe src="https://www.youtube.com/embed/{options[tag_name]}?rel=0" allowfullscreen></iframe></div>'
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.urls import reverse
|
||||
from django.http import HttpRequest, HttpResponse, Http404
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from .. import settings
|
||||
@@ -12,6 +12,8 @@ from ..models import Page
|
||||
from .base import View,FormView
|
||||
from ..forms import PageForm,PageAdminForm,PageDeleteForm
|
||||
|
||||
|
||||
|
||||
class PageView(View):
|
||||
template_name = "tinywiki/page/view.html"
|
||||
bs_template_name = "tinywiki/page/bs-view.html"
|
||||
@@ -39,7 +41,8 @@ class PageView(View):
|
||||
or (user.pk == page.author.pk and user.has_perm('page.tinywiki-edit'))):
|
||||
can_edit_page = True
|
||||
if (user.has_perm('page.tinywiki-delete-all')
|
||||
or (user.pk == page.author.pk and user.has_perm('page.tinywiki-delete'))):
|
||||
or (user.pk == page.author.pk
|
||||
and user.has_perm('page.tinywiki-delete'))):
|
||||
can_delete_page = True
|
||||
|
||||
kwargs.update({'page':page,
|
||||
@@ -48,7 +51,6 @@ class PageView(View):
|
||||
'subtitle':page.title})
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
def get(self, request: HttpRequest, slug: str) -> HttpResponse:
|
||||
try:
|
||||
page = Page.objects.get(slug=slug)
|
||||
@@ -60,6 +62,7 @@ class PageView(View):
|
||||
self.get_template_name(),
|
||||
self.get_context_data(page=page))
|
||||
|
||||
|
||||
class PageCreateView(LoginRequiredMixin, UserPassesTestMixin, FormView):
|
||||
template_name = "tinywiki/page/create.html"
|
||||
bs_template_name = "tinywiki/page/bs-create.html"
|
||||
|
||||
Reference in New Issue
Block a user