Django Web Sitesi Başlangıç Ayarları Nasıl Yapılır? Django Başlangıç Kılavuzu

Django ile web sitesi mi geliştiriyorsunuz? Bu yazımda sizlere projeniz için en ideal Django ayarlarının nasıl olması gerektiğini, ayarları doğru yapmak için ipuçlarını ve en iyi ayarlar için tavsiyelerimi anlatacağım.

Her geliştirici Django öğrenebilir. Ancak her geliştirici idealist olmadığından bir proje geliştirirken altyapının hazırlanmasında ince dokunuşlara dikkat etmez ve dolayısıyla da standartlaşmanın uzağında kalır. Ancak bu projeniz büyüdükçe sizin canınızı sıkabilecek hale gelebilir. Bu yazı sizlere bir proje hazırlarken başlangıç ayarlarınız ve projenin geneli için bir standart oluşturmanızı sağlayacaktır.

Django Web Sitesi Başlangıç Ayarları Kılavuzu

1. Django Kullanıcı Modeli Genişletme (User Model Extending): Öncelikle her ne kadar django yerleşik kimlik doğrulama sistemi harika olsa da Django'nun kendi resmi dokümantasyonlarında da tavsiye edildiği üzere (buradan okuyabilirsiniz), mevcut kullanıcıdan yeni bir kullanıcı modeli genişletin. Bunun yapılmasının tavsiye edilmesindeki en önemli sebep, ileride projenizde mesela kullanıcı resmi/avatar, telefon vs. farklı ek kullanıcı bilgileri veya giriş için kullanıcı adı yerine email kullanımı gibi farklı özelliklere sahip kullanıcı modelleri oluşturacak olduğunuzda sistemin hata vermemesi. Emin olun bu oldukça hassas bir konu ve mutlaka hatalar oluşacaktır. Şimdi gelin bunu nasıl yapacağımızdan bahsedelim.

Bunun amaca göre birden çok yöntemi olmakla birlikte, bu yazımda sizlere sadece mevcut yerleşik kullanıcı sisteminin genişletilmesinden bahsedeceğim. Yani projede kullanıcılar için bir giriş imkanı olmadığını farz ederek tamamen farklı bir kullanıcı modeli oluşturmayacak, sadece ilerisi için temel bir genişletme yapmayı göstereceğim. Yakında farklı rollere sahip kullanıcı modeli genişletme yöntemlerine yönelik detaylı bir yazı ayrıyeten yayınlarım.

Önemli Not: Kullanıcı modeli genişletme mutlaka ilk migrate komutundan önce, yani veri tabanı oluşturulmadan önce hazırlanmalıdır.

    a) Yeni bir uygulama oluşturun ve adını örnek olarak accounts olarak belirleyin.

python manage.py startapp accounts

    b) Kullanıcı modelini oluşturun.

>>> models.py

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

    class Meta: # Meta sınıfının kullanımı gerekli değildir.
        verbose_name_plural = "Kullanıcılar"
        verbose_name = "Kullanıcı"

    c) Ayar dosyasında hangi kullanıcı modelinin kullanılacağını belirtin.

>>> settings.py

AUTH_USER_MODEL = 'accounts.User'

    d) Ayar dosyasında oluşturduğumuz accounts uygulamasını ekleyin.

>>> settings.py

INSTALLED_APPS = [
    ...
    'accounts.apps.AccountsConfig',
    ...

    e) Admin panelinde gözükmesi için uygulamanızı kaydettirin.

>>> admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

admin.site.register(User, UserAdmin)

Bonus Bilgi: Eğer güzel görünümlü tam bir değişim istiyorsanız admin.py dosyasında uygulamanın kaydını şu şekilde ekleyebilirsiniz. Bu sayede genişlettiğiniz accounts uygulaması admin panelinde dahili kullanıcı uygulaması gibi gözükecektir. Bu da benden size hediye olsun :)

>>> admin.py

from django.contrib import admin
from accounts.models import User

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    list_display = ["username", "email", "date_joined"]
    readonly_fields = ("date_joined", "last_login", )
    fieldsets = (
        (None, {
            "fields": ("username", "password", "first_name", "last_name", "email")
        }),
        ("GENEL BİLGİLER", {
            "fields": ("is_active", "is_staff", "is_superuser", "groups", "user_permissions", "date_joined", "last_login")
        })
    )

    def has_delete_permission(self, request, obj=None):
        return True

    def has_add_permission(self, request):
        return True

    f) Şimdi veritabanını oluşturabilirsiniz.

python manage.py migrate

