feat(#49): add product provenance (suppliers, organizations, geography)
- Country, Department, Municipality models (municipality unique per department) - Organization and Supplier models, Supplier links to organization and municipality - Product.suppliers M2M to link products to one or more suppliers - Authenticated CRUD APIs for organizations, suppliers, countries, departments and municipalities (write only for administrators) - product_provenance domain data embedded in sale/catalog summaries (public and authenticated) - seed_geography endpoint (admin, idempotent) and scripts/seed_geography.py using pycountry + DANE municipalities CSV - TDD tests for models, API permissions/CRUD and summary provenance data
This commit is contained in:
@@ -20,6 +20,14 @@ from .payments import (
|
||||
)
|
||||
from .admin import AdminCodeValidateView
|
||||
from .store_settings import StoreSettingsView
|
||||
from .provenance import (
|
||||
OrganizationView,
|
||||
SupplierView,
|
||||
CountryView,
|
||||
DepartmentView,
|
||||
MunicipalityView,
|
||||
SeedGeographyView,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Catalogue Images
|
||||
@@ -49,4 +57,11 @@ __all__ = [
|
||||
"AdminCodeValidateView",
|
||||
# Store Settings
|
||||
"StoreSettingsView",
|
||||
# Provenance
|
||||
"OrganizationView",
|
||||
"SupplierView",
|
||||
"CountryView",
|
||||
"DepartmentView",
|
||||
"MunicipalityView",
|
||||
"SeedGeographyView",
|
||||
]
|
||||
|
||||
112
tienda_ilusion/don_confiao/api/provenance.py
Normal file
112
tienda_ilusion/don_confiao/api/provenance.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from ..models.geography import Country, Department, Municipality
|
||||
from ..models.provenance import Organization, Supplier
|
||||
from ..serializers import (
|
||||
CountrySerializer,
|
||||
DepartmentSerializer,
|
||||
MunicipalitySerializer,
|
||||
OrganizationSerializer,
|
||||
SupplierSerializer,
|
||||
)
|
||||
from ..permissions import IsAdministrator, IsNotPublico
|
||||
|
||||
|
||||
class _AdminWriteMixin:
|
||||
def get_permissions(self):
|
||||
if self.action in ("list", "retrieve"):
|
||||
return [IsNotPublico()]
|
||||
return [IsAuthenticated(), IsAdministrator()]
|
||||
|
||||
|
||||
class OrganizationView(_AdminWriteMixin, viewsets.ModelViewSet):
|
||||
queryset = Organization.objects.all()
|
||||
serializer_class = OrganizationSerializer
|
||||
|
||||
|
||||
class SupplierView(_AdminWriteMixin, viewsets.ModelViewSet):
|
||||
queryset = Supplier.objects.select_related(
|
||||
"organization", "municipality"
|
||||
)
|
||||
serializer_class = SupplierSerializer
|
||||
|
||||
|
||||
class CountryView(_AdminWriteMixin, viewsets.ModelViewSet):
|
||||
queryset = Country.objects.all()
|
||||
serializer_class = CountrySerializer
|
||||
|
||||
|
||||
class DepartmentView(_AdminWriteMixin, viewsets.ModelViewSet):
|
||||
queryset = Department.objects.select_related("country")
|
||||
serializer_class = DepartmentSerializer
|
||||
|
||||
|
||||
class MunicipalityView(_AdminWriteMixin, viewsets.ModelViewSet):
|
||||
queryset = Municipality.objects.select_related(
|
||||
"department", "country"
|
||||
)
|
||||
serializer_class = MunicipalitySerializer
|
||||
|
||||
|
||||
class SeedGeographyView(APIView):
|
||||
permission_classes = [IsAuthenticated, IsAdministrator]
|
||||
|
||||
def post(self, request):
|
||||
country_data = request.data.get("country") or {}
|
||||
country_name = country_data.get("name")
|
||||
if not country_name:
|
||||
return Response(
|
||||
{"detail": "country.name is required"}, status=400
|
||||
)
|
||||
|
||||
country, _ = Country.objects.get_or_create(
|
||||
name=country_name,
|
||||
defaults={"code": country_data.get("code", "")},
|
||||
)
|
||||
|
||||
departments_created = 0
|
||||
departments = {}
|
||||
for department_data in request.data.get("departments", []):
|
||||
department, created = Department.objects.get_or_create(
|
||||
name=department_data["name"],
|
||||
defaults={"country": country},
|
||||
)
|
||||
departments[department.name] = department
|
||||
if created:
|
||||
departments_created += 1
|
||||
|
||||
municipalities_created = 0
|
||||
for municipality_data in request.data.get(
|
||||
"municipalities", []
|
||||
):
|
||||
department_name = municipality_data["department"]
|
||||
department = departments.get(department_name)
|
||||
if department is None:
|
||||
department, created = Department.objects.get_or_create(
|
||||
name=department_name, defaults={"country": country}
|
||||
)
|
||||
departments[department.name] = department
|
||||
if created:
|
||||
departments_created += 1
|
||||
|
||||
_, created = Municipality.objects.get_or_create(
|
||||
name=municipality_data["name"],
|
||||
department=department,
|
||||
defaults={"country": country},
|
||||
)
|
||||
if created:
|
||||
municipalities_created += 1
|
||||
|
||||
return Response(
|
||||
{
|
||||
"country": country.name,
|
||||
"departments_created": departments_created,
|
||||
"municipalities_created": municipalities_created,
|
||||
"total_departments": Department.objects.count(),
|
||||
"total_municipalities": Municipality.objects.count(),
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
Reference in New Issue
Block a user