Примеры Django

Примеры веб-фреймворка Django, включая модели, представления, шаблоны, Django REST Framework и современные паттерны Django

💻 Django Hello World python

🟢 simple

Настройка проекта Django и приложение Hello World, включая модели, представления и шаблоны

⏱️ 25 min 🏷️ django, python, web framework, mvc
Prerequisites: Python basics, HTML/CSS basics, MVC concepts
# Django Hello World Examples

# 1. Project Structure
"""
myproject/
├── myproject/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   ├── wsgi.py
│   └── asgi.py
├── hello/
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── views.py
│   ├── urls.py
│   └── templates/
│       └── hello/
│           ├── base.html
│           ├── index.html
│           └── greeting.html
├── manage.py
└── requirements.txt
"""

# 2. requirements.txt
"""
Django>=4.2.0
"""

# 3. Create Django Project and App
"""
# Commands to set up the project:
django-admin startproject myproject
cd myproject
python manage.py startapp hello
"""

# 4. Project Settings (myproject/settings.py)
import os
from pathlib import Path

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

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-your-secret-key-here'

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

ALLOWED_HOSTS = ['localhost', '127.0.0.1']

# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'hello',  # Our custom app
]

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',
]

ROOT_URLCONF = 'myproject.urls'

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',
            ],
        },
    },
]

WSGI_APPLICATION = 'myproject.wsgi.application'

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

# 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',
    },
]

# Internationalization
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True

# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static']

# Default primary key field type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# 5. Main URLs (myproject/urls.py)
from django.contrib import admin
from django.urls import path, include
from django.http import HttpResponse

def hello_world(request):
    return HttpResponse("Hello, World! This is the main Django project.")

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', hello_world, name='hello_world'),
    path('hello/', include('hello.urls')),
]

# 6. App Configuration (hello/apps.py)
from django.apps import AppConfig

class HelloConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'hello'

# 7. Models (hello/models.py)
from django.db import models
from django.contrib.auth.models import User

class Greeting(models.Model):
    name = models.CharField(max_length=100)
    message = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_active = models.BooleanField(default=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)

    def __str__(self):
        return f"{self.name} - {self.message[:50]}"

    class Meta:
        ordering = ['-created_at']

class Visitor(models.Model):
    ip_address = models.GenericIPAddressField()
    user_agent = models.TextField(blank=True)
    visit_count = models.PositiveIntegerField(default=1)
    first_visit = models.DateTimeField(auto_now_add=True)
    last_visit = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"Visitor from {self.ip_address} ({self.visit_count} visits)"

# 8. Views (hello/views.py)
from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponse, JsonResponse
from django.views import View
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils import timezone
from django.contrib import messages
from .models import Greeting, Visitor
from .forms import GreetingForm

# Function-based views
def hello_world(request):
    """Simple Hello World view"""
    return HttpResponse("Hello, World! Welcome to Django!")

def hello_name(request, name):
    """Hello World with name parameter"""
    context = {
        'name': name,
        'current_time': timezone.now(),
    }
    return render(request, 'hello/greeting.html', context)

def greeting_form(request):
    """Greeting form view"""
    if request.method == 'POST':
        name = request.POST.get('name', 'World')
        message = request.POST.get('message', 'Hello!')
        Greeting.objects.create(name=name, message=message)
        messages.success(request, f'Greeting from {name} created successfully!')
        return redirect('hello:greeting_list')

    return render(request, 'hello/greeting_form.html')

# Class-based views
class HelloWorldView(View):
    """Class-based Hello World view"""
    def get(self, request):
        return HttpResponse("Hello, World! This is a class-based view.")

class PersonalizedHelloView(View):
    """Class-based view with name parameter"""
    def get(self, request, name):
        context = {'name': name}
        return render(request, 'hello/personalized_hello.html', context)

class GreetingListView(ListView):
    """List all greetings"""
    model = Greeting
    template_name = 'hello/greeting_list.html'
    context_object_name = 'greetings'
    paginate_by = 10