2. django-environ Paketi ile Ortam Değişkenlerini .env Dosyasında Gizleyin: Özellikle Secret Key veya veri tabanı bilgileri gibi önemli olan verileri gizlemek amacıyla ve tek bir dosyadan bir çok önemli olan ayarları düzenleyebilmek amacıyla django-environ paketini kullanarak tüm bilgileri .env dosyasında toplayın. Bu işlem için dahili ortam değişkeni sistemi de kullanılabilir. Ancak django-environ işini oldukça iyi yaptığı için ben onu kullanmayı tercih ediyorum.

    a) django-environ paketini yükleyin.

python -m pip install django-environ

    b) Ayar dosyasına django-environ için gerekli parametreleri ekleyin.

>>> settings.py

import environ
import os

env = environ.Env(
    # aksini belirtmediğiniz müddetçe başlangıç değeri False olacaktır.
    DEBUG=(bool, False)
)

# Projenin ana dizin yolunu belirtin
BASE_DIR = Path(__file__).resolve().parent.parent

# Varsayılan değişkenleri .env dosyasından alın
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))

    c) Şimdi de ayar dosyasında gizlemek istediğimiz bilgileri düzenleyin.

>>> settings.py

# .env dosyasındaki DEBUG değerini alır
DEBUG = env('DEBUG')

# Raises Django's ImproperlyConfigured
# Eğer SECRET_KEY .env dosyasında yoksa hata verir
try:
    SECRET_KEY = env('SECRET_KEY')
except KeyError as e:
    raise RuntimeError("SECRET_KEY .env dosyasında bulunamadı") from e

ALLOWED_HOSTS = tuple(env.list('ALLOWED_HOSTS', default=[]))

DATABASES = {
    # DEBUG modun SQLite kullanmak isterseniz olduğu gibi bırakın
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }

    # Veya PostgreSQL veri tabanı kullanmak için
    # Not: PostgreSQL kullanacaksanız üstteki SQLite ayarlarını silin!
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': env('NAME'),
        'USER': env('USER'),
        'PASSWORD': env('PASSWORD'),
        'HOST': env('HOST'),
        'PORT': env('PORT'),
    }
}

    d) Projenin ana dizininde .env dosyasını oluşturun.

>>> .env

#
# DJANGO
#
SECRET_KEY=django_secret_keyi_buraya_yazin
DEBUG=True_veya_False
ALLOWED_HOSTS=alanadiniz.com,IP_adresiniz

#
# DATABASE
#
NAME=veritabani_adiniz
USER=veritabani_kullanici_adi
PASSWORD=veritabani_sifreniz
HOST=localhost
PORT=5432

Not: Eğer projenizi github'a yüklüyorsanız mutlaka .env dosyasını .gitignore dosyasına ekleyiniz. Bu sayede bu önemli dosyaya herkesin ulaşabilmesinin önüne geçmiş olursunuz.

3. Proje Dosya Yerleşimini Yaparken Düzenli ve Planlı Olun: Her yiğidin kendi yoğurt yeme sitili vardır. Ancak yazılımda düzen ve temiz çalışma çok önemlidir. Bu yazının da amacı olan standartlaştırma projenin içerik dosya dizinlemesinde de önemli rol oynamaktadır. 

    a) Uygulamalara ait tüm modelleri uygulama_adi/models/ klasöründe toplayın.

    b) Uygulamalara ait tüm görünümleri uygulama_adi/views/ klasöründe toplayın.

    c) Uygulamalara ait tüm şablonları uygulama_adi/templates/uygulama_adi/ klasöründe toplayın.

    d) Varsa uygulamalara ait tüm formları uygulama_adi/forms/ klasöründe toplayın.

Bu ve benzer örnekler arttırılabilir. Örneğin benzer şekilde sinyaller için de aynı yöntemi uygulayabilirsiniz. Önemli olan düzenli olmanızdır. Burada bilmeniz gereken tek husus her klasörün içerisine __init__.py dosyası ekleyerek, modelin veya görünümün sınıf adını eklemektir. Örnek olarak:

>>> uygulama_adi/models/__init__.py

from .abstract_models import *
from .index import *
from .blog import *
from .contact import *
...

Örnek Proje Dizini:

Proje_Klasor_Adi/
|---- accounts/
      |---- migrations/
​​​​​​​            |---- ...
      |---- __init_.py
      |---- admin.py
      |---- apps.py
      |---- models.py
|---- config/ # proje oluştururken adını config yaparsanız klasör adı da öyle olur.
      |---- __init__.py
      |---- asgi.py
