📚 Tutorial Django

Ghid complet de instalare și configurare pentru Django

← Înapoi la Tutoriale

🚀 Tutorial Django - Feed JSON Mixbox

🎯 Acest tutorial te va ajuta să creezi un API REST în Django pentru generarea feed-ului JSON cu produse.

📋 Cerințe Preliminare

  • Python 3.8+
  • Django 4.x sau mai nou
  • Django REST Framework
  • Bază de date (PostgreSQL, MySQL, SQLite)
  • pip pentru managementul pachetelor
  • Django REST Framework
  • Bază de date (PostgreSQL, MySQL, SQLite)
  • pip pentru managementul pachetelor
  • 📄 Formatul feed-ului JSON

    Feed-ul generează un obiect JSON cu toate produsele indexate după SKU:

    {
      "SKU12345": {
        "sku": "SKU12345",
        "name": "Numele produsului",
        "qty": "10",
        "price": 150.50,
        "sale_price": 120.00,
        "last_update_date": "2025-08-08 10:30:00",
        "description": "Descrierea completă a produsului...",
        "weight": "2.5",
        "images": [
          {"src": "https://site-ul-vostru.com/wp-content/uploads/image1.jpg"},
          {"src": "https://site-ul-vostru.com/wp-content/uploads/image2.jpg"}
        ],
        "permalink": "https://site-ul-vostru.com/product/numele-produsului/",
        "categories_tree": [
          {
            "id": 25,
            "name": "Categoria Principală",
            "parent": 0,
            "image": "https://site-ul-vostru.com/category-image.jpg",
            "subcategories": [
              {
                "id": 30,
                "name": "Subcategoria",
                "parent": 25,
                "image": "https://site-ul-vostru.com/subcategory-image.jpg",
                "subcategories": []
              }
            ]
          }
        ],
        "breadcrumbs": [
          "Categoria Principală > Subcategoria"
        ],
        "brands": [
          {
            "name": "NUMELE BRANDULUI",
            "image": "https://site-ul-vostru.com/brand-logo.jpg"
          }
        ]
      }
    }
    

    📦 Pasul 1: Instalare Pachet

    Descarcă pachetul Django pentru feed JSON:

    După descărcare, extrage fișierele și navighează în director:

    cd django-product-feed
    pip install -r requirements.txt

    🔧 Pasul 2: Configurare Model

    Creează modelul pentru produse în products/models.py:

    # products/models.py
    from django.db import models
    from django.core.validators import MinValueValidator
    from django.utils import timezone
    
    
    class Product(models.Model):
        """Model pentru produse în feed"""
    
        name = models.CharField(
            max_length=255,
            verbose_name="Nume produs",
            help_text="Numele complet al produsului"
        )
    
        description = models.TextField(
            blank=True,
            verbose_name="Descriere",
            help_text="Descrierea detaliată a produsului"
        )
    
        price = models.DecimalField(
            max_digits=10,
            decimal_places=2,
            validators=[MinValueValidator(0)],
            verbose_name="Preț",
            help_text="Prețul produsului în lei"
        )
    
        sku = models.CharField(
            max_length=100,
            unique=True,
            verbose_name="SKU",
            help_text="Cod unic al produsului"
        )
    
        category = models.CharField(
            max_length=100,
            verbose_name="Categorie",
            help_text="Categoria produsului"
        )
    
        brand = models.CharField(
            max_length=100,
            blank=True,
            verbose_name="Brand",
            help_text="Brand-ul produsului"
        )
    
        subcategories = models.JSONField(
            default=list,
            blank=True,
            verbose_name="Subcategorii",
            help_text="Lista de subcategorii"
        )
    
        image_url = models.URLField(
            blank=True,
            verbose_name="URL Imagine",
            help_text="URL către imaginea produsului"
        )
    
        stock_quantity = models.PositiveIntegerField(
            default=0,
            verbose_name="Stoc",
            help_text="Cantitatea în stoc"
        )
    
        is_active = models.BooleanField(
            default=True,
            verbose_name="Activ",
            help_text="Dacă produsul este activ în feed"
        )
    
        tags = models.JSONField(
            default=list,
            blank=True,
            verbose_name="Tag-uri",
            help_text="Lista de tag-uri pentru produs"
        )
    
        attributes = models.JSONField(
            default=dict,
            blank=True,
            verbose_name="Atribute",
            help_text="Atribute suplimentare ale produsului"
        )
    
        created_at = models.DateTimeField(
            auto_now_add=True,
            verbose_name="Creat la"
        )
    
        updated_at = models.DateTimeField(
            auto_now=True,
            verbose_name="Actualizat la"
        )
    
        class Meta:
            verbose_name = "Produs"
            verbose_name_plural = "Produse"
            ordering = ['-created_at']
            indexes = [
                models.Index(fields=['name']),
                models.Index(fields=['category']),
                models.Index(fields=['price']),
                models.Index(fields=['is_active']),
                models.Index(fields=['sku']),
            ]
    
        def __str__(self):
            return f"{self.name} ({self.sku})"
    
        def get_absolute_url(self):
            """URL către pagina produsului"""
            from django.urls import reverse
            return reverse('product-detail', kwargs={'pk': self.pk})
    
        @property
        def is_in_stock(self):
            """Verifică dacă produsul este în stoc"""
            return self.stock_quantity > 0
    
        @property
        def display_price(self):
            """Preț formatat pentru afișare"""
            return f"{self.price} RON"

    🌐 Pasul 3: Creare Serializer

    Creează serializer pentru API în products/serializers.py:

    # products/serializers.py
    from rest_framework import serializers
    from .models import Product
    
    
    class ProductSerializer(serializers.ModelSerializer):
        """Serializer pentru modelul Product"""
    
        display_price = serializers.ReadOnlyField()
        is_in_stock = serializers.ReadOnlyField()
        url = serializers.ReadOnlyField(source='get_absolute_url')
    
        class Meta:
            model = Product
            fields = [
                'id', 'name', 'description', 'price', 'display_price',
                'sku', 'category', 'image_url', 'stock_quantity',
                'is_active', 'is_in_stock', 'tags', 'attributes',
                'url', 'created_at', 'updated_at'
            ]
            read_only_fields = ['id', 'created_at', 'updated_at', 'display_price', 'is_in_stock', 'url']
    
    
    class ProductFeedSerializer(serializers.ModelSerializer):
        """Serializer special pentru feed-ul JSON"""
    
        class Meta:
            model = Product
            fields = [
                'id', 'name', 'description', 'price', 'sku',
                'category', 'image_url', 'stock_quantity',
                'is_active', 'tags', 'attributes',
                'created_at', 'updated_at'
            ]

    🎮 Pasul 4: Creare ViewSet

    Creează ViewSet pentru API în products/views.py:

    # products/views.py
    from django.shortcuts import get_object_or_404
    from django.utils.decorators import method_decorator
    from django.views.decorators.cache import cache_page
    from django_filters.rest_framework import DjangoFilterBackend
    from rest_framework import viewsets, status
    from rest_framework.decorators import action
    from rest_framework.response import Response
    from rest_framework.permissions import AllowAny, IsAuthenticated
    from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
    from .models import Product
    from .serializers import ProductSerializer, ProductFeedSerializer
    
    
    class ProductViewSet(viewsets.ReadOnlyModelViewSet):
        """ViewSet pentru produse"""
    
        queryset = Product.objects.filter(is_active=True)
        serializer_class = ProductSerializer
        permission_classes = [AllowAny]
        throttle_classes = [AnonRateThrottle, UserRateThrottle]
    
        filter_backends = [DjangoFilterBackend]
        filterset_fields = ['category', 'is_active']
        search_fields = ['name', 'description', 'sku']
        ordering_fields = ['name', 'price', 'created_at', 'updated_at']
        ordering = ['-created_at']
    
        @method_decorator(cache_page(60 * 5))  # Cache 5 minute
        def list(self, request, *args, **kwargs):
            """Lista produse cu paginare și filtrare"""
            return super().list(request, *args, **kwargs)
    
        @method_decorator(cache_page(60 * 10))  # Cache 10 minute
        def retrieve(self, request, *args, **kwargs):
            """Detaliile unui produs"""
            return super().retrieve(request, *args, **kwargs)
    
        @method_decorator(cache_page(60 * 5))
        # @action(detail=False, methods=['get'], url_path='feed')
        def feed(self, request):
            """Endpoint special pentru feed-ul JSON"""
    
            # Verificare API key (opțional)
            api_key = request.query_params.get('api_key')
            if hasattr(self, 'required_api_key') and api_key != self.required_api_key:
                return Response(
                    {'error': 'Invalid API key'},
                    status=status.HTTP_401_UNAUTHORIZED
                )
    
            # Filtrare parametri
            queryset = self.get_queryset()
    
            # Filtrare după preț
            min_price = request.query_params.get('min_price')
            max_price = request.query_params.get('max_price')
    
            if min_price:
                queryset = queryset.filter(price__gte=min_price)
            if max_price:
                queryset = queryset.filter(price__lte=max_price)
    
            # Filtrare după categorie
            category = request.query_params.get('category')
            if category:
                queryset = queryset.filter(category=category)
    
            # Filtrare după stoc
            in_stock = request.query_params.get('in_stock')
            if in_stock == 'true':
                queryset = queryset.filter(stock_quantity__gt=0)
    
            # Paginare
            page = self.paginate_queryset(queryset)
            if page is not None:
                serializer = ProductFeedSerializer(page, many=True)
                return self.get_paginated_response(serializer.data)
    
            # Fără paginare - format Mixbox
            serializer = ProductFeedSerializer(queryset, many=True)
    
            # Structura răspunsului în formatul Mixbox
            response_data = {}
    
            for product_data in serializer.data:
                sku = product_data.get('sku') or f"PROD-{product_data.get('id')}"
    
                response_data[sku] = {
                    'sku': sku,
                    'name': product_data.get('name'),
                    'qty': str(product_data.get('stock_quantity', 0)),
                    'price': float(product_data.get('price', 0)),
                    'sale_price': product_data.get('sale_price'),
                    'last_update_date': product_data.get('updated_at', timezone.now().strftime('%Y-%m-%d %H:%M:%S')),
                    'description': product_data.get('description'),
                    'weight': product_data.get('weight'),
                    'images': [{'src': product_data.get('image_url')}] if product_data.get('image_url') else [],
                    'permalink': f"{request.scheme}://{request.get_host()}/products/{product_data.get('id')}/",
                    'categories_tree': [{
                        'id': 25,
                        'name': product_data.get('category', 'Categoria Principală'),
                        'parent': 0,
                        'image': 'https://site-ul-vostru.com/category-image.jpg',
                        'subcategories': product_data.get('subcategories', [{
                            'id': 30,
                            'name': 'Subcategoria',
                            'parent': 25,
                            'image': 'https://site-ul-vostru.com/subcategory-image.jpg',
                            'subcategories': []
                        }])
                    }],
                    'breadcrumbs': [product_data.get('category', 'Categoria Principală') + ' > Subcategoria'],
                    'brands': [{'name': product_data.get('brand'), 'image': 'https://site-ul-vostru.com/brand-logo.jpg'}] if product_data.get('brand') else []
                }
    
            return Response(response_data)
    
        # @action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
        def private_feed(self, request):
            """Feed privat pentru utilizatori autentificați"""
    
            # Log acces pentru audit
            import logging
            logger = logging.getLogger(__name__)
            logger.info(f'Private feed accessed by user {request.user.id}')
    
            # Adaugă informații suplimentare pentru utilizatori autentificați
            queryset = self.get_queryset()
            serializer = ProductFeedSerializer(queryset, many=True)
    
            # Structura răspunsului în formatul Mixbox pentru feed privat
            response_data = {}
    
            for product_data in serializer.data:
                sku = product_data.get('sku') or f"PROD-{product_data.get('id')}"
    
                response_data[sku] = {
                    'sku': sku,
                    'name': product_data.get('name'),
                    'qty': str(product_data.get('stock_quantity', 0)),
                    'price': float(product_data.get('price', 0)),
                    'sale_price': product_data.get('sale_price'),
                    'last_update_date': product_data.get('updated_at', timezone.now().strftime('%Y-%m-%d %H:%M:%S')),
                    'description': product_data.get('description'),
                    'weight': product_data.get('weight'),
                    'images': [{'src': product_data.get('image_url')}] if product_data.get('image_url') else [],
                    'permalink': f"{request.scheme}://{request.get_host()}/products/{product_data.get('id')}/",
                    'categories_tree': [{
                        'id': 25,
                        'name': product_data.get('category', 'Categoria Principală'),
                        'parent': 0,
                        'image': 'https://site-ul-vostru.com/category-image.jpg',
                        'subcategories': product_data.get('subcategories', [{
                            'id': 30,
                            'name': 'Subcategoria',
                            'parent': 25,
                            'image': 'https://site-ul-vostru.com/subcategory-image.jpg',
                            'subcategories': []
                        }])
                    }],
                    'breadcrumbs': [product_data.get('category', 'Categoria Principală') + ' > Subcategoria'],
                    'brands': [{'name': product_data.get('brand'), 'image': 'https://site-ul-vostru.com/brand-logo.jpg'}] if product_data.get('brand') else [],
                    # Informații suplimentare pentru feed privat
                    'internal_notes': product_data.get('internal_notes'),
                    'supplier_info': product_data.get('supplier_info'),
                    'profit_margin': product_data.get('profit_margin')
                }
    
            return Response(response_data)

    🛣️ Pasul 5: Configurare URL-uri

    Creează URL-urile în products/urls.py:

    # products/urls.py
    from django.urls import path, include
    from rest_framework.routers import DefaultRouter
    from . import views
    
    router = DefaultRouter()
    router.register(r'products', views.ProductViewSet)
    
    urlpatterns = [
        path('', include(router.urls)),
    ]

    Include URL-urile în project/urls.py:

    # project/urls.py
    from django.contrib import admin
    from django.urls import path, include
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('api/', include('products.urls')),
    ]

    ⚙️ Pasul 6: Configurare Settings

    Adaugă configurațiile în settings.py:

    # settings.py
    
    # Django REST Framework
    INSTALLED_APPS = [
        # ... alte apps
        'rest_framework',
        'django_filters',
        'products',
    ]
    
    # REST Framework settings
    REST_FRAMEWORK = {
        'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
        'PAGE_SIZE': 100,
        'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.AnonRateThrottle',
            'rest_framework.throttling.UserRateThrottle'
        ],
        'DEFAULT_THROTTLE_RATES': {
            'anon': '100/hour',
            'user': '1000/hour'
        },
        'DEFAULT_RENDERER_CLASSES': [
            'rest_framework.renderers.JSONRenderer',
            'rest_framework.renderers.BrowsableAPIRenderer',
        ]
    }
    
    # Cache settings
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        }
    }
    
    # Pentru producție, folosește Redis sau Memcached
    # CACHES = {
    #     'default': {
    #         'BACKEND': 'django.core.cache.backends.redis.RedisCache',
    #         'LOCATION': 'redis://127.0.0.1:6379/1',
    #     }
    # }
    
    # API Key (opțional)
    PRODUCT_FEED_API_KEY = 'your_secret_api_key_here'

    📦 Pasul 7: Requirements.txt

    Creează fișierul requirements.txt:

    Django==4.2.5
    djangorestframework==3.14.0
    django-filter==23.3
    psycopg2-binary==2.9.7
    djangorestframework-simplejwt==5.2.2
    redis==4.6.0
    python-decouple==3.8

    🧪 Pasul 8: Testare API

    Migrează baza de date și testează API-ul:

    # Migrări
    python manage.py makemigrations
    python manage.py migrate
    
    # Creează superuser
    python manage.py createsuperuser
    
    # Pornește server-ul
    python manage.py runserver
    
    # Testează endpoint-urile
    curl "http://localhost:8000/api/products/feed/"
    curl "http://localhost:8000/api/products/feed/?category=electronics"
    curl "http://localhost:8000/api/products/feed/?min_price=100&max_price=500"

    🚀 Pasul 9: Deployment

    Pentru deployment în producție:

    # 1. Configurează settings pentru producție
    DEBUG = False
    ALLOWED_HOSTS = ['yourdomain.com']
    
    # 2. Configurează baza de date de producție
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'productdb',
            'USER': 'dbuser',
            'PASSWORD': 'dbpass',
            'HOST': 'localhost',
            'PORT': '5432',
        }
    }
    
    # 3. Configurează static files
    STATIC_URL = '/static/'
    STATIC_ROOT = '/var/www/static/'
    
    # 4. Rulează colecția static
    python manage.py collectstatic --noinput
    
    # 5. Configurează Gunicorn
    # gunicorn project.wsgi:application --bind 0.0.0.0:8000

    📊 Pasul 10: Monitorizare

    Adaugă logging pentru monitorizare:

    # settings.py
    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            'file': {
                'level': 'INFO',
                'class': 'logging.FileHandler',
                'filename': 'logs/django.log',
            },
        },
        'loggers': {
            'django': {
                'handlers': ['file'],
                'level': 'INFO',
                'propagate': True,
            },
            'products': {
                'handlers': ['file'],
                'level': 'INFO',
                'propagate': False,
            },
        },
    }
    ✅ Felicitări! Ai implementat cu succes un API REST în Django pentru feed JSON. API-ul tău este acum gata să servească date pentru alte aplicații!

    🔗 Link-uri Utile