class GreetingDetailView(DetailView):
    """Detail view for a single greeting"""
    model = Greeting
    template_name = 'hello/greeting_detail.html'
    context_object_name = 'greeting'

class GreetingCreateView(LoginRequiredMixin, CreateView):
    """Create a new greeting"""
    model = Greeting
    form_class = GreetingForm
    template_name = 'hello/greeting_form.html'
    success_url = reverse_lazy('hello:greeting_list')

    def form_valid(self, form):
        form.instance.user = self.request.user
        messages.success(self.request, 'Greeting created successfully!')
        return super().form_valid(form)

class GreetingUpdateView(LoginRequiredMixin, UpdateView):
    """Update an existing greeting"""
    model = Greeting
    form_class = GreetingForm
    template_name = 'hello/greeting_form.html'
    success_url = reverse_lazy('hello:greeting_list')

    def form_valid(self, form):
        messages.success(self.request, 'Greeting updated successfully!')
        return super().form_valid(form)

class GreetingDeleteView(LoginRequiredMixin, DeleteView):
    """Delete a greeting"""
    model = Greeting
    template_name = 'hello/greeting_confirm_delete.html'
    success_url = reverse_lazy('hello:greeting_list')

    def delete(self, request, *args, **kwargs):
        messages.success(request, 'Greeting deleted successfully!')
        return super().delete(request, *args, **kwargs)

# API Views
def api_greetings(request):
    """API endpoint to get all greetings"""
    greetings = Greeting.objects.filter(is_active=True)
    data = [
        {
            'id': g.id,
            'name': g.name,
            'message': g.message,
            'created_at': g.created_at.isoformat(),
            'user': g.user.username if g.user else None
        }
        for g in greetings
    ]
    return JsonResponse({'greetings': data})

def api_hello(request):
    """API Hello World endpoint"""
    data = {
        'message': 'Hello, World!',
        'framework': 'Django',
        'version': '4.2+',
        'timestamp': timezone.now().isoformat()
    }
    return JsonResponse(data)

# 9. Forms (hello/forms.py)
from django import forms
from .models import Greeting

class GreetingForm(forms.ModelForm):
    class Meta:
        model = Greeting
        fields = ['name', 'message', 'is_active']
        widgets = {
            'name': forms.TextInput(attrs={
                'class': 'form-control',
                'placeholder': 'Enter your name'
            }),
            'message': forms.Textarea(attrs={
                'class': 'form-control',
                'rows': 4,
                'placeholder': 'Enter your message'
            }),
            'is_active': forms.CheckboxInput(attrs={
                'class': 'form-check-input'
            })
        }

    def clean_name(self):
        name = self.cleaned_data.get('name')
        if len(name.strip()) < 2:
            raise forms.ValidationError('Name must be at least 2 characters long.')
        return name.strip()

    def clean_message(self):
        message = self.cleaned_data.get('message')
        if len(message.strip()) < 5:
            raise forms.ValidationError('Message must be at least 5 characters long.')
        return message.strip()

# 10. App URLs (hello/urls.py)
from django.urls import path
from . import views

app_name = 'hello'

urlpatterns = [
    # Function-based views
    path('world/', views.hello_world, name='hello_world'),
    path('world/<str:name>/', views.hello_name, name='hello_name'),
    path('form/', views.greeting_form, name='greeting_form'),

    # Class-based views
    path('class/', views.HelloWorldView.as_view(), name='hello_class'),
    path('class/<str:name>/', views.PersonalizedHelloView.as_view(), name='personalized_hello'),

    # CRUD views
    path('greetings/', views.GreetingListView.as_view(), name='greeting_list'),
    path('greetings/<int:pk>/', views.GreetingDetailView.as_view(), name='greeting_detail'),
    path('greetings/create/', views.GreetingCreateView.as_view(), name='greeting_create'),
    path('greetings/<int:pk>/edit/', views.GreetingUpdateView.as_view(), name='greeting_update'),
    path('greetings/<int:pk>/delete/', views.GreetingDeleteView.as_view(), name='greeting_delete'),

    # API endpoints
    path('api/hello/', views.api_hello, name='api_hello'),
    path('api/greetings/', views.api_greetings, name='api_greetings'),
]

