Compare commits

...

12 Commits

Author SHA1 Message Date
Wouter Vermeer
99c0b4ae6d cleanup and slight nav-bar additions.
Some checks failed
Gitea Test. / tests (push) Failing after 3s
2026-04-30 19:33:22 +02:00
Wouter Vermeer
33661e14da added Core for core pages, added static files & templates.
Some checks failed
Gitea Test. / tests (push) Failing after 3s
2026-04-30 17:48:33 +02:00
Wouter Vermeer
bef2577746 moved DEBUG variable into compose files. 2026-04-30 17:47:48 +02:00
Wouter Vermeer
2b5f28d8f1 added global static files + templates folders. updated .env variables to be lowercase as my styler auto-updated it to be lowercase. 2026-04-30 17:46:52 +02:00
Wouter Vermeer
a06026cb37 fixed nginx .conf.template as it is required to be called 'default.conf.template' to be loaded into the default. 2026-04-30 17:44:04 +02:00
Wouter Vermeer
5c132f5d37 fixed nginx .conf.template as it is required to be called 'default.conf.template' to be loaded into the default. 2026-04-30 17:43:39 +02:00
Wouter Vermeer
7db860a4eb added tailwindcss 2026-04-30 17:42:44 +02:00
Wouter Vermeer
6d6534a541 auto style changes 2026-04-30 17:42:06 +02:00
Wouter Vermeer
358bd4cbf2 reworded docker compose files to segregate responsibilites. Dev version no longer uses the nginx proxy to proxy, merely to be the static file server. Also dev version no longer used gunicorn as it was screwing up the live reload functionality and didn't provide any other benefit. 2026-04-30 17:41:21 +02:00
Wouter Vermeer
a0a0cfab8a changes?
All checks were successful
Gitea Test. / tests (push) Successful in 12s
2026-04-29 13:35:52 +02:00
Wouter Vermeer
3323e581b2 added linting automation. Fixed nginx config as file name was wrong and as such didn't get loaded.
All checks were successful
Gitea Test. / tests (push) Successful in 49s
2026-04-29 13:24:48 +02:00
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
35 changed files with 480 additions and 101 deletions

View File

@@ -1,18 +1,36 @@
---
services: services:
web: web:
command: gunicorn --capture-output --enable-stdio-inheritance -b 0.0.0.0:8000 website.wsgi:application command: python manage.py runserver 0.0.0.0:8000
volumes: volumes: [./src:/src]
- ./src:/src
env_file: env_file:
- path: .env.template - path: .env.template
required: true required: true
- path: .env - path: .env
required: false required: false
environment:
DJANGO_RELOAD: true
DEBUG: true
ALLOWED_HOSTS: localhost,127.0.0.1
ports: [127.0.0.1:8000:8000]
db: db:
env_file: env_file:
- path: .env.template - path: .env.template
required: true required: true
- path: .env - path: .env
required: false required: false
proxy:
restart: unless-stopped
ports: [127.0.0.1:80:80]
environment: [NGINX_HOSTNAME=localhost, NGINX_PORT=80]
tailwind:
image: python:3.14-slim
working_dir: /src
command: >
sh -c "apt-get update && apt-get install -y curl &&
curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64
-o /usr/local/bin/tailwindcss &&
chmod +x /usr/local/bin/tailwindcss &&
tailwindcss -i ./static/css/input.css -o ./static/css/output.css --watch"
volumes: [.:/src]
networks: [frontend]

View File

@@ -1,10 +1,25 @@
---
services: services:
web: web:
command: gunicorn website.wsgi:application
restart: unless-stopped
environment:
ALLOWED_HOSTS: ${NGINX_HOSTNAME}
DJANGO_SETTINGS_MODULE: ${DJANGO_SETTINGS_MODULE}
env_file: env_file:
- path: .env - path: .env
required: true required: true
db: db:
restart: unless-stopped
env_file: env_file:
- path: .env - path: .env
required: true required: true
adminer:
restart: unless-stopped
proxy:
restart: unless-stopped
ports: [80:80, 443:443]
environment:
- NGINX_HOSTNAME=${NGINX_HOSTNAME}
- NGINX_PORT=80
- NGINX_SSL_PORT=443