​​​​​​​      |---- settings.py
​​​​​​​      |---- urls.py
​​​​​​​      |---- wsgi.py
|---- mainsite/ # ana uygulama adınızı mainsite yapabilirsiniz.
​​​​​​​      |---- forms/
​​​​​​​            |---- __init__.py
​​​​​​​            |---- contact.py
​​​​​​​      |---- migrations/
​​​​​​​            |---- ...
​​​​​​​      |---- models/
​​​​​​​            |---- __init__.py
​​​​​​​            |---- abstract_models.py
​​​​​​​            |---- blog.py
​​​​​​​            |---- contact.py
​​​​​​​            |---- ...
​​​​​​​      |---- templates/
​​​​​​​            |---- mainsite/
​​​​​​​                  |---- blog_details.html
​​​​​​​                  |---- blog_list.html
​​​​​​​                  |---- contact.html
​​​​​​​                  |---- index.html
​​​​​​​                  |---- ...
​​​​​​​      |---- views/
​​​​​​​            |---- __init__.py
​​​​​​​            |---- blog.py
​​​​​​​            |---- contact.py
​​​​​​​            |---- index.py
      |---- __init_.py
      |---- admin.py
      |---- apps.py
      |---- urls.py
|---- media/
​​​​​​​      |---- img/
|---- static/
​​​​​​​      |---- css/
​​​​​​​      |---- img/
​​​​​​​      |---- js/
|---- templates/ # burada base olarak adlandırılan ana/genel şablonlar bulunur.
|---- .env
|---- .gitignore # github kullanılıyorsa
|---- db.sqlite3 # debug mode için SQLite veri tabanı kullanılıyorsa
|---- manage.py
|---- requirements.txt # proje için gerekli paketler dosyası

4. Geliştirme Aşamasında Static ve Media Dosyalarınızı urls.py Dosyasında Sunma: Projenizi geliştirirken Django'nun static ve media dosyalarınızı gösterebilmesi için ana proje dizininizde bulunan urls.py dosyasında değişiklik yapmanız gerekir.

from config import settings

if settings.DEBUG:
    from django.conf.urls.static import static
    from django.contrib.staticfiles.urls import staticfiles_urlpatterns

    # serve static and media files from development server
    urlpatterns += staticfiles_urlpatterns()
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Elbette settings.py dosyasında da gerekli ayarların yapılı olduğundan emin olunuz. Bir sonraki aşamada yer alan örnekte bu ayarları görebilirsiniz.

5. Düzenli Bir Ayar Dosyası (settings.py) Oluşturun: Django ile bir proje geliştirirken ayarlar büyük projelerde o kadar karmaşık hale gelebilir ki, bunun için düzeni sağlamak amacıyla birkaç yöntem mevcut.

    a) İlk yöntem settings.py dosyasını bölümlemek. Örnek olarak:

Proje_Klasor_Adi/
|---- config/
​​​​​​​      |---- settings/
​​​​​​​            |---- __init__.py
​​​​​​​            |---- base.py
​​​​​​​            |---- development.py
​​​​​​​            |---- production.py
      |---- __init__.py
      |---- asgi.py
​​​​​​​      |---- urls.py
​​​​​​​      |---- wsgi.py

Gördüğünüz gibi settings klasörünün içerisinde artık üç farklı ayar dosyamız bulunuyor. base.py dosyasının içerisinde genel (tüm ayar seviyesinin kullanacağı) bilgiler yer alırken, development.py dosyasında geliştirme ortamında kullanacağımız ayarlar, yine aynı şekilde production.py dosyasında ise gerçek ortamda kullanacağımız ayarlar yer alıyor. Her bir üst seviyedeki ayar dosyasında, bir alt seviyeden ayarları import etmemiz gerekiyor. Örneğin:

>>> development.py

from .base import * # base.py ayarlarımızın hepsini alıyoruz.

ALLOWED_HOSTS = ['127.0.0.1']

DEBUG = True

veya

>>> production.py

from .base import *

ALLOWED_HOSTS = ['alanadiniz.com,IP_ADRESINIZ']

DEBUG = False

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': env('NAME'),
        'USER': env('USER'),
        'PASSWORD': env('PASSWORD'),
        'HOST': env('HOST'),
        'PORT': env('PORT'),
    }
}

    b) İkinci yöntem ise settings.py dosyasında düzenli olmak. Küçük projelerde (kendi sitem de buna dahil) ben bu yöntemi kullanıyorum.

from pathlib import Path
import environ
import os

# ==============================================================================
# CORE SETTINGS
# ==============================================================================
env = environ.Env(
    # set casting, default value
    DEBUG=(bool, False)
)

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# Take environment variables from .env file
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))

# False if not in os.environ because of casting above
DEBUG = env('DEBUG')

# Raises Django's ImproperlyConfigured
# exception if SECRET_KEY not in os.environ
# SECURITY WARNING: keep the secret key used in production secret!
try:
    SECRET_KEY = env('SECRET_KEY')
except KeyError as e:
    raise RuntimeError("Could not find a SECRET_KEY in environment") from e

ALLOWED_HOSTS = tuple(env.list('ALLOWED_HOSTS', default=[]))

ROOT_URLCONF = 'config.urls'

