Compare commits

...

85 Commits

Author SHA1 Message Date
WGAVermeer
255cb9bc2f cleanup moving from windows to linux.
All checks were successful
Gitea Test. / tests (push) Successful in 12s
2026-04-10 18:27:35 +02:00
WGAVermeer
d8800daba3 created base gallery app, made start on documentation.
Some checks failed
Gitea Test. / tests (push) Has been cancelled
2026-04-09 10:35:28 +02:00
WGAVermeer
5fd943db85 cleaned up dockerfile, removed useless expose as the port is configured in the compose.yaml file. Moved standard ENV into buildfile as they should always be executed no matter the compose variant executed. 2026-04-09 10:34:37 +02:00
WGAVermeer
00db9182d9 added admindocs for automatic documentation. 2026-04-09 10:32:38 +02:00
WGAVermeer
3f52db35c2 added requirements for enforced linting. 2026-04-09 10:31:43 +02:00
WGAVermeer
16c1913e69 hardened makefile by referencing 'web' container as it is specified in the docker compose file. 2026-04-09 10:31:01 +02:00
WGAVermeer
5490593b94 added database healthcheck, removed old test profile, implemented network segregation for containers. 2026-04-09 10:29:53 +02:00
c4785455eb Merge pull request 'woutervermeer-patch-1' (#20) from woutervermeer-patch-1 into pre-prod
All checks were successful
Gitea Test. / tests (push) Successful in 6s
Reviewed-on: #20
2026-03-28 11:33:25 +00:00
b463e5b18b Merge branch 'pre-prod' into woutervermeer-patch-1
All checks were successful
Gitea Test. / tests (pull_request) Successful in 6s
2026-03-28 11:32:49 +00:00
WGAVermeer
76c0cf3e74 Merge branch 'pre-prod' of https://gitea.woutervermeer.com/Quatsh/Quatsh-Website into pre-prod
All checks were successful
Gitea Test. / tests (push) Successful in 13s
2026-03-28 12:31:03 +01:00
WGAVermeer
68689ea885 Updated readme, started extra documentation, started feature list/plan. 2026-03-28 12:30:57 +01:00
74ee2f1965 Delete .gitea/workflows/build_test.yaml
All checks were successful
Gitea Test. / tests (pull_request) Successful in 6s
2026-03-26 10:12:42 +00:00
048828f2d2 Update .gitea/workflows/test.yaml 2026-03-26 10:12:10 +00:00
f6293b7a0c Merge pull request 'Align pre-prod with main' (#19) from main into pre-prod
All checks were successful
Gitea Test. / tests (push) Successful in 5s
Reviewed-on: #19
2026-03-26 09:53:27 +00:00
e0675c8684 Merge pull request 'Update .env.template' (#18) from woutervermeer-patch-1 into main
Some checks failed
Gitea build & Test. / tests (push) Failing after 5s
Deploy to production / deploy (push) Failing after 31s
Gitea build & Test. / tests (pull_request) Failing after 6s
Gitea Test. / tests (pull_request) Successful in 6s
Reviewed-on: #18
2026-03-26 09:52:42 +00:00
4b18769137 Update .env.template
Some checks failed
Gitea build & Test. / tests (pull_request) Failing after 7s
Gitea Test. / tests (pull_request) Successful in 6s
2026-03-26 09:52:33 +00:00
90b7bb0dee Merge pull request 'pre-prod' (#17) from pre-prod into main
Some checks failed
Gitea build & Test. / tests (push) Failing after 6s
Deploy to production / deploy (push) Failing after 1m19s
Reviewed-on: #17
2026-03-26 08:17:36 +00:00
8cfe5c6f9b Add .gitea/workflows/test.yaml
Some checks failed
Gitea Test. / tests (push) Successful in 6s
Gitea build & Test. / tests (pull_request) Failing after 6s
Gitea Test. / tests (pull_request) Successful in 6s
2026-03-26 08:15:11 +00:00
9397c2d4f2 Update .gitea/workflows/build_test.yaml
Some checks failed
Gitea build & Test. / tests (pull_request) Failing after 6s
2026-03-26 08:14:25 +00:00
313702f9fe Update Makefile
Some checks failed
Gitea build & Test. / tests (push) Failing after 13s
Gitea build & Test. / tests (pull_request) Failing after 7s
2026-03-26 08:09:28 +00:00
WGAVermeer
49199b7184 slightly updated README.md
Some checks failed
Gitea build & Test. / tests (push) Failing after 7s
2026-03-26 08:27:10 +01:00
WGAVermeer
c17e3e85f2 added auto-reload when developing.
Some checks failed
Gitea build & Test. / tests (push) Failing after 21s
2026-03-25 17:11:14 +01:00
WGAVermeer
be54afa093 fix makefile pt2
Some checks failed
Gitea build & Test. / tests (push) Failing after 27s
2026-03-25 15:47:29 +01:00
WGAVermeer
fd5414bd5a fix type in makefile
Some checks failed
Gitea build & Test. / tests (push) Failing after 2s
2026-03-25 15:46:46 +01:00
WGAVermeer
6b635955eb Merge branch 'pre-prod' of https://gitea.woutervermeer.com/woutervermeer/Quatsh-Website into pre-prod
Some checks failed
Gitea build & Test. / tests (push) Failing after 3s
2026-03-25 15:44:27 +01:00
WGAVermeer
0610053a3e added nginx as a proxy. Still need to enable ssl. 2026-03-25 15:44:17 +01:00
79560c5835 Merge pull request 'woutervermeer-patch-1' (#16) from woutervermeer-patch-1 into pre-prod
Some checks failed
Gitea build & Test. / tests (push) Failing after 6s
Reviewed-on: woutervermeer/Quatsh-Website#16
2026-03-11 08:49:35 +00:00
c1aa69dd8b Update .gitea/workflows/build_test.yaml
Some checks failed
Gitea build & Test. / tests (pull_request) Failing after 6s
2026-03-11 08:49:20 +00:00
d17b2f0a2a Merge pull request 'Change workflow stuff, update Readme, update .env handling' (#15) from pre-prod into main
Some checks failed
Gitea build & Test. / tests (push) Successful in 5s
Deploy to production / deploy (push) Failing after 26s
Reviewed-on: woutervermeer/Quatsh-Website#15
2026-03-11 08:39:52 +00:00
41a61fb318 Update .gitea/workflows/build_test.yaml
All checks were successful
Gitea build & Test. / tests (push) Successful in 6s
Gitea build & Test. / tests (pull_request) Successful in 5s
2026-03-11 08:38:41 +00:00
cba945375c Update .gitea/workflows/deploy.yml
All checks were successful
Gitea build & Test. / tests (push) Successful in 6s
2026-03-11 08:38:29 +00:00
WGAVermeer
1c545638b9 run deploy check aswell. 2026-03-11 09:26:45 +01:00
WGAVermeer
5fa195a0dc Merge branch 'pre-prod' of https://gitea.woutervermeer.com/woutervermeer/Quatsh-Website into pre-prod
All checks were successful
Gitea build & Test. / tests (push) Successful in 18s
2026-03-11 09:24:52 +01:00
WGAVermeer
f8dc8fc684 remove hardcoded .env 2026-03-11 09:24:45 +01:00
df587931b5 Merge pull request 'woutervermeer-patch-1' (#14) from woutervermeer-patch-1 into pre-prod
Some checks failed
Gitea build & Test. / tests (push) Failing after 3s
Reviewed-on: woutervermeer/Quatsh-Website#14
2026-03-11 08:23:40 +00:00
WGAVermeer
01ea42b64a default to non-debug mode.
Some checks failed
Gitea build & Test. / tests (push) Failing after 3s
2026-03-11 09:22:58 +01:00
WGAVermeer
fe6c0df123 Updated settings to import secret key.
Some checks failed
Gitea build & Test. / tests (push) Failing after 3s
2026-03-11 09:21:36 +01:00
357c81ff08 Update README.md
All checks were successful
Gitea build & Test. / tests (pull_request) Successful in 6s
2026-03-11 07:30:02 +00:00
WGAVermeer
26c6f46531 added poll app for testing purposes. Will be removed later.
Some checks failed
Gitea build & Test. / tests (push) Failing after 3s
2026-03-11 08:13:35 +01:00
WGAVermeer
13a317d3df Make Dev enters into the webserver shell.
Fixed database env variables to properly connect.
2026-03-11 08:12:07 +01:00
026bce1f16 Merge pull request 'Update README.md' (#13) from woutervermeer-patch-1 into main
All checks were successful
Deploy to production / deploy (push) Successful in 18s
Reviewed-on: woutervermeer/Quatsh-Website#13
2026-03-09 12:41:08 +00:00
b1f44f2ce0 Update README.md
All checks were successful
Gitea build & Test. / tests (pull_request) Successful in 6s
2026-03-09 12:40:37 +00:00
761227e05e Merge pull request 'pre-prod' (#12) from pre-prod into main
All checks were successful
Deploy to production / deploy (push) Successful in 19s
Reviewed-on: woutervermeer/Quatsh-Website#12
2026-03-08 14:34:16 +00:00
c165c03579 Merge pull request 'woutervermeer-patch-5' (#11) from woutervermeer-patch-5 into pre-prod
All checks were successful
Gitea build & Test. / tests (push) Successful in 6s
Gitea build & Test. / tests (pull_request) Successful in 5s
Reviewed-on: woutervermeer/Quatsh-Website#11
2026-03-08 14:31:30 +00:00
badf0dde7a Update Makefile
All checks were successful
Gitea build & Test. / tests (pull_request) Successful in 5s
2026-03-08 14:31:15 +00:00
b1f25ebcc3 Merge pull request 'Update .gitea/workflows/deploy.yml' (#10) from woutervermeer-patch-5 into main
Some checks failed
Deploy to production / deploy (push) Has been cancelled
Reviewed-on: woutervermeer/Quatsh-Website#10
2026-03-08 14:30:03 +00:00
01c8640d38 Update .gitea/workflows/deploy.yml
All checks were successful
Gitea build & Test. / tests (pull_request) Successful in 7s
2026-03-08 14:29:55 +00:00
f511599273 Merge pull request 'Update .gitea/workflows/deploy.yml' (#9) from woutervermeer-patch-4 into main
All checks were successful
Deploy to production / deploy (push) Successful in 4s
Reviewed-on: woutervermeer/Quatsh-Website#9
2026-03-08 14:23:11 +00:00
bac4c56485 Update .gitea/workflows/deploy.yml
All checks were successful
Gitea build & Test. / tests (pull_request) Successful in 6s
2026-03-08 14:23:03 +00:00
e1a6e4f21f Merge pull request 'Update .gitea/workflows/build_test.yaml' (#8) from woutervermeer-patch-3 into main
Some checks failed
Deploy to production / deploy (push) Has been cancelled
Reviewed-on: woutervermeer/Quatsh-Website#8
2026-03-08 14:22:40 +00:00
2f1102bebc Update .gitea/workflows/build_test.yaml
All checks were successful
Gitea build & Test. / tests (pull_request) Successful in 6s
2026-03-08 14:22:31 +00:00
f24bf52136 Merge pull request 'Update .gitea/workflows/deploy.yml' (#7) from woutervermeer-patch-1 into main
Some checks failed
Gitea build & Test. / tests (push) Successful in 6s
Deploy to production / deploy (push) Has been cancelled
Reviewed-on: woutervermeer/Quatsh-Website#7
2026-03-08 14:19:53 +00:00
0f98959d37 Update .gitea/workflows/deploy.yml
All checks were successful
Gitea build & Test. / tests (pull_request) Successful in 6s
2026-03-08 14:19:44 +00:00
e99f44d8ad Merge pull request 'pre-prod' (#6) from pre-prod into main
All checks were successful
Gitea build & Test. / tests (push) Successful in 6s
Reviewed-on: woutervermeer/Quatsh-Website#6
2026-03-08 14:18:31 +00:00
5b9d70db22 Update .gitea/workflows/deploy.yml
All checks were successful
Gitea build & Test. / tests (push) Successful in 6s
Gitea build & Test. / tests (pull_request) Successful in 5s
2026-03-08 14:17:19 +00:00
ac4e92aa07 Merge pull request 'Update .gitea/workflows/build_test.yaml' (#4) from woutervermeer-patch-2 into pre-prod
All checks were successful
Gitea build & Test. / tests (push) Successful in 6s
Gitea build & Test. / tests (pull_request) Successful in 5s
Reviewed-on: woutervermeer/Quatsh-Website#4
2026-03-08 14:15:24 +00:00
b94c208798 Merge pull request 'woutervermeer-patch-3' (#3) from woutervermeer-patch-3 into pre-prod
All checks were successful
Gitea build & Test. / docker (push) Successful in 6s
Reviewed-on: woutervermeer/Quatsh-Website#3
2026-03-08 14:14:50 +00:00
f293a34923 Add .gitea/workflows/deploy.yml
All checks were successful
Gitea build & Test. / docker (pull_request) Successful in 7s
2026-03-08 14:14:34 +00:00
56c54c973f Update .gitea/workflows/build_test.yaml
All checks were successful
Gitea build & Test. / tests (pull_request) Successful in 6s
2026-03-08 14:13:48 +00:00
3984dfb058 Merge pull request 'Update .gitea/workflows/build_test.yaml' (#2) from woutervermeer-patch-1 into main
All checks were successful
Gitea build & Test. / docker (push) Successful in 5s
Reviewed-on: woutervermeer/Quatsh-Website#2
2026-03-08 14:08:07 +00:00
bcb72b42e1 Update .gitea/workflows/build_test.yaml
All checks were successful
Gitea build & Test. / docker (pull_request) Successful in 7s
2026-03-08 14:07:52 +00:00
86c3cfe426 revert 7ecd65b9c2
All checks were successful
Gitea build & Test. / docker (push) Successful in 6s
revert cleanup
2026-03-08 13:56:48 +00:00
WGAVermeer
7ecd65b9c2 cleanup
Some checks failed
Gitea build & Test. / docker (push) Failing after 5s
2026-03-08 14:54:29 +01:00
WGAVermeer
8e4e2b8434 pray
All checks were successful
Gitea build & Test. / docker (push) Successful in 6s
2026-03-08 14:53:24 +01:00
WGAVermeer
dda1b29626 fix typo in compose.test.yaml
Some checks failed
Gitea build & Test. / docker (push) Failing after 6s
2026-03-08 14:41:27 +01:00
WGAVermeer
d999fece6f specified db info
Some checks failed
Gitea build & Test. / docker (push) Failing after 3s
2026-03-08 14:39:31 +01:00
WGAVermeer
cbf5e4b503 choose correct .env
Some checks failed
Gitea build & Test. / docker (push) Failing after 19s
2026-03-08 14:36:06 +01:00
WGAVermeer
9959272051 test version runs
Some checks failed
Gitea build & Test. / docker (push) Failing after 3s
2026-03-08 14:20:43 +01:00
WGAVermeer
3fca18e000 remove quotes from compose.test.yaml
Some checks failed
Gitea build & Test. / docker (push) Failing after 3s
2026-03-08 14:02:39 +01:00
WGAVermeer
0f57d9f32b change gitignore to push template
Some checks failed
Gitea build & Test. / docker (push) Failing after 3s
2026-03-08 13:58:34 +01:00
WGAVermeer
518364faa9 Merge branch 'main' of https://gitea.woutervermeer.com/woutervermeer/Quatsh-Website 2026-03-08 13:57:30 +01:00
WGAVermeer
b680c1ab5f fix .env.template
:
2026-03-08 13:57:24 +01:00
27257a2977 Update .gitea/workflows/build.yaml
Some checks failed
Gitea build & Test. / docker (push) Failing after 3s
2026-03-08 12:55:34 +00:00
WGAVermeer
8049cb2e62 Merge branch 'main' of https://gitea.woutervermeer.com/woutervermeer/Quatsh-Website
Some checks failed
Gitea build & run. / docker (push) Failing after 3s
2026-03-08 13:54:48 +01:00
WGAVermeer
600baaee80 attempting creation of testing mode. 2026-03-08 13:54:16 +01:00
14f2665070 Update .gitea/workflows/build.yaml
Some checks failed
Gitea build & run. / docker (push) Failing after 4s
2026-03-08 12:24:59 +00:00
WGAVermeer
acc116a6c1 added dev make file for ease in development.
Some checks failed
Gitea build & run. / docker (push) Failing after 3s
2026-03-08 13:20:45 +01:00
vps - Wouter
c9eb6d52be fix issue, dockerfile was using a cached image
Some checks failed
Gitea build & run. / docker (push) Failing after 3s
2026-03-08 11:47:41 +00:00
WGAVermeer
6511120965 updated .env insertion for db to use new .env variables
Some checks failed
Gitea build & run. / docker (push) Failing after 3s
2026-03-06 19:14:36 +01:00
b25196545f Merge pull request 'init_development' (#1) from init_development into main
Some checks failed
Gitea build & run. / docker (push) Failing after 3s
Reviewed-on: woutervermeer/Quatsh-Website#1
2026-03-06 18:11:55 +00:00
WGAVermeer
9e8d43e210 updated .env insertation
Some checks failed
Gitea build & run. / docker (pull_request) Failing after 10s
2026-03-06 18:25:33 +01:00
WGAVermeer
5160a06aa2 updated .env insertation 2026-03-06 18:16:21 +01:00
WGAVermeer
9b57180e20 updated workflow 2026-03-06 16:59:38 +01:00
WGAVermeer
d359cb9780 working database connection in Django 2026-03-06 16:56:14 +01:00
WGAVermeer
860b0ef033 renamed to src 2026-03-06 15:12:32 +01:00
46 changed files with 983 additions and 139 deletions

View File

@@ -0,0 +1,14 @@
services:
web:
command: python manage.py check --deploy
restart: "no"
env_file:
- path: .env.template
required: true
db:
env_file:
- path: .env.template
required: true

View File

@@ -0,0 +1,18 @@
services:
web:
command: gunicorn --capture-output --enable-stdio-inheritance -b 0.0.0.0:8000 website.wsgi:application
volumes:
- ./src:/src
env_file:
- path: .env.template
required: true
- path: .env
required: false
db:
env_file:
- path: .env.template
required: true
- path: .env
required: false

View File

@@ -0,0 +1,10 @@
services:
web:
env_file:
- path: .env
required: true
db:
env_file:
- path: .env
required: true

View File

@@ -0,0 +1,14 @@
services:
web:
command: python manage.py check --deploy; python -Wa manage.py test --noinput --parallel
restart: "no"
env_file:
- path: .env.template
required: true
db:
env_file:
- path: .env.template
required: true

20
.env.template Normal file
View File

@@ -0,0 +1,20 @@
#NGINX
NGINX_HOSTNAME=localhost
# Django
DJANGO_SETTINGS_MODULE=website.settings
DJANGO_SECRET_KEY=CWHZCAZBNV57tDkwGHJwTUu3PCSnGG45
DEBUG=TRUE
#ALLOWED_HOSTS=localhost
# Database (PostgreSQL)
POSTGRES_USER=test_user
POSTGRES_PASSWORD=test_password
POSTGRES_HOST=db
POSTGRES_PORT=5432
POSTGRES_DBNAME=test_dbname # == POSTGRES_DB
POSTGRES_DB=test_db
# Gunicorn
GUNICORN_WORKERS=3
GUNICORN_TIMEOUT=120

View File

@@ -0,0 +1,38 @@
name: Deploy to production
run-name: deploy-${{ gitea.actor }}
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts
- name: Deploy
run: |
ssh -i ~/.ssh/id_rsa ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << 'EOF'
cd ~/Quatsh-Website
echo "Pulling latest code..."
git pull origin main
echo "Building containers..."
make prod
echo "Running Django deploy check"
docker compose run --rm web python manage.py check --deploy
echo "Deployment complete."
EOF

View File

@@ -0,0 +1,18 @@
name: Gitea Test.
run-name: ${{ gitea.actor }}
on:
push:
branches:
- main
- pre-prod
pull_request:
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: test
run: make test

5
.gitignore vendored
View File

@@ -136,6 +136,7 @@ venv/
ENV/
env.bak/
venv.bak/
!.env.template
# Spyder project settings
.spyderproject
@@ -174,3 +175,7 @@ cython_debug/
# PyPI configuration file
.pypirc
# gunicon webserver
gunicorn.ctl

3
.nginx/.conf Normal file
View File

@@ -0,0 +1,3 @@
events {}
http {}

View File

@@ -0,0 +1,49 @@
server {
listen ${NGINX_PORT};
server_name ${NGINX_HOSTNAME};
location /static/ {
alias /app/static/;
expires 30d;
add_header Cache-Control "public";
}
location /media/ {
alias /app/media;
expires 30d;
add_header Cache-Control "public";
}
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen ${NGINX_SSL_PORT};
server_name ${NGINX_HOSTNAME};
location /static/ {
alias /app/static/;
expires 30d;
add_header Cache-Control "public";
}
location /media/ {
alias /app/media;
expires 30d;
add_header Cache-Control "public";
}
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

View File

@@ -1,18 +1,15 @@
FROM python:3.14-alpine
RUN mkdir /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
WORKDIR /src
RUN pip install --upgrade pip
COPY requirements.txt /src/
RUN pip install --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
COPY requirements.txt /app/
COPY gunicorn.conf.py /app/
COPY ./app/ /app/
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 8000
COPY gunicorn.conf.py /src/
COPY ./src/ /src/
CMD ["gunicorn", "website.wsgi:application"]

28
Makefile Normal file
View File

@@ -0,0 +1,28 @@
.SHELLFLAGS := -ec
prod:
docker compose down
docker compose --env-file .env -f ./compose.yaml -f ./.docker-compose-files/compose.prod.yaml up -d --build
docker compose exec web python manage.py collectstatic --noinput
docker compose exec web python manage.py check --deploy
docker compose exec web python manage.py migrate
dev:
docker compose down
docker compose -f ./compose.yaml -f ./.docker-compose-files/compose.dev.yaml up --build -d
docker compose exec web python manage.py collectstatic --noinput
docker compose exec -it web sh
dev_restart:
docker compose down
docker compose -f ./compose.yaml -f ./.docker-compose-files/compose.dev.yaml up -d
docker compose exec -it web sh
dev_restart_with_logs:
docker compose down
docker compose -f ./compose.yaml -f ./.docker-compose-files/compose.dev.yaml up
test:
docker compose --env-file .env.template -f ./compose.yaml -f ./.docker-compose-files/compose.test.yaml up --build --abort-on-container-exit --exit-code-from web

View File

@@ -1,2 +1,70 @@
# Quatsh-Website
## Requirements
### Install make
Running this requires the use of "make" and docker.
~"make" tends to come pre-installed on linux but not on Windows, to install it on Windows I recommend using Chocolatey as follows~
```shell
choco install make
```
EDIT: I've swapped over to using [[scoop](https://github.com/ScoopInstaller/Scoop#readme)] for these kinds of installations.
```shell
scoop install make
```
### Install docker
<https://docs.docker.com/get-started/get-docker/>
## Running for the first time
### Env
Before running the container environment has to be setup first, so first run:
```
cp .env.template .env
```
Then make any changes to the .env file.
### Running the website
To run the website 3 options have been provided
#### Development
For development purposes only as the src folder has been mounted in the container to allow for the making of changes without rebuilding the entire image.
Properly utilising this also requires the DEBUG environment variable to be set to TRUE.
```shell
make dev
```
#### Testing
Runs the testing environment and exits.
```shell
make test
```
#### Production
Using `make prod` is identical but preferred here.
```shell
make prod
```
Or
```shell
make
```

View File

@@ -1,117 +0,0 @@
"""
Django settings for website project.
Generated by 'django-admin startproject' using Django 6.0.3.
For more information on this file, see
https://docs.djangoproject.com/en/6.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/6.0/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-c$q7wdq+u@ow74wp!&zzkxdylkueu)(+34e%!e0du&bjwoqz9z'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'website.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'website.wsgi.application'
# Database
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/6.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/6.0/howto/static-files/
STATIC_URL = 'static/'

View File

@@ -1,9 +1,75 @@
services:
web:
build: .
ports:
- 8000:8000
command: gunicorn website.wsgi:application
depends_on:
db:
condition: service_healthy
environment:
- PYTHONDONTWRITEBYTECODE=1
- PYTHONUNBUFFERED=1
ALLOWED_HOSTS: ${NGINX_HOSTNAME}
DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_MODULE}
VIRTUAL_HOST: localhost
#VIRTUAL_PORT: 8000
restart: unless-stopped
volumes:
- static_volume:/app/static
- media_volume:/app/media
networks:
- frontend
- backend
db:
image: postgres:18
environment:
POSTGRES_DB: ${POSTGRES_DBNAME}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DBNAME}"]
interval: 5s
retries: 5
networks:
- backend
adminer:
image: adminer
depends_on:
- db
restart: always
ports:
- 127.0.0.1:8080:8080
networks:
- backend
proxy:
image: nginx:stable
volumes:
- ./.nginx/.templates:/etc/nginx/templates
- static_volume:/app/static:ro
- media_volume:/app/media:ro
restart: unless-stopped
ports:
- 80:80
- 443:443
environment:
- NGINX_HOSTNAME=${NGINX_HOSTNAME}
- NGINX_PORT=80
- NGINX_SSL_PORT=443
depends_on:
- web
networks:
- frontend
networks:
frontend:
backend:
volumes:
postgres_data:
static_volume:
media_volume:

View File

@@ -1,5 +0,0 @@
services:
web:
volumes:
- ./app:/app

24
docs/ROADMAP.md Normal file
View File

@@ -0,0 +1,24 @@
# Goals
This document contains the wanted feature list and their current state.
## Basic Pages
-[] Homepage
-[] Boards
-[] Committee page
-[] Contact page
-[] Donator page/Friends of Quatsh
-[] Membership page
## Main Features
-[] Signup form with email SMTP link.
-[] Gallery
-[] Login page
## Extra's
-[] Allow google-workspace login with SSO?
-[] Add Redis for Caching
-[] Setup SMTP with automatic switching to in-mem email saving OR print to stdout when in DEBUG mode.

View File

@@ -0,0 +1,177 @@
# Architectural Decision Records
This document records the major architectural decisions made during the development of the Quatsh website, including the context and reasoning behind each choice. Future maintainers should add to this document when making significant architectural decisions.
---
## ADR-001 — Move away from WordPress
**Date:** 2026
**Status:** Decided
### Context
The previous Quatsh website was built on WordPress, hosted on a shared webhosting service (cloud86). Over time, development had ground to a halt due to several compounding problems:
- WordPress stores page content in a serialised format inside a database, making it difficult to version control, diff, or reason about changes.
- The site relied heavily on third-party plugins, creating a fragile dependency chain where updating one plugin risked breaking others.
- PHP and the WordPress ecosystem were unfamiliar to the association's technically-minded members, narrowing the pool of people who could contribute.
- The shared webhosting service was more expensive and less flexible than a VPS for a project of this nature.
- The page-stucture made DRY development difficult leading to a lot of code duplication across different pages.
- Direct database access using Raw-SQL made it easy to make mistakes.
### Decision
Replace the WordPress site with a purpose-built web application on a VPS.
### Consequences
- Full control over the codebase, dependencies, and infrastructure.
- Performance issues can actually be debugged and fixed.
- Higher initial setup cost in developer time, offset by lower long-term maintenance friction and cheaper hosting.
- The association is no longer dependent on a third-party hosting provider's ecosystem.
---
## ADR-002 — Django as the web framework
**Date:** 2026
**Status:** Decided
### Context
Having decided to build a custom web application, a framework and language needed to be chosen. The primary constraints were:
- **Maintainability over time** — the project will be handed off to future developers. The current maintainer will only be a member for a limited number of years.
- **Familiarity within the target contributor pool** — a student sports association at a university. New contributors are likely to be students.
- **Batteries included** — the association does not have the capacity to assemble and maintain a bespoke stack of micro-libraries.
### Decision
Use Django with Python as the primary framework and language.
### Reasoning
- Python is the most commonly taught language at Dutch universities, maximising the pool of potential future contributors.
- Django is "batteries included" — authentication, admin interface, ORM, migrations, form handling, and email are all built in. This reduces the number of third-party dependencies and the surface area a new maintainer needs to learn.
- A single Django repository centralises the entire codebase (backend, templates, static files, configuration) making handoff to future maintainers straightforward.
- The Django admin interface provides board members with basic data management capabilities without requiring a custom-built admin panel from scratch.
### Alternatives considered
- **Flask / FastAPI** — too minimal; would require assembling many additional libraries, increasing long-term maintenance burden.
- **Node.js** — less common in the university curriculum than Python; splits frontend and backend concerns in a way that adds complexity for a small team.
- **Laravel (PHP)** — would have kept the PHP ecosystem from WordPress, but PHP is less familiar to the target contributor pool and does not solve the maintainability problem.
### Consequences
- New contributors with basic Python knowledge can get up to speed quickly.
- The project is well-positioned for handoff to future maintainers.
- Django's monolithic nature means the entire application is in one place, which is appropriate for a project of this scale.
---
## ADR-003 — Gunicorn + nginx as the serving stack
**Date:** 2026
**Status:** Decided
### Context
Django's built-in development server is not suitable for production. A production-grade serving stack was needed.
### Decision
Use Gunicorn as the WSGI application server behind nginx acting as a reverse proxy and static file server.
### Reasoning
- Gunicorn is the standard WSGI server for Django in production and is well documented alongside Django.
- nginx is significantly more efficient than Django/Gunicorn at serving static files and handling slow clients.
- nginx handles SSL termination, keeping TLS configuration out of the application layer.
- This is the most common Django deployment pattern, meaning future maintainers are likely to be familiar with it or find documentation easily.
### Consequences
- Static files must be collected via `manage.py collectstatic` before deployment (handled in the Makefile).
- nginx and the Django container share a read-only/read-write volume respectively for static and media files.
---
## ADR-004 — Docker Compose for container orchestration
**Date:** 2026
**Status:** Decided
### Context
The application consists of multiple services (Django, nginx, Postgres) that need to run together consistently across development and production environments.
### Decision
Use Docker with Docker Compose for local development and production deployment, with separate compose override files per environment.
### Reasoning
- Docker ensures the application runs identically regardless of the host machine, reducing "works on my machine" issues during handoff.
- Docker is an easier way of getting the full stack operational on a new contributors machine compared to making them do multiple different installs.
- Docker Compose is simple enough for a non-specialist maintainer to understand and operate.
- Separate override files (`compose.dev.yaml`, `compose.prod.yaml`, `compose.test.yaml`) allow environment-specific configuration without duplicating the base service definitions.
- A VPS with Docker is significantly cheaper than a managed hosting service while providing more control.
### Consequences
- New maintainers need minimal Docker literacy, but this is a reasonable baseline expectation.
- The Makefile wraps common Docker Compose commands to reduce the surface area a maintainer needs to know day-to-day.
- Data persistence is handled via named Docker volumes.
---
## ADR-005 — PostgreSQL as the database
**Date:** 2026
**Status:** Decided
### Context
Django supports multiple database backends. A production database needed to be chosen.
### Decision
Use PostgreSQL 18.
### Reasoning
- PostgreSQL is the recommended database for Django in production.
- It is robust, well-documented, and widely understood.
### Consequences
- The Postgres Docker volume must be mounted at `/var/lib/postgresql` (not `/data`) when using the official `postgres:18` image. See the note in `compose.yaml`.
---
## ADR-006 — PEP8 as style guide
**Date:** 2026/04/08
**Status:** Decided
### Context
Styleguides are used to ensure the long-term maintainability and readability of a codebase.
### Decision
Use PEP8 as the style guide with enforced type hinting. Enforced using 'ruff' and 'MyPy'.
### Reasoning
- PEP8 is the standard style guide for python development. It has great tooling to ensure enforcement of the style guide.
- Type hinting greatly improves the readability of code as you are better able to reason. It also helps keep variable names short as "picture_array" is instead written as "pictures: Sequence".
### Consequences
Linting checks have to integrated into the CI/CD pipeline.
'Contributing.md' needs to be created to make it clear how to contribute.
---
*When making a new architectural decision, copy the template below and append it to this file.*
---
## ADR-00X — [Title]
**Date:**
**Status:** Proposed / Decided / Superseded by ADR-00X
### Context
<!-- What is the situation that requires a decision? What constraints exist? -->
### Decision
<!-- What was decided? -->
### Reasoning
<!-- Why was this the right choice given the context? -->
### Alternatives considered
<!-- What else was evaluated and why was it not chosen? -->
### Consequences
<!-- What are the implications of this decision, positive and negative? -->

31
docs/environment.md Normal file
View File

@@ -0,0 +1,31 @@
# Details on environment variables
## Nginx
### NGINX_HOSTNAME
## Django
### DEBUG
### DJANGO_SETTINGS_MODULE
### DJANGO_SECRET_KEY
## Database (PostgreSQL)
### POSTGRES_HOST
### POSTGRES_PORT
### POSTGRES_USER
### POSTGRES_PASSWORD
### POSTGRES_DBNAME
## Gunicorn
### GUNICORN_WORKERS
### GUNICORN_TIMEOUT

22
docs/installation_faq.md Normal file
View File

@@ -0,0 +1,22 @@
# Common Issues & Questions
## Environment
### Postgres Database
The database login-details are set upon first-launch.
Therefore trying to change them later, after first-launch, will require a change of these details.
A way of sidestepping manually changing this in the DB-shell is by deleting the postgres data volume.
> [!WARNING]
> Do not run this command in prod, it will delete ALL data in our database.
```shell
docker volume rm quatsh-website_postgres-data
```
After running this command a Django Migration has to be run to remake the tables.
``` shell
docker exec quatsh-website-web-1 python manage.py migrate
```

72
docs/templates/template.md vendored Normal file
View File

@@ -0,0 +1,72 @@
# Feature Name
> One sentence description of what this feature does and who it is for.
## Status
<!-- Delete all that do not apply -->
`Planned` `In Progress` `In Review` `Complete` `Deferred`
## Background
<!-- Why are we building this? What problem does it solve? Link to any relevant roadmap phase. -->
## Scope
<!-- What is included in this feature. Be explicit about what is out of scope too. -->
**In scope:**
-
**Out of scope:**
-
## User Stories
<!-- Who interacts with this feature and what do they want to achieve? -->
- As a **[member / board member / anonymous visitor]**, I want to **[action]** so that **[outcome]**.
## Data Model
<!-- What models are created or modified? List fields and their types. -->
```python
class ExampleModel(models.Model):
pass
```
**Related models:**
- `User`
## GDPR Considerations
<!-- See docs/gdpr/ for shared policies. Only document what is specific to this feature. -->
| Question | Answer |
|---|---|
| Personal data collected | |
| Legal basis | Legitimate interest / Consent / Legal obligation |
| Retention period | See `docs/gdpr/retention-policy.md` / [custom policy] |
| Erasure behaviour | Cascade delete / Anonymise / Transfer ownership |
| Visible to non-members | Yes / No |
## Open Questions
<!-- Unresolved decisions that are blocking or will affect implementation. -->
- [ ]
## Decisions Log
<!-- Record decisions made during development so future contributors understand why things are the way they are. -->
| Date | Decision | Reasoning |
|---|---|---|
| YYYY-MM-DD | | |
## Tasks
<!-- Break the feature down into concrete implementation steps. -->
### Backend
- [ ]
### Frontend
- [ ]
### Tests
- [ ]
## Related
<!-- Links to related feature files, external docs, or GitHub issues. -->
-

View File

@@ -1,2 +1,9 @@
Django==6.0.3
gunicorn==25.1.0
psycopg [binary] ==3.3.3
django-browser-reload
django-watchfiles
docutils==0.22.4
ruff==0.15.9
Mypy==1.20
django-stubs==5.0.2

3
src/gallery/admin.py Normal file
View File

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

5
src/gallery/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class GalleryConfig(AppConfig):
name = 'gallery'

View File

3
src/gallery/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
src/gallery/tests.py Normal file
View File

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

3
src/gallery/views.py Normal file
View File

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

0
src/polls/__init__.py Normal file
View File

7
src/polls/admin.py Normal file
View File

@@ -0,0 +1,7 @@
from django.contrib import admin
# Register your models here.
from .models import Question
admin.site.register(Question)

5
src/polls/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class PollsConfig(AppConfig):
name = 'polls'

View File

@@ -0,0 +1,32 @@
# Generated by Django 6.0.3 on 2026-03-25 14:26
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Question',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question_text', models.CharField(max_length=200)),
('pub_date', models.DateTimeField(verbose_name='date published')),
],
),
migrations.CreateModel(
name='Choice',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('choice_text', models.CharField(max_length=200)),
('votes', models.IntegerField(default=0)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.question')),
],
),
]

View File

20
src/polls/models.py Normal file
View File

@@ -0,0 +1,20 @@
from django.db import models
# Create your models here.
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField("date published")
def __str__(self):
return self.question_text
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text

View File

@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>My test page</title>
</head>
<body>
<h1>Hey This is a test of watchfiles + browser reload!</h1>
{{ question }}
</body>
</html>

View File

@@ -0,0 +1,19 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>My test page</title>
</head>
<body>
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
</body>
</html>

3
src/polls/tests.py Normal file
View File

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

14
src/polls/urls.py Normal file
View File

@@ -0,0 +1,14 @@
from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path("", views.index, name="index"),
# ex: /polls/5/
path("<int:question_id>/", views.detail, name="detail"),
# ex: /polls/5/results/
path("<int:question_id>/results/", views.results, name="results"),
# ex: /polls/5/vote/
path("<int:question_id>/vote/", views.vote, name="vote"),
]

24
src/polls/views.py Normal file
View File

@@ -0,0 +1,24 @@
from django.http import HttpResponse, Http404
from django.shortcuts import get_object_or_404, render
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by("-pub_date")[:5]
context = {"latest_question_list": latest_question_list}
return render(request, "polls/index.html", context)
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/detail.html", {"question": question})
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)

0
src/website/__init__.py Normal file
View File

129
src/website/settings.py Normal file
View File

@@ -0,0 +1,129 @@
"""
Django settings for website project.
Generated by 'django-admin startproject' using Django 6.0.3.
For more information on this file, see
https://docs.djangoproject.com/en/6.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/6.0/ref/settings/
"""
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv("DEBUG") == "TRUE"
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "127.0.0.1").split(",")
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Application definition
INSTALLED_APPS = [
"polls.apps.PollsConfig",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.admindocs",
"django_browser_reload",
"django_watchfiles",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django_browser_reload.middleware.BrowserReloadMiddleware",
]
ROOT_URLCONF = "website.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "website.wsgi.application"
# Database
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.getenv("POSTGRES_DBNAME"),
"USER": os.getenv("POSTGRES_USER"),
"PASSWORD": os.getenv("POSTGRES_PASSWORD"),
"HOST": os.getenv("POSTGRES_HOST"),
"PORT": os.getenv("POSTGRES_PORT"),
}
}
# Password validation
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/6.0/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "Europe/Amsterdam"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/6.0/howto/static-files/
STATIC_ROOT = "/app/static/"
STATIC_URL = "static/"

View File

@@ -14,9 +14,13 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path("polls/", include("polls.urls")),
path("admin/doc/", include("django.contrib.admindocs.urls")),
path("admin/", admin.site.urls),
path("__reload__/", include("django_browser_reload.urls")),
]