View File

@@ -1,14 +1,17 @@
---
services: services:
web: web:
command: python manage.py check --deploy; python -Wa manage.py test --noinput --parallel command: python manage.py check --deploy; python -Wa manage.py test --noinput
restart: "no" --parallel
environment:
DEBUG: false
DJANGO_SETTINGS_MODULE: website.settings.test
env_file: env_file:
- path: .env.template - path: .env.template
required: true required: true
db: db:
# Tmpfs keeps tests fast and isolated — no persistent volume needed
tmpfs: [/var/lib/postgresql/data]
env_file: env_file:
- path: .env.template - path: .env.template
required: true required: true

View File

@@ -4,8 +4,6 @@ NGINX_HOSTNAME=localhost
# Django # Django
DJANGO_SETTINGS_MODULE=website.settings DJANGO_SETTINGS_MODULE=website.settings
DJANGO_SECRET_KEY=CWHZCAZBNV57tDkwGHJwTUu3PCSnGG45 DJANGO_SECRET_KEY=CWHZCAZBNV57tDkwGHJwTUu3PCSnGG45
DEBUG=TRUE
#ALLOWED_HOSTS=localhost
# Database (PostgreSQL) # Database (PostgreSQL)
POSTGRES_USER=test_user POSTGRES_USER=test_user

View File

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

View File

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

3
.gitignore vendored
View File

@@ -178,4 +178,5 @@ cython_debug/
# gunicon webserver # gunicon webserver
gunicorn.ctl gunicorn.ctl
# tailwind stuff
output.css

View File

@@ -1,4 +1,4 @@
FROM python:3.14-alpine FROM python:3.14-slim
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 PYTHONUNBUFFERED=1
@@ -12,4 +12,11 @@ RUN pip install --upgrade pip && \
COPY gunicorn.conf.py /src/ COPY gunicorn.conf.py /src/
COPY ./src/ /src/ COPY ./src/ /src/
RUN apt-get update && apt-get install -y --no-install-recommends curl && \
curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 -o /usr/local/bin/tailwindcss && \
chmod +x /usr/local/bin/tailwindcss && \
apt-get remove -y curl && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*
RUN tailwindcss build -i ./static/css/input.css -o ./static/css/output.css --minify
CMD ["gunicorn", "website.wsgi:application"] CMD ["gunicorn", "website.wsgi:application"]

View File

@@ -26,3 +26,7 @@ dev_restart_with_logs:
test: 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 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
lint:
docker compose exec web ruff check
docker compose exec web mypy .

View File

@@ -1,74 +1,41 @@
---
services: services:
web: web:
build: . build: .
command: gunicorn website.wsgi:application
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
volumes: [static_volume:/app/static, media_volume:/app/media]
environment: networks: [frontend, backend]
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: db:
image: postgres:18 image: postgres:18
environment: environment:
POSTGRES_DB: ${POSTGRES_DBNAME} POSTGRES_DB: ${POSTGRES_DBNAME}
POSTGRES_USER: ${POSTGRES_USER} POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes: volumes: [postgres_data:/var/lib/postgresql]
- postgres_data:/var/lib/postgresql
restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DBNAME}"] test: [CMD-SHELL, 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DBNAME}']
interval: 5s interval: 5s
retries: 5 retries: 5
networks: networks: [backend]
- backend
adminer:
image: adminer
depends_on:
- db
restart: always
ports:
- 127.0.0.1:8080:8080
networks:
- backend
proxy: proxy:
image: nginx:stable image: nginx:stable
volumes: volumes:
- ./.nginx/.templates:/etc/nginx/templates - ./.nginx/.templates:/etc/nginx/templates
- static_volume:/app/static:ro - static_volume:/app/static:ro
- media_volume:/app/media:ro - media_volume:/app/media:ro
restart: unless-stopped depends_on: [web]
ports: networks: [frontend]
- 80:80 adminer:
- 443:443 image: adminer
environment: depends_on: [db]
- NGINX_HOSTNAME=${NGINX_HOSTNAME} ports: [127.0.0.1:8080:8080]
- NGINX_PORT=80 networks: [backend]
- NGINX_SSL_PORT=443
depends_on:
- web
networks:
- frontend
networks: networks:
frontend: frontend:
backend: backend:
internal: true
volumes: volumes:
postgres_data: postgres_data:
static_volume: static_volume:

View File

@@ -0,0 +1,176 @@
# 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.
- 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
- The Makefile wraps common Docker Compose commands to reduce increase ease-of-use.
- 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.
Linting tools will have to installed.
---
*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? -->

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

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

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

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

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

View File

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View File

@@ -0,0 +1,13 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Home{% endblock %}
{% block content %}
<h1>Welcome</h1>
<div>
{% include "components/carousel.html" with carousel_id="hero" carousel_images=carousel_images %}
</div>
{% endblock %}

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

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

9
src/core/urls.py Normal file
View File

@@ -0,0 +1,9 @@
from django.urls import path
from . import views
app_name = "core"
urlpatterns = [
path("", views.index, name="index"),
]

10
src/core/views.py Normal file
View File

@@ -0,0 +1,10 @@
from django.shortcuts import render
from django.templatetags.static import static
def index(request):
return render(
request,
"core/index.html",
{"carousel_images": [static("/core/img/5x5-2023-11-16-at-14.56.56-1.jpeg")]},
)

View File

@@ -0,0 +1,4 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Home{% endblock %}
{% block content %}<h1>Welcome</h1>{% endblock %}

9
src/gallery/urls.py Normal file
View File

@@ -0,0 +1,9 @@
from django.urls import path
from . import views
app_name = "gallery"
urlpatterns = [
path("", views.index, name="index"),
]

View File

@@ -1,3 +1,5 @@
from django.shortcuts import render from django.shortcuts import render
# Create your views here.
def index(request):
return render(request, "gallery/index.html")

View File

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

View File