WSGI_APPLICATION = 'config.wsgi.application'

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# ==============================================================================
# APPLICATION SETTINGS
# ==============================================================================

INSTALLED_APPS = [
    ....
    'accounts.apps.AccountsConfig',
    'mainsite.apps.MainsiteConfig',
    # third party
    'ckeditor',
    ...
]

# ==============================================================================
# MIDDLEWARE SETTINGS
# ==============================================================================

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# ==============================================================================
# TEMPLATES SETTINGS
# ==============================================================================

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates']
        ,
        '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',
            ],
        },
    },
]

# ==============================================================================
# DATABASE SETTINGS
# ==============================================================================

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }

    # 'default': {
    #     'ENGINE': 'django.db.backends.postgresql_psycopg2',
    #     'NAME': env('NAME'),
    #     'USER': env('USER'),
    #     'PASSWORD': env('PASSWORD'),
    #     'HOST': env('HOST'),
    #     'PORT': env('PORT'),
    # }
}

# ==============================================================================
# AUTHENTICATION AND AUTHORIZATION SETTINGS
# ==============================================================================

# Password validation
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Extended User Model Settings
AUTH_USER_MODEL = 'accounts.User'

# ==============================================================================
# LOCALIZATION SETTINGS
# ==============================================================================

LANGUAGE_CODE = 'tr-TR'
TIME_ZONE = 'Europe/Istanbul'
USE_I18N = True
USE_L10N = True
USE_TZ = True

# ==============================================================================
# STATIC AND MEDIA FILES SETTINGS
# ==============================================================================

# Static Files
STATIC_URL = '/static/'
# STATIC_ROOT = '/home/kullanici_adi/proje_klasor_adi/staticfiles/'  # in production mode
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles/')
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'

# Media files
MEDIA_URL = '/media/'
# MEDIA_ROOT = os.path.join(BASE_DIR, 'mediafiles/')  # in production mode
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

# IMAGEKIT SETTINGS
IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = 'imagekit.cachefiles.strategies.Optimistic'

# ==============================================================================
# THIRD-PARTY APPLICATION SETTINGS
# ==============================================================================

# CKEDITOR SETTINGS
CKEDITOR_UPLOAD_PATH = os.path.join("img/uploads/")
AWS_QUERYSTRING_AUTH = False
CKEDITOR_IMAGE_BACKEND = "pillow"
CKEDITOR_BROWSE_SHOW_DIRS = True

CKEDITOR_CONFIGS = {
    'default': {
        'toolbar': 'Advanced',
        'width': '100%',
        'tabSpaces': 4,
        'extraPlugins': ','.join(
            [
               'codesnippet',
            ]
        ),
    },
}

# CRISPY SETTINGS
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap4"
CRISPY_TEMPLATE_PACK = "bootstrap4"

# ==============================================================================
# EMAIL SETTINGS
# ==============================================================================

EMAIL_HOST = "smtp.gmail.com"
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')  # Should use gmail app password for stability reasons.
EMAIL_ADMIN = env('EMAIL_ADMIN')
DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL')

if DEBUG:
    EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
    EMAIL_HOST = "smtp.gmail.com"
    EMAIL_PORT = 465
    EMAIL_USE_TLS = False
    EMAIL_USE_SSL = True
else:  # in production mode
    EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
    EMAIL_PORT = 587
    EMAIL_USE_TLS = True
    EMAIL_USE_SSL = False

# ==============================================================================
# SERVER SECURITY SETTINGS IN PRODUCTION MODE
# ==============================================================================

if not DEBUG:
    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PHOTO', 'https')
    SECURE_SSL_REDIRECT = True
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True
    SECURE_BROWSER_XSS_FILTER = True
    SECURE_CONTENT_TYPE_NOSNIFF = True
    SECURE_HSTS_SECONDS = 31536000  # 1 year
    SECURE_HSTS_INCLUDE_SUBDOMAINS = True
    SECURE_HSTS_PRELOAD = True
    X_FRAME_OPTIONS = "DENY"
    SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin"

# ==============================================================================
# LOGGING SETTINGS
# ==============================================================================

# LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': os.path.join(BASE_DIR, 'debug.log'),
            'formatter': 'verbose'
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file'],
            'level': 'INFO',
            'propagate': True,
        },
    },
}

Django web sitesi başlangıç kılavuzu olarak tüm bu örneklerin size faydalı olacağını umuyorum. Yazımı beğendiyseniz desteklemek amacıyla paylaşabilir ve yeni yazılarımdan haberdar olabilmek için email listeme kayıt olabilirsiniz.

Paylaş

Yeni Blog Yazılarımdan Haberdar Olun

Yeni yazılarımdan anında haberdar olmak için email listeme abone olun. Size spam göndermeyeceğime söz veriyorum!