Ghid complet de instalare și configurare pentru Django
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"
}
]
}
}
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
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"
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'
]
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)
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')),
]
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'
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
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"
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
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,
},
},
}