Compare commits
No commits in common. "8ed93ebc12c35cd884749a22ffbc11b80fd42fe0" and "6464703381a46bbd6ec90d4ca43d6abed7ddb53c" have entirely different histories.
8ed93ebc12
...
6464703381
22 changed files with 20 additions and 306 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,9 +1,7 @@
|
||||||
/build
|
/build
|
||||||
*/mediafiles
|
*/uploads
|
||||||
*/staticfiles
|
|
||||||
__pycache__
|
__pycache__
|
||||||
*.pyc
|
*.pyc
|
||||||
.venv
|
.venv
|
||||||
.env*
|
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
|
||||||
|
|
43
README.md
43
README.md
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Web backend for hosting a wedding website.
|
Web backend for hosting a wedding website.
|
||||||
|
|
||||||
# Run Local server:
|
## Run development server:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
|
@ -10,49 +10,10 @@ cd wedding_site
|
||||||
python manage.py runserver
|
python manage.py runserver
|
||||||
```
|
```
|
||||||
|
|
||||||
# Do Migrations
|
## Do Migrations
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd wedding_site
|
cd wedding_site
|
||||||
python manage.py makemigrations primary
|
python manage.py makemigrations primary
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
# Run Dev Server in container
|
|
||||||
|
|
||||||
```sh
|
|
||||||
podman-compose -f compose.yaml up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
Bring it down with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
podman-compose down -v
|
|
||||||
```
|
|
||||||
|
|
||||||
# Run Prod Server
|
|
||||||
|
|
||||||
```sh
|
|
||||||
podman-compose -f compose.prod.yaml up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
Sync static files:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
podman-compose -f compose.prod.yaml exec web python manage.py collectstatic --no-input --clear
|
|
||||||
```
|
|
||||||
|
|
||||||
Check cert
|
|
||||||
|
|
||||||
```sh
|
|
||||||
podman-compose -f compose.prod.yaml exec acme-companion /app/cert_status
|
|
||||||
```
|
|
||||||
|
|
||||||
Force renew cert:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
podman-compose -f compose.prod.yaml exec acme-companion /app/force_renew
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build: ./wedding_site
|
|
||||||
command: gunicorn wedding_site.wsgi:application --bind 0.0.0.0:8000
|
|
||||||
volumes:
|
|
||||||
- ./wedding_site/:/usr/src/app/
|
|
||||||
- static_volume:/staticfiles
|
|
||||||
- media_volume:/mediafiles
|
|
||||||
expose:
|
|
||||||
- 8000
|
|
||||||
env_file:
|
|
||||||
- ./.env.prod
|
|
||||||
|
|
||||||
nginx-proxy:
|
|
||||||
container_name: nginx-proxy
|
|
||||||
build: ./nginx
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- 443:443
|
|
||||||
- 80:80
|
|
||||||
volumes:
|
|
||||||
- static_volume:/staticfiles
|
|
||||||
- media_volume:/mediafiles
|
|
||||||
- certs:/etc/nginx/certs
|
|
||||||
- html:/usr/share/nginx/html
|
|
||||||
- vhost:/etc/nginx/vhost.d
|
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
|
||||||
depends_on:
|
|
||||||
- web
|
|
||||||
|
|
||||||
acme-companion:
|
|
||||||
image: docker.io/nginxproxy/acme-companion
|
|
||||||
env_file:
|
|
||||||
- ./.env.prod.proxy-companion
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- certs:/etc/nginx/certs
|
|
||||||
- html:/usr/share/nginx/html
|
|
||||||
- vhost:/etc/nginx/vhost.d
|
|
||||||
- acme:/etc/acme.sh
|
|
||||||
depends_on:
|
|
||||||
- nginx-proxy
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
static_volume:
|
|
||||||
media_volume:
|
|
||||||
certs:
|
|
||||||
html:
|
|
||||||
vhost:
|
|
||||||
acme:
|
|
|
@ -1,52 +0,0 @@
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build: ./wedding_site
|
|
||||||
command: gunicorn wedding_site.wsgi:application --bind 0.0.0.0:8000
|
|
||||||
volumes:
|
|
||||||
- ./wedding_site/:/usr/src/app/
|
|
||||||
- static_volume:/staticfiles
|
|
||||||
- media_volume:/mediafiles
|
|
||||||
expose:
|
|
||||||
- 8000
|
|
||||||
env_file:
|
|
||||||
- ./.env.staging
|
|
||||||
|
|
||||||
nginx-proxy:
|
|
||||||
container_name: nginx-proxy
|
|
||||||
build: ./nginx
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- 443:443
|
|
||||||
- 80:80
|
|
||||||
volumes:
|
|
||||||
- static_volume:/staticfiles
|
|
||||||
- media_volume:/mediafiles
|
|
||||||
- certs:/etc/nginx/certs
|
|
||||||
- html:/usr/share/nginx/html
|
|
||||||
- vhost:/etc/nginx/vhost.d
|
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
|
||||||
depends_on:
|
|
||||||
- web
|
|
||||||
|
|
||||||
acme-companion:
|
|
||||||
image: docker.io/nginxproxy/acme-companion
|
|
||||||
env_file:
|
|
||||||
- ./.env.staging.proxy-companion
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- certs:/etc/nginx/certs
|
|
||||||
- html:/usr/share/nginx/html
|
|
||||||
- vhost:/etc/nginx/vhost.d
|
|
||||||
- acme:/etc/acme.sh
|
|
||||||
depends_on:
|
|
||||||
- nginx-proxy
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
static_volume:
|
|
||||||
media_volume:
|
|
||||||
certs:
|
|
||||||
html:
|
|
||||||
vhost:
|
|
||||||
acme:
|
|
12
compose.yaml
12
compose.yaml
|
@ -1,12 +0,0 @@
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build: ./wedding_site
|
|
||||||
command: python manage.py runserver 0.0.0.0:8000
|
|
||||||
volumes:
|
|
||||||
- ./wedding_site/:/usr/src/app/
|
|
||||||
ports:
|
|
||||||
- 8000:8000
|
|
||||||
env_file:
|
|
||||||
- ./.env.dev
|
|
|
@ -1 +0,0 @@
|
||||||
pacman -Sy podman aardvark-dns
|
|
|
@ -1,4 +0,0 @@
|
||||||
FROM docker.io/nginxproxy/nginx-proxy
|
|
||||||
|
|
||||||
COPY vhost.d/default /etc/nginx/vhost.d/default
|
|
||||||
COPY custom.conf /etc/nginx/conf.d/custom.conf
|
|
|
@ -1 +0,0 @@
|
||||||
client_max_body_size 100M;
|
|
|
@ -1,9 +0,0 @@
|
||||||
location /static/ {
|
|
||||||
alias /staticfiles/;
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /media/ {
|
|
||||||
alias /mediafiles/;
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
}
|
|
|
@ -1 +1,4 @@
|
||||||
podman-compose
|
django
|
||||||
|
markdown
|
||||||
|
pillow
|
||||||
|
beautifulsoup4
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
# pull official base image
|
|
||||||
FROM python:3.11.4-slim-buster
|
|
||||||
|
|
||||||
# set environment variables
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE 1
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
|
||||||
|
|
||||||
# install dependencies
|
|
||||||
RUN pip install --upgrade pip
|
|
||||||
COPY ./requirements.txt .
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
|
|
||||||
RUN mkdir staticfiles
|
|
||||||
RUN mkdir mediafiles
|
|
||||||
|
|
||||||
ADD ./mediafiles /mediafiles
|
|
||||||
|
|
||||||
# copy project
|
|
||||||
COPY . .
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.0.2 on 2024-02-18 09:58
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('primary', '0009_page_pretty_name'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='page',
|
|
||||||
name='prority',
|
|
||||||
field=models.IntegerField(default=0),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.0.2 on 2024-02-18 10:02
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('primary', '0010_page_prority'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='page',
|
|
||||||
old_name='prority',
|
|
||||||
new_name='priority',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -18,7 +18,6 @@ class Page(models.Model):
|
||||||
pretty_name = models.CharField(max_length=200, default="")
|
pretty_name = models.CharField(max_length=200, default="")
|
||||||
published = models.BooleanField(default=False)
|
published = models.BooleanField(default=False)
|
||||||
navigable = models.BooleanField(default=False)
|
navigable = models.BooleanField(default=False)
|
||||||
priority = models.IntegerField(default=0)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
|
@ -33,9 +33,9 @@ body {
|
||||||
|
|
||||||
img {
|
img {
|
||||||
margin:0 auto;
|
margin:0 auto;
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
max-width:100%;
|
|
||||||
max-height:300px;
|
max-height:300px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 4px 8px 0;
|
box-shadow: 0 4px 8px 0;
|
||||||
|
@ -107,10 +107,6 @@ ul {
|
||||||
list-style-position: inside;
|
list-style-position: inside;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul li {
|
|
||||||
padding: 5px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-input{
|
.login-input{
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -147,7 +143,7 @@ input[type=submit] {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
width: 75%;
|
width: 50%;
|
||||||
margin:0 auto;
|
margin:0 auto;
|
||||||
font-family: GlacialIndifference;
|
font-family: GlacialIndifference;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,4 @@
|
||||||
content="width=device-width, initial-scale=1">
|
content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<link rel="shortcut icon" type="image/png" href="{% static 'primary/favicon.ico' %}"/>
|
|
||||||
<link rel="stylesheet" href="{% static 'primary/style.css' %}">
|
<link rel="stylesheet" href="{% static 'primary/style.css' %}">
|
|
@ -7,7 +7,6 @@ urlpatterns = [
|
||||||
path("", views.index, name="index"),
|
path("", views.index, name="index"),
|
||||||
path('login', views.login_view, name="login"),
|
path('login', views.login_view, name="login"),
|
||||||
path("home", views.home, name="home"),
|
path("home", views.home, name="home"),
|
||||||
path("afters", views.afters, name="afters"),
|
|
||||||
path("schedule", views.schedule, name="schedule"),
|
path("schedule", views.schedule, name="schedule"),
|
||||||
path("thingstodo", views.things_to_do, name="thingstodo"),
|
path("thingstodo", views.things_to_do, name="thingstodo"),
|
||||||
path("travel", views.travel, name="travel"),
|
path("travel", views.travel, name="travel"),
|
||||||
|
|
|
@ -6,8 +6,6 @@ from django.template import Context
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
from django.views.defaults import page_not_found
|
|
||||||
|
|
||||||
import markdown
|
import markdown
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
@ -26,17 +24,6 @@ _TEMPLATE = """
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def handler404(request, exception, template_name="404.html"):
|
|
||||||
page_names = ["home", "schedule", "thingstodo", "afters"]
|
|
||||||
working_path = request.path.lower()
|
|
||||||
if working_path and working_path[0] == "/":
|
|
||||||
working_path = working_path[1:]
|
|
||||||
if working_path and working_path[-1] == "/":
|
|
||||||
working_path = working_path[:-1]
|
|
||||||
if working_path in page_names:
|
|
||||||
return redirect(working_path)
|
|
||||||
return page_not_found(request, exception, template_name)
|
|
||||||
|
|
||||||
def get_site_header(site):
|
def get_site_header(site):
|
||||||
template = Template(site.header)
|
template = Template(site.header)
|
||||||
context = Context({"site": site})
|
context = Context({"site": site})
|
||||||
|
@ -59,11 +46,8 @@ def index(request):
|
||||||
return HttpResponse(soup.prettify())
|
return HttpResponse(soup.prettify())
|
||||||
|
|
||||||
def login_view(request):
|
def login_view(request):
|
||||||
if request.method == "GET":
|
|
||||||
token = request.GET["token"]
|
|
||||||
else:
|
|
||||||
token = request.POST["token"]
|
token = request.POST["token"]
|
||||||
user = authenticate(request, username="guest", password=token.lower())
|
user = authenticate(request, username="guest", password=token)
|
||||||
if user is not None:
|
if user is not None:
|
||||||
login(request, user)
|
login(request, user)
|
||||||
return redirect("home")
|
return redirect("home")
|
||||||
|
@ -74,13 +58,9 @@ def login_view(request):
|
||||||
def home(request):
|
def home(request):
|
||||||
return get_page("Home")
|
return get_page("Home")
|
||||||
|
|
||||||
@login_required(login_url="/")
|
|
||||||
def afters(request):
|
|
||||||
return get_page("Afters")
|
|
||||||
|
|
||||||
def get_page_header(site: Site):
|
def get_page_header(site: Site):
|
||||||
|
|
||||||
pages = site.page_set.order_by("priority").filter(navigable=True)
|
pages = site.page_set.filter(navigable=True)
|
||||||
template = Template(site.page_header)
|
template = Template(site.page_header)
|
||||||
context = Context({"pages" : pages})
|
context = Context({"pages" : pages})
|
||||||
return template.render(context)
|
return template.render(context)
|
||||||
|
@ -107,8 +87,7 @@ def get_page(name:str):
|
||||||
img_name = img["src"]
|
img_name = img["src"]
|
||||||
db_images = Image.objects.filter(name=img_name)
|
db_images = Image.objects.filter(name=img_name)
|
||||||
if db_images:
|
if db_images:
|
||||||
url = db_images[0].content.url.replace("uploads/","")
|
img["src"] = db_images[0].content.url
|
||||||
img["src"] = url
|
|
||||||
|
|
||||||
return HttpResponse(soup.prettify())
|
return HttpResponse(soup.prettify())
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
django
|
|
||||||
markdown
|
|
||||||
pillow
|
|
||||||
beautifulsoup4
|
|
||||||
gunicorn
|
|
|
@ -11,7 +11,6 @@ https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
@ -20,15 +19,13 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||||
|
|
||||||
SECRET_KEY = os.environ.get("SECRET_KEY")
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-bj9fez3qztt5e2lrzpgh%%nat@w^kn!k@l92l=+#%wm)4)p^5m'
|
||||||
|
|
||||||
if "DJANGO_DEBUG" in os.environ:
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = os.environ.get("DJANGO_DEBUG") == 1
|
DEBUG = True
|
||||||
else:
|
|
||||||
DEBUG=False
|
|
||||||
|
|
||||||
if "DJANGO_ALLOWED_HOSTS" in os.environ:
|
ALLOWED_HOSTS = []
|
||||||
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ")
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
@ -115,29 +112,12 @@ USE_I18N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
||||||
|
|
||||||
if "CSRF_TRUSTED_ORIGINS" in os.environ:
|
|
||||||
CSRF_TRUSTED_ORIGINS = os.environ.get("CSRF_TRUSTED_ORIGINS").split(" ")
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
if "DJANGO_STATIC_ROOT" in os.environ:
|
|
||||||
STATIC_ROOT = os.environ.get("DJANGO_STATIC_ROOT")
|
|
||||||
else:
|
|
||||||
STATIC_ROOT = BASE_DIR / "staticfiles"
|
|
||||||
|
|
||||||
MEDIA_URL = 'media/'
|
|
||||||
|
|
||||||
if "DJANGO_MEDIA_ROOT" in os.environ:
|
|
||||||
MEDIA_ROOT = os.environ.get("DJANGO_MEDIA_ROOT")
|
|
||||||
else:
|
|
||||||
MEDIA_ROOT = BASE_DIR / "mediafiles"
|
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
|
|
@ -19,16 +19,7 @@ from django.urls import path, include
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
if "DJANGO_ADMIN_PATH" in os.environ:
|
|
||||||
ADMIN_URL = os.environ.get("DJANGO_ADMIN_PATH") + "/"
|
|
||||||
else:
|
|
||||||
ADMIN_URL = "admin/"
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include('primary.urls')),
|
path("", include('primary.urls')),
|
||||||
path(ADMIN_URL, admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
handler404 = 'primary.views.handler404'
|
|
||||||
|
|
Loading…
Reference in a new issue