KR_Django - somaz94/python-study GitHub Wiki
Django๋ ํ์ด์ฌ์ ๋ํ์ ์ธ ์น ํ๋ ์์ํฌ์ด๋ค.
# ํ๋ก์ ํธ ์์ฑ
django-admin startproject myproject
# ์ฑ ์์ฑ
python manage.py startapp myapp
# ์๋ฒ ์คํ
python manage.py runserver
# models.py
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Meta:
ordering = ['-created_at']
verbose_name = '์ฌ์ฉ์'
verbose_name_plural = '์ฌ์ฉ์ ๋ชฉ๋ก'
โ
ํน์ง:
- MVC ์ํคํ ์ฒ (Django์์๋ MTV ํจํด)
- ORM ์ง์
- ๊ด๋ฆฌ์ ์ธํฐํ์ด์ค
- ๊ฐ๋ ฅํ ํ ํ๋ฆฟ ์์คํ
- ํผ ์ฒ๋ฆฌ ํ๋ ์์ํฌ
myproject/
โโโ manage.py # ํ๋ก์ ํธ ๊ด๋ฆฌ ์คํฌ๋ฆฝํธ
โโโ myproject/ # ํ๋ก์ ํธ ์ค์ ํจํค์ง
โ โโโ __init__.py
โ โโโ settings.py # ํ๋ก์ ํธ ์ค์
โ โโโ urls.py # ๋ฃจํธ URL ์ค์
โ โโโ asgi.py # ASGI ์ค์ (๋น๋๊ธฐ)
โ โโโ wsgi.py # WSGI ์ค์ (๋ฐฐํฌ)
โโโ myapp/ # ์ ํ๋ฆฌ์ผ์ด์
ํจํค์ง
โโโ __init__.py
โโโ admin.py # ๊ด๋ฆฌ์ ์ธํฐํ์ด์ค
โโโ apps.py # ์ฑ ์ค์
โโโ migrations/ # ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์
โโโ models.py # ๋ฐ์ดํฐ ๋ชจ๋ธ
โโโ tests.py # ํ
์คํธ
โโโ views.py # ๋ทฐ ํจ์/ํด๋์ค
Django์ URL ๋ผ์ฐํ
์์คํ
๊ณผ ๋ทฐ ๊ตฌํ ๋ฐฉ๋ฒ์ด๋ค.
# urls.py (ํ๋ก์ ํธ ๋ ๋ฒจ)
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
path('', include('myapp.urls')),
]
# urls.py (์ฑ ๋ ๋ฒจ)
from django.urls import path
from . import views
app_name = 'myapp' # URL ๋ค์์คํ์ด์ค
urlpatterns = [
path('users/', views.user_list, name='user_list'),
path('users/<int:pk>/', views.user_detail, name='user_detail'),
path('users/create/', views.user_create, name='user_create'),
path('users/<int:pk>/update/', views.user_update, name='user_update'),
path('users/<int:pk>/delete/', views.user_delete, name='user_delete'),
]
# views.py (ํจ์ ๊ธฐ๋ฐ ๋ทฐ)
from django.shortcuts import render, get_object_or_404, redirect
from .models import User
from .forms import UserForm
def user_list(request):
users = User.objects.all()
return render(request, 'myapp/user_list.html', {'users': users})
def user_detail(request, pk):
user = get_object_or_404(User, pk=pk)
return render(request, 'myapp/user_detail.html', {'user': user})
# views.py (ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ)
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
class UserListView(ListView):
model = User
template_name = 'myapp/user_list.html'
context_object_name = 'users'
paginate_by = 10
class UserDetailView(DetailView):
model = User
template_name = 'myapp/user_detail.html'
context_object_name = 'user'
โ
ํน์ง:
- URL ํจํด ๋งค์นญ
- ํจ์ ๊ธฐ๋ฐ ๋ทฐ์ ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ
- ํ ํ๋ฆฟ ๋ ๋๋ง
- URL ๋ค์์คํ์ด์ค
- ๋งค๊ฐ๋ณ์ ์บก์ฒ
<!-- templates/myapp/user_list.html -->
{% extends 'base.html' %}
{% block content %}
<h1>์ฌ์ฉ์ ๋ชฉ๋ก</h1>
<ul>
{% for user in users %}
<li>
<a href="{% url 'myapp:user_detail' user.pk %}">
{{ user.name }} ({{ user.email }})
</a>
</li>
{% empty %}
<li>๋ฑ๋ก๋ ์ฌ์ฉ์๊ฐ ์์ต๋๋ค.</li>
{% endfor %}
</ul>
<a href="{% url 'myapp:user_create' %}" class="btn btn-primary">
์ ์ฌ์ฉ์ ๋ฑ๋ก
</a>
{# ํ์ด์ง๋ค์ด์
#}
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">์ด์ </a>
{% endif %}
<span class="current">
{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">๋ค์</a>
{% endif %}
</div>
{% endif %}
{% endblock %}
Django ํผ ์์คํ
์ ์ฌ์ฉํ ๋ฐ์ดํฐ ๊ฒ์ฆ๊ณผ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ด๋ค.
# forms.py
from django import forms
from .models import User
class UserForm(forms.ModelForm):
confirm_email = forms.EmailField(label='์ด๋ฉ์ผ ํ์ธ')
class Meta:
model = User
fields = ['name', 'email']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
}
help_texts = {
'email': '์ ํจํ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์
๋ ฅํ์ธ์.',
}
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
if self.instance.pk is None or self.instance.email != email:
raise forms.ValidationError('์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค.')
return email
def clean(self):
cleaned_data = super().clean()
email = cleaned_data.get('email')
confirm_email = cleaned_data.get('confirm_email')
if email and confirm_email and email != confirm_email:
self.add_error('confirm_email', '์ด๋ฉ์ผ ์ฃผ์๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.')
return cleaned_data
# views.py (ํผ ์ฌ์ฉ)
def user_create(request):
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():
user = form.save()
return redirect('myapp:user_detail', pk=user.pk)
else:
form = UserForm()
return render(request, 'myapp/user_form.html', {'form': form})
โ
ํน์ง:
- ํผ ์ ํจ์ฑ ๊ฒ์ฌ
- CSRF ๋ณดํธ
- ๋ชจ๋ธ ์ฐ๋
- ์์ ฏ ์ปค์คํฐ๋ง์ด์ง
- ํด๋ฆฐ ๋ฉ์๋ ํ์ฉ
<!-- templates/myapp/user_form.html -->
{% extends 'base.html' %}
{% block content %}
<h1>{{ user.pk|yesno:'์ฌ์ฉ์ ์์ ,์ ์ฌ์ฉ์ ๋ฑ๋ก' }}</h1>
<form method="post" novalidate>
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}
<small class="form-text text-muted">
{{ field.help_text }}
</small>
{% endif %}
{% if field.errors %}
<div class="invalid-feedback">
{% for error in field.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">์ ์ฅ</button>
<a href="{% url 'myapp:user_list' %}" class="btn btn-secondary">์ทจ์</a>
</form>
{% endblock %}
Django์ ์ฌ์ฉ์ ์ธ์ฆ ์์คํ
๊ณผ ๊ถํ ๊ด๋ฆฌ ๋ฐฉ๋ฒ์ด๋ค.
# settings.py
INSTALLED_APPS = [
# ...
'django.contrib.auth',
'django.contrib.contenttypes',
# ...
]
AUTH_USER_MODEL = 'myapp.CustomUser'
# models.py
from django.contrib.auth.models import AbstractUser, BaseUserManager, Permission
from django.db import models
class CustomUserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError('์ด๋ฉ์ผ์ ํ์์
๋๋ค')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
return self.create_user(email, password, **extra_fields)
class CustomUser(AbstractUser):
username = None # ๊ธฐ๋ณธ username ํ๋ ์ ๊ฑฐ
email = models.EmailField('์ด๋ฉ์ผ ์ฃผ์', unique=True)
phone = models.CharField('์ ํ๋ฒํธ', max_length=15, blank=True)
USERNAME_FIELD = 'email' # ๋ก๊ทธ์ธ์ ์ฌ์ฉํ ํ๋
REQUIRED_FIELDS = [] # createsuperuser ๋ช
๋ น ์คํ ์ ์๊ตฌํ๋ ํ๋
objects = CustomUserManager()
def __str__(self):
return self.email
# views.py (๋ก๊ทธ์ธ๊ณผ ๋ก๊ทธ์์)
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
def login_view(request):
if request.method == 'POST':
email = request.POST.get('email')
password = request.POST.get('password')
user = authenticate(request, email=email, password=password)
if user is not None:
login(request, user)
return redirect('home')
else:
error_message = '์ด๋ฉ์ผ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค'
return render(request, 'myapp/login.html', locals())
@login_required
def logout_view(request):
logout(request)
return redirect('login')
โ
ํน์ง:
- ์ฌ์ฉ์ ์ธ์ฆ ์์คํ
- ์ปค์คํ ์ฌ์ฉ์ ๋ชจ๋ธ
- ๊ถํ ๊ด๋ฆฌ
- ์ธ์ ์ฒ๋ฆฌ
- ๋น๋ฐ๋ฒํธ ํด์ฑ
# permissions.py
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from django.core.exceptions import PermissionDenied
class StaffRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.is_staff
# ํจ์ ๊ธฐ๋ฐ ๋ทฐ์์ ๊ถํ ํ์ธ
from django.contrib.auth.decorators import login_required, permission_required
@login_required
def profile_view(request):
return render(request, 'myapp/profile.html')
@permission_required('myapp.change_user')
def user_update(request, pk):
# ์ฌ์ฉ์ ์
๋ฐ์ดํธ ๋ก์ง
pass
# ํด๋์ค ๊ธฐ๋ฐ ๋ทฐ์์ ๊ถํ ํ์ธ
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class UserUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = User
form_class = UserForm
template_name = 'myapp/user_form.html'
permission_required = 'myapp.change_user'
def get_success_url(self):
return reverse_lazy('myapp:user_detail', kwargs={'pk': self.object.pk})
Django์์ RESTful API๋ฅผ ๊ตฌํํ๊ณ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ด๋ค.
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'django.contrib.staticfiles',
]
# REST Framework ์ค์
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}
# serializers.py
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'name', 'email', 'created_at']
read_only_fields = ['created_at']
def validate_email(self, value):
if User.objects.filter(email=value).exists():
if self.instance is None or self.instance.email != value:
raise serializers.ValidationError('์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค.')
return value
# views.py (API ๋ทฐ)
from rest_framework import viewsets, permissions, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django.core.cache import cache
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ['name', 'email']
ordering_fields = ['name', 'created_at']
@method_decorator(cache_page(60 * 15)) # 15๋ถ ์บ์
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@action(detail=True, methods=['get'])
def profile(self, request, pk=None):
user = self.get_object()
data = {
'id': user.id,
'name': user.name,
'email': user.email,
'created_at': user.created_at,
}
return Response(data)
# urls.py (API URL)
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'users', views.UserViewSet)
urlpatterns = [
path('', include(router.urls)),
path('auth/', include('rest_framework.urls')),
]
โ
ํน์ง:
- RESTful API ์ค๊ณ
- ์ง๋ ฌํ ๋ฐ ์ญ์ง๋ ฌํ
- ์บ์ ์์คํ
- ์ฑ๋ฅ ์ต์ ํ
- API ๊ถํ ๊ด๋ฆฌ
# ์ ์์ค ์บ์ API
from django.core.cache import cache
def get_active_users():
# ์บ์์์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ์๋
active_users = cache.get('active_users')
if active_users is None:
# ์บ์์ ์์ผ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๊ฐ์ ธ์ ์บ์์ ์ ์ฅ
active_users = User.objects.filter(is_active=True)
cache.set('active_users', active_users, 60 * 5) # 5๋ถ๊ฐ ์บ์
return active_users
# ์บ์ ํ
ํ๋ฆฟ ์กฐ๊ฐ
from django.core.cache import cache
from django.template.loader import render_to_string
def render_user_list():
# ์บ์๋ HTML ์กฐ๊ฐ ๊ฐ์ ธ์ค๊ธฐ ์๋
fragment = cache.get('user_list_html')
if fragment is None:
users = User.objects.all()[:10]
fragment = render_to_string('fragments/user_list.html', {'users': users})
cache.set('user_list_html', fragment, 60 * 10) # 10๋ถ๊ฐ ์บ์
return fragment
# ๋ทฐ ๋ ๋ฒจ ์บ์
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 15๋ถ ์บ์
def cached_view(request):
return render(request, 'cached_template.html')
Django์์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
์ ์ฒ๋ฆฌํ๊ณ ๊ด๋ฆฌ์ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌ์ฑํ๋ ๋ฐฉ๋ฒ์ด๋ค.
# ๋น๋๊ธฐ ์์
์ค์ (Celery)
# celery.py
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
app = Celery('myproject')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
# tasks.py
from celery import shared_task
from django.core.mail import send_mail
from django.template.loader import render_to_string
@shared_task
def send_notification_email(user_id):
from .models import User # ์ํ ์ฐธ์กฐ ๋ฐฉ์ง๋ฅผ ์ํด ๋ด๋ถ์์ ์ํฌํธ
user = User.objects.get(id=user_id)
context = {
'user': user,
'site_name': 'My Django Site',
}
html_message = render_to_string('emails/notification.html', context)
plain_message = render_to_string('emails/notification.txt', context)
send_mail(
subject='์๋ก์ด ์๋ฆผ',
message=plain_message,
from_email='[email protected]',
recipient_list=[user.email],
html_message=html_message,
fail_silently=False,
)
return f"Notification sent to {user.email}"
# ๊ด๋ฆฌ์ ์ธํฐํ์ด์ค ์ค์
# admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import User
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
list_display = ['name', 'email', 'created_at', 'actions_buttons']
list_filter = ['created_at', 'is_active']
search_fields = ['name', 'email']
readonly_fields = ['created_at']
fieldsets = [
(None, {'fields': ['name', 'email']}),
('์ถ๊ฐ ์ ๋ณด', {'fields': ['created_at'], 'classes': ['collapse']}),
]
def actions_buttons(self, obj):
return format_html(
'<a class="button" href="{}">๋ณด๊ธฐ</a> '
'<a class="button" onclick="return confirm(\'์ ๋ง ์ด๋ฉ์ผ์ ๋ฐ์กํ์๊ฒ ์ต๋๊น?\');" href="{}">์ด๋ฉ์ผ ๋ฐ์ก</a>',
f'/admin/users/{obj.id}/view/',
f'/admin/users/{obj.id}/send_email/'
)
actions_buttons.short_description = '์์
'
def send_notification(self, request, queryset):
for user in queryset:
send_notification_email.delay(user.id)
self.message_user(request, f"{queryset.count()}๋ช
์ ์ฌ์ฉ์์๊ฒ ์๋ฆผ์ด ๋ฐ์ก๋์์ต๋๋ค.")
send_notification.short_description = "์ ํํ ์ฌ์ฉ์์๊ฒ ์๋ฆผ ๋ฐ์ก"
actions = ['send_notification']
โ
ํน์ง:
- ๋น๋๊ธฐ ์์ ์ฒ๋ฆฌ
- ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ ํ
- ๊ด๋ฆฌ์ ์ธํฐํ์ด์ค ์ปค์คํฐ๋ง์ด์ง
- ์ด๋ฉ์ผ ํ ํ๋ฆฟ ๋ ๋๋ง
- ๊ด๋ฆฌ์ ์ก์ ์ ์
# admin_views.py
from django.contrib.admin.views.decorators import staff_member_required
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import path
from django.contrib import messages
from .models import User
from .tasks import send_notification_email
@staff_member_required
def send_user_email(request, user_id):
user = get_object_or_404(User, id=user_id)
task = send_notification_email.delay(user.id)
messages.success(request, f"{user.email}์๊ฒ ์๋ฆผ ์ด๋ฉ์ผ์ ๋ฐ์ก ์ค์
๋๋ค. ์์
ID: {task.id}")
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/admin/'))
# ๊ด๋ฆฌ์ URL ํ์ฅ
class UserAdmin(admin.ModelAdmin):
# ... ๊ธฐ์กด ์ฝ๋ ...
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
'<int:user_id>/send_email/',
self.admin_site.admin_view(send_user_email),
name='user-send-email',
),
]
return custom_urls + urls
Django ์ ํ๋ฆฌ์ผ์ด์
์ ํ
์คํธ์ ๋ฐฐํฌ ๋ฐฉ๋ฒ์ด๋ค.
# tests.py
from django.test import TestCase, Client
from django.urls import reverse
from .models import User
class UserModelTest(TestCase):
def setUp(self):
self.user = User.objects.create(
name="ํ
์คํธ ์ฌ์ฉ์",
email="[email protected]"
)
def test_user_creation(self):
self.assertEqual(self.user.name, "ํ
์คํธ ์ฌ์ฉ์")
self.assertEqual(self.user.email, "[email protected]")
self.assertTrue(isinstance(self.user, User))
def test_str_representation(self):
self.assertEqual(str(self.user), self.user.name)
class UserViewTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create(
name="ํ
์คํธ ์ฌ์ฉ์",
email="[email protected]"
)
self.list_url = reverse('myapp:user_list')
self.detail_url = reverse('myapp:user_detail', args=[self.user.id])
def test_user_list_view(self):
response = self.client.get(self.list_url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "ํ
์คํธ ์ฌ์ฉ์")
self.assertTemplateUsed(response, 'myapp/user_list.html')
def test_user_detail_view(self):
response = self.client.get(self.detail_url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "[email protected]")
self.assertTemplateUsed(response, 'myapp/user_detail.html')
# ํผ ํ
์คํธ
class UserFormTest(TestCase):
def test_valid_form(self):
data = {'name': '์ ์ฌ์ฉ์', 'email': '[email protected]', 'confirm_email': '[email protected]'}
form = UserForm(data=data)
self.assertTrue(form.is_valid())
def test_invalid_email(self):
# ์ด๋ฉ์ผ ๋ถ์ผ์น ํ
์คํธ
data = {'name': '์ ์ฌ์ฉ์', 'email': '[email protected]', 'confirm_email': '[email protected]'}
form = UserForm(data=data)
self.assertFalse(form.is_valid())
self.assertIn('confirm_email', form.errors)
# settings/production.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = ['www.example.com', 'example.com']
# ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
# ๋ณด์ ์ค์
SECURE_HSTS_SECONDS = 31536000 # 1๋
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
# ์บ์ ์ค์
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': f"redis://{os.environ.get('REDIS_HOST', 'localhost')}:6379/1",
}
}
# ์ ์ ํ์ผ ์ฒ๋ฆฌ
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
# ๋ฏธ๋์ด ํ์ผ ์ฒ๋ฆฌ
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME')
AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com"
# Dockerfile
FROM python:3.9-slim
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# ๋ฐฐํฌ ์ ์คํฌ๋ฆฝํธ ์คํ
RUN python manage.py collectstatic --noinput
RUN python manage.py compress --force
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]
# docker-compose.yml
version: '3.8'
services:
web:
build: .
restart: always
env_file:
- .env
volumes:
- static_data:/app/staticfiles
depends_on:
- db
- redis
networks:
- app_network
db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- .env
networks:
- app_network
redis:
image: redis:6-alpine
volumes:
- redis_data:/data
networks:
- app_network
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/ssl:/etc/nginx/ssl
- static_data:/var/www/static
depends_on:
- web
networks:
- app_network
celery:
build: .
command: celery -A myproject worker -l INFO
env_file:
- .env
depends_on:
- db
- redis
networks:
- app_network
volumes:
postgres_data:
redis_data:
static_data:
networks:
app_network:
โ
ํน์ง:
- ๋จ์ ํ ์คํธ ์์ฑ
- ํตํฉ ํ ์คํธ ๊ตฌํ
- ํ๋ก๋์ ์ค์ ๋ถ๋ฆฌ
- Docker ์ปจํ ์ด๋ํ
- ์๋ํ๋ ๋ฐฐํฌ ํ์ดํ๋ผ์ธ
โ
๋ชจ๋ฒ ์ฌ๋ก:
-
ORM ์ต์ ํ:
select_related
์prefetch_related
๋ฅผ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ ์ ์ค์ด๊ธฐ - ์บ์ฑ ์ ๋ต: ์ ์ ํ ๋ ๋ฒจ์์ ์บ์ ์ ์ฉ (๋ทฐ, ํ ํ๋ฆฟ, DB ์ฟผ๋ฆฌ)
- ๋ณด์ ์ค์ : HTTPS ๊ฐ์ , CSRF ๋ณดํธ, XSS ๋ฐฉ์ง ์ค์ ์ ์ฉ
- ํ ์คํธ ์์ฑ: ๋ชจ๋ธ, ๋ทฐ, ํผ, API์ ๋ํ ํ ์คํธ ์ผ์ด์ค ๊ตฌํ
- ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง: Django Debug Toolbar, django-silk ๋ฑ์ ๋๊ตฌ ํ์ฉ
- ๋ฐฐํฌ ์ ๋ต: ํ๊ฒฝ๋ณ ์ค์ ๋ถ๋ฆฌ, CI/CD ํ์ดํ๋ผ์ธ ๊ตฌ์ถ
- ๋ก๊น ์ค์ : ๊ตฌ์กฐํ๋ ๋ก๊ทธ ํ์๊ณผ ์ ์ ํ ๋ก๊ทธ ๋ ๋ฒจ ์ฌ์ฉ
- ๋ฌธ์ํ: ์ฝ๋ ์ฃผ์๊ณผ API ๋ฌธ์ ์์ฑ
- ๋ฏธ๋ค์จ์ด ์ต์ ํ: ๋ถํ์ํ ๋ฏธ๋ค์จ์ด ์ ๊ฑฐ, ์ปค์คํ ๋ฏธ๋ค์จ์ด ์์ฑ
- ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ: ์ค์ ๊ฐ์ ํ๊ฒฝ ๋ณ์๋ก ๋ถ๋ฆฌํ์ฌ ๋ณด์ ๊ฐํ
- ๋ง์ด๊ทธ๋ ์ด์ ๊ด๋ฆฌ: ๋ง์ด๊ทธ๋ ์ด์ ํ์ผ ์ฃผ๊ธฐ์ ์ ๋ฆฌ, ์ถฉ๋ ๋ฐฉ์ง
- ์ ์ ํ์ผ ์ต์ ํ: ๋ณํฉ, ์์ถ, CDN ํ์ฉ์ผ๋ก ์ฑ๋ฅ ํฅ์
- ๋น๋๊ธฐ ์์ ์ ๋ต: ์ฌ์ฉ์ ์๋ต์ ์ํฅ์ ์ฃผ๋ ์์ ์ ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ์ฒ๋ฆฌ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ๋ฑ์ฑ: ์์ฃผ ์กฐํํ๋ ํ๋์ ์ธ๋ฑ์ค ์ค์
- ํผ ๊ฒ์ฆ ๊ฐํ: ์๋ฒ ์ธก ์ ํจ์ฑ ๊ฒ์ฌ์ ํด๋ผ์ด์ธํธ ์ธก ๊ฒ์ฆ ๋ณํ