Local settings w Django


Poznaj bezpieczny i skuteczny sposób lokalnej konfiguracji swojej aplikacji


Ile razy mieliście sytuację, w której pisaliście aplikację, wszystko odpowiednio konfigurowaliście i po jakimś czasie próbując uruchomić ją lokalnie występowały dziwne błędy? Ja miałem tak za każdym razem jak stawiałem nową aplikację. Byłem bardzo leniwy i nie chciało mi się tego poprawiać. Nawet przy budowaniu tego bloga źle skonfigurowałem pliki statyczne i dostęp do bazy. Z każdą poprawką musiałem nadpisywać sobie ustawienia. Aż w końcu doszło do sytuacji w której zacomitowałem błędną bazę i wrzuciłem to na produkcję. Takie rzeczy nie powinny mieć miejsca. W końcu znalazłem bardzo fajny i skuteczny sposób, mianowicie local_settings .

Import lokalnych ustawień

Żeby użyć lokalnych ustawień wystarczy na samym końcu pliku settings.py dodać odpowiedni import.

try:
    from local_settings import *
except ImportError:
    # No local settings was found, skipping.
    pass

Wiem, że ten zapis nie jest zbyt elegancki i sam w jednym z wpisów tłumaczyłem, żeby tak nie robić, ale w tym przypadku jest to uzasadnione. Chodzi bowiem o to, że takie nadpisywane ustawienia mają wiele zastosowań. Możemy podejść do tego z innej strony i wymusić dodanie tego pliku do projektu. Wystarczy wyrzucić odpowiedni wyjątek

try:
    from local_settings import *
except ImportError:
    raise Exception("A local_settings.py file is required")

Tylko jest tu jedna bardzo ważna uwaga, którą ponownie potwórzę. Plik ten nie może być przechowywany w repo! Także pierwszą i kluczową rzeczą jest dodanie go do gitignore . I co to nam właściwie daje? A no dość spory komfort, bo z jednej strony wszyscy, którzy będą chcieli pracować nad naszym kodem będą mieli niski próg wejścia, a z drugiej strony na serwerze produkcyjnym wszystkie hasła będą bezpieczne.

Debug i Django Debug Toolbar

Jak wspomniałem wcześniej ustawienia lokalne mają wiele zastosowań. Jednym z nich jest nadpisywanie ustawień. Podczas błędnego działania aplikacji powinniśmy wyświetlać użytkownikom odpowiednią zaślepkę. Nie chcemy bowiem, żeby widzieli pełną ścieżkę błędu lub informacje z djdt . Obie opcje są bardzo proste do ustawienia i można je włączyć lub wyłączyć poprzez zmianę odpowiedniej flagi w ustawieniach. Ciągłe przestawianie tego jak chcemy zbadać jakiś błąd jest jednak uciążliwe. I tu lokalne ustawienia przychodzą nam z pomocą. Wystarczy, że w głównych ustawieniach będziemy mieli

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

Lokalnie natomiast nadpiszemy to. W efekcie plik local_settings.py będzie wyglądał następująco

DEBUG = True

Tym prostym zabiegiem jesteśmy bezpieczni, bo na produkcji nikt nie zobaczy informacji, które powinien znać tylko deweloper, a z drugiej strony lokalnie nie będziemy musieli nadpisywać tego przy każdej potrzebie. Proste, a bardzo pomocne.

Backend bazy danych

Spójrzmy teraz na to z innej strony. Nasz kod jest na repozytorium. Jeśli jest to kod OpenSource, to w pliku settings.py nie powinnismy trzymać żadnych haseł, zwłaszcza do bazy danych. Z drugiej strony, warto gdybyśmy lokalnie mieli podpiętą inną bazę danych, na przykład do testów. To również jest bardzo proste. Wystarczy, że w głównych ustawieniach będziemy mieli domyślną konfigurację bazy