# 11. Base Template (hello/templates/hello/base.html)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Django Hello World{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            background-color: #f8f9fa;
        }
        .navbar-brand {
            font-weight: bold;
        }
        .footer {
            background-color: #343a40;
            color: white;
            padding: 20px 0;
            margin-top: 50px;
        }
    </style>
    {% block extra_css %}{% endblock %}
</head>
<body>
    <!-- Navigation -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{% url 'hello:hello_world' %}">Django Demo</a>

            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>

            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav me-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'hello:hello_world' %}">Hello World</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'hello:greeting_list' %}">Greetings</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'hello:greeting_create' %}">Add Greeting</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'hello:api_hello' %}">API</a>
                    </li>
                </ul>

                <ul class="navbar-nav">
                    {% if user.is_authenticated %}
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'admin:index' %}">Admin</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="#">Welcome, {{ user.username }}</a>
                        </li>
                    {% else %}
                        <li class="nav-item">
                            <a class="nav-link" href="{% url 'admin:login' %}">Login</a>
                        </li>
                    {% endif %}
                </ul>
            </div>
        </div>
    </nav>

    <!-- Messages -->
    {% if messages %}
        <div class="container mt-3">
            {% for message in messages %}
                <div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
                    {{ message }}
                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                </div>
            {% endfor %}
        </div>
    {% endif %}

    <!-- Main Content -->
    <main class="container my-4">
        {% block content %}{% endblock %}
    </main>

    <!-- Footer -->
    <footer class="footer">
        <div class="container text-center">
            <p>&copy; 2024 Django Hello World Demo. Built with Django {{ VERSION }}.</p>
        </div>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
    {% block extra_js %}{% endblock %}
</body>
</html>

# 12. Index Template (hello/templates/hello/index.html)
{% extends 'hello/base.html' %}

{% block title %}Django Hello World{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-8 mx-auto">
        <div class="card text-center">
            <div class="card-header bg-primary text-white">
                <h1>🌍 Hello, World!</h1>
            </div>
            <div class="card-body">
                <p class="lead">Welcome to your Django application!</p>
                <p>This is a simple demonstration of Django's capabilities:</p>

                <div class="row mt-4">
                    <div class="col-md-4">
                        <div class="card h-100">
                            <div class="card-body">
                                <h5 class="card-title">⚡ Fast</h5>
                                <p class="card-text">Django helps you build web applications quickly and efficiently.</p>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="card h-100">
                            <div class="card-body">
                                <h5 class="card-title">🔒 Secure</h5>
                                <p class="card-text">Built-in security features protect against common vulnerabilities.</p>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="card h-100">
                            <div class="card-body">
                                <h5 class="card-title">📈 Scalable</h5>
                                <p class="card-text">Django scales to handle millions of requests per day.</p>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="mt-4">
                    <a href="{% url 'hello:greeting_list' %}" class="btn btn-primary btn-lg me-2">View Greetings</a>
                    <a href="{% url 'hello:greeting_create' %}" class="btn btn-success btn-lg">Add Greeting</a>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

# 13. Greeting Template (hello/templates/hello/greeting.html)
{% extends 'hello/base.html' %}

{% block title %}Hello, {{ name }}!{% endblock %}

{% block content %}
<div class="row">
    <div class="col-md-6 mx-auto">
        <div class="card">
            <div class="card-header bg-success text-white">
                <h2>👋 Hello, {{ name }}!</h2>
            </div>
            <div class="card-body">
                <p class="lead">Welcome to the Django application!</p>
                <p><strong>Current Time:</strong> {{ current_time|date:"F j, Y, g:i a" }}</p>

                <div class="mt-3">
                    <a href="{% url 'hello:hello_world' %}" class="btn btn-secondary">Back to Home</a>
                    <a href="{% url 'hello:greeting_create' %}" class="btn btn-primary">Create a Greeting</a>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

# 14. Run the Application
"""
# Commands to run the application:
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

# Visit these URLs:
# http://127.0.0.1:8000/ - Main Hello World
# http://127.0.0.1:8000/hello/world/ - App Hello World
# http://127.0.0.1:8000/hello/world/John/ - Personalized greeting
# http://127.0.0.1:8000/admin/ - Django admin
# http://127.0.0.1:8000/hello/greetings/ - Greeting list
# http://127.0.0.1:8000/hello/api/hello/ - API endpoint
"""

💻 Django REST Framework python

🟡 intermediate ⭐⭐⭐⭐

Расширенные примеры Django REST API, включая сериализаторы, наборы представлений, аутентификацию и паттерны проектирования API

⏱️ 50 min 🏷️ django, rest framework, api, serializers, viewsets
Prerequisites: Django basics, REST API concepts, Authentication
# Django REST Framework Examples

# 1. requirements.txt
"""
Django>=4.2.0
djangorestframework>=3.14.0
django-cors-headers>=4.0.0
django-filter>=23.0
Pillow>=10.0.0
python-decouple>=3.8
"""

# 2. Project Settings (settings.py additions)
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',
    'django_filters',
    'corsheaders',
    'api',  # Our API app
]

