Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ build
django/cohiva/base_config.py
django/.envrc
django/.venv-path
django/.playwright-mcp
django/.mcp.json
25 changes: 25 additions & 0 deletions django/.playwright-mcp-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"launchOptions": {
"args": [
"--disable-blink-features=AutomationControlled",
"--disable-extensions",
"--disable-component-extensions-with-background-pages",
"--disable-background-networking",
"--disable-sync",
"--disable-translate",
"--hide-scrollbars",
"--metrics-recording-only",
"--mute-audio",
"--no-first-run",
"--disable-password-manager-reauthentication",
"--password-store=basic"
]
},
"contextOptions": {
"ignoreHTTPSErrors": true,
"bypassCSP": true,
"acceptDownloads": true,
"javaScriptEnabled": true,
"serviceWorkers": "block"
}
}
2 changes: 1 addition & 1 deletion django/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ If you need to set up step-by-step or are preparing a production deployment, fol

## Install required system packages

Supported Python versions: 3.9 – 3.13
Supported Python versions: 3.9 (legacy) and 3.11 – 3.13

Example for Debian 11 (should work on most Debian/Ubuntu based systems):

Expand Down
89 changes: 86 additions & 3 deletions django/bootstrap_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,8 +558,49 @@ def setup_config(install_dir, venv_path):
base_config.write_text(content)
print_info(f"INSTALL_DIR updated to: {install_dir}")

# Ask about certificate generation
print()
print_step("Certificate Generation")
print()
print("Cohiva needs SSL/TLS certificates for SAML2 authentication.")
print("Choose certificate generation method:")
print(" 1. Quick setup with default values (recommended for development)")
print(" Country: CH, State: Zurich, City: Zurich")
print(" Organization: Cohiva Development, Common Name: localhost")
print(" 2. Provide custom certificate information (interactive)")
print()

while True:
cert_choice = input("Select option [1-2, default: 1]: ").strip()

# Default to option 1
if not cert_choice:
cert_choice = "1"

if cert_choice in ["1", "2"]:
break
print_error("Invalid choice. Please enter 1 or 2.")

# Run setup.py to create directories and keys
run_command([str(python_path), "setup.py"], env=env)
setup_cmd = [str(python_path), "setup.py"]
if cert_choice == "1":
setup_cmd.append("--use-default-certs")
print()
print_info("Generating certificates with default values...")

run_command(setup_cmd, env=env)

# Uncomment CSRF/Session cookie settings for local development
settings_file = Path("cohiva/settings.py")
if settings_file.exists():
content = settings_file.read_text()
# Uncomment the CSRF and session cookie settings for local HTTP development
content = content.replace(
"# SESSION_COOKIE_SECURE = False", "SESSION_COOKIE_SECURE = False"
)
content = content.replace("# CSRF_COOKIE_SECURE = False", "CSRF_COOKIE_SECURE = False")
settings_file.write_text(content)
print_info("Enabled local development cookie settings (insecure cookies for HTTP)")

# Uncomment CSRF/Session cookie settings for local development
settings_file = Path("cohiva/settings.py")
Expand Down Expand Up @@ -740,14 +781,56 @@ def create_superuser(venv_path):
"""Create superuser account."""
print_step("Creating superuser account...")
print()
print("Please create an admin user for accessing the Cohiva admin interface:")

# Ask if user wants demo credentials or custom ones
print("Choose superuser creation method:")
print(" 1. Quick setup with demo credentials (username: demo, password: demo)")
print(" 2. Create custom superuser (interactive)")
print()

while True:
choice = input("Select option [1-2, default: 1]: ").strip()

# Default to option 1
if not choice:
choice = "1"

if choice in ["1", "2"]:
break
print_error("Invalid choice. Please enter 1 or 2.")

# Set up environment for venv
env = os.environ.copy()
env["VIRTUAL_ENV"] = str(venv_path)
env["PATH"] = f"{venv_path / 'bin'}:{env['PATH']}"

run_command(["./manage.py", "createsuperuser"], env=env)
if choice == "1":
# Create demo superuser non-interactively
print()
print_info("Creating superuser with demo credentials...")
print_warn("IMPORTANT: Change these credentials in production!")
print()

env["DJANGO_SUPERUSER_USERNAME"] = "demo"
env["DJANGO_SUPERUSER_EMAIL"] = "[email protected]"
env["DJANGO_SUPERUSER_PASSWORD"] = "demo"

try:
run_command(
["./manage.py", "createsuperuser", "--noinput"], env=env, capture_output=False
)
print()
print_info("Demo superuser created successfully")
print_info(" Username: demo")
print_info(" Password: demo")
print_warn(" Remember to change these credentials!")
except subprocess.CalledProcessError:
print_warn("Superuser creation failed (may already exist)")
else:
# Interactive superuser creation
print()
print("Please create an admin user for accessing the Cohiva admin interface:")
run_command(["./manage.py", "createsuperuser"], env=env)


