MITupdated 2mo ago
ã¹ã±ãŒã©ãã«ã§ä¿å®å¯èœãªã¢ããªã±ãŒã·ã§ã³ã®ããã®æ¬çªã°ã¬ãŒãã®Djangoã¢ãŒããã¯ãã£ãã¿ãŒã³ã
What can you do with Django Patterns?
name: django-patterns description: Django architecture patterns, REST API design with DRF, ORM best practices, caching, signals, middleware, and production-grade Django apps.
Django éçºãã¿ãŒã³
ã¹ã±ãŒã©ãã«ã§ä¿å®å¯èœãªã¢ããªã±ãŒã·ã§ã³ã®ããã®æ¬çªã°ã¬ãŒãã®Djangoã¢ãŒããã¯ãã£ãã¿ãŒã³ã
ãã€æå¹åããã
- DjangoãŠã§ãã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ãããšã
- Django REST Framework APIãèšèšãããšã
- Django ORMãšã¢ãã«ãæ±ããšã
- Djangoãããžã§ã¯ãæ§é ãèšå®ãããšã
- ãã£ãã·ã³ã°ãã·ã°ãã«ãããã«ãŠã§ã¢ãå®è£ ãããšã
ãããžã§ã¯ãæ§é
æšå¥šã¬ã€ã¢ãŠã
myproject/
âââ config/
â âââ __init__.py
â âââ settings/
â â âââ __init__.py
â â âââ base.py # åºæ¬èšå®
â â âââ development.py # éçºèšå®
â â âââ production.py # æ¬çªèšå®
â â âââ test.py # ãã¹ãèšå®
â âââ urls.py
â âââ wsgi.py
â âââ asgi.py
âââ manage.py
âââ apps/
âââ __init__.py
âââ users/
â âââ __init__.py
â âââ models.py
â âââ views.py
â âââ serializers.py
â âââ urls.py
â âââ permissions.py
â âââ filters.py
â âââ services.py
â âââ tests/
âââ products/
âââ ...
åå²èšå®ãã¿ãŒã³
# config/settings/base.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = env('DJANGO_SECRET_KEY')
DEBUG = False
ALLOWED_HOSTS = []
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'corsheaders',
# Local apps
'apps.users',
'apps.products',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'config.urls'
WSGI_APPLICATION = 'config.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('DB_NAME'),
'USER': env('DB_USER'),
'PASSWORD': env('DB_PASSWORD'),
'HOST': env('DB_HOST'),
'PORT': env('DB_PORT', default='5432'),
}
}
# config/settings/development.py
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
DATABASES['default']['NAME'] = 'myproject_dev'
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# config/settings/production.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# ãã®ã³ã°
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'WARNING',
'class': 'logging.FileHandler',
'filename': '/var/log/django/django.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'WARNING',
'propagate': True,
},
},
}
ã¢ãã«èšèšãã¿ãŒã³
ã¢ãã«ã®ãã¹ããã©ã¯ãã£ã¹
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.core.validators import MinValueValidator, MaxValueValidator
class User(AbstractUser):
"""AbstractUserãæ¡åŒµããã«ã¹ã¿ã ãŠãŒã¶ãŒã¢ãã«ã"""
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20, blank=True)
birth_date = models.DateField(null=True, blank=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
class Meta:
db_table = 'users'
verbose_name = 'user'
verbose_name_plural = 'users'
ordering = ['-date_joined']
def __str__(self):
return self.email
def get_full_name(self):
return f"{self.first_name} {self.last_name}".strip()
class Product(models.Model):
"""é©åãªãã£ãŒã«ãèšå®ãæã€Productã¢ãã«ã"""
name = models.CharField(max_length=200)
slug = models.SlugField(unique=True, max_length=250)
description = models.TextField(blank=True)
price = models.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(0)]
)
stock = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
category = models.ForeignKey(
'Category',
on_delete=models.CASCADE,
related_name='products'
)
tags = models.ManyToManyField('Tag', blank=True, related_name='products')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'products'
ordering = ['-created_at']
indexes = [
models.Index(fields=['slug']),
models.Index(fields=['-created_at']),
models.Index(fields=['category', 'is_active']),
]
constraints = [
models.CheckConstraint(
check=models.Q(price__gte=0),
name='price_non_negative'
)
]
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
QuerySetã®ãã¹ããã©ã¯ãã£ã¹
from django.db import models
class ProductQuerySet(models.QuerySet):
"""Productã¢ãã«ã®ã«ã¹ã¿ã QuerySetã"""
def active(self):
"""ã¢ã¯ãã£ããªè£œåã®ã¿ãè¿ãã"""
return self.filter(is_active=True)
def with_category(self):
"""N+1ã¯ãšãªãé¿ããããã«é¢é£ã«ããŽãªãéžæã"""
return self.select_related('category')
def with_tags(self):
"""å€å¯Ÿå€ãªã¬ãŒã·ã§ã³ã·ããã®ããã«ã¿ã°ãããªãã§ããã"""
return self.prefetch_related('tags')
def in_stock(self):
"""åšåº«ã0ãã倧ãã補åãè¿ãã"""
return self.filter(stock__gt=0)
def search(self, query):
"""ååãŸãã¯èª¬æã§è£œåãæ€çŽ¢ã"""
return self.filter(
models.Q(name__icontains=query) |
models.Q(description__icontains=query)
)
class Product(models.Model):
# ... ãã£ãŒã«ã ...
objects = ProductQuerySet.as_manager() # ã«ã¹ã¿ã QuerySetã䜿çš
# 䜿çšäŸ
Product.objects.active().with_category().in_stock()
ãããŒãžã£ãŒã¡ãœãã
class ProductManager(models.Manager):
"""è€éãªã¯ãšãªçšã®ã«ã¹ã¿ã ãããŒãžã£ãŒã"""
def get_or_none(self, **kwargs):
"""DoesNotExistã®ä»£ããã«ãªããžã§ã¯ããŸãã¯Noneãè¿ãã"""
try:
return self.get(**kwargs)
except self.model.DoesNotExist:
return None
def create_with_tags(self, name, price, tag_names):
"""é¢é£ã¿ã°ãæã€è£œåãäœæã"""
product = self.create(name=name, price=price)
tags = [Tag.objects.get_or_create(name=name)[0] for name in tag_names]
product.tags.set(tags)
return product
def bulk_update_stock(self, product_ids, quantity):
"""è€æ°ã®è£œåã®åšåº«ãäžæ¬æŽæ°ã"""
return self.filter(id__in=product_ids).update(stock=quantity)
# ã¢ãã«å
class Product(models.Model):
# ... ãã£ãŒã«ã ...
custom = ProductManager()
Django REST Frameworkãã¿ãŒã³
ã·ãªã¢ã©ã€ã¶ãŒãã¿ãŒã³
from rest_framework import serializers
from django.contrib.auth.password_validation import validate_password
from .models import Product, User
class ProductSerializer(serializers.ModelSerializer):
"""Productã¢ãã«ã®ã·ãªã¢ã©ã€ã¶ãŒã"""
category_name = serializers.CharField(source='category.name', read_only=True)
average_rating = serializers.FloatField(read_only=True)
discount_price = serializers.SerializerMethodField()
class Meta:
model = Product
fields = [
'id', 'name', 'slug', 'description', 'price',
'discount_price', 'stock', 'category_name',
'average_rating', 'created_at'
]
read_only_fields = ['id', 'slug', 'created_at']
def get_discount_price(self, obj):
"""該åœããå Žåã¯å²åŒäŸ¡æ Œãèšç®ã"""
if hasattr(obj, 'discount') and obj.discount:
return obj.price * (1 - obj.discount.percent / 100)
return obj.price
def validate_price(self, value):
"""äŸ¡æ Œãéè² ã§ããããšã確èªã"""
if value < 0:
raise serializers.ValidationError("Price cannot be negative.")
return value
class ProductCreateSerializer(serializers.ModelSerializer):
"""補åäœæçšã®ã·ãªã¢ã©ã€ã¶ãŒã"""
class Meta:
model = Product
fields = ['name', 'description', 'price', 'stock', 'category']
def validate(self, data):
"""è€æ°ãã£ãŒã«ãã®ã«ã¹ã¿ã æ€èšŒã"""
if data['price'] > 10000 and data['stock'] > 100:
raise serializers.ValidationError(
"Cannot have high-value products with large stock."
)
return data
class UserRegistrationSerializer(serializers.ModelSerializer):
"""ãŠãŒã¶ãŒç»é²çšã®ã·ãªã¢ã©ã€ã¶ãŒã"""
password = serializers.CharField(
write_only=True,
required=True,
validators=[validate_password],
style={'input_type': 'password'}
)
password_confirm = serializers.CharField(write_only=True, style={'input_type': 'password'})
class Meta:
model = User
fields = ['email', 'username', 'password', 'password_confirm']
def validate(self, data):
"""ãã¹ã¯ãŒããäžèŽããããšãæ€èšŒã"""
if data['password'] != data['password_confirm']:
raise serializers.ValidationError({
"password_confirm": "Password fields didn't match."
})
return data
def create(self, validated_data):
"""ããã·ã¥åããããã¹ã¯ãŒãã§ãŠãŒã¶ãŒãäœæã"""
validated_data.pop('password_confirm')
password = validated_data.pop('password')
user = User.objects.create(**validated_data)
user.set_password(password)
user.save()
return user
ViewSetãã¿ãŒã³
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from django_filters.rest_framework import DjangoFilterBackend
from .models import Product
from .serializers import ProductSerializer, ProductCreateSerializer
from .permissions import IsOwnerOrReadOnly
from .filters import ProductFilter
from .services import ProductService
class ProductViewSet(viewsets.ModelViewSet):
"""Productã¢ãã«çšã®ViewSetã"""
queryset = Product.objects.select_related('category').prefetch_related('tags')
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_class = ProductFilter
search_fields = ['name', 'description']
ordering_fields = ['price', 'created_at', 'name']
ordering = ['-created_at']
def get_serializer_class(self):
"""ã¢ã¯ã·ã§ã³ã«åºã¥ããŠé©åãªã·ãªã¢ã©ã€ã¶ãŒãè¿ãã"""
if self.action == 'create':
return ProductCreateSerializer
return ProductSerializer
def perform_create(self, serializer):
"""ãŠãŒã¶ãŒã³ã³ããã¹ãã§ä¿åã"""
serializer.save(created_by=self.request.user)
@action(detail=False, methods=['get'])
def featured(self, request):
"""泚ç®ã®è£œåãè¿ãã"""
featured = self.queryset.filter(is_featured=True)[:10]
serializer = self.get_serializer(featured, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def purchase(self, request, pk=None):
"""補åã賌å
¥ã"""
product = self.get_object()
service = ProductService()
result = service.purchase(product, request.user)
return Response(result, status=status.HTTP_201_CREATED)
@action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
def my_products(self, request):
"""çŸåšã®ãŠãŒã¶ãŒãäœæãã補åãè¿ãã"""
products = self.queryset.filter(created_by=request.user)
page = self.paginate_queryset(products)
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
ã«ã¹ã¿ã ã¢ã¯ã·ã§ã³
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def add_to_cart(request):
"""補åããŠãŒã¶ãŒã®ã«ãŒãã«è¿œå ã"""
product_id = request.data.get('product_id')
quantity = request.data.get('quantity', 1)
try:
product = Product.objects.get(id=product_id)
except Product.DoesNotExist:
return Response(
{'error': 'Product not found'},
status=status.HTTP_404_NOT_FOUND
)
cart, _ = Cart.objects.get_or_create(user=request.user)
CartItem.objects.create(
cart=cart,
product=product,
quantity=quantity
)
return Response({'message': 'Added to cart'}, status=status.HTTP_201_CREATED)
ãµãŒãã¹ã¬ã€ã€ãŒãã¿ãŒã³
# apps/orders/services.py
from typing import Optional
from django.db import transaction
from .models import Order, OrderItem
class OrderService:
"""泚æé¢é£ã®ããžãã¹ããžãã¯çšã®ãµãŒãã¹ã¬ã€ã€ãŒã"""
@staticmethod
@transaction.atomic
def create_order(user, cart: Cart) -> Order:
"""ã«ãŒãããæ³šæãäœæã"""
order = Order.objects.create(
user=user,
total_price=cart.total_price
)
for item in cart.items.all():
OrderItem.objects.create(
order=order,
product=item.product,
quantity=item.quantity,
price=item.product.price
)
# ã«ãŒããã¯ãªã¢
cart.items.all().delete()
return order
@staticmethod
def process_payment(order: Order, payment_data: dict) -> bool:
"""泚æã®æ¯æããåŠçã"""
# 決æžã²ãŒããŠã§ã€ãšã®çµ±å
payment = PaymentGateway.charge(
amount=order.total_price,
token=payment_data['token']
)
if payment.success:
order.status = Order.Status.PAID
order.save()
# 確èªã¡ãŒã«ãéä¿¡
OrderService.send_confirmation_email(order)
return True
return False
@staticmethod
def send_confirmation_email(order: Order):
"""泚æç¢ºèªã¡ãŒã«ãéä¿¡ã"""
# ã¡ãŒã«éä¿¡ããžãã¯
pass
ãã£ãã·ã³ã°æŠç¥
ãã¥ãŒã¬ãã«ã®ãã£ãã·ã³ã°
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
@method_decorator(cache_page(60 * 15), name='dispatch') # 15å
class ProductListView(generic.ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
ãã³ãã¬ãŒããã©ã°ã¡ã³ãã®ãã£ãã·ã³ã°
{% load cache %}
{% cache 500 sidebar %}
... é«ã³ã¹ããªãµã€ãããŒã³ã³ãã³ã ...
{% endcache %}
äœã¬ãã«ãã£ãã·ã³ã°
from django.core.cache import cache
def get_featured_products():
"""ãã£ãã·ã³ã°ä»ãã§æ³šç®ã®è£œåãååŸã"""
cache_key = 'featured_products'
products = cache.get(cache_key)
if products is None:
products = list(Product.objects.filter(is_featured=True))
cache.set(cache_key, products, timeout=60 * 15) # 15å
return products
QuerySetã®ãã£ãã·ã³ã°
from django.core.cache import cache
def get_popular_categories():
cache_key = 'popular_categories'
categories = cache.get(cache_key)
if categories is None:
categories = list(Category.objects.annotate(
product_count=Count('products')
).filter(product_count__gt=10).order_by('-product_count')[:20])
cache.set(cache_key, categories, timeout=60 * 60) # 1æé
return categories
ã·ã°ãã«
ã·ã°ãã«ãã¿ãŒã³
# apps/users/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from .models import Profile
User = get_user_model()
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""ãŠãŒã¶ãŒãäœæããããšãã«ãããã¡ã€ã«ãäœæã"""
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""ãŠãŒã¶ãŒãä¿åããããšãã«ãããã¡ã€ã«ãä¿åã"""
instance.profile.save()
# apps/users/apps.py
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.users'
def ready(self):
"""ã¢ããªãæºåã§ãããã·ã°ãã«ãã€ã³ããŒãã"""
import apps.users.signals
ããã«ãŠã§ã¢
ã«ã¹ã¿ã ããã«ãŠã§ã¢
# middleware/active_user_middleware.py
import time
from django.utils.deprecation import MiddlewareMixin
class ActiveUserMiddleware(MiddlewareMixin):
"""ã¢ã¯ãã£ããŠãŒã¶ãŒã远跡ããããã«ãŠã§ã¢ã"""
def process_request(self, request):
"""åä¿¡ãªã¯ãšã¹ããåŠçã"""
if request.user.is_authenticated:
# æçµã¢ã¯ãã£ãæå»ãæŽæ°
request.user.last_active = timezone.now()
request.user.save(update_fields=['last_active'])
class RequestLoggingMiddleware(MiddlewareMixin):
"""ãªã¯ãšã¹ããã®ã³ã°çšã®ããã«ãŠã§ã¢ã"""
def process_request(self, request):
"""ãªã¯ãšã¹ãéå§æå»ããã°ã"""
request.start_time = time.time()
def process_response(self, request, response):
"""ãªã¯ãšã¹ãæéããã°ã"""
if hasattr(request, 'start_time'):
duration = time.time() - request.start_time
logger.info(f'{request.method} {request.path} - {response.status_code} - {duration:.3f}s')
return response
ããã©ãŒãã³ã¹æé©å
N+1ã¯ãšãªã®é²æ¢
# Bad - N+1ã¯ãšãª
products = Product.objects.all()
for product in products:
print(product.category.name) # å補åã«å¯ŸããŠåå¥ã®ã¯ãšãª
# Good - select_relatedã§åäžã¯ãšãª
products = Product.objects.select_related('category').all()
for product in products:
print(product.category.name)
# Good - å€å¯Ÿå€ã®ããã®prefetch
products = Product.objects.prefetch_related('tags').all()
for product in products:
for tag in product.tags.all():
print(tag.name)
ããŒã¿ããŒã¹ã€ã³ããã¯ã¹
class Product(models.Model):
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(unique=True)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [
models.Index(fields=['name']),
models.Index(fields=['-created_at']),
models.Index(fields=['category', 'created_at']),
]
äžæ¬æäœ
# äžæ¬äœæ
Product.objects.bulk_create([
Product(name=f'Product {i}', price=10.00)
for i in range(1000)
])
# äžæ¬æŽæ°
products = Product.objects.all()[:100]
for product in products:
product.is_active = True
Product.objects.bulk_update(products, ['is_active'])
# äžæ¬åé€
Product.objects.filter(stock=0).delete()
ã¯ã€ãã¯ãªãã¡ã¬ã³ã¹
| ãã¿ãŒã³ | 説æ |
|---|---|
| åå²èšå® | dev/prod/testèšå®ã®åé¢ |
| ã«ã¹ã¿ã QuerySet | åå©çšå¯èœãªã¯ãšãªã¡ãœãã |
| ãµãŒãã¹ã¬ã€ã€ãŒ | ããžãã¹ããžãã¯ã®åé¢ |
| ViewSet | REST APIãšã³ããã€ã³ã |
| ã·ãªã¢ã©ã€ã¶ãŒæ€èšŒ | ãªã¯ãšã¹ã/ã¬ã¹ãã³ã¹å€æ |
| select_related | å€éšããŒæé©å |
| prefetch_related | å€å¯Ÿå€æé©å |
| ãã£ãã·ã¥ãã¡ãŒã¹ã | é«ã³ã¹ãæäœã®ãã£ãã·ã³ã° |
| ã·ã°ãã« | ã€ãã³ãé§åã¢ã¯ã·ã§ã³ |
| ããã«ãŠã§ã¢ | ãªã¯ãšã¹ã/ã¬ã¹ãã³ã¹åŠç |
èŠããŠãããŠãã ãã: Djangoã¯å€ãã®ã·ã§ãŒãã«ãããæäŸããŸãããæ¬çªã¢ããªã±ãŒã·ã§ã³ã§ã¯ãæ§é ãšçµç¹ãç°¡æœãªã³ãŒããããéèŠã§ããä¿å®æ§ãéèŠããŠæ§ç¯ããŠãã ããã
Install
Add Django Patterns to your client. Pick the one you use.
npx skills add edp43273-glitch/181-skillsInstalls every skill in the repository, then prompts for which to keep.
/plugin marketplace add edp43273-glitch/181-skillsAdds the repository as a plugin marketplace; install individual plugins with `/plugin install`.
git clone https://github.com/edp43273-glitch/181-skills
cp -r docs/ja-JP/skills/django-patterns ~/.claude/skills/A skill is a plain directory. Copy it into `.claude/skills/` in a project or in your home directory.
Score
76 / 100
Good