DATABASES = {
    'default': { 
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

Natomiast teraz zamieścimy plik local_settings.py na serwerze produkcyjnym już z normalną konfiguracją bazy

DATABASES = {
    'default': {
        'ENGINE': 'engine',
        'NAME': 'name',
        'USER': 'user',
        'PASSWORD': 'password',
        'HOST': 'host',
    }
}

Konfiguracja skrzynki pocztowej

Jeśli kiedykolwiek wysyłaliście wiadomości do użytkowników mailem ze swojej aplikacji to wiecie, że wymaga to podania danych do skrzynki pocztowej w ustawieniach. No i tu rodzi się problem, bo nikt nie chce żeby dane do jego skrzynki były ogólnodostępne. Jak widać jest to dokładnie ten sam problem co z bazą danych. Wystarczy, że cała konfiguracja skrzynki będzie w pliku local_settings.py na zdalnym serwerze

EMAIL_HOST = 'host'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'user'
EMAIL_HOST_PASSWORD = 'password'
EMAIL_USE_TLS = True

Sentry

Przy większych aplikacjach monitorowanie jest niezbędne. Jednym z popularniejszych narzędzi, które to ułatwia jest sentry. Konfiguracja jest bardzo prosta i odbywa się w kilku krokach. Można ją znaleźć pod tym adresem . Do czego jednak zmierzam? Wyobraźmy sobie, że mamy na przykład 3 środowiska. Jedno lokalne, na którym pracujemy, drugie testowe na zdalnym serwerze i trzecie produkcyjne. Wiadomo, że na tym testowym dużego ruchu nie będzie, a przynajmniej nie powinno. Również informacje o błędach średnio nas tam interesują. Dlatego warto dodać konfigurację sentry tylko w local_settings na serwerze produkcyjnym

RAVEN_CONFIG = {
    'dsn': 'https://<key>:<secret>@sentry.io/<project>',
    # If you are using git, you can also automatically configure the
    # release based on the git info.
    'release': raven.fetch_git_sha(os.path.abspath(os.pardir)),
}

W ten sposób będziemy zbierać informację o błędach tylko z tego miejsca, które nas interesuje.

Middleware

Czasem korzystamy z gotowych middleware, które udostępniają ciekawe funkcje. Mogą to być nasze własne kawałki kodu albo gotowe, napisane przez kogoś innego. Może się zdarzyć, że ktoś będzie próbował wejść na nasz adres po IP. Możemy też mieć naszą stronę dostępną pod różnymi domenami. Spróbujmy zatem napisać prosty middleware, który przekieruje użytkownika na poprawny adres jeśli ten wejdzie w nieaktywną lub błędną domenę, która dalej kieruje na nasz serwer

from django.http import HttpResponseRedirect

DOMAIN = 'ddeby.pl'
SITE = 'https://ddeby.pl'


class RedirectMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if not request.get_host().startswith(DOMAIN):
            return HttpResponseRedirect('https://ddeby.pl/')

        return self.get_response(request)

Teraz wystarczy dodać odpowiedni wpis w ustawieniach

MIDDLEWARE = [
    [ ... ]
    'app.middleware.RedirectMiddleware'
]

W ten prosty sposób mamy proste zabezpieczenie. Ale jest tu jedna luka w myśleniu. Lokalnie nie zawsze chcemy nadpisywać sobie hosty i wchodzimy przez localhost:8000 . Musielibyśmy w local_settings nadpisywać cały blok z middleware. Jeśliby byłoby ich więcej, to stałoby się to trudne do utrzymania. Zróbmy zatem prostą sztuczkę i dodajmy flagę, którą możemy sterować. Nasz poprawiony middleware powinien zatem wyglądać następująco

from django.conf import settings
from django.http import HttpResponseRedirect


DOMAIN = 'ddeby.pl'
SITE = 'https://ddeby.pl'

ALLOW_DOMAIN_REDIRECTS = getattr(settings, 'ALLOW_DOMAIN_REDIRECTS', False)


class RedirectMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if ALLOW_DOMAIN_REDIRECTS and not request.get_host().startswith(DOMAIN):
            return HttpResponseRedirect('https://ddeby.pl/')

        return self.get_response(request)

Teraz mamy już więcej możliwości. Możemy w normalnych ustawieniach zostawić to włączone i wyłączać w tych lokalnych. Możemy również zostawić to wyłączone i włączać w ustawieniach produkcyjnych. Wszystko zależy od zapotrzebowania. Jeśli pracujemy głównie samodzielnie i często eksperymentujemy z różnymi serwerami, to warto skorzystać z pierwszej opcji. Jeśli jednak nasze serwery są raczej niezmienne a my pracujemy w większym gronie, to drugie rozwiązanie wydaje się być bardziej rozsądne.

Podsumowanie

Ustawienia lokalne to nie jest niezbędna wiedza dla programistów. Jednak doświadczeni deweloperzy, pracujący w większych gronach i przy dużej ilości projektów zapewne doceniają taką możliwość. A Wy znacie inne, ciekawe zastosowania local_settings.py. Podzielcie się nimi w komentarzu

Cze 27, 2019

Najnowsze wpisy

Zobacz wszystkie