# REST Framework Configuration
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
    'DEFAULT_FILTER_BACKENDS': [
        'django_filters.rest_framework.DjangoFilterBackend',
        'rest_framework.filters.SearchFilter',
        'rest_framework.filters.OrderingFilter',
    ],
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}

# CORS Configuration
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "http://127.0.0.1:3000",
]

# 3. Models (api/models.py)
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils.text import slugify
import uuid

class Category(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=100, unique=True, blank=True)
    description = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def __str__(self):
        return self.name

class Product(models.Model):
    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('published', 'Published'),
        ('archived', 'Archived'),
    ]

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True, blank=True)
    description = models.TextField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    image = models.ImageField(upload_to='products/', blank=True, null=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
    is_featured = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='products')

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('api:product-detail', kwargs={'slug': self.slug})

class Review(models.Model):
    RATING_CHOICES = [(i, str(i)) for i in range(1, 6)]

    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='reviews')
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    rating = models.IntegerField(choices=RATING_CHOICES)
    title = models.CharField(max_length=100)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    is_approved = models.BooleanField(default=False)

    class Meta:
        unique_together = ['product', 'user']

    def __str__(self):
        return f"{self.product.name} - {self.user.username} ({self.rating}/5)"

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)
    products = models.ManyToManyField(Product, related_name='tags', blank=True)

    def __str__(self):
        return self.name

# 4. Serializers (api/serializers.py)
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Product, Category, Review, Tag

class CategorySerializer(serializers.ModelSerializer):
    products_count = serializers.IntegerField(source='products.count', read_only=True)

    class Meta:
        model = Category
        fields = ['id', 'name', 'slug', 'description', 'products_count', 'created_at']

class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ['id', 'name']

class ReviewSerializer(serializers.ModelSerializer):
    user = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = Review
        fields = ['id', 'user', 'rating', 'title', 'content', 'created_at', 'is_approved']
        read_only_fields = ['user', 'created_at', 'is_approved']

class ProductListSerializer(serializers.ModelSerializer):
    category_name = serializers.CharField(source='category.name', read_only=True)
    average_rating = serializers.SerializerMethodField()
    reviews_count = serializers.SerializerMethodField()

    class Meta:
        model = Product
        fields = [
            'id', 'name', 'slug', 'description', 'price', 'image',
            'category_name', 'status', 'is_featured', 'created_at',
            'average_rating', 'reviews_count'
        ]

    def get_average_rating(self, obj):
        approved_reviews = obj.reviews.filter(is_approved=True)
        if approved_reviews.exists():
            return round(sum(review.rating for review in approved_reviews) / approved_reviews.count(), 1)
        return 0

    def get_reviews_count(self, obj):
        return obj.reviews.filter(is_approved=True).count()

