Changeset - 3826b6fb66e7
[Not reviewed]
1 6 4
Ben Sturmfels (bsturmfels) - 2 months ago 2024-03-20 03:10:00
ben@sturm.com.au
Switch settings to use "the one true way" approach

The advantage of this approach is that the production and dev configurations are
in version control, so there's less opportunity for surprises.

As advocated by Jacob Kaplan-Moss (OSCON 2011) and Two Scoops of Django book.
10 files changed with 65 insertions and 16 deletions:
0 comments (0 inline, 0 general)
.gitignore
Show inline comments
 
*~
 
#*#
 
__pycache__
 
conservancy/djangocommonsettings.py
 
conservancy/static/docs/Transcript_Full_Vizios_MSJ_HearingDeptC-33.231005.pdf
 
conservancy/static/docs/SFC_response_to_summary_judgement.pdf
 
conservancy/static/docs/Vizio_summary_judgement_reply_brief.pdf
 
conservancy/static/docs/2023-4-28 VIZIOs Motion for Summary Judgment with Reservation.pdf
 

	
 
/conservancy/secrets.json
Dockerfile-debian-bookworm
Show inline comments
 
# To build the Docker image with the necessary dependencies:
 
# docker build --tag sfconservancy.org-bookworm --file Dockerfile-debian-bookworm .
 
#
 
# To run the website, first ensure you have a
 
# "conservancy/djangocommonsettings.py" file, with an appropriate database path.
 
#
 
# [FIRST RUN ONLY] If you don't have an existing copy of the database, run:
 
# touch conservancy-website.sqlite3
 
#
 
# Start the application with:
 
# docker run --tty --interactive --rm=true --publish=8000:8000 \
 
#   --mount type=bind,source=$(pwd),target=/var/www/website \
 
#   --mount type=bind,source=$(pwd)/conservancy-website.sqlite3,target=/var/lib/www/database/conservancy-website.sqlite3 \
 
#   sfconservancy.org-bookworm:latest
 
#
 
# [FIRST RUN ONLY] In a separate terminal, run `docker ps` noting "CONTAINER ID".
 
# Then run the database migrations with:
 
# docker exec -it [CONTAINER ID] /usr/bin/python3 manage.py migrate
 
#
 
# Visit the site at http://127.0.0.1:8000
 

	
 
ARG DEBIAN_FRONTEND=noninteractive
 

	
 
FROM debian:bookworm
 
RUN apt-get update && apt-get upgrade -y
 
RUN apt-get install -y python3 python3-pip python3-wheel sqlite3
 
RUN apt-get install -y python3-django python3-bs4 python3-html5lib python3-django-countries
 
COPY ./requirements.txt /var/www/website/requirements.txt
 
WORKDIR /var/www/website/
 
RUN python3 -m pip install -r requirements.txt --break-system-packages
README.md
Show inline comments
...
 
@@ -17,52 +17,48 @@ The content and text (such as the HTML files) is currently
 
[CC-BY-SA-3.0](CC-By-SA-3.0).
 

	
 

	
 
## Server configuration
 

	
 
Conservancy's webserver runs on a standard Debian installation. For
 
configuration requirements, see `deploy/ansible/install.yml`.
 

	
 

	
 
## CDN
 

	
 
Requests to any URL starting with `/videos/` are forwarded to our CDN server to
 
reduce bandwidth use. These files are uploaded to the CDN manually via the
 
Rackspace API. Note that the Apache rewrite rule requires that the file **not
 
exist** on disk for the redirect to be applied.
 

	
 

	
 
## Local development
 

	
 
Python dependencies in `requirements.txt` are tied to the versions available in
 
Debian:
 

	
 
    python3 -m pip install -r requirements.txt
 

	
 
You'll need a copy of `conservancy/djangocommonsettings.py`, a file that not
 
committed to the repository that has database settings and other
 
environment-specific config.
 

	
 
To run the tests, install `pytest-django` and run pytest:
 

	
 
    python3 -m pip install pytest-django
 
    python3 -m pytest
 

	
 
Then run:
 

	
 
    python3 manage.py migrate
 
    python3 manage.py runserver
 

	
 
There is also a Dockerfile available if that's more convenient. See that file
 
for details.
 

	
 

	
 
## Deploying
 

	
 
To deploy, run `bin/deploy`. This requires SSH access to `hickory.sfconservancy.org`.
 

	
 

	
 
## Link checking
 

	
 
To check for broken links, log on to `hickory.sfconservancy.org` and run
 