def load_demo_data(venv_path):
Expand Down
1 change: 1 addition & 0 deletions django/cohiva/formats/de/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Swiss German date/time formats for Django localization
30 changes: 30 additions & 0 deletions django/cohiva/formats/de/formats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Custom date/time format overrides for Swiss German (de-ch).

This file is used when LANGUAGE_CODE is set to 'de-ch' but allows
overriding Django's default de_CH formats if needed.
Currently we're using Django's built-in de_CH locale which already
has the correct Swiss German formats.
"""

# Import Django's de_CH formats as base
from django.conf.locale.de_CH.formats import * # noqa: F403

# Override DATE_FORMAT to use shorter format (d.m.Y instead of j. F Y)
DATE_FORMAT = "d.m.Y"
DATETIME_FORMAT = "d.m.Y H:i"

# Add ISO format as additional input option
DATE_INPUT_FORMATS = [
"%d.%m.%Y", # Swiss format: 25.12.2024
"%d.%m.%y", # Swiss short: 25.12.24
"%Y-%m-%d", # ISO format: 2024-12-25 (added)
]

DATETIME_INPUT_FORMATS = [
"%d.%m.%Y %H:%M:%S",
"%d.%m.%Y %H:%M:%S.%f",
"%d.%m.%Y %H:%M",
"%Y-%m-%d %H:%M:%S", # ISO format (added)
"%Y-%m-%d %H:%M", # ISO format (added)
]
44 changes: 40 additions & 4 deletions django/cohiva/settings_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
"crispy_forms", # For Unfold forms
"email_obfuscator",
"django_tables2",
"select2",
Expand Down Expand Up @@ -271,15 +272,21 @@
MIDDLEWARE += (
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
# LocaleMiddleware MUST be after SessionMiddleware (needs session data for language preferences)
# and BEFORE CommonMiddleware (needs to process language before URL resolution)
"django.middleware.locale.LocaleMiddleware",
#'portal.views.debug_middleware',
"geno.middleware.SessionExpiryMiddleware", ## Restrict cookie timeout for geno module
# SessionExpiryMiddleware should run early to set session timeouts before request processing
"geno.middleware.SessionExpiryMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
# LoginRedirectMiddleware MUST be after AuthenticationMiddleware because it processes
# the 302 redirect responses generated by authentication checks and routes them to
# the appropriate login page (/admin/login/ vs /portal/login/)
"geno.middleware.LoginRedirectMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
## For django-datetime-widget
"django.middleware.locale.LocaleMiddleware",
#'wagtail.contrib.redirects.middleware.RedirectMiddleware',
)

Expand Down Expand Up @@ -358,6 +365,11 @@
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = "de-ch"

# Available languages for the site
LANGUAGES = [
("de", "Deutsch"),
]

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
Expand All @@ -373,6 +385,15 @@

locale.setlocale(locale.LC_ALL, ("de_CH", "UTF-8"))

# Path to custom format modules for localized date/time formats
# See cohiva/formats/de/formats.py for Swiss German formats
FORMAT_MODULE_PATH = "cohiva.formats"

# Locale paths for custom translation files
LOCALE_PATHS = [
BASE_DIR / "locale",
]

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/

Expand All @@ -384,7 +405,18 @@

STATIC_URL = "/static/"

## Cache Buster --> Need to test this first! (duplicate files! / error...)
## Cache Buster for development: appends file modification time to static URLs
## This ensures CSS/JS changes are immediately reflected without manual cache clearing
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "cohiva.storage.CacheBustingStaticFilesStorage",
},
}

## For production, use ManifestStaticFilesStorage instead (requires collectstatic):
##STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'

# Additional locations of static files
Expand Down Expand Up @@ -1143,3 +1175,7 @@
},
"TABS": lambda request: admin.site.navigation.generate_unfold_tabs(request),
}

# Crispy Forms Configuration for Unfold
CRISPY_TEMPLATE_PACK = "unfold_crispy"
CRISPY_ALLOWED_TEMPLATE_PACKS = ["unfold_crispy"]
33 changes: 33 additions & 0 deletions django/cohiva/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
Custom static file storage backends for cache busting.
"""

import os

from django.contrib.staticfiles.storage import StaticFilesStorage


class CacheBustingStaticFilesStorage(StaticFilesStorage):
"""
Static files storage that appends file modification time as a query parameter.
This ensures browsers reload static files when they change during development.
"""

def url(self, name):
url = super().url(name)

# Get the file path
try:
# Try to get the actual file path
file_path = self.path(name)
if os.path.exists(file_path):
# Get file modification time
mtime = int(os.path.getmtime(file_path))
# Append as query parameter
separator = "&" if "?" in url else "?"
url = f"{url}{separator}v={mtime}"
except (NotImplementedError, AttributeError, OSError):
# If we can't get the file path or mtime, just return the original URL
pass

return url
Loading