@@ -2,6 +2,8 @@ from django.urls import path
from . import views from . import views
app_name = "polls"
urlpatterns = [ urlpatterns = [
# ex: /polls/ # ex: /polls/
path("", views.index, name="index"), path("", views.index, name="index"),

1
src/static/css/input.css Normal file
View File

@@ -0,0 +1 @@
@import "tailwindcss"

68
src/static/js/carousel.js Normal file
View File

@@ -0,0 +1,68 @@
function initCarousel(id) {
document.addEventListener("DOMContentLoaded", function () {
const track = document.getElementById(id);
const originalSlides = Array.from(track.children);
const total = originalSlides.length;
if (total <= 1) {
return;
}
const firstClone = originalSlides[0].cloneNode(true);
const lastClone = originalSlides[total - 1].cloneNode(true);
track.appendChild(firstClone);
track.insertBefore(lastClone, originalSlides[0]);
let current = 1;
let isTransitioning = false;
track.style.transform = `translateX(-${current * 100}%)`;
const dots = document.querySelectorAll(`.${id}_dot`);
function updateDots(index) {
const realIndex = (index - 1 + total) % total;
dots.forEach((d, i) => {
d.classList.toggle("opacity-100", i === realIndex);
d.classList.toggle("opacity-50", i !== realIndex);
});
}
function goTo(index) {
if (isTransitioning) return;
isTransitioning = true;
track.style.transition = "transform 500ms ease-in-out";
current = index;
track.style.transform = `translateX(-${current * 100}%)`;
updateDots(current);
}
function next() {
goTo(current + 1);
}
function prev() {
goTo(current - 1);
}
track.addEventListener("transitionend", function () {
if (current === 0) {
track.style.transition = "none";
current = total;
track.style.transform = `translateX(-${current * 100}%)`;
}
if (current === total + 1) {
track.style.transition = "none";
current = 1;
track.style.transform = `translateX(-${current * 100}%)`;
}
isTransitioning = false;
});
setInterval(next, 5000);
updateDots(current);
window[`${id}_goTo`] = (i) => goTo(i + 1);
window[`${id}_next`] = next;
window[`${id}_prev`] = prev;
});
}

18
src/templates/base.html Normal file
View File

@@ -0,0 +1,18 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
{% block title %}My Site{% endblock %}
</title>
<link rel="stylesheet" href="{% static 'css/output.css' %}">
</head>
<body>
{% include "components/topbar.html" %}
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>

View File

@@ -0,0 +1,28 @@
{% load static %}
<div class="relative w-full overflow-hidden">
<div id="{{ carousel_id }}" class="flex w-full">
{% for image in carousel_images %}
<div class="w-full flex-shrink-0">
<img src="{{ image }}" class="w-full object-cover">
</div>
{% endfor %}
</div>
{% if carousel_images|length > 1 %}
<button onclick="{{ carousel_id }}_prev()"
class="absolute left-2 top-1/2 -translate-y-1/2 bg-black/50 text-white px-3 py-1 rounded-full">
&#8592;
</button>
<button onclick="{{ carousel_id }}_next()"
class="absolute right-2 top-1/2 -translate-y-1/2 bg-black/50 text-white px-3 py-1 rounded-full">
&#8594;
</button>
<div class="absolute bottom-2 w-full flex justify-center gap-2">
{% for image in carousel_images %}
<span class="{{ carousel_id }}_dot w-2 h-2 rounded-full bg-white opacity-50 cursor-pointer"
onclick="{{ carousel_id }}_goTo({{ forloop.counter0 }})"></span>
{% endfor %}
</div>
{% endif %}
</div>
<script src="{% static 'js/carousel.js' %}"></script>
<script>initCarousel('{{ carousel_id }}');</script>

View File

@@ -0,0 +1,8 @@
<nav>
<ul>
<li><a href="{% url 'core:index' %}">Home</a></li>
<li><a href="{% url 'polls:index' %}">Polls</a></li>
<li><a href="{% url 'gallery:index' %}">Gallery</a></li>
</ul>
<!-- rest of your topbar -->
</nav>

View File

@@ -10,8 +10,8 @@ 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/
""" """
from pathlib import Path
import os import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@@ -24,7 +24,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv("DEBUG") == "TRUE" DEBUG = os.getenv("DEBUG", "false") == "true"
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "127.0.0.1").split(",") ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "127.0.0.1").split(",")
@@ -34,6 +34,8 @@ SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
INSTALLED_APPS = [ INSTALLED_APPS = [
"polls.apps.PollsConfig", "polls.apps.PollsConfig",
"core.apps.CoreConfig",
"gallery.apps.GalleryConfig",
"django.contrib.admin", "django.contrib.admin",
"django.contrib.auth", "django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
@@ -41,8 +43,6 @@ INSTALLED_APPS = [
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
"django.contrib.admindocs", "django.contrib.admindocs",
"django_browser_reload",
"django_watchfiles",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -53,15 +53,19 @@ MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"django_browser_reload.middleware.BrowserReloadMiddleware",
] ]
if os.getenv("DJANGO_RELOAD", "false") == "true":
INSTALLED_APPS.append("django_browser_reload")
INSTALLED_APPS.append("django_watchfiles")
MIDDLEWARE.append("django_browser_reload.middleware.BrowserReloadMiddleware")
ROOT_URLCONF = "website.urls" ROOT_URLCONF = "website.urls"
TEMPLATES = [ TEMPLATES = [
{ {
"BACKEND": "django.template.backends.django.DjangoTemplates", "BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [], "DIRS": [BASE_DIR / "templates"],
"APP_DIRS": True, "APP_DIRS": True,
"OPTIONS": { "OPTIONS": {
"context_processors": [ "context_processors": [
@@ -127,3 +131,7 @@ USE_TZ = True
STATIC_ROOT = "/app/static/" STATIC_ROOT = "/app/static/"
STATIC_URL = "static/" STATIC_URL = "static/"
STATICFILES_DIRS = [
BASE_DIR / "static",
]

View File

@@ -15,12 +15,18 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
import os
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import include, path
urlpatterns = [ urlpatterns = [
path("", include("core.urls")),
path("polls/", include("polls.urls")), path("polls/", include("polls.urls")),
path("gallery/", include("gallery.urls")),
path("admin/doc/", include("django.contrib.admindocs.urls")), path("admin/doc/", include("django.contrib.admindocs.urls")),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("__reload__/", include("django_browser_reload.urls")),
] ]
if os.getenv("DJANGO_RELOAD", "false") == "true":
urlpatterns.append(path("__reload__/", include("django_browser_reload.urls")))