Merge pull request 'Automatic docker image building and releasing (closes #8 #9 #11)' (#32) from dev into main
Build dev docker image / build (push) Successful in 2s
Build dev docker image / release (push) Has been skipped

Reviewed-on: #32
This commit was merged in pull request #32.
This commit is contained in:
2026-03-30 12:49:51 +02:00
25 changed files with 1160 additions and 301 deletions
+57
View File
@@ -0,0 +1,57 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
*.sqlite3
*.sqlite3-journal
# Environments
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# VisualStudioCode
.vscode/
.history/
*.vsix
*.code-workspace
# Others
.git/
.gitignore
.prettierignore
.github/
.gitea/
# Project specific
/static/
/media/
/docs/
+11
View File
@@ -0,0 +1,11 @@
---
name: "Bug"
about: "Report a bug"
title: "[BUG] "
ref: "main"
assignees: ["kennyboy55"]
labels:
- bug
---
Please describe the bug, and add relevant logs.
+74
View File
@@ -0,0 +1,74 @@
name: Build dev docker image
on:
push:
branches:
- dev
- main
tags:
- '*'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
# Checkout repository for build
- name: Checkout friend-event-rsvp repository
uses: actions/checkout@v4
with:
ref: ${{ gitea.ref }}
# Login to registry
- name: Gitea package registry login
run: |
echo "${{ secrets.ACCESS_TOKEN }}" | docker login gitea.furb.it \
-u "${{ secrets.USERNAME }}" \
--password-stdin
# Build image
- name: Build Docker image - gitea.furb.it/${{ gitea.repository }}:${{ gitea.ref_name }}
run: |
docker build \
-f Dockerfile \
--label friend-event-rsvp.ref=${{ gitea.ref_name }} \
-t gitea.furb.it/${{ gitea.repository }}:${{ gitea.ref_name }} \
.
- name: Tag Docker image - gitea.furb.it/${{ gitea.repository }}:latest
if: startsWith(gitea.ref, 'refs/tags/')
run: |
docker image tag \
gitea.furb.it/${{ gitea.repository }}:${{ gitea.ref_name }} \
gitea.furb.it/${{ gitea.repository }}:latest
# Push images
- name: Push Docker image
run: |
docker push gitea.furb.it/${{ gitea.repository }}:${{ gitea.ref_name }}
- name: Push Docker image :latest
if: startsWith(gitea.ref, 'refs/tags/')
run: |
docker push gitea.furb.it/${{ gitea.repository }}:latest
release:
runs-on: ubuntu-latest
needs: build
if: startsWith(gitea.ref, 'refs/tags/')
steps:
# Create release using gitea-release-action
- name: Create release
uses: akkuman/gitea-release-action@v1
with:
server: "https://gitea.furb.it"
token: ${{ secrets.ACCESS_TOKEN }}
repository: ${{ gitea.repository }}
tag_name: ${{ gitea.ref_name }}
name: ${{ gitea.ref_name }}
body: |
"## Docker images"
"`gitea.furb.it/${{ gitea.repository }}:${{ gitea.ref_name }}`"
"`gitea.furb.it/${{ gitea.repository }}:latest`"
+3 -99
View File
@@ -62,32 +62,6 @@ local_settings.py
db.sqlite3 db.sqlite3
db.sqlite3-journal db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv # pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies # However, in case of collaboration, if having platform-specific dependencies or dependencies
@@ -95,39 +69,6 @@ ipython_config.py
# install all needed dependencies. # install all needed dependencies.
#Pipfile.lock #Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments # Environments
.env .env
.venv .venv
@@ -137,43 +78,6 @@ ENV/
env.bak/ env.bak/
venv.bak/ venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# ---> VisualStudioCode # ---> VisualStudioCode
.vscode/* .vscode/*
!.vscode/settings.json !.vscode/settings.json
@@ -188,6 +92,6 @@ cython_debug/
# Built Visual Studio Code Extensions # Built Visual Studio Code Extensions
*.vsix *.vsix
# Custom # Project specific
events/static/img/* /static/
!events/static/img/placeholder*.jpg /media/
+4
View File
@@ -0,0 +1,4 @@
# ignored files - prettier interferes with django templates and github actions
*.html
*.yml
*.yaml
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["otovo-oss.htmx-tags", "esbenp.prettier-vscode"]
}
+19 -19
View File
@@ -1,20 +1,20 @@
{ {
// Use IntelliSense to learn about possible attributes. // Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes. // Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
{ "name": "Python Debugger: Django",
"name": "Python Debugger: Django", "type": "debugpy",
"type": "debugpy", "request": "launch",
"request": "launch", "args": ["runserver"],
"args": [ "django": true,
"runserver" "autoStartBrowser": false,
], "program": "${workspaceFolder}/manage.py",
"django": true, "env": {
"autoStartBrowser": false, "DEBUG": "1"
"program": "${workspaceFolder}/manage.py" }
} }
] ]
} }
+4 -9
View File
@@ -1,17 +1,12 @@
{ {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[python]": { "[python]": {
"editor.formatOnType": true, "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "ms-python.black-formatter"
}, },
"python.analysis.autoImportCompletions": true, "python.analysis.autoImportCompletions": true,
"python.analysis.autoFormatStrings": true, "python.analysis.autoFormatStrings": true,
"python.analysis.completeFunctionParens": true, "python.analysis.completeFunctionParens": true,
"python.analysis.enableTroubleshootMissingImports": true, "python.analysis.enableTroubleshootMissingImports": true,
"github.copilot.editor.enableCodeActions": false,
"github.copilot.enable": {
"*": false,
"plaintext": false,
"markdown": false,
"scminput": false
}
} }
+38
View File
@@ -0,0 +1,38 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Enable environment",
"type": "shell",
"command": "pipenv shell",
"runOptions": {
"runOn": "folderOpen"
}
},
{
"label": "Update python packages",
"type": "shell",
"command": "pipenv update"
},
{
"label": "Make Migrations",
"type": "shell",
"command": "python3 manage.py makemigrations"
},
{
"label": "Run Migrations",
"type": "shell",
"command": "python3 manage.py migrate"
},
{
"label": "Setup Dev Server",
"dependsOn": ["Make Migrations", "Run Migrations"]
},
{
"label": "Run Dev Server",
"type": "shell",
"dependsOn": ["Setup Dev Server"],
"command": "DEBUG=1 python3 manage.py runserver"
}
]
}
+40
View File
@@ -0,0 +1,40 @@
FROM python:3.12-alpine3.23
LABEL maintainer="Kenneth van Ewijk (kennyboy55)"
# Add dependencies
#RUN apk add --no-cache postgresql-libs postgresql-client
RUN apk add --no-cache tini
# Install pipenv
RUN pip install pipenv
#Print all logs without buffering it.
ENV PYTHONUNBUFFERED=1 \
DOCKER=true
#Create app dir
RUN mkdir -p /opt/rsvp
RUN mkdir -p /opt/rsvp/static
WORKDIR /opt/rsvp
# Copy Pipfile and Pipfile.lock
COPY Pipfile Pipfile.lock ./
# Install python dependencies
RUN pipenv install --deploy --system
# Copy application code
COPY . .
RUN mkdir -p /data
# The volume containing dynamic data
VOLUME /data
#This port will be used by daphne
EXPOSE 8000
# Run the application
RUN chmod +x start.sh
ENTRYPOINT ["/sbin/tini", "--", "/opt/rsvp/start.sh"]
+5
View File
@@ -4,8 +4,13 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
channels = {version = "*", extras = ["daphne", "types"]}
daphne = "*"
django = "*" django = "*"
django-markdown = "*"
pillow = "*" pillow = "*"
blacknoise = "*"
twisted = {extras = ["http2", "tls"], version = "*"}
[dev-packages] [dev-packages]
Generated
+657 -1
View File
@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "7899ae66bf28a24ee2f8c26aa923021a00ce17118ca4adcc9d7c3da571133239" "sha256": "07e2a33b6016199f2401424d35bc436e5355db12dd734f543d2633b4dd31a22d"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -16,6 +16,14 @@
] ]
}, },
"default": { "default": {
"anyio": {
"hashes": [
"sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708",
"sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"
],
"markers": "python_version >= '3.10'",
"version": "==4.13.0"
},
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
"sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce",
@@ -24,6 +32,278 @@
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==3.11.1" "version": "==3.11.1"
}, },
"attrs": {
"hashes": [
"sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309",
"sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"
],
"markers": "python_version >= '3.9'",
"version": "==26.1.0"
},
"autobahn": {
"hashes": [
"sha256:0ad4c10c897ad67d31be2ef8547ed2922875d90ddb95553787cc46c271f822de",
"sha256:0c226329ddec154c6f3b491ea3e4713035f0326c96ebfd6b305bf90f27a2fba1",
"sha256:16df879672c60f1f3fe452138c80f0fd221b3cb2ee5a14390c80f33b994104c1",
"sha256:18b12e8af7fc115487715afa10b3f5b5a4b5989bebbe05b71722cf9fce7b1bfb",
"sha256:220748f21e91bd4a538d2d3de640cc17ee30b79f1c04a6c3dcdef321d531ee1c",
"sha256:3ec6a3719a00fd57b044e4694f3d6e9335892f4ef21f045f090495da7385d240",
"sha256:5f079393a7626eb448c8accf21151f5f206d02f8e9cee4313d62a5ca30a3aaed",
"sha256:6ffe28048ef96eb0f925f24c2569bd72332e120f4cb31cd6c40dd66718a5f85e",
"sha256:754c06a54753aeb7e8d10c5cbf03249ad9e2a1a32bca8be02865c6f00628a98c",
"sha256:9abda5cf817c0f8a19a55a67a031adf2fc70ed351719b5bd9e6fa0f5f4bc8f89",
"sha256:9b3a6c7d54a9f0434a435d88b86555510e5d0a84aa87042e292f29f707cab237",
"sha256:b5297a782fc7d0a26842438ef1342549ceee29496cda52672ac44635c79eeb94",
"sha256:ba1867aafdbe585d3d4a5abd35238a78ab54ab3de5bd12a21bca20379c9f512b",
"sha256:bc17f6cab9438156d2701c293c76fd02a144f9be0a992c065dfee1935ce4845b",
"sha256:c0c3f1d5dafda52f8dc962ab583b6f3473b7b7186cab082d05372ed43a8261a5",
"sha256:c840ee136bfaf6560467160129b0b25a0e33c9a51e2b251e98c5474f27583915",
"sha256:e9e2a962f2de0bc4c53b452916458417a15f5137c956245ac6d0a783a83fa1f7"
],
"markers": "python_version >= '3.11'",
"version": "==25.12.2"
},
"automat": {
"hashes": [
"sha256:0017591a5477066e90d26b0e696ddc143baafd87b588cfac8100bc6be9634de0",
"sha256:04e9bce696a8d5671ee698005af6e5a9fa15354140a87f4870744604dcdd3ba1"
],
"markers": "python_version >= '3.9'",
"version": "==25.4.16"
},
"blacknoise": {
"hashes": [
"sha256:1914b179ed37c237e3b914f759afb926371dd60f87649684b5e17a9c55c60b32",
"sha256:e44d38fbabba18dffb682d9463b670b63c4fbc94c128263aa1a6365618912593"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==1.2.0"
},
"cbor2": {
"hashes": [
"sha256:0322296b9d52f55880e300ba8ba09ecf644303b99b51138bbb1c0fb644fa7c3e",
"sha256:0485d3372fc832c5e16d4eb45fa1a20fc53e806e6c29a1d2b0d3e176cedd52b9",
"sha256:08388ea54195738602b4c4999966bcaef6f0b17d293c9658658409d9fff96f57",
"sha256:1d02b65f070fd726bdc310d927228975bb655d155bf059b6eb7cacefb3dca86f",
"sha256:1da96ce5d852fe3d342c1eb2c202a52d1c97edfddc9230f1be7e02674662bf26",
"sha256:1f223dffb1bcdd2764665f04c1152943d9daa4bc124a576cd8dee1cad4264313",
"sha256:23606d31ba1368bd1b6602e3020ee88fe9523ca80e8630faf6b2fc904fd84560",
"sha256:2372d357d403e7912f104ff085950ffc82a5854d6d717f1ca1ce16a40a0ef5a7",
"sha256:25bec7beb2089465382b1be72e78667fe9090598800826559c3e3008cf0db743",
"sha256:27695cbd70c90b8de5c4a284642c2836449b14e2c2e07e3ffe0744cb7669a01b",
"sha256:2a54fbb32cb828c214f7f333a707e4aec61182e7efdc06ea5d9596d3ecee624a",
"sha256:3095dc49e75572841a9534cbfdabc2a17487ea4ee33341436abc4a7ac7245a3a",
"sha256:34a6cb15e6ab6a8eae94ad2041731cd3ef786af43a8df99f847969af5b902ee7",
"sha256:380e534482b843e43442b87d8777a7bf9bed20cb7526f89b780c3400f617304b",
"sha256:420d2490c7836c81151b4bd591c35cffc55391e33e7e333c50fda391bcea7d31",
"sha256:422817286c1d0ce947fb2f7eca9212b39bddd7231e8b452e2d2cc52f15332dba",
"sha256:4753a6d1bc71054d9179557bc65740860f185095ccb401d46637fff028a5b3ec",
"sha256:4aa07b392cc3d76fb31c08a46a226b58c320d1c172ff3073e864409ced7bc50f",
"sha256:4cd43d8fc374b31643b2830910f28177a606a7bc84975a62675dd3f2e320fc7b",
"sha256:5326336f633cc89dfe543c78829c16c3a6449c2c03277d1ddba99086c3323363",
"sha256:53cfa49e0df9c639beb871d480de098eedc81eb63ff29f2dc922720d7577b676",
"sha256:55bea0dd9a7d354e35f4e5fe58ceab393e76962713749dc3a0a64a0e5d19545e",
"sha256:5e702b02d42a5ace45425b595ffe70fe35aebaf9a3cdfdc2c758b6189c744422",
"sha256:65f8eac3268c608533f326f0fd9010ab1b2a8a917b05edaf3853116336821669",
"sha256:7221483fad0c63afa4244624d552abf89d7dfdbc5f5edfc56fc1ff2b4b818975",
"sha256:7d1ddc4541e7367ac58c2470cc0df847f7137167fe4f5729e2d3cc0b993d7da4",
"sha256:837754ece9052b3f607047e1741e5f852a538aa2b0ee3db11c82a8fa11804aa4",
"sha256:85c7a46279ac8f226e1059275221e6b3d0e370d2bb6bd0500f9780781615bcea",
"sha256:86baf870d4c0bfc6f79de3801f3860a84ab76d9c8b0abb7f081f2c14c38d79d3",
"sha256:971d425b3a23b75953d8853d5f9911bdeefa09d759ee3b5e6b07b5ff3cbd9073",
"sha256:9a4907e0c3035bb8836116854ed8e56d8aef23909d601fa59706320897ec2551",
"sha256:a9d6e4e0f988b0e766509a8071975a8ee99f930e14a524620bf38083106158d2",
"sha256:ac684fe195c39821fca70d18afbf748f728aefbfbf88456018d299e559b8cae0",
"sha256:ae6c706ac1d85a0b3cb3395308fd0c4d55e3202b4760773675957e93cdff45fc",
"sha256:cc5efec69055c3c470997935d95762be7e4bfd1248d88fb1a33bb7e0f45712e9",
"sha256:d1a21c006760f95acd9509cc5a7d15d6fc82e58f721f94fa9039b4e77189a6e5",
"sha256:d8524a8c142c3cc228e635f8a97499a6c0b18ca91382e8276565658035cdcb6d",
"sha256:dcf0f695873e5c94bd072d6af8698e72b8fb7f7a18f37e0bced1041b7111a6cf",
"sha256:f29e5c3abcc91c1aeefecde0e057bf33f1655588d3065c6560c30ceb3be6f333",
"sha256:f797532d13469f2193e5c16e827d8df7a8c33674b19be755790b54ab231e6a73",
"sha256:f7c9751a9611601ab326d8f5837f01379195bbf06175fb4effeb552140e7c9e8",
"sha256:fb7afe77f8d269e42d7c4b515c6fd14f1ccc0625379fb6829b269f493d16eddd",
"sha256:fbb06f34aa645b4deca66643bba3d400d20c15312d1fe88d429be60c1ab50f27",
"sha256:fbdcf4d74acbeb7672e6413e81cd2c1ced1a4a8cf949484ac54e9af5265c3c72"
],
"markers": "python_version >= '3.9'",
"version": "==5.9.0"
},
"cffi": {
"hashes": [
"sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb",
"sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b",
"sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f",
"sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9",
"sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44",
"sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2",
"sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c",
"sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75",
"sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65",
"sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e",
"sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a",
"sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e",
"sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25",
"sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a",
"sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe",
"sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b",
"sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91",
"sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592",
"sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187",
"sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c",
"sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1",
"sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94",
"sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba",
"sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb",
"sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165",
"sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529",
"sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca",
"sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c",
"sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6",
"sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c",
"sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0",
"sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743",
"sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63",
"sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5",
"sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5",
"sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4",
"sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d",
"sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b",
"sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93",
"sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205",
"sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27",
"sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512",
"sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d",
"sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c",
"sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037",
"sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26",
"sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322",
"sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb",
"sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c",
"sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8",
"sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4",
"sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414",
"sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9",
"sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664",
"sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9",
"sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775",
"sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739",
"sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc",
"sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062",
"sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe",
"sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9",
"sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92",
"sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5",
"sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13",
"sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d",
"sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26",
"sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f",
"sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495",
"sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b",
"sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6",
"sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c",
"sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef",
"sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5",
"sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18",
"sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad",
"sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3",
"sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7",
"sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5",
"sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534",
"sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49",
"sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2",
"sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5",
"sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453",
"sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"
],
"markers": "python_version >= '3.9'",
"version": "==2.0.0"
},
"channels": {
"extras": [
"daphne",
"types"
],
"hashes": [
"sha256:f2bb6bfb73ad7fb4705041d07613c7b4e69528f01ef8cb9fb6c21d9295f15667",
"sha256:fef47e9055a603900cf16cef85f050d522d9ac4b3daccf24835bd9580705c176"
],
"markers": "python_version >= '3.9'",
"version": "==4.3.2"
},
"constantly": {
"hashes": [
"sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9",
"sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd"
],
"markers": "python_version >= '3.8'",
"version": "==23.10.4"
},
"cryptography": {
"hashes": [
"sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70",
"sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d",
"sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a",
"sha256:12f0fa16cc247b13c43d56d7b35287ff1569b5b1f4c5e87e92cc4fcc00cd10c0",
"sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97",
"sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30",
"sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759",
"sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c",
"sha256:2ea0f37e9a9cf0df2952893ad145fd9627d326a59daec9b0802480fa3bcd2ead",
"sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275",
"sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58",
"sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f",
"sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361",
"sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507",
"sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa",
"sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b",
"sha256:50575a76e2951fe7dbd1f56d181f8c5ceeeb075e9ff88e7ad997d2f42af06e7b",
"sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8",
"sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8",
"sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72",
"sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175",
"sha256:6728c49e3b2c180ef26f8e9f0a883a2c585638db64cf265b49c9ba10652d430e",
"sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124",
"sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a",
"sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c",
"sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f",
"sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d",
"sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4",
"sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c",
"sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290",
"sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca",
"sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d",
"sha256:90e5f0a7b3be5f40c3a0a0eafb32c681d8d2c181fc2a1bdabe9b3f611d9f6b1a",
"sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed",
"sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a",
"sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb",
"sha256:a3e84d5ec9ba01f8fd03802b2147ba77f0c8f2617b2aff254cedd551844209c8",
"sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707",
"sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410",
"sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736",
"sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2",
"sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4",
"sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013",
"sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19",
"sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b",
"sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738",
"sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463",
"sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77",
"sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4"
],
"markers": "python_version >= '3.8' and python_full_version not in '3.9.0, 3.9.1'",
"version": "==46.0.6"
},
"daphne": {
"hashes": [
"sha256:5f898e700a1fda7addf1541d7c328606415e96a7bd768405f0463c312fcb31b3",
"sha256:881e96b387b95b35ad85acd855f229d7f5b79073d6649089c8a33f661885e055"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==4.2.1"
},
"django": { "django": {
"hashes": [ "hashes": [
"sha256:2e5974441491ddb34c3f13d5e7a9f97b07ba03bf70234c0a9c68b79bbb235bc3", "sha256:2e5974441491ddb34c3f13d5e7a9f97b07ba03bf70234c0a9c68b79bbb235bc3",
@@ -33,6 +313,159 @@
"markers": "python_version >= '3.12'", "markers": "python_version >= '3.12'",
"version": "==6.0.3" "version": "==6.0.3"
}, },
"django-markdown": {
"hashes": [
"sha256:118d3a3b0dda0279785c007720fe594ccf9fa4cc1d6cee878bb81d36a02dd668",
"sha256:bab6719cac47c7139c9f489816a99fd656caa56d364a2b7d2137db698219b402"
],
"index": "pypi",
"version": "==0.8.4"
},
"django-stubs": {
"hashes": [
"sha256:d885044bd0876610f3eb969d6b5ed22f386002a879fdcb369cd8efa0502dbbce",
"sha256:e1ca63634221b57a55e16562b9b6d1849aeee2cabfd0fc026084dbe8aa893366"
],
"markers": "python_version >= '3.10'",
"version": "==6.0.1"
},
"django-stubs-ext": {
"hashes": [
"sha256:17415759b9a3f4b4da7998ac3b08c7dc5137f9a019490b918aece1a8a4c2ade4",
"sha256:633b280f89c0cbb7e3ce2f2f842e0acc43d8091378e75f84921d6be675d052dc"
],
"markers": "python_version >= '3.10'",
"version": "==6.0.1"
},
"h2": {
"hashes": [
"sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1",
"sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd"
],
"version": "==4.3.0"
},
"hpack": {
"hashes": [
"sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496",
"sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca"
],
"markers": "python_version >= '3.9'",
"version": "==4.1.0"
},
"hyperframe": {
"hashes": [
"sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5",
"sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08"
],
"markers": "python_version >= '3.9'",
"version": "==6.1.0"
},
"hyperlink": {
"hashes": [
"sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b",
"sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"
],
"version": "==21.0.0"
},
"idna": {
"hashes": [
"sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea",
"sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"
],
"version": "==3.11"
},
"incremental": {
"hashes": [
"sha256:87d3480dbb083c1d736222511a8cf380012a8176c2456d01ef483242abbbcf8c",
"sha256:a34450716b1c4341fe6676a0598e88a39e04189f4dce5dc96f656e040baa10b3"
],
"markers": "python_version >= '3.8'",
"version": "==24.11.0"
},
"markdown": {
"hashes": [
"sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950",
"sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36"
],
"markers": "python_version >= '3.10'",
"version": "==3.10.2"
},
"msgpack": {
"hashes": [
"sha256:0051fffef5a37ca2cd16978ae4f0aef92f164df86823871b5162812bebecd8e2",
"sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014",
"sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931",
"sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b",
"sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b",
"sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999",
"sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029",
"sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0",
"sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9",
"sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c",
"sha256:350ad5353a467d9e3b126d8d1b90fe05ad081e2e1cef5753f8c345217c37e7b8",
"sha256:354e81bcdebaab427c3df4281187edc765d5d76bfb3a7c125af9da7a27e8458f",
"sha256:365c0bbe981a27d8932da71af63ef86acc59ed5c01ad929e09a0b88c6294e28a",
"sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42",
"sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e",
"sha256:41d1a5d875680166d3ac5c38573896453bbbea7092936d2e107214daf43b1d4f",
"sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7",
"sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb",
"sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef",
"sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf",
"sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245",
"sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794",
"sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af",
"sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff",
"sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e",
"sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296",
"sha256:67016ae8c8965124fdede9d3769528ad8284f14d635337ffa6a713a580f6c030",
"sha256:6bde749afe671dc44893f8d08e83bf475a1a14570d67c4bb5cec5573463c8833",
"sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939",
"sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa",
"sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90",
"sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c",
"sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717",
"sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406",
"sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a",
"sha256:8b696e83c9f1532b4af884045ba7f3aa741a63b2bc22617293a2c6a7c645f251",
"sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2",
"sha256:94fd7dc7d8cb0a54432f296f2246bc39474e017204ca6f4ff345941d4ed285a7",
"sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e",
"sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b",
"sha256:9fba231af7a933400238cb357ecccf8ab5d51535ea95d94fc35b7806218ff844",
"sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9",
"sha256:a605409040f2da88676e9c9e5853b3449ba8011973616189ea5ee55ddbc5bc87",
"sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b",
"sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c",
"sha256:a8f6e7d30253714751aa0b0c84ae28948e852ee7fb0524082e6716769124bc23",
"sha256:ad09b984828d6b7bb52d1d1d0c9be68ad781fa004ca39216c8a1e63c0f34ba3c",
"sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e",
"sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620",
"sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69",
"sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f",
"sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68",
"sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27",
"sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46",
"sha256:db6192777d943bdaaafb6ba66d44bf65aa0e9c5616fa1d2da9bb08828c6b39aa",
"sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00",
"sha256:e64c8d2f5e5d5fda7b842f55dec6133260ea8f53c4257d64494c534f306bf7a9",
"sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84",
"sha256:ea5405c46e690122a76531ab97a079e184c0daf491e588592d6a23d3e32af99e",
"sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20",
"sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e",
"sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162"
],
"markers": "platform_python_implementation == 'CPython'",
"version": "==1.1.2"
},
"packaging": {
"hashes": [
"sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4",
"sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"
],
"markers": "python_version >= '3.8'",
"version": "==26.0"
},
"pillow": { "pillow": {
"hashes": [ "hashes": [
"sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9",
@@ -131,6 +564,57 @@
"markers": "python_version >= '3.10'", "markers": "python_version >= '3.10'",
"version": "==12.1.1" "version": "==12.1.1"
}, },
"priority": {
"hashes": [
"sha256:6bc1961a6d7fcacbfc337769f1a382c8e746566aaa365e78047abe9f66b2ffbe",
"sha256:be4fcb94b5e37cdeb40af5533afe6dd603bd665fe9c8b3052610fc1001d5d1eb"
],
"version": "==1.3.0"
},
"py-ubjson": {
"hashes": [
"sha256:b9bfb8695a1c7e3632e800fb83c943bf67ed45ddd87cd0344851610c69a5a482"
],
"version": "==0.16.1"
},
"pyasn1": {
"hashes": [
"sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf",
"sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde"
],
"markers": "python_version >= '3.8'",
"version": "==0.6.3"
},
"pyasn1-modules": {
"hashes": [
"sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a",
"sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"
],
"markers": "python_version >= '3.8'",
"version": "==0.4.2"
},
"pycparser": {
"hashes": [
"sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29",
"sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"
],
"markers": "implementation_name != 'PyPy'",
"version": "==3.0"
},
"pyopenssl": {
"hashes": [
"sha256:df94d28498848b98cc1c0ffb8ef1e71e40210d3b0a8064c9d29571ed2904bf81",
"sha256:f293934e52936f2e3413b89c6ce36df66a0b34ae1ea3a053b8c5020ff2f513fc"
],
"version": "==26.0.0"
},
"service-identity": {
"hashes": [
"sha256:6b047fbd8a84fd0bb0d55ebce4031e400562b9196e1e0d3e0fe2b8a59f6d4a85",
"sha256:b8683ba13f0d39c6cd5d625d2c5f65421d6d707b013b375c355751557cbe8e09"
],
"version": "==24.2.0"
},
"sqlparse": { "sqlparse": {
"hashes": [ "hashes": [
"sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba",
@@ -138,6 +622,178 @@
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==0.5.5" "version": "==0.5.5"
},
"starlette": {
"hashes": [
"sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149",
"sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b"
],
"markers": "python_version >= '3.10'",
"version": "==1.0.0"
},
"twisted": {
"extras": [
"http2",
"tls"
],
"hashes": [
"sha256:1deb272358cb6be1e3e8fc6f9c8b36f78eb0fa7c2233d2dbe11ec6fee04ea316",
"sha256:8559f654d01a54a8c3efe66d533d43f383531ebf8d81d9f9ab4769d91ca15df7"
],
"markers": "python_full_version >= '3.8.0'",
"version": "==25.5.0"
},
"txaio": {
"hashes": [
"sha256:5f6cd6c6b397fc3305790d15efd46a2d5b91cdbefa96543b4f8666aeb56ba026",
"sha256:9f232c21e12aa1ff52690e365b5a0ecfd42cc27a6ec86e1b92ece88f763f4b78"
],
"markers": "python_version >= '3.11'",
"version": "==25.12.2"
},
"types-channels": {
"hashes": [
"sha256:9aaf517b5bb6599a8ba115859ead443b7ab711745760574e6761fd2979192121",
"sha256:fa09eead7716581354fa0ff313a5685e4d54fb6e386b565404ae36607fdb65d4"
],
"version": "==4.3.0.20260321"
},
"types-pyyaml": {
"hashes": [
"sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3",
"sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6"
],
"markers": "python_version >= '3.9'",
"version": "==6.0.12.20250915"
},
"typing-extensions": {
"hashes": [
"sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466",
"sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"
],
"markers": "python_version >= '3.9'",
"version": "==4.15.0"
},
"ujson": {
"hashes": [
"sha256:006428d3813b87477d72d306c40c09f898a41b968e57b15a7d88454ecc42a3fb",
"sha256:02f93da7a4115e24f886b04fd56df1ee8741c2ce4ea491b7ab3152f744ad8f8e",
"sha256:0727363b05ab05ee737a28f6200dc4078bce6b0508e10bd8aab507995a15df61",
"sha256:085b6ce182cdd6657481c7c4003a417e0655c4f6e58b76f26ee18f0ae21db827",
"sha256:09b4beff9cc91d445d5818632907b85fb06943b61cb346919ce202668bf6794a",
"sha256:0a3ae28f0b209be5af50b54ca3e2123a3de3a57d87b75f1e5aa3d7961e041983",
"sha256:0d2e8db5ade3736a163906154ca686203acc7d1d30736cbf577c730d13653d84",
"sha256:0e00cec383eab2406c9e006bd4edb55d284e94bb943fda558326048178d26961",
"sha256:0fe6b8b8968e11dd9b2348bd508f0f57cf49ab3512064b36bc4117328218718e",
"sha256:0fe9128e75c6aa6e9ae06c1408d6edd9179a2fef0fe6d9cda3166b887eba521d",
"sha256:14b2e1eb528d77bc0f4c5bd1a7ebc05e02b5b41beefb7e8567c9675b8b13bcf4",
"sha256:15d416440148f3e56b9b244fdaf8a09fcf5a72e4944b8e119f5bf60417a2bfc8",
"sha256:15e555c4caca42411270b2ed2b2ebc7b3a42bb04138cef6c956e1f1d49709fe2",
"sha256:16b4fe9c97dc605f5e1887a9e1224287291e35c56cbc379f8aa44b6b7bcfe2bb",
"sha256:1b5c6ceb65fecd28a1d20d1eba9dbfa992612b86594e4b6d47bb580d2dd6bcb3",
"sha256:1d072a403d82aef8090c6d4f728e3a727dfdba1ad3b7fa3a052c3ecbd37e73cb",
"sha256:2324d9a0502317ffc35d38e153c1b2fa9610ae03775c9d0f8d0cca7b8572b04e",
"sha256:2a248750abce1c76fbd11b2e1d88b95401e72819295c3b851ec73399d6849b3d",
"sha256:2ea6206043385343aff0b7da65cf73677f6f5e50de8f1c879e557f4298cac36a",
"sha256:31348a0ffbfc815ce78daac569d893349d85a0b57e1cd2cdbba50b7f333784da",
"sha256:38051f36423f084b909aaadb3b41c9c6a2958e86956ba21a8489636911e87504",
"sha256:3c2f947e55d3c7cfe124dd4521ee481516f3007d13c6ad4bf6aeb722e190eb1b",
"sha256:3ed5cb149892141b1e77ef312924a327f2cc718b34247dae346ed66329e1b8be",
"sha256:3ff4ede90ed771140caa7e1890de17431763a483c54b3c1f88bd30f0cc1affc0",
"sha256:40aa43a7a3a8d2f05e79900858053d697a88a605e3887be178b43acbcd781161",
"sha256:42d875388fbd091c7ea01edfff260f839ba303038ffb23475ef392012e4d63dd",
"sha256:457fabc2700a8e6ddb85bc5a1d30d3345fe0d3ec3ee8161a4e032ec585801dfa",
"sha256:460e76a4daff214ae33ab959494962c93918cb44714ea3e3f748b14aa37f8a87",
"sha256:50524f4f6a1c839714dbaff5386a1afb245d2d5ec8213a01fbc99cea7307811e",
"sha256:51acc750ec7a2df786cdc868fb16fa04abd6269a01d58cf59bafc57978773d8e",
"sha256:55ede2a7a051b3b7e71a394978a098d71b3783e6b904702ff45483fad434ae2d",
"sha256:561f89cc82deeae82e37d4a4764184926fb432f740a9691563a391b13f7339a4",
"sha256:56ba3f7abbd6b0bb282a544dc38406d1a188d8bb9164f49fdb9c2fee62cb29da",
"sha256:57930ac9519099b852e190d2c04b1fb5d97ea128db33bce77ed874eccb4c7f09",
"sha256:58a11cb49482f1a095a2bd9a1d81dd7c8fb5d2357f959ece85db4e46a825fd00",
"sha256:64df53eef4ac857eb5816a56e2885ccf0d7dff6333c94065c93b39c51063e01d",
"sha256:6879aed770557f0961b252648d36f6fdaab41079d37a2296b5649fd1b35608e0",
"sha256:6ad57654570464eb1b040b5c353dee442608e06cff9102b8fcb105565a44c9ed",
"sha256:6c0aed6a4439994c9666fb8a5b6c4eac94d4ef6ddc95f9b806a599ef83547e3b",
"sha256:76bf3e7406cf23a3e1ca6a23fb1fb9ea82f4f6bd226fe226e09146b0194f85dc",
"sha256:7bbf05c38debc90d1a195b11340cc85cb43ab3e753dc47558a3a84a38cbc72da",
"sha256:7ddb08b3c2f9213df1f2e3eb2fbea4963d80ec0f8de21f0b59898e34f3b3d96d",
"sha256:7e07f6f644d2c44d53b7a320a084eef98063651912c1b9449b5f45fcbdc6ccd2",
"sha256:85833bca01aa5cae326ac759276dc175c5fa3f7b3733b7d543cf27f2df12d1ef",
"sha256:8712b61eb1b74a4478cfd1c54f576056199e9f093659334aeb5c4a6b385338e5",
"sha256:871c0e5102e47995b0e37e8df7819a894a6c3da0d097545cd1f9f1f7d7079927",
"sha256:89e302abd3749f6d6699691747969a5d85f7c73081d5ed7e2624c7bd9721a2ab",
"sha256:937794042342006f707837f38d721426b11b0774d327a2a45c0bd389eb750a87",
"sha256:93bc91fdadcf046da37a214eaa714574e7e9b1913568e93bb09527b2ceb7f759",
"sha256:94c5f1621cbcab83c03be46441f090b68b9f307b6c7ec44d4e3f6d5997383df4",
"sha256:973b7d7145b1ac553a7466a64afa8b31ec2693d7c7fff6a755059e0a2885dfd2",
"sha256:99cc80facad240b0c2fb5a633044420878aac87a8e7c348b9486450cba93f27c",
"sha256:9a5fcbe7b949f2e95c47ea8a80b410fcdf2da61c98553b45a4ee875580418b68",
"sha256:9b3b86ec3e818f3dd3e13a9de628e88a9990f4af68ecb0b12dd3de81227f0a26",
"sha256:9b3cf13facf6f77c283af0e1713e5e8c47a0fe295af81326cb3cb4380212e797",
"sha256:9c5a52987a990eb1bae55f9000994f1afdb0326c154fb089992f839ab3c30688",
"sha256:a2d79c6635ccffcbfc1d5c045874ba36b594589be81d50d43472570bb8de9c57",
"sha256:a6ec5bf6bc361f2f0f9644907a36ce527715b488988a8df534120e5c34eeda94",
"sha256:a7bf9cc97f05048ac8f3e02cd58f0fe62b901453c24345bfde287f4305dcc31c",
"sha256:ab9056d94e5db513d9313b34394f3a3b83e6301a581c28ad67773434f3faccab",
"sha256:adf28d13a33f9d750fe7a78fb481cac298fa257d8863d8727b2ea4455ea41235",
"sha256:b62cb9a7501e1f5c9ffe190485501349c33e8862dde4377df774e40b8166871f",
"sha256:bacbd3c69862478cbe1c7ed4325caedec580d8acf31b8ee1b9a1e02a56295cad",
"sha256:bb349dbba57c76eec25e5917e07f35aabaf0a33b9e67fc13d188002500106487",
"sha256:bd03472c36fa3a386a6deb887113b9e3fa40efba8203eb4fe786d3c0ccc724f6",
"sha256:bf85a00ac3b56a1e7a19c5be7b02b5180a0895ac4d3c234d717a55e86960691c",
"sha256:ca0c7ce828bb76ab78b3991904b477c2fd0f711d7815c252d1ef28ff9450b052",
"sha256:ccbfd94e59aad4a2566c71912b55f0547ac1680bfac25eb138e6703eb3dd434e",
"sha256:d1831c07bd4dce53c4b666fa846c7eba4b7c414f2e641a4585b7f50b72f502dc",
"sha256:d22cad98c2a10bbf6aa083a8980db6ed90d4285a841c4de892890c2b28286ef9",
"sha256:d30ad4359413c8821cc7b3707f7ca38aa8bc852ba3b9c5a759ee2d7740157315",
"sha256:e0dd3676ea0837cd70ea1879765e9e9f6be063be0436de9b3ea4b775caf83654",
"sha256:e584d0cdd37cac355aca52ed788d1a2d939d6837e2870d3b70e585db24025a50",
"sha256:e6369ac293d2cc40d52577e4fa3d75a70c1aae2d01fa3580a34a4e6eff9286b9",
"sha256:efae5df7a8cc8bdb1037b0f786b044ce281081441df5418c3a0f0e1f86fe7bb3",
"sha256:f19b3af31d02a2e79c5f9a6deaab0fb3c116456aeb9277d11720ad433de6dfc6",
"sha256:f7a0430d765f9bda043e6aefaba5944d5f21ec43ff4774417d7e296f61917382",
"sha256:fb94245a715b4d6e24689de12772b85329a1f9946cbf6187923a64ecdea39e65"
],
"markers": "python_version >= '3.10'",
"version": "==5.12.0"
},
"zope-interface": {
"hashes": [
"sha256:0009d2d3c02ea783045d7804da4fd016245e5c5de31a86cebba66dd6914d59a2",
"sha256:05a0e42d6d830f547e114de2e7cd15750dc6c0c78f8138e6c5035e51ddfff37c",
"sha256:0723507127f8269b8f3f22663168f717e9c9742107d1b6c9f419df561b71aa6d",
"sha256:16c69da19a06566664ddd4785f37cad5693a51d48df1515d264c20d005d322e2",
"sha256:2499de92e8275d0dd68f84425b3e19e9268cd1fa8507997900fa4175f157733c",
"sha256:2bf9cf275468bafa3c72688aad8cfcbe3d28ee792baf0b228a1b2d93bd1d541a",
"sha256:34f877d1d3bb7565c494ed93828fa6417641ca26faf6e8f044e0d0d500807028",
"sha256:3bf73a910bb27344def2d301a03329c559a79b308e1e584686b74171d736be4e",
"sha256:46c7e4e8cbc698398a67e56ca985d19cb92365b4aafbeb6a712e8c101090f4cb",
"sha256:532367553e4420c80c0fc0cabcc2c74080d495573706f66723edee6eae53361d",
"sha256:561ce42390bee90bae51cf1c012902a8033b2aaefbd0deed81e877562a116d48",
"sha256:6068322004a0158c80dfd4708dfb103a899635408c67c3b10e9acec4dbacefec",
"sha256:624b6787fc7c3e45fa401984f6add2c736b70a7506518c3b537ffaacc4b29d4c",
"sha256:6f4b4dfcfdfaa9177a600bb31cebf711fdb8c8e9ed84f14c61c420c6aa398489",
"sha256:788c293f3165964ec6527b2d861072c68eef53425213f36d3893ebee89a89623",
"sha256:845d14e580220ae4544bd4d7eb800f0b6034fe5585fc2536806e0a26c2ee6640",
"sha256:8f094bfb49179ec5dc9981cb769af1275702bd64720ef94874d9e34da1390d4c",
"sha256:9a4e785097e741a1c953b3970ce28f2823bd63c00adc5d276f2981dd66c96c15",
"sha256:9b05a919fdb0ed6ea942e5a7800e09a8b6cdae6f98fee1bef1c9d1a3fc43aaa0",
"sha256:a1ef4b43659e1348f35f38e7d1a6bbc1682efde239761f335ffc7e31e798b65b",
"sha256:a87fc7517f825a97ff4a4ca4c8a950593c59e0f8e7bfe1b6f898a38d5ba9f9cf",
"sha256:aae807efc7bd26302eb2fea05cd6de7d59269ed6ae23a6de1ee47add6de99b8c",
"sha256:afb20c371a601d261b4f6edb53c3c418c249db1a9717b0baafc9a9bb39ba1224",
"sha256:bc9ded9e97a0ed17731d479596ed1071e53b18e6fdb2fc33af1e43f5fd2d3aaa",
"sha256:c31acfa3d7cde48bec45701b0e1f4698daffc378f559bfb296837d8c834732f6",
"sha256:c65ade7ea85516e428651048489f5e689e695c79188761de8c622594d1e13322",
"sha256:ccc62b5712dd7bd64cfba3ee63089fb11e840f5914b990033beeae3b2180b6cb",
"sha256:ccf52f7d44d669203c2096c1a0c2c15d52e36b2e7a9413df50f48392c7d4d080",
"sha256:d2bb8e7364e18f083bf6744ccf30433b2a5f236c39c95df8514e3c13007098ce",
"sha256:dfc4f44e8de2ff4eba20af4f0a3ca42d3c43ab24a08e49ccd8558b7a4185b466",
"sha256:f777e68c76208503609c83ca021a6864902b646530a1a39abb9ed310d1100664"
],
"markers": "python_version >= '3.10'",
"version": "==8.2"
} }
}, },
"develop": {} "develop": {}
+56 -41
View File
@@ -1,53 +1,68 @@
![Build](https://gitea.furb.it/kennyboy55/friend-event-rsvp/actions/workflows/build-docker.yml/badge.svg)
# friend-event-rsvp # friend-event-rsvp
A personal event RSVP tool. Meant for personal home use and a single administrator. Unique feature is multiple activities per event. A personal event RSVP tool. Meant for personal home use and a single administrator. Unique feature is multiple activities per event.
# Development ## Production
`pip install --user pipenv`
Inside the project root: `pipenv install` Make sure there are no deployment issues
Everytime you start: `pipenv shell` `python manage.py check --deploy`
Check that is works by running `which python` Collect and compress all static files:
# To run `python manage.py collectstatic --clear` <-- Clear is optional
Requires Daphne (websockets)
`python manage.py runserver`
## So long as the migrations are not part of the repo `python -m blacknoise.compress static/`
`python manage.py makemigrations`
`python manage.py migrate` Run the server
`daphne rsvpproject.asgi:application`
## Development
### Setup environment
Install pipenv: `pip install --user pipenv`
Inside the project root (friend-event-rsvp): `pipenv install`
Everytime you start work: `pipenv shell`
Check that is works by running: `which python`. This should return a specific python location inside your user folder.
Requirements (installed from Pipfile):
- Django
- Channels
- Daphne
- BlackNoise
### Publish docker image
Build
`docker build -t gitea.furb.it/kennyboy55/friend-event-rsvp:dev .`
How to tag:
`docker push gitea.furb.it/kennyboy55/friend-event-rsvp:dev`
### Run
Run the server during development: `DEBUG=1 python manage.py runserver`
This will automatically restart on changes and log to the console.
### Setup database
This needs to be done every time a model chance is made:
- Create migrations file (add to repo): `python manage.py makemigrations`
- Update local database: `python manage.py migrate`
### To add a superuser
You will be prompted for a password:
## To add a superuser
`python manage.py createsuperuser --username <username> --email <email>` `python manage.py createsuperuser --username <username> --email <email>`
# Ideas and notes You can use this to login to the django admin environment: `http://127.0.0.1:8000/django-admin/`
## Een
Emails! Om te editen, als er comments of wijzigingen zijn!
## Twee
Admin paneel: 4 kolommen op basis van de status: open, incomplete, yes, no
Met een websocket.
En dat je dan ook live kan zien dat er gasten toegevoegd worden, en als ze dingen veranderen enzo.
## Drie
Als iemand geen gasten mag meenemen, die hele stap niet tonen.
## Vier
Activity start en eind tijd klopt niks van hoe het getoond wordt..
## Vijf
Extra gasten als property van een activity, die gewoon getoont worden als groep?
## Zes
De buttons tijdens een rsvp renderen serverside en met oob-swapping elke keer terugsturen:
- Geen previous op eerste pagina
- Geen next, maar finish op laatste pagina
- hx-confirm op back-to-event als nog niet voltooid.
# Things to keep in mind
- Move to Postgress
- Dockerfile
- Redis for channels?
+9
View File
@@ -0,0 +1,9 @@
services:
app:
image: gitea.furb.it/kennyboy55/friend-event-rsvp:dev
container_name: friend-event-rsvp
ports:
- "8000:8000"
env_file: .env
volumes:
- ./data:/data
+15
View File
@@ -0,0 +1,15 @@
# ---------------------------------------------------------------------------
# This template contains only required options.
# ---------------------------------------------------------------------------
# random secret key, use for example `base64 /dev/urandom | head -c50` to generate one
SECRET_KEY=
# your default timezone See https://timezonedb.com/time-zones for a list of timezones
TZ=Europe/Amsterdam
# allowed hosts (see documentation), must be set to your hostname(s)
ALLOWED_HOSTS=events.mydomain.com
# enable debug log
#DEBUG=1
+65 -79
View File
@@ -1,4 +1,4 @@
# Generated by Django 6.0 on 2025-12-20 13:32 # Generated by Django 6.0.3 on 2026-03-27 12:24
import datetime import datetime
import django.db.models.deletion import django.db.models.deletion
@@ -38,6 +38,7 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('title', models.CharField(max_length=255)), ('title', models.CharField(max_length=255)),
('description', models.TextField(blank=True)), ('description', models.TextField(blank=True)),
('location', models.CharField(max_length=255)),
('start_time', models.DateTimeField()), ('start_time', models.DateTimeField()),
('end_time', models.DateTimeField(blank=True, null=True)), ('end_time', models.DateTimeField(blank=True, null=True)),
('guest_limit', models.PositiveIntegerField(blank=True, null=True)), ('guest_limit', models.PositiveIntegerField(blank=True, null=True)),
@@ -66,7 +67,7 @@ class Migration(migrations.Migration):
('slug', models.SlugField(max_length=255)), ('slug', models.SlugField(max_length=255)),
('description', models.TextField(blank=True)), ('description', models.TextField(blank=True)),
('image', models.ImageField(blank=True, null=True, upload_to=events.models.event_image_path)), ('image', models.ImageField(blank=True, null=True, upload_to=events.models.event_image_path)),
('allow_rsvp_without_invite', models.BooleanField(default=True)), ('allow_rsvp_without_invite', models.BooleanField(default=False)),
('allow_comments', models.BooleanField(default=True)), ('allow_comments', models.BooleanField(default=True)),
('default_email_updates_optin', models.BooleanField(default=True)), ('default_email_updates_optin', models.BooleanField(default=True)),
('guest_limit', models.PositiveIntegerField(blank=True, null=True)), ('guest_limit', models.PositiveIntegerField(blank=True, null=True)),
@@ -78,21 +79,6 @@ class Migration(migrations.Migration):
'ordering': ['-id'], 'ordering': ['-id'],
}, },
), ),
migrations.CreateModel(
name='Comment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=255)),
('text', models.TextField()),
('notify_subscribers', models.BooleanField(default=False)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='events.event')),
],
options={
'abstract': False,
},
),
migrations.AddField( migrations.AddField(
model_name='activitygroup', model_name='activitygroup',
name='event', name='event',
@@ -103,46 +89,6 @@ class Migration(migrations.Migration):
name='event', name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='events.event'), field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='events.event'),
), ),
migrations.CreateModel(
name='GuestListTemplate',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=255)),
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='GuestListItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('email', models.EmailField(blank=True, max_length=254)),
('is_main_invitee', models.BooleanField(default=True)),
('template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='events.guestlisttemplate')),
],
),
migrations.CreateModel(
name='Invite',
fields=[
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('code', models.CharField(db_index=True, max_length=64, unique=True)),
('recipient_name', models.CharField(blank=True, max_length=255)),
('recipient_email', models.EmailField(blank=True, max_length=254)),
('personalized_message', models.TextField(blank=True)),
('allow_bring_guests', models.BooleanField(default=False)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='events.event')),
],
options={
'abstract': False,
},
),
migrations.CreateModel( migrations.CreateModel(
name='Guest', name='Guest',
fields=[ fields=[
@@ -154,7 +100,21 @@ class Migration(migrations.Migration):
('is_main_invitee', models.BooleanField(default=False)), ('is_main_invitee', models.BooleanField(default=False)),
('created_by_host', models.BooleanField(default=False)), ('created_by_host', models.BooleanField(default=False)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guests', to='events.event')), ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guests', to='events.event')),
('invite', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='guests', to='events.invite')), ],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Comment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('text', models.TextField()),
('notify_subscribers', models.BooleanField(default=False)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='events.event')),
('guest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='events.guest')),
], ],
options={ options={
'abstract': False, 'abstract': False,
@@ -187,25 +147,6 @@ class Migration(migrations.Migration):
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='events.question')), ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='events.question')),
], ],
), ),
migrations.CreateModel(
name='RSVP',
fields=[
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('edit_token', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('responder_email', models.EmailField(blank=True, max_length=254)),
('send_email_updates', models.BooleanField(default=True)),
('status', models.CharField(choices=[('yes', 'Yes'), ('no', 'No'), ('maybe', 'Maybe'), ('pending', 'Pending')], default='pending', max_length=10)),
('notes', models.TextField(blank=True)),
('activities', models.ManyToManyField(related_name='rsvps', through='events.ActivitySelection', to='events.activity')),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rsvps', to='events.event')),
('invite', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rsvps', to='events.invite')),
],
options={
'abstract': False,
},
),
migrations.CreateModel( migrations.CreateModel(
name='Response', name='Response',
fields=[ fields=[
@@ -214,15 +155,60 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('value_text', models.TextField(blank=True)), ('value_text', models.TextField(blank=True)),
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='events.activity')), ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='events.activity')),
('choice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='events.questionchoice')),
('guest', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='events.guest')), ('guest', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='events.guest')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='events.question')), ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='events.question')),
('rsvp', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='events.rsvp')),
], ],
options={ options={
'abstract': False, 'abstract': False,
}, },
), ),
migrations.CreateModel(
name='ResponseChoice',
fields=[
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('choice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='response_choices', to='events.questionchoice')),
('response', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='response_choices', to='events.response')),
],
options={
'unique_together': {('response', 'choice')},
},
),
migrations.AddField(
model_name='response',
name='choices',
field=models.ManyToManyField(blank=True, related_name='responses', through='events.ResponseChoice', to='events.questionchoice'),
),
migrations.CreateModel(
name='RSVP',
fields=[
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('invite', models.SlugField(max_length=255, unique=True)),
('edit_token', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('responder_name', models.CharField(blank=True, max_length=255)),
('responder_email', models.EmailField(max_length=254)),
('send_email_updates', models.BooleanField(default=True)),
('personalized_message', models.TextField(blank=True)),
('allow_bring_guests', models.BooleanField(default=False)),
('page', models.CharField(choices=[('email', 'Email'), ('guests', 'Guests'), ('activities', 'Activities'), ('questions', 'Questions'), ('notes', 'Notes'), ('completed', 'Completed')], default='email', max_length=16)),
('furthest_page', models.CharField(choices=[('email', 'Email'), ('guests', 'Guests'), ('activities', 'Activities'), ('questions', 'Questions'), ('notes', 'Notes'), ('completed', 'Completed')], default='email', max_length=16)),
('status', models.CharField(choices=[('unopened', 'Not Opened'), ('not_started', 'Not Started'), ('incomplete', 'Incomplete'), ('not_coming', 'Not coming'), ('joining', 'Yes, joining')], default='unopened', max_length=16)),
('notes', models.TextField(blank=True)),
('activities', models.ManyToManyField(related_name='rsvps', through='events.ActivitySelection', to='events.activity')),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rsvps', to='events.event')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='response',
name='rsvp',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='events.rsvp'),
),
migrations.AddField( migrations.AddField(
model_name='guest', model_name='guest',
name='rsvp', name='rsvp',
+5 -17
View File
@@ -1,4 +1,5 @@
# models.py # models.py
import os
import uuid import uuid
from typing import Self from typing import Self
from datetime import timedelta from datetime import timedelta
@@ -23,8 +24,8 @@ class TimestampedModel(models.Model):
def event_image_path(instance, filename): def event_image_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename> # file will be uploaded to MEDIA_ROOT/<event_slug>/overview<.extension>
return "events/static/img/{0}/{1}".format(instance.slug, filename) return "{0}/overview{1}".format(instance.slug, os.path.splitext(filename)[1])
class Event(TimestampedModel): class Event(TimestampedModel):
"""The main event which contains activities. """The main event which contains activities.
@@ -61,13 +62,6 @@ class Event(TimestampedModel):
@property @property
def markdown(self) -> str: def markdown(self) -> str:
return markdown.markdown(self.description) return markdown.markdown(self.description)
@property
def static_image_url(self) -> str:
image_str = str(self.image)
if len(image_str) > 0:
return image_str[14:]
return "img/placeholder.jpg"
@property @property
def start_time(self): def start_time(self):
@@ -111,8 +105,8 @@ class ActivityGroup(TimestampedModel):
def activity_image_path(instance, filename): def activity_image_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename> # file will be uploaded to MEDIA_ROOT/<event_slug>/overview<.extension>
return "events/static/img/{0}/{1}".format(instance.slug, filename) return "{0}/{1}{2}".format(instance.event.slug, instance.id, os.path.splitext(filename)[1])
class Activity(TimestampedModel): class Activity(TimestampedModel):
"""An individual activity within an event. """An individual activity within an event.
@@ -143,12 +137,6 @@ class Activity(TimestampedModel):
def markdown(self) -> str: def markdown(self) -> str:
return markdown.markdown(self.description) return markdown.markdown(self.description)
def static_image_url(self) -> str:
image_str = str(self.image)
if len(image_str) > 0:
return image_str[14:]
return "img/placeholder_activity.jpg"
def is_locked(self) -> bool: def is_locked(self) -> bool:
if not self.allow_rsvp: if not self.allow_rsvp:
return True return True
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

+3 -2
View File
@@ -15,10 +15,11 @@
</div> </div>
{% if event.image %}
<div class="card"> <div class="card">
{% load static %} <img src="/media/{{ event.image }}" alt="{{ event.title }} image">
<img src="{% static event.static_image_url %}">
</div> </div>
{% endif %}
{% if response %} {% if response %}
<div class="card {% if response.joining %}bg-green{% elif response.not_coming %}bg-red{% elif response.in_progress %}bg-orange{% endif %}"> <div class="card {% if response.joining %}bg-green{% elif response.not_coming %}bg-red{% elif response.in_progress %}bg-orange{% endif %}">
@@ -52,7 +52,8 @@
</div> </div>
</div> </div>
{% load static %} {% if activity.image %}
<img class="thumbnail" src="{% static activity.static_image_url %}" alt="{{ activity.title }} image"> <img class="thumbnail" src="/media/{{ activity.image }}" alt="{{ activity.title }} image">
{% endif %}
</div> </div>
</div> </div>
+18 -1
View File
@@ -8,15 +8,32 @@ https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/
""" """
import os import os
import logging
from channels.auth import AuthMiddlewareStack from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator from channels.security.websocket import AllowedHostsOriginValidator
from blacknoise import BlackNoise
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
logger = logging.getLogger(__file__)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rsvpproject.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rsvpproject.settings')
django_asgi_app = get_asgi_application() from django.conf import settings
if settings.DEBUG:
logger.warning("DEBUG enabled")
if settings.DOCKER:
logger.warning("DOCKER enabled")
logger.info(f"ALLOWED_HOSTS={settings.ALLOWED_HOSTS}")
django_asgi_app = BlackNoise(get_asgi_application())
django_asgi_app.add(settings.STATIC_ROOT, "/static")
django_asgi_app.add(settings.MEDIA_ROOT, "/media")
from events.routing import websocket_urlpatterns from events.routing import websocket_urlpatterns
+54 -15
View File
@@ -10,24 +10,59 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/6.0/ref/settings/ https://docs.djangoproject.com/en/6.0/ref/settings/
""" """
import os
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. bool_map = {"true": True, "false": False}
BASE_DIR = Path(__file__).resolve().parent.parent
def extract_bool(env_key, default:bool):
retval = default
env_val = os.getenv(env_key)
if env_val:
# Try as int
try:
bool_as_num = int(env_val)
retval = bool(bool_as_num)
# Try as string
except ValueError:
# Convert string to boolean using the dictionary
retval = bool_map.get(env_val.lower(), default)
return retval
def extract_comma_list(env_key, default=None):
if os.getenv(env_key):
return [item.strip() for item in os.getenv(env_key).split(',')] # pyright: ignore[reportOptionalMemberAccess]
else:
if default:
return [default]
else:
return []
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-!o*-8y!xf%@2@wm3qd43^)sj6019#lv@!ze3z$81ndn@*$0$4*' SECRET_KEY = os.getenv('SECRET_KEY', 'INSECURE_STANDARD_KEY_SET_IN_ENV')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = extract_bool('DEBUG', False)
DOCKER = extract_bool('DOCKER', False)
ALLOWED_HOSTS = [] ALLOWED_HOSTS = extract_comma_list('ALLOWED_HOSTS', '127.0.0.1')
CSRF_TRUSTED_ORIGINS = extract_comma_list('CSRF_TRUSTED_ORIGINS')
BASE_DIR = Path(__file__).resolve().parent.parent
DOCKER_DIR = BASE_DIR
if DOCKER:
# We are running in docker
DOCKER_DIR = Path('/data')
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
@@ -83,24 +118,24 @@ LOGGING = {
'handlers': { 'handlers': {
'console': { 'console': {
'class': 'logging.StreamHandler', 'class': 'logging.StreamHandler',
'formatter': 'standard',
}, },
'file': { # 'file': {
'level': 'DEBUG', # 'level': 'DEBUG' if DEBUG else 'INFO',
'class': 'logging.FileHandler', # 'class': 'logging.FileHandler',
'filename': 'error.log', # 'filename': 'error.log',
'formatter': 'simple', # 'formatter': 'simple',
}, # },
}, },
'loggers': { 'loggers': {
'events': { 'events': {
'handlers': ['file', 'console'], 'handlers': ['console'],
'level': 'DEBUG', 'level': 'DEBUG' if DEBUG else 'INFO',
'propagate': True, 'propagate': True,
}, },
}, },
} }
WSGI_APPLICATION = 'rsvpproject.wsgi.application'
ASGI_APPLICATION = 'rsvpproject.asgi.application' ASGI_APPLICATION = 'rsvpproject.asgi.application'
@@ -110,7 +145,7 @@ ASGI_APPLICATION = 'rsvpproject.asgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3', 'NAME': DOCKER_DIR / 'db.sqlite3',
} }
} }
@@ -163,3 +198,7 @@ USE_TZ = True
# https://docs.djangoproject.com/en/6.0/howto/static-files/ # https://docs.djangoproject.com/en/6.0/howto/static-files/
STATIC_URL = 'static/' STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / 'static'
MEDIA_URL = 'media/'
MEDIA_ROOT = DOCKER_DIR / 'media'
-16
View File
@@ -1,16 +0,0 @@
"""
WSGI config for rsvpproject project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rsvpproject.settings')
application = get_wsgi_application()
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
echo "Migrating database"
python manage.py migrate
echo "Collecting and optimizing static files, this may take a while..."
python manage.py collectstatic --noinput --clear
python -m blacknoise.compress static/
echo "Creating all necessary directories..."
mkdir -p /data/media
echo "Done"
daphne -b 0.0.0.0 -p 8000 rsvpproject.asgi:application