diff --git a/.gitignore b/.gitignore index ee8979d..55b0589 100644 --- a/.gitignore +++ b/.gitignore @@ -439,6 +439,8 @@ pip-selfcheck.json # Built Visual Studio Code Extensions *.vsix -djangocourse/privsettings.py - .venv* + +django_project/local/ +.data +db.sqlite diff --git a/Dockerfile.devel.yml b/Dockerfile.devel.yml index b92d585..f090bce 100644 --- a/Dockerfile.devel.yml +++ b/Dockerfile.devel.yml @@ -1,12 +1,20 @@ FROM docker.io/library/python:3.13-trixie ENV PYTHONUNBUFFERED=1 -RUN apt-update \ - && apt-upgrade -y \ +ENV DEBUG="1" +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\ - #&& python -m pip install upgrade pip \ + && python -m pip install --upgrade pip \ && pip install poetry \ && mkdir /app \ - && mkdir -p /data/static /data/media + && mkdir -p /data/static /data/media /data/database +VOLUME "/data" WORKDIR /app COPY . . @@ -16,4 +24,3 @@ RUN poetry install --all-groups \ EXPOSE 8000 ENTRYPOINT ["/app/start-django.sh"] - diff --git a/django_project/local/__init__.py b/django_project/local/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_project/settings.py b/django_project/settings.py index 4bd3266..cf3f067 100644 --- a/django_project/settings.py +++ b/django_project/settings.py @@ -11,6 +11,8 @@ https://docs.djangoproject.com/en/5.2/ref/settings/ """ from pathlib import Path +import sys + from environ import Env # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -20,12 +22,24 @@ ENV = Env( ALLOWED_HOSTS=(list,['*']), STATIC_URL=(str,"static/"), STATIC_ROOT=(Path,BASE_DIR/".static"), - MEDIA_URL=("media/") + MEDIA_URL=(str,"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 # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ @@ -40,8 +54,7 @@ ALLOWED_HOSTS = ENV.list("ALLOWED_HOSTS") # Application definition - -INSTALLED_APPS = [ +django_apps= [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -50,6 +63,23 @@ INSTALLED_APPS = [ '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 = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -58,6 +88,30 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', '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' @@ -91,6 +145,8 @@ DATABASES = { # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators +AUTH_USER_MODEL = "user.UserProfile" + AUTH_PASSWORD_VALIDATORS = [ { '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 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" + + diff --git a/django_project/urls.py b/django_project/urls.py index 6f57803..6d2693e 100644 --- a/django_project/urls.py +++ b/django_project/urls.py @@ -18,6 +18,8 @@ from django.contrib import admin from django.urls import path,include from django.conf import settings urlpatterns = [ + path('',include("tinywiki.urls")), + path("user/",include("user.urls")), path('admin/', admin.site.urls), ] @@ -28,5 +30,5 @@ if settings.DEBUG: *debug_toolbar_urls(), *static(settings.STATIC_URL, document_root=settings.STATIC_ROOT), *static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), - path('__reload__/',include("django_browser_relaod.urls")) + path('__reload__/',include("django_browser_reload.urls")) ] \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 9c69095..06ca481 100644 --- a/poetry.lock +++ b/poetry.lock @@ -80,6 +80,18 @@ files = [ [package.extras] 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]] name = "certifi" version = "2025.8.3" @@ -410,13 +422,13 @@ bcrypt = ["bcrypt"] [[package]] 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." optional = false python-versions = ">=3.8" groups = ["main"] 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] @@ -438,14 +450,14 @@ steam = ["python3-openid (>=3.0.8,<4)"] [[package]] name = "django-browser-reload" -version = "1.18.0" +version = "1.19.0" description = "Automatically reload your browser in development." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ - {file = "django_browser_reload-1.18.0-py3-none-any.whl", hash = "sha256:ed4cc2fb83c3bf6c30b54107a1a6736c0b896e62e4eba666d81005b9f2ecf6f8"}, - {file = "django_browser_reload-1.18.0.tar.gz", hash = "sha256:c5f0b134723cbf2a0dc9ae1ee1d38e42db28fe23c74cdee613ba3ef286d04735"}, + {file = "django_browser_reload-1.19.0-py3-none-any.whl", hash = "sha256:ac67c304654b77811abb4ebfa3802554a4e4b4ab030f8623b21e7b606efea160"}, + {file = "django_browser_reload-1.19.0.tar.gz", hash = "sha256:034939f770832fde374035e9e4d904c6f990d75598edb777626b5202587315d7"}, ] [package.dependencies] @@ -560,6 +572,22 @@ setuptools = ">=61.0" [package.extras] 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]] name = "oauthlib" version = "3.3.1" @@ -577,6 +605,131 @@ rsa = ["cryptography (>=3.0.0)"] signals = ["blinker (>=1.4.0)"] 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]] name = "pyasn1" version = "0.6.1" @@ -931,4 +1084,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.14" -content-hash = "84268673ea099e0b577d38525faa67b358098040438c30985c376d71c7326fa0" +content-hash = "cdc572d2076797697db50f74bfecc2a67620b6a7bcc8eb3a799e816848c0c261" diff --git a/pyproject.toml b/pyproject.toml index 9809de6..a4b3419 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,10 +7,14 @@ authors = [ requires-python = ">=3.12,<3.14" dependencies = [ "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-widget-tweaks (>=1.5.0,<2.0.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] diff --git a/start-django.sh b/start-django.sh index 68c9cf5..bef0549 100755 --- a/start-django.sh +++ b/start-django.sh @@ -1,4 +1,46 @@ #!/bin/bash + +echo "Migrating database ..." 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 -poetry run python manage.py runserver 0.0.0.0:8000 +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 + rc=$? + if [ $rc -ne 0 ]; then + echo "Developemnt server was not started!" >&2 + exit $rc + fi +fi diff --git a/tinywiki/__init__.py b/tinywiki/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tinywiki/admin.py b/tinywiki/admin.py new file mode 100644 index 0000000..ea5d68b --- /dev/null +++ b/tinywiki/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/tinywiki/apps.py b/tinywiki/apps.py new file mode 100644 index 0000000..c312d82 --- /dev/null +++ b/tinywiki/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TinywikiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'tinywiki' diff --git a/tinywiki/enums.py b/tinywiki/enums.py new file mode 100644 index 0000000..b76539d --- /dev/null +++ b/tinywiki/enums.py @@ -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, +) diff --git a/tinywiki/migrations/0001_initial.py b/tinywiki/migrations/0001_initial.py new file mode 100644 index 0000000..f3f46bb --- /dev/null +++ b/tinywiki/migrations/0001_initial.py @@ -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')), + ], + ), + ] diff --git a/tinywiki/migrations/0002_initial_data.py b/tinywiki/migrations/0002_initial_data.py new file mode 100644 index 0000000..1956752 --- /dev/null +++ b/tinywiki/migrations/0002_initial_data.py @@ -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) + ] + \ No newline at end of file diff --git a/tinywiki/migrations/__init__.py b/tinywiki/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tinywiki/models.py b/tinywiki/models.py new file mode 100644 index 0000000..e61ceed --- /dev/null +++ b/tinywiki/models.py @@ -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) + diff --git a/tinywiki/settings.py b/tinywiki/settings.py new file mode 100644 index 0000000..22cdbd3 --- /dev/null +++ b/tinywiki/settings.py @@ -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', + } +} diff --git a/tinywiki/templates/tinywiki/base.html b/tinywiki/templates/tinywiki/base.html new file mode 100644 index 0000000..e69de29 diff --git a/tinywiki/tests.py b/tinywiki/tests.py new file mode 100644 index 0000000..de8bdc0 --- /dev/null +++ b/tinywiki/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/tinywiki/urls.py b/tinywiki/urls.py new file mode 100644 index 0000000..42dfa18 --- /dev/null +++ b/tinywiki/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +app_name = "tinywiki" + +urlpatterns = [ + +] \ No newline at end of file diff --git a/tinywiki/views.py b/tinywiki/views.py new file mode 100644 index 0000000..c60c790 --- /dev/null +++ b/tinywiki/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/user/__init__.py b/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/admin.py b/user/admin.py new file mode 100644 index 0000000..ea5d68b --- /dev/null +++ b/user/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/user/apps.py b/user/apps.py new file mode 100644 index 0000000..995b7a0 --- /dev/null +++ b/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user' diff --git a/user/managers.py b/user/managers.py new file mode 100644 index 0000000..6574b2f --- /dev/null +++ b/user/managers.py @@ -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) + \ No newline at end of file diff --git a/user/migrations/0001_initial.py b/user/migrations/0001_initial.py new file mode 100644 index 0000000..78b1b3d --- /dev/null +++ b/user/migrations/0001_initial.py @@ -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, + }, + ), + ] diff --git a/user/migrations/0002_default_superuser.py b/user/migrations/0002_default_superuser.py new file mode 100644 index 0000000..95fbf23 --- /dev/null +++ b/user/migrations/0002_default_superuser.py @@ -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), + ] diff --git a/user/migrations/__init__.py b/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user/models.py b/user/models.py new file mode 100644 index 0000000..c28bcb5 --- /dev/null +++ b/user/models.py @@ -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() + diff --git a/user/tests.py b/user/tests.py new file mode 100644 index 0000000..de8bdc0 --- /dev/null +++ b/user/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/user/urls.py b/user/urls.py new file mode 100644 index 0000000..3c7f1ac --- /dev/null +++ b/user/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from .views import * + +app_name = "user" +urlpatterns = [ + +] diff --git a/user/views.py b/user/views.py new file mode 100644 index 0000000..c60c790 --- /dev/null +++ b/user/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.