`linkchecker https://sfconservancy.org/`.
 

	
TODO.md
Show inline comments
 
# To-do
 

	
 
* remove `ForceCanonicalHostnameMiddleware` by ensuring canonical redirect and HTTPS redirect is done by Apache
 
* serve a 400 in Apache for a hostname we don't explicitly support
 
* use `<detail>` elements for supporter page hidden sections, rather than complex jQuery - or consider Alpine.js
 
* replace `internalNavigate` with inline flexbox layout
 
* add tests for main pages returning 200
 
* standardise settings to replace `settings.py` and `djangocommonsettings.py`
 
  with `settings/prod.py` and move `SECRET_KEY` to an environment variable
 

	
 

	
 
# Done
 

	
 
* standardise settings to replace `settings.py` and `djangocommonsettings.py`
 
  with `settings/prod.py` and move `SECRET_KEY` to an environment variable
 
* migrate to Django 4.2 LTS
 
* review `apache2` directory - may be unused
 
* add deployment script that runs migrations and collects static files
 
* switch `ParameterValidator` to use `SECRET_KEY` if possible to minimize
 
  non-standard settings
 
* install staticfiles app
conservancy/local_context_processors.py
Show inline comments
 
from datetime import datetime as DateTime
 

	
 
from . import settings
 
from django.conf import settings
 

	
 
from .fundgoal.models import FundraisingGoal
 

	
 
SITE_FUNDGOAL = 'cy2023-end-year-match'
 

	
 
def fundgoal_lookup(fundraiser_sought):
 
    try:
 
        return FundraisingGoal.objects.get(fundraiser_code_name=fundraiser_sought)
 
    except FundraisingGoal.DoesNotExist:
 
        # we have no object!  do something
 
        return None
 

	
 
def sitefundraiser(request):
 
    return {
 
        'datetime_now': DateTime.now(),
 
        'sitefundgoal': fundgoal_lookup(SITE_FUNDGOAL),
 
    }
 

	
 
if settings.FORCE_CANONICAL_HOSTNAME:
 
    _HOST_URL_VAR = {'host_url': 'https://' + settings.FORCE_CANONICAL_HOSTNAME}
 
    def host_url(request):
 
        return _HOST_URL_VAR
 
else:
 
    def host_url(request):
 
        return {'host_url': request.build_absolute_uri('/').rstrip('/')}
conservancy/settings/__init__.py
Show inline comments
 
new file 100644
conservancy/settings/base.py
Show inline comments
 
file renamed from conservancy/settings.py to conservancy/settings/base.py
 
# Copyright 2005-2008, James Garrison
 
# Copyright 2010, Bradley M. Kuhn
 

	
 
# This software's license gives you freedom; you can copy, convey,
 
# propagate, redistribute, modify and/or redistribute modified versions of
 
# this program under the terms of the GNU Affero General Public License
 
# (AGPL) as published by the Free Software Foundation (FSF), either
 
# version 3 of the License, or (at your option) any later version of the
 
# AGPL published by the FSF.
 
#
 
# This program is distributed in the hope that it will be useful, but
 
# WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
 
# General Public License for more details.
 
#
 
# You should have received a copy of the GNU Affero General Public License
 
# along with this program in a file in the toplevel directory called
 
# "AGPLv3".  If not, see <http://www.gnu.org/licenses/>.
 

	
 
from pathlib import Path
 

	
 
from .djangocommonsettings import *
 

	
 
SITE_ID = 2
 
ROOT_URLCONF = 'conservancy.urls'
 

	
 
FORCE_CANONICAL_HOSTNAME = False if DEBUG else 'sfconservancy.org'
 

	
 
REDIRECT_TABLE = {
 
    'www.sf-conservancy.org': 'sfconservancy.org',
 
}
 

	
 
LOGGING = {
 
    'version': 1,
 
    'disable_existing_loggers': False,
 
    'formatters': {
 
        'default': {
 
            'format': '%(asctime)s %(levelname)s %(name)s: %(message)s',
 
            'datefmt': '%Y-%m-%d %H:%M:%S',
 
        },
 
    },
 
    'filters': {
 
        'require_debug_false': {
 
            '()': 'django.utils.log.RequireDebugFalse'
 
        }
 
    },
 
    'handlers': {
 
        'mail_admins': {
 
            'level': 'ERROR',
 
            'filters': ['require_debug_false'],
 
            'class': 'django.utils.log.AdminEmailHandler'
 
        },
...
 
@@ -80,49 +76,49 @@ INSTALLED_APPS = [
 
    'django.contrib.sessions',
 
    'django.contrib.sites',
 
    'django.contrib.admin',
 
    'django.contrib.messages',
 
    'django.contrib.humanize',
 
    'django.contrib.staticfiles',
 
    'conservancy.blog',
 
    'conservancy.contacts',
 
    'conservancy.contractpatch',
 
    'conservancy.events',
 
    'conservancy.news',
 
    'conservancy.staff',
 
    # 'conservancy.summit_registration',
 
    'conservancy.worldmap',
 
    'conservancy.supporters',
 
    'conservancy.fundgoal',
 
    'conservancy.assignment',
 
    'conservancy.fossy',
 
    'conservancy.podjango',
 
    'conservancy.usethesource.apps.UseTheSourceConfig',
 
]
 

	
 
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
 

	
 
BASE_DIR = Path(__file__).resolve().parent
 
BASE_DIR = Path(__file__).resolve().parent.parent
 
TEMPLATES = [
 
    {
 
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
 
        'DIRS': [
 
            BASE_DIR / 'templates',
 
            BASE_DIR / 'static',
 
        ],
 
        'APP_DIRS': True,
 
        'OPTIONS': {
 
            'context_processors': [
 
                'django.template.context_processors.debug',
 
                'django.template.context_processors.request',
 
                'django.contrib.auth.context_processors.auth',
 
                'django.contrib.messages.context_processors.messages',
 
                'conservancy.local_context_processors.host_url',
 
                'conservancy.local_context_processors.sitefundraiser',
 
            ]
 
        }
 
    }
 
]
 

	
 
# Internationalization
 
TIME_ZONE = 'America/New_York'
 
LANGUAGE_CODE = 'en-us'
conservancy/settings/dev.py
Show inline comments
 
new file 100644
 
from .base import *
 

	
 
DEBUG = True
 
ALLOWED_HOSTS = ['*']
 

	
 
FORCE_CANONICAL_HOSTNAME = False
 

	
 
DATABASES = {
 
    'default': {
 
        'NAME': 'conservancy-website.sqlite3',
 
        'ENGINE': 'django.db.backends.sqlite3',
 
    }
 
}
 

	
 
SECRET_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
 

	
 
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
conservancy/settings/prod.py
Show inline comments
 
new file 100644
 
import json
 

	
 
from django.core.exceptions import ImproperlyConfigured
 

	
 
from .base import *
 

	
 
DEBUG = False
 
ALLOWED_HOSTS = ['www.sfconservancy.org', 'sfconservancy.org']
 

	
 
FORCE_CANONICAL_HOSTNAME = 'sfconservancy.org'
 

	
 
ADMINS = [
 
    ('Bradley M. Kuhn', 'sysadmin@sfconservancy.org'),
 
    ('Ben Sturmfels', 'sysadmin+conservancy@sturm.com.au'),
 
]
 

	
 
MANAGERS = [
 
    ('Bradley M. Kuhn', 'sysadmin@sfconservancy.org'),
 
]
 

	
 
DATABASES = {
 
    'default': {
 
        'NAME': '/var/lib/www/database/conservancy-website.sqlite3',
 
        'ENGINE': 'django.db.backends.sqlite3',
 
    }
 
}
 

	
 
# Apache/mod_wsgi doesn't make it straightforward to pass environment variables
 
# to Django (can't use the Apache config).
 
with open(BASE_DIR / 'secrets.json') as f:
 
    secrets = json.load(f)
 

	
 
def get_secret(secrets, setting):
 
    try:
 
        return secrets[setting]
 
    except KeyError:
 
        raise ImproperlyConfigured(f'Missing secret \'{setting}\'')
 

	
 
SECRET_KEY = get_secret(secrets, 'SECRET_KEY')
 

	
 
SESSION_COOKIE_SECURE = True
manage.py
Show inline comments
 
#!/usr/bin/env python
 
"""Django command-line utility for administration tasks.
 

	
 
See https://docs.djangoproject.com/en/4.0/ref/django-admin/
 
"""
 
import os
 
import sys
 

	
 
if __name__ == '__main__':
 
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'conservancy.settings')
 
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'conservancy.settings.dev')
 
    try:
 
        from django.core.management import execute_from_command_line
 
    except ImportError as exc:
 
        raise ImportError(
 
            "Couldn't import Django. Are you sure it's installed and "
 
            "available on your PYTHONPATH environment variable? Did you "
 
            "forget to activate a virtual environment?"
 
        ) from exc
 
    execute_from_command_line(sys.argv)
0 comments (0 inline, 0 general)