2025.09.14-18:02:28

This commit is contained in:
2025-09-14 18:02:28 +02:00
parent 565ef0cad1
commit ff37c9cd8b
32 changed files with 733 additions and 22 deletions

6
.gitignore vendored
View File

@@ -439,6 +439,8 @@ pip-selfcheck.json
# Built Visual Studio Code Extensions # Built Visual Studio Code Extensions
*.vsix *.vsix
djangocourse/privsettings.py
.venv* .venv*
django_project/local/
.data
db.sqlite

View File

@@ -1,12 +1,20 @@
FROM docker.io/library/python:3.13-trixie FROM docker.io/library/python:3.13-trixie
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
RUN apt-update \ ENV DEBUG="1"
&& apt-upgrade -y \ ENV DATABASE_URL="sqlite:////data/database/tinywiki.sqlite"
ENV MEDIA_URL="media/"
ENV MEDIA_ROOT="/data/media"
ENV STATIC_URL="static/"
ENV STATIC_ROOT="/data/static"
RUN apt update \
&& apt upgrade -y \
&& apt install -y gettext libxmlsec1-dev xmlsec1\ && apt install -y gettext libxmlsec1-dev xmlsec1\
#&& python -m pip install upgrade pip \ && python -m pip install --upgrade pip \
&& pip install poetry \ && pip install poetry \
&& mkdir /app \ && mkdir /app \
&& mkdir -p /data/static /data/media && mkdir -p /data/static /data/media /data/database
VOLUME "/data"
WORKDIR /app WORKDIR /app
COPY . . COPY . .
@@ -16,4 +24,3 @@ RUN poetry install --all-groups \
EXPOSE 8000 EXPOSE 8000
ENTRYPOINT ["/app/start-django.sh"] ENTRYPOINT ["/app/start-django.sh"]

View File

View File

@@ -11,6 +11,8 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
""" """
from pathlib import Path from pathlib import Path
import sys
from environ import Env from environ import Env
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@@ -20,12 +22,24 @@ ENV = Env(
ALLOWED_HOSTS=(list,['*']), ALLOWED_HOSTS=(list,['*']),
STATIC_URL=(str,"static/"), STATIC_URL=(str,"static/"),
STATIC_ROOT=(Path,BASE_DIR/".static"), STATIC_ROOT=(Path,BASE_DIR/".static"),
MEDIA_URL=("media/") MEDIA_URL=(str,"media/"),
MEDIA_ROOT=(Path,BASE_DIR/".media"), MEDIA_ROOT=(Path,BASE_DIR/".media"),
SECRET_KEY=(str,'django-insecure-tqis9c9@z_=cq36ic4h-l7h!ln8*@_*+e96z0m^-^mx_avdcw*') SECRET_KEY=(str,'django-insecure-tqis9c9@z_=cq36ic4h-l7h!ln8*@_*+e96z0m^-^mx_avdcw*'),
EMAIL_BACKEND=(str,"console"),
) )
DEBUG = True DEBUG = ENV.bool("DEBUG")
DEBUG = ENV.bool("DEBUG")
if DEBUG:
_env_file = Path(ENV.path("DOTENV",str(BASE_DIR/'.env.dev'))).resolve()
if _env_file.is_file():
ENV.read_env(_env_file)
else:
_env_file = Path(ENV.path("DOTENV",str(BASE_DIR/'.env.prod'))).resolve()
if _env_file.is_file():
ENV.read_env(_env_file)
del _env_file
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
@@ -40,8 +54,7 @@ ALLOWED_HOSTS = ENV.list("ALLOWED_HOSTS")
# Application definition # Application definition
django_apps= [
INSTALLED_APPS = [
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
@@ -50,6 +63,23 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', 'django.contrib.staticfiles',
] ]
third_party_apps = [
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.oauth2', # required for github
'allauth.socialaccount.providers.github',
'allauth.socialaccount.providers.google',
'allauth.socialaccount.providers.openid',
'allauth.socialaccount.providers.steam',
'widget_tweaks',
]
project_apps = [
'user',
'tinywiki',
]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
@@ -58,6 +88,30 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
'allauth.account.middleware.AccountMiddleware',
]
if DEBUG:
third_party_apps += [
"django_browser_reload",
"debug_toolbar",
]
MIDDLEWARE = [
"debug_toolbar.middleware.DebugToolbarMiddleware",
*MIDDLEWARE,
'django_browser_reload.middleware.BrowserReloadMiddleware',
]
import socket
hostname, _x, ips = socket.gethostbyname_ex(socket.gethostname())
podman_ips = [ip for ip in ips]
docker_ips = [ip[:-1] + "1" for ip in ips]
INTERNAL_IPS = podman_ips + docker_ips + ["127.0.0.1", "localhost"] + ["192.168.65.1"]
INSTALLED_APPS = [
*django_apps,
*third_party_apps,
*project_apps,
] ]
ROOT_URLCONF = 'django_project.urls' ROOT_URLCONF = 'django_project.urls'
@@ -91,6 +145,8 @@ DATABASES = {
# Password validation # Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_USER_MODEL = "user.UserProfile"
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
@@ -130,3 +186,19 @@ MEDIA_ROOT = ENV("MEDIA_ROOT")
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
if ENV("EMAIL_BACKEND") == 'smtp':
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = ENV("EMAIL_HOST","localhost")
EMAIL_PORT = ENV.int("EMAIL_PORT",25)
EMAIL_HOST_USER = ENV("EMAIL_HOST_USER","")
EMAIL_HOST_PASSWORD = ENV("EMAIL_HOST_PASSWORD","")
EMAIL_SSL_KEYFILE = ENV("EMAIL_SSL_KEYFILE",None)
EMAIL_SSL_CERTFILE = ENV("EMAIL_SSL_CERTFILE",None)
EMAIL_TIMEOUT = ENV("EMAIL_TIMEOUT", 60)
else:
if ENV("EMAIL_BACKEND") != 'console':
print("Email backend not known falling back to console!",file=sys.stderr)
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

View File

@@ -18,6 +18,8 @@ from django.contrib import admin
from django.urls import path,include from django.urls import path,include
from django.conf import settings from django.conf import settings
urlpatterns = [ urlpatterns = [
path('',include("tinywiki.urls")),
path("user/",include("user.urls")),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
] ]
@@ -28,5 +30,5 @@ if settings.DEBUG:
*debug_toolbar_urls(), *debug_toolbar_urls(),
*static(settings.STATIC_URL, document_root=settings.STATIC_ROOT), *static(settings.STATIC_URL, document_root=settings.STATIC_ROOT),
*static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),
path('__reload__/',include("django_browser_relaod.urls")) path('__reload__/',include("django_browser_reload.urls"))
] ]

167
poetry.lock generated
View File

@@ -80,6 +80,18 @@ files = [
[package.extras] [package.extras]
visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"] visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"]
[[package]]
name = "bbcode"
version = "1.1.0"
description = "A pure python bbcode parser and formatter."
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "bbcode-1.1.0-py2.py3-none-any.whl", hash = "sha256:83802f4b40c92426841a98350bd6ff9ea8fdf8f9b37df1968a88c5864fd225fa"},
{file = "bbcode-1.1.0.tar.gz", hash = "sha256:eac4fb1d0f6c7ce5c41e4b5c0522562b15a1ac036fb9131adc59e9a28c7dc1d0"},
]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2025.8.3" version = "2025.8.3"
@@ -410,13 +422,13 @@ bcrypt = ["bcrypt"]
[[package]] [[package]]
name = "django-allauth" name = "django-allauth"
version = "65.11.0" version = "65.11.2"
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication." description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"] groups = ["main"]
files = [ files = [
{file = "django_allauth-65.11.0.tar.gz", hash = "sha256:d08ee0b60a1a54f84720bb749518628c517c9af40b6cfb3bc980206e182745ab"}, {file = "django_allauth-65.11.2.tar.gz", hash = "sha256:7b7e771d3384d0e247d0d6aef31b0cb589f92305b7e975e70056a513525906e7"},
] ]
[package.dependencies] [package.dependencies]
@@ -438,14 +450,14 @@ steam = ["python3-openid (>=3.0.8,<4)"]
[[package]] [[package]]
name = "django-browser-reload" name = "django-browser-reload"
version = "1.18.0" version = "1.19.0"
description = "Automatically reload your browser in development." description = "Automatically reload your browser in development."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["dev"] groups = ["main", "dev"]
files = [ files = [
{file = "django_browser_reload-1.18.0-py3-none-any.whl", hash = "sha256:ed4cc2fb83c3bf6c30b54107a1a6736c0b896e62e4eba666d81005b9f2ecf6f8"}, {file = "django_browser_reload-1.19.0-py3-none-any.whl", hash = "sha256:ac67c304654b77811abb4ebfa3802554a4e4b4ab030f8623b21e7b606efea160"},
{file = "django_browser_reload-1.18.0.tar.gz", hash = "sha256:c5f0b134723cbf2a0dc9ae1ee1d38e42db28fe23c74cdee613ba3ef286d04735"}, {file = "django_browser_reload-1.19.0.tar.gz", hash = "sha256:034939f770832fde374035e9e4d904c6f990d75598edb777626b5202587315d7"},
] ]
[package.dependencies] [package.dependencies]
@@ -560,6 +572,22 @@ setuptools = ">=61.0"
[package.extras] [package.extras]
scripts = ["click (>=6.0)"] scripts = ["click (>=6.0)"]
[[package]]
name = "markdown"
version = "3.9"
description = "Python implementation of John Gruber's Markdown."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280"},
{file = "markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a"},
]
[package.extras]
docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
testing = ["coverage", "pyyaml"]
[[package]] [[package]]
name = "oauthlib" name = "oauthlib"
version = "3.3.1" version = "3.3.1"
@@ -577,6 +605,131 @@ rsa = ["cryptography (>=3.0.0)"]
signals = ["blinker (>=1.4.0)"] signals = ["blinker (>=1.4.0)"]
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "pillow"
version = "11.3.0"
description = "Python Imaging Library (Fork)"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"},
{file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"},
{file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"},
{file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"},
{file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"},
{file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"},
{file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"},
{file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"},
{file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"},
{file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"},
{file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"},
{file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"},
{file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"},
{file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"},
{file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"},
{file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"},
{file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"},
{file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"},
{file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"},
{file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"},
{file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"},
{file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"},
{file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"},
{file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"},
{file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"},
{file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"},
{file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"},
{file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"},
{file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"},
{file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"},
{file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"},
{file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"},
{file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"},
{file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"},
{file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"},
{file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"},
{file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"},
{file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"},
{file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"},
{file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"},
{file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"},
{file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"},
{file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"},
{file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"},
{file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"},
{file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"},
{file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"},
{file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"},
{file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"},
{file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"},
{file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"},
{file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"},
{file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"},
{file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"},
{file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"},
{file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"},
{file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"},
{file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"},
{file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"},
{file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"},
{file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"},
{file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"},
{file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"},
{file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"},
{file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"},
{file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"},
{file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"},
{file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"},
{file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"},
{file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"},
{file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"},
{file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"},
{file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"},
{file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"},
{file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"},
{file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"},
{file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"},
{file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"},
{file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"},
{file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"},
{file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"},
{file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"},
{file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"},
{file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"},
{file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"},
{file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"},
{file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"},
{file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"},
{file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"},
{file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"},
{file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"},
{file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"},
{file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"},
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"},
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"},
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"},
{file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"},
{file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"},
{file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"},
{file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"},
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"},
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"},
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"},
{file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"},
{file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"},
{file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"},
]
[package.extras]
docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
fpx = ["olefile"]
mic = ["olefile"]
test-arrow = ["pyarrow"]
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"]
typing = ["typing-extensions ; python_version < \"3.10\""]
xmp = ["defusedxml"]
[[package]] [[package]]
name = "pyasn1" name = "pyasn1"
version = "0.6.1" version = "0.6.1"
@@ -931,4 +1084,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.12,<3.14" python-versions = ">=3.12,<3.14"
content-hash = "84268673ea099e0b577d38525faa67b358098040438c30985c376d71c7326fa0" content-hash = "cdc572d2076797697db50f74bfecc2a67620b6a7bcc8eb3a799e816848c0c261"

View File

@@ -7,10 +7,14 @@ authors = [
requires-python = ">=3.12,<3.14" requires-python = ">=3.12,<3.14"
dependencies = [ dependencies = [
"django (>=5.2.4,<6.0.0)", "django (>=5.2.4,<6.0.0)",
"django-allauth[saml2,socialaccount,steam] (>=65.11.0,<66.0.0)",
"django-extensions (>=4.1,<5.0)", "django-extensions (>=4.1,<5.0)",
"django-widget-tweaks (>=1.5.0,<2.0.0)", "django-widget-tweaks (>=1.5.0,<2.0.0)",
"django-environ (>=0.12.0,<0.13.0)", "django-environ (>=0.12.0,<0.13.0)",
"django-allauth[socialaccount,steam] (>=65.11.2,<66.0.0)",
"django-browser-reload (>=1.19.0,<2.0.0)",
"pillow (>=11.3.0,<12.0.0)",
"bbcode (>=1.1.0,<2.0.0)",
"markdown (>=3.9,<4.0)",
] ]
[tool.poetry] [tool.poetry]

View File

@@ -1,4 +1,46 @@
#!/bin/bash #!/bin/bash
echo "Migrating database ..."
poetry run python manage.py migrate poetry run python manage.py migrate
if [ $? -ne 0 ]; then
echo "Unable to migrate database!" >&2
exit 5
fi
echo "Running collectstatic ..."
yes yes | poetry run python manage.py collectstatic yes yes | poetry run python manage.py collectstatic
if [ $? -ne 0 ]; then
echo "Unable to collect static files!" >&2
exit 5
fi
if [ -z "$DEBUG" -o $DEBUG != "1" ]; then
echo "Starting UWSGI server ..."
venv="$(poetry env info | head -n 6 | grep Path | awk '{print $2}')"
poetry run uwsgi \
--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 \
--harakiri 60 \
--max-requests 5000 \
--vacuum \
--venv "$venv" \
--home "$venv"
rc=$?
if [ $rc -ne 0 ]; then
echo "UWSGI Server not started" >&2
exit $rc
fi
else
echo "Starting development server ..."
poetry run python manage.py runserver 0.0.0.0:8000 poetry run python manage.py runserver 0.0.0.0:8000
rc=$?
if [ $rc -ne 0 ]; then
echo "Developemnt server was not started!" >&2
exit $rc
fi
fi

0
tinywiki/__init__.py Normal file
View File

3
tinywiki/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
tinywiki/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class TinywikiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'tinywiki'

42
tinywiki/enums.py Normal file
View File

@@ -0,0 +1,42 @@
from enum import StrEnum
from django.utils.translation import gettext,gettext_lazy,gettext_noop as _
class WikiContentType(StrEnum):
MARKDOWN = "markdown"
BBCODE = "bbcode"
@property
def str_raw(self)->str:
mapping = {
WikiContentType.MARKDOWN: _("Markdown"),
WikiContentType.BBCODE: _("BBCode")
}
return mapping[self]
@staticmethod
def from_string(string:str)->"WikiContentType":
mapping = {
WikiContentType.MARKDOWN.value: WikiContentType.MARKDOWN,
WikiContentType.BBCODE.value: WikiContentType.BBCODE,
}
return mapping[string.lower()]
@property
def str_lazy(self)->str:
return gettext_lazy(self.str_raw)
@property
def str(self)->str:
return gettext(self.str_raw)
def __str__(self):
return self.str
def __repr__(self):
return f"<{self.__qualname__}: {self.value.upper()}>"
WIKI_CONTENT_TYPES = (
WikiContentType.MARKDOWN,
WikiContentType.BBCODE,
)

View File

@@ -0,0 +1,44 @@
# Generated by Django 5.2.4 on 2025-09-14 13:31
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Image',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(max_length=255, verbose_name='slug')),
('alt', models.CharField(max_length=511, verbose_name='alternative text')),
('description', models.CharField(blank=True, max_length=1023, null=True, verbose_name='description')),
('image', models.ImageField(upload_to='tinywiki/img', verbose_name='image file')),
('uploaded_at', models.DateTimeField(auto_now_add=True, verbose_name='upladed at')),
('uploaded_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tinywiki_image_uploads', to=settings.AUTH_USER_MODEL, verbose_name='uploaded by')),
],
),
migrations.CreateModel(
name='Page',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(max_length=255, unique=True, verbose_name='slug')),
('title', models.CharField(max_length=255, verbose_name='title')),
('content_type_data', models.CharField(choices=[('markdown', 'Markdown'), ('bbcode', 'BBCode')], default='bbcode', verbose_name='content type')),
('content', models.TextField(verbose_name='Page content')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
('last_edited_at', models.DateTimeField(auto_now=True, verbose_name='last edited at')),
('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tinywiki_athors', to=settings.AUTH_USER_MODEL, verbose_name='author')),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tinywiki_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
('last_edited_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tinywiki_last_edited', to=settings.AUTH_USER_MODEL, verbose_name='last edited by')),
],
),
]

View File

@@ -0,0 +1,26 @@
# Generated by Django 5.2.4 on 2025-09-14 13:15
from django.db import migrations
from .. import settings
class Migration(migrations.Migration):
dependencies = [
('tinywiki', '0001_initial'),
]
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)
def init_default_pages(apps,schema_editor)->None:
from ..models import Page,Image
#TODO
def init_user_pages(apps,schema_edit)->None:
from ..models import Page,Image
#TODO
operations = [
migrations.RunPython(init_tinywiki_user),
migrations.RunPython(init_default_pages)
]

View File

91
tinywiki/models.py Normal file
View File

@@ -0,0 +1,91 @@
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 tinywiki.enums import WIKI_CONTENT_TYPES, WikiContentType
import markdown
import bbcode
class Page(models.Model):
slug = models.SlugField(_("slug"),
max_length=255,
null=False,
blank=False,
unique=True)
title = models.CharField(_("title"),
max_length=255,
null=False,
blank=False)
author = models.ForeignKey(get_user_model(),
on_delete=models.SET_NULL,
verbose_name=_("author"),
null=True,
blank=True,
related_name="tinywiki_athors")
content_type_data = models.CharField(_("content type"),
choices=[(i.value,i.str_lazy) for i in WIKI_CONTENT_TYPES],
default=WikiContentType.BBCODE.value)
content = models.TextField(_("Page content"),
null=False,
blank=False)
created_at = models.DateTimeField(_("created at"),
auto_now_add=True)
created_by = models.ForeignKey(get_user_model(),
on_delete=models.SET_NULL,
verbose_name=_("created by"),
null=True,
blank=True,
related_name="tinywiki_created")
last_edited_at = models.DateTimeField(_("last edited at"),
auto_now=True)
last_edited_by = models.ForeignKey(get_user_model(),
on_delete=models.SET_NULL,
verbose_name=_("last edited by"),
null=True,
blank=True,
related_name="tinywiki_last_edited")
@property
def content_type(self)->WikiContentType:
return WikiContentType.from_string(self.content_type_data)
@property
def html_content(self)->SafeText|str:
if self.content_type == WikiContentType.MARKDOWN:
return mark_safe(markdown.markdown(self.content))
elif self.content_type == WikiContentType.BBCODE:
return mark_safe(bbcode.render_html(self.content))
return self.content
class Image(models.Model):
models.ManyToManyField(Page, verbose_name=_(""))
slug = models.SlugField(_("slug"),
max_length=255)
alt = models.CharField(_("alternative text"),
max_length=511,
null=False,
blank=False)
description = models.CharField(_("description"),
max_length=1023,
null=True,
blank=True)
image = models.ImageField(_("image file"),
upload_to="tinywiki/img")
uploaded_by = models.ForeignKey(get_user_model(),
on_delete=models.SET_NULL,
verbose_name=_("uploaded by"),
null=True,
blank=True,
related_name="tinywiki_image_uploads")
uploaded_at = models.DateTimeField(_("uploaded at"),
auto_now_add=True)

19
tinywiki/settings.py Normal file
View File

@@ -0,0 +1,19 @@
from pathlib import Path
from django.conf import settings
TINYWIKI_USER_CONFIG = getattr(settings,
"TINYWIKI_USER_CONFIG",
{
"username":"TinyWiki",
"email":"tinywiki@example.com"
})
TINYWIKI_USER_LOOKUP = getattr(settings,
"TINYWIKI_USER_LOOKUP",
{'username':"TinyWiki"})
TINYWIKI_BOOSTRAP_TAGS = {
'img': {
'class':'img-fluid',
}
}

View File

3
tinywiki/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

7
tinywiki/urls.py Normal file
View File

@@ -0,0 +1,7 @@
from django.urls import path
app_name = "tinywiki"
urlpatterns = [
]

3
tinywiki/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

0
user/__init__.py Normal file
View File

3
user/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
user/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class UserConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'user'

29
user/managers.py Normal file
View File

@@ -0,0 +1,29 @@
from django.contrib.auth.models import BaseUserManager
from django.utils.translation import gettext as _
class UserProfileManager(BaseUserManager):
def create_user(self,username:str, email:str,password=None,**extra_fields):
if not email:
raise ValueError(_("The email must be set!"))
if not username:
raise ValueError(_("Username must be set!"))
email = self.normalize_email(email)
user = self.model(username=username,email=email,**extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username:str, email:str, password=None,**extra_fields):
extra_fields.setdefault('is_staff',True)
extra_fields.setdefault('is_superuser',True)
if extra_fields.get('is_staff') is not True:
raise ValueError(_("Superuser must have is_staff=True"))
if extra_fields.get('is_superuser') is not True:
raise ValueError(_("Superuser must have is_superuser=True"))
return self.create_user(username,email,password,**extra_fields)

View File

@@ -0,0 +1,39 @@
# Generated by Django 5.2.4 on 2025-09-13 21:38
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('email', models.EmailField(max_length=255, unique=True, verbose_name='email address')),
('username', models.CharField(max_length=63, unique=True, verbose_name='username')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
),
]

View File

@@ -0,0 +1,76 @@
# Generated by Django 5.2.4 on 2025-08-26 05:22
from django.db import migrations
class Migration(migrations.Migration):
def create_debug_superuser(apps,schema_editor):
from django.conf import settings
from user.models import UserProfile
from allauth.account.models import EmailAddress
if settings.DEBUG and UserProfile.objects.count() == 0:
user = UserProfile.objects.create_superuser("debug-admin","debug-admin@example.com","Pa55w.rd")
account_emailaddress = EmailAddress.objects.create(user=user,email=user.email,verified=True,primary=True)
def create_superuser(apps,schema_editor):
from django.conf import settings
from user.models import UserProfile
from allauth.account.models import EmailAddress
username = settings.ENV.str("SUPERUSER_USERNAME",default="")
email = settings.ENV.str("SUPERUSER_EMAIL",default="")
password = settings.ENV.str("SUPERUSER_PASSWORD",default="")
if username and email:
if not password:
password = None
user = UserProfile.objects.create_superuser(username,email,password)
account_emailaddress = EmailAddress.objects.create(user=user,email=user.email,verified=True,primary=True)
for i in range(0,100):
username = settings.ENV.str(f"SUPERUSER{i:d}_USERNAME",default="")
email = settings.ENV.str(f"SUPERUSER{i:d}_EMAIL",default="")
password = settings.ENV.str(f"SUPERUSER{i:d}_PASSWORD",default="")
if username and email:
if not password:
password = None
user = UserProfile.objects.create_superuser(username,email,password)
account_emailaddress = EmailAddress.objects.create(user=user,email=user.email,verified=True,primary=True)
def create_staffuser(apps,schema_editor):
from django.conf import settings
from user.models import UserProfile
from allauth.account.models import EmailAddress
username = settings.ENV.str("STAFFUSER_USERNAME",default="")
email = settings.ENV.str("STAFFUSER_EMAIL",default="")
password = settings.ENV.str("STAFFUSER_PASSWORD",default="")
if username and email:
if not password:
password = None
user = UserProfile.objects.create_user(username,email,password,is_staff=True)
account_emailaddress = EmailAddress.objects.create(user=user,email=user.email,verified=True,primary=True)
for i in range(0,100):
username = settings.ENV.str(f"STAFFUSER{i:d}_USERNAME",default="")
email = settings.ENV.str(f"STAFFUSER{i:d}_EMAIL",default="")
password = settings.ENV.str(f"STAFFUSER{i:d}_PASSWORD",default="")
if username and email:
if not password:
password = None
user = UserProfile.objects.create_user(username,email,password,is_staff=True)
account_emailaddress = EmailAddress.objects.create(user=user,email=user.email,verified=True,primary=True)
dependencies = [
('user', '0001_initial'),
]
operations = [
migrations.RunPython(create_superuser),
migrations.RunPython(create_debug_superuser),
]

View File

19
user/models.py Normal file
View File

@@ -0,0 +1,19 @@
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.safestring import mark_safe
from django.conf import settings
from .managers import UserProfileManager
from django.utils.translation import gettext_lazy as _
class UserProfile(AbstractUser):
email = models.EmailField(_("email address"),
max_length=255,
unique=True)
username = models.CharField(_("username"),
max_length=63,
unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
objects = UserProfileManager()

3
user/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

7
user/urls.py Normal file
View File

@@ -0,0 +1,7 @@
from django.urls import path
from .views import *
app_name = "user"
urlpatterns = [
]

3
user/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.