class ProductDetailSerializer(serializers.ModelSerializer):
    category = CategorySerializer(read_only=True)
    reviews = ReviewSerializer(many=True, read_only=True)
    tags = TagSerializer(many=True, read_only=True)
    created_by = serializers.StringRelatedField(read_only=True)
    average_rating = serializers.SerializerMethodField()

    class Meta:
        model = Product
        fields = [
            'id', 'name', 'slug', 'description', 'price', 'image',
            'category', 'status', 'is_featured', 'created_at', 'updated_at',
            'created_by', 'reviews', 'tags', 'average_rating'
        ]

    def get_average_rating(self, obj):
        approved_reviews = obj.reviews.filter(is_approved=True)
        if approved_reviews.exists():
            return round(sum(review.rating for review in approved_reviews) / approved_reviews.count(), 1)
        return 0

class ProductCreateUpdateSerializer(serializers.ModelSerializer):
    tags = serializers.ListField(
        child=serializers.CharField(max_length=50),
        write_only=True,
        required=False
    )

    class Meta:
        model = Product
        fields = [
            'name', 'slug', 'description', 'price', 'image',
            'category', 'status', 'is_featured', 'tags'
        ]

    def create(self, validated_data):
        tags_data = validated_data.pop('tags', [])
        product = Product.objects.create(**validated_data)

        for tag_name in tags_data:
            tag, created = Tag.objects.get_or_create(name=tag_name.lower())
            product.tags.add(tag)

        return product

    def update(self, instance, validated_data):
        tags_data = validated_data.pop('tags', None)

        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()

        if tags_data is not None:
            instance.tags.clear()
            for tag_name in tags_data:
                tag, created = Tag.objects.get_or_create(name=tag_name.lower())
                instance.tags.add(tag)

        return instance

class UserSerializer(serializers.ModelSerializer):
    products_count = serializers.IntegerField(source='products.count', read_only=True)

    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'first_name', 'last_name', 'products_count']
        read_only_fields = ['id']

# 5. Views (api/views.py)
from rest_framework import viewsets, status, permissions, filters
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from django_filters.rest_framework import DjangoFilterBackend
from django.shortcuts import get_object_or_404
from django.db.models import Avg, Count
from django.contrib.auth.models import User
from .models import Product, Category, Review, Tag
from .serializers import (
    ProductListSerializer, ProductDetailSerializer, ProductCreateUpdateSerializer,
    CategorySerializer, ReviewSerializer, UserSerializer, TagSerializer
)

class StandardResultsSetPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 100

class ProductViewSet(viewsets.ModelViewSet):
    """
    ViewSet for viewing and editing products.
    Provides list, create, retrieve, update, destroy actions.
    """
    queryset = Product.objects.all()
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['category', 'status', 'is_featured']
    search_fields = ['name', 'description']
    ordering_fields = ['name', 'price', 'created_at']
    ordering = ['-created_at']
    pagination_class = StandardResultsSetPagination

    def get_serializer_class(self):
        if self.action == 'list':
            return ProductListSerializer
        elif self.action in ['create', 'update', 'partial_update']:
            return ProductCreateUpdateSerializer
        return ProductDetailSerializer

    def get_permissions(self):
        if self.action in ['create', 'update', 'partial_update', 'destroy']:
            permission_classes = [permissions.IsAuthenticated]
        else:
            permission_classes = [permissions.IsAuthenticatedOrReadOnly]
        return [permission() for permission in permission_classes]

    def perform_create(self, serializer):
        serializer.save(created_by=self.request.user)

    @action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
    def add_review(self, request, pk=None):
        product = self.get_object()

        # Check if user already reviewed
        if Review.objects.filter(product=product, user=request.user).exists():
            return Response(
                {'error': 'You have already reviewed this product.'},
                status=status.HTTP_400_BAD_REQUEST
            )

        serializer = ReviewSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(product=product, user=request.user)
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=True, methods=['get'])
    def reviews(self, request, pk=None):
        product = self.get_object()
        reviews = product.reviews.filter(is_approved=True)
        serializer = ReviewSerializer(reviews, many=True)
        return Response(serializer.data)

    @action(detail=False, methods=['get'])
    def featured(self, request):
        """Get all featured products"""
        featured_products = Product.objects.filter(is_featured=True, status='published')
        page = self.paginate_queryset(featured_products)
        serializer = ProductListSerializer(page, many=True)
        return self.get_paginated_response(serializer.data)

    @action(detail=False, methods=['get'])
    def by_category(self, request):
        """Filter products by category slug"""
        category_slug = request.query_params.get('slug')
        if not category_slug:
            return Response(
                {'error': 'Category slug parameter is required.'},
                status=status.HTTP_400_BAD_REQUEST
            )

        category = get_object_or_404(Category, slug=category_slug)
        products = Product.objects.filter(category=category, status='published')
        page = self.paginate_queryset(products)
        serializer = ProductListSerializer(page, many=True)
        return self.get_paginated_response(serializer.data)

class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
    """
    ViewSet for viewing categories.
    """
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    lookup_field = 'slug'

    @action(detail=True, methods=['get'])
    def products(self, request, slug=None):
        """Get all products in this category"""
        category = self.get_object()
        products = Product.objects.filter(category=category, status='published')
        page = StandardResultsSetPagination()
        result_page = page.paginate_queryset(products)
        serializer = ProductListSerializer(result_page, many=True)
        return page.get_paginated_response(serializer.data)

class ReviewViewSet(viewsets.ModelViewSet):
    """
    ViewSet for managing reviews.
    """
    queryset = Review.objects.filter(is_approved=True)
    serializer_class = ReviewSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def get_permissions(self):
        if self.action in ['create']:
            permission_classes = [permissions.IsAuthenticated]
        else:
            permission_classes = [permissions.IsAuthenticatedOrReadOnly]
        return [permission() for permission in permission_classes]

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    ViewSet for viewing user information.
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]

    @action(detail=False, methods=['get'])
    def me(self, request):
        """Get current user information"""
        serializer = self.get_serializer(request.user)
        return Response(serializer.data)

    @action(detail=True, methods=['get'])
    def products(self, request, pk=None):
        """Get all products created by this user"""
        user = self.get_object()
        products = Product.objects.filter(created_by=user)
        page = StandardResultsSetPagination()
        result_page = page.paginate_queryset(products)
        serializer = ProductListSerializer(result_page, many=True)
        return page.get_paginated_response(serializer.data)

# API Views
@api_view(['GET'])
@permission_classes([permissions.AllowAny])
def api_stats(request):
    """Get API statistics"""
    stats = {
        'total_products': Product.objects.count(),
        'published_products': Product.objects.filter(status='published').count(),
        'total_categories': Category.objects.count(),
        'total_reviews': Review.objects.filter(is_approved=True).count(),
        'total_users': User.objects.count(),
    }

    # Add rating statistics
    avg_rating = Review.objects.filter(is_approved=True).aggregate(
        avg_rating=Avg('rating')
    )['avg_rating'] or 0

    stats['average_rating'] = round(avg_rating, 1)

    return Response(stats)

@api_view(['GET'])
@permission_classes([permissions.AllowAny])
def search_products(request):
    """Search products with advanced filtering"""
    query = request.GET.get('q', '')
    category = request.GET.get('category', '')
    min_price = request.GET.get('min_price', '')
    max_price = request.GET.get('max_price', '')
    rating = request.GET.get('rating', '')

    products = Product.objects.filter(status='published')

    if query:
        products = products.filter(
            models.Q(name__icontains=query) |
            models.Q(description__icontains=query)
        )

    if category:
        products = products.filter(category__slug=category)

    if min_price:
        products = products.filter(price__gte=min_price)

    if max_price:
        products = products.filter(price__lte=max_price)

    # Filter by rating (products with average rating >= specified rating)
    if rating:
        rating_int = int(rating)
        product_ids = []
        for product in products:
            avg_rating = product.reviews.filter(is_approved=True).aggregate(
                avg_rating=Avg('rating')
            )['avg_rating'] or 0
            if avg_rating >= rating_int:
                product_ids.append(product.id)
        products = Product.objects.filter(id__in=product_ids)

    page = StandardResultsSetPagination()
    result_page = page.paginate_queryset(products)
    serializer = ProductListSerializer(result_page, many=True)
    return page.get_paginated_response(serializer.data)

# 6. URLs (api/urls.py)
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from rest_framework.authtoken import views as drf_views
from . import api_views

router = DefaultRouter()
router.register(r'products', api_views.ProductViewSet, basename='product')
router.register(r'categories', api_views.CategoryViewSet, basename='category')
router.register(r'reviews', api_views.ReviewViewSet, basename='review')
router.register(r'users', api_views.UserViewSet, basename='user')

urlpatterns = [
    path('', include(router.urls)),
    path('auth/login/', drf_views.obtain_auth_token, name='api_token_auth'),
    path('stats/', api_views.api_stats, name='api_stats'),
    path('search/', api_views.search_products, name='search_products'),
]

# 7. Main URLs (project/urls.py additions)
from django.urls import path, include

urlpatterns = [
    # ... existing urls
    path('api/', include('api.urls')),
]

# 8. Admin (api/admin.py)
from django.contrib import admin
from django.utils.html import format_html
from .models import Product, Category, Review, Tag

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ['name', 'slug', 'created_at']
    prepopulated_fields = {'slug': ('name',)}
    search_fields = ['name']

class ReviewInline(admin.TabularInline):
    model = Review
    extra = 1
    readonly_fields = ['user', 'created_at']

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ['name', 'category', 'price', 'status', 'is_featured', 'created_by', 'thumbnail']
    list_filter = ['status', 'is_featured', 'category', 'created_at']
    search_fields = ['name', 'description']
    prepopulated_fields = {'slug': ('name',)}
    inlines = [ReviewInline]
    readonly_fields = ['created_at', 'updated_at']

    def thumbnail(self, obj):
        if obj.image:
            return format_html('<img src="{}" width="50" height="50" />', obj.image.url)
        return 'No image'
    thumbnail.short_description = 'Image'

    def save_model(self, request, obj, form, change):
        if not change:  # If creating a new object
            obj.created_by = request.user
        super().save_model(request, obj, form, change)

@admin.register(Review)
class ReviewAdmin(admin.ModelAdmin):
    list_display = ['product', 'user', 'rating', 'is_approved', 'created_at']
    list_filter = ['rating', 'is_approved', 'created_at']
    search_fields = ['product__name', 'user__username', 'title']
    actions = ['approve_reviews']

    def approve_reviews(self, request, queryset):
        queryset.update(is_approved=True)
    approve_reviews.short_description = 'Mark selected reviews as approved'

@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
    list_display = ['name']
    search_fields = ['name']

# 9. Commands to Run
"""
# Install dependencies:
pip install -r requirements.txt

# Setup database:
python manage.py makemigrations api
python manage.py migrate

# Create superuser:
python manage.py createsuperuser

# Create sample data:
python manage.py shell

# In shell:
from api.models import Category, Product, Tag
from django.contrib.auth.models import User

user = User.objects.get(username='admin')

# Create categories
electronics = Category.objects.create(name='Electronics', description='Electronic devices and gadgets')
books = Category.objects.create(name='Books', description='Books and literature')

# Create products
laptop = Product.objects.create(
    name='Laptop Pro',
    description='High-performance laptop for professionals',
    price=1299.99,
    category=electronics,
    status='published',
    created_by=user
)

# Create tags
tech_tag = Tag.objects.create(name='technology')
laptop.tags.add(tech_tag)

# Run development server:
python manage.py runserver

# API Endpoints:
# GET /api/products/ - List all products
# GET /api/products/<slug>/ - Get product detail
# POST /api/products/ - Create product (requires auth)
# PUT /api/products/<slug>/ - Update product (requires auth)
# DELETE /api/products/<slug>/ - Delete product (requires auth)
# GET /api/categories/ - List all categories
# GET /api/categories/<slug>/ - Get category details
# GET /api/products/featured/ - Get featured products
# GET /api/products/by_category/?slug=<slug> - Get products by category
# GET /api/stats/ - Get API statistics
# GET /api/search/?q=<query> - Search products
# POST /api/auth/login/ - Get auth token
# GET /api/users/me/ - Get current user (requires auth)
"""

💻 Расширенные паттерны Django python

🔴 complex ⭐⭐⭐⭐⭐

Расширенные паттерны Django, включая пользовательские менеджеры, сигналы, промежуточное ПО, фоновые задачи и оптимизацию производительности

⏱️ 65 min 🏷️ django, advanced, optimization, patterns, middleware
Prerequisites: Advanced Django, Database optimization, Python advanced concepts
# Django Advanced Patterns

# 1. Custom Model Managers
# models/managers.py
from django.db import models
from django.db.models import Q, Count, Avg
from django.utils import timezone

class ProductManager(models.Manager):
    """Custom manager for Product model with common query methods"""

    def get_queryset(self):
        return super().get_queryset().select_related('category', 'created_by')

    def active(self):
        """Get only active/published products"""
        return self.get_queryset().filter(status='published')

    def featured(self):
        """Get only featured products"""
        return self.active().filter(is_featured=True)

    def by_category(self, category_slug):
        """Get products by category slug"""
        return self.active().filter(category__slug=category_slug)

    def search(self, query):
        """Search products by name and description"""
        return self.active().filter(
            Q(name__icontains=query) | Q(description__icontains=query)
        )

    def with_avg_rating(self):
        """Annotate products with average rating"""
        return self.get_queryset().annotate(
            avg_rating=Avg('reviews__rating', filter=Q(reviews__is_approved=True))
        )

# 2. Custom Middleware
# middleware.py
import time
from django.utils.deprecation import MiddlewareMixin
from django.http import JsonResponse
from django.core.cache import cache

class RequestTimingMiddleware(MiddlewareMixin):
    """Middleware to measure request processing time"""

    def process_request(self, request):
        request.start_time = time.time()
        return None

    def process_response(self, request, response):
        if hasattr(request, 'start_time'):
            duration = time.time() - request.start_time
            response['X-Response-Time'] = f"{duration:.3f}s"
        return response

# 3. Custom Management Commands
# management/commands/generate_reports.py
from django.core.management.base import BaseCommand
from django.db.models import Count, Avg, Sum
from datetime import datetime, timedelta
import json

class Command(BaseCommand):
    help = 'Generate various business reports'

    def handle(self, *args, **options):
        reports = {
            'total_products': Product.objects.count(),
            'published_products': Product.objects.filter(status='published').count(),
            'generated_at': datetime.now().isoformat()
        }

        with open('reports.json', 'w') as f:
            json.dump(reports, f, indent=2)

# 4. Custom Template Tags and Filters
# templatetags/shop_tags.py
from django import template
from django.utils.safestring import mark_safe

register = template.Library()

@register.filter
def currency(value):
    """Format currency"""
    try:
        return f"${float(value):,.2f}"
    except (ValueError, TypeError):
        return "$0.00"

@register.filter
def percentage(value):
    """Convert decimal to percentage"""
    try:
        return f"{float(value) * 100:.1f}%"
    except (ValueError, TypeError):
        return "0%"

# 5. Custom Validators
# validators.py
from django.core.exceptions import ValidationError
import re

def validate_sku(value):
    """Validate SKU format"""
    pattern = r'^[A-Z0-9]{3,20}$'
    if not re.match(pattern, value):
        raise ValidationError('SKU must be 3-20 uppercase letters and numbers.')

# 6. Usage Examples
"""
# Test custom managers:
from myapp.models import Product

# Get featured products with average rating
featured_products = Product.objects.featured().with_avg_rating()

# Search products
search_results = Product.objects.search('laptop').active()

# Test custom filters in templates:
{{ product.price|currency }}
{{ product.discount_percentage|percentage }}
"""