本文へ移動
cccskills
無料GitHub で公開

python-django-stack

Python and Django conventions for multi-tenant SaaS POS projects: views, services, naming, JSON APIs, PDFs, and cart patterns. Trigger: When writing any view, service, API endpoint, or model code in apps/.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md14.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

When to Use

  • Writing any new view, service method, or API endpoint
  • Designing a new feature and deciding where logic lives
  • Adding a JSON API response
  • Implementing a PDF report or cart operation
  • Structuring the architecture of a new app

Django Project Structure

Recommended Apps

A well-structured Django project follows modular app patterns:

apps/
├── core/           # Configuration, shared models (Store)
├── users/          # User model, authentication
├── employees/     # Employees (tenant)
├── catalog/        # Products, categories (tenant)
├── cart/           # Storefront cart (tenant)
├── orders/         # Orders (tenant)
├── pos/            # Point of Sale (tenant)
├── storefront/     # Public store (tenant)
└── analytics/      # Reports and metrics (tenant)

Per-App File Structure

apps/catalog/
├── __init__.py
├── models.py        # Models (Product, Category, ProductImage)
├── views.py         # Views
├── urls.py          # Routes
├── services.py      # Business logic
├── serializers.py  # Serialization (if using DRF)
├── reports.py      # PDF reports
├── signals.py       # Signals
├── admin.py        # Admin config
├── tests/
│   ├── __init__.py
│   └── test_{feature}.py
└── migrations/

View Patterns

FBV vs CBV

TypeWhen to UseExamples
FBV (Function-Based)All API endpoints, all POS/manager views, any view with business logicapi_cart_add, api_search_products, pos_checkout
CBV (Class-Based)Only basic storefront list/detailHomeView, CategoryListView, ProductDetailView

Rule: CBV for generic read-only views with simple templates. FBV for everything else (APIs, complex logic, POS).

# FBV — always for APIs
@require_pos_access
@require_POST
def api_cart_add(request):
    cart = POSCart(request)
    data = json.loads(request.body)
    cart.add(data["product_id"], data["quantity"])
    return JsonResponse({"success": True, "cart_count": cart.count()})

# CBV — only for basic storefront
class ProductDetailView(TemplateView):
    template_name = "storefront/product.html"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["product"] = get_object_or_404(Product, slug=self.kwargs["slug"])
        return ctx

Decorator Order

Custom access decorator outermost, HTTP method decorator innermost:

@require_pos_access      # outermost — runs first, guards access
@require_POST            # innermost — closest to the function
def api_cart_add(request):
    ...

Always use @require_POST / @require_GET explicitly. Never if request.method == "POST" inside an API view.

Service Layer

Business logic lives in services.py modules. Services are classes with @staticmethod only — no instance state, no __init__:

class OrderService:
    @staticmethod
    def create_order(order: Order, cart: Cart) -> Order:
        with transaction.atomic():
            order.save()
            OrderItem.objects.bulk_create(items)
            Product.objects.bulk_update(products, ["stock"])
            cart.clear()
            return order

    @staticmethod
    def cancel_order(order_id: int, reason: str) -> Order:
        ...

Views call OrderService.create_order(...). No business logic in views.

When to Create a Service

  • Complex business logic involving multiple models
  • Transactions spanning multiple DB operations
  • Logic reusable across multiple views
  • Anything that isn't "delegate to another component"

Naming Conventions

PatternConventionExamples
AJAX API viewsapi_{resource}_{action}api_cart_add, api_search_products, api_temporal_cart_save
Module-private helpersleading __build_pos_order, _sort_by_category
Module constantsUPPER_SNAKE_CASEVALID_PAYMENT_METHODS = {"cash", "transfer", "card"}
URL names{scope}_{resource}_{action}api_catalog_products, pos_checkout
Service methodscamelCase (PEP8 inconsistent but accepted)createOrder, calculateTotal
Model methodssnake_caseget_display_price(), is_available()

JSON API Response Shapes

List Endpoint with Pagination

return JsonResponse({
    "results": [...],
    "total": N,
    "has_more": bool,
})

Successful Mutation

return JsonResponse({"success": True, "cart_count": N})

Error Response

return JsonResponse({"success": False, "error": "error message"}, status=400)

HTTP Status Codes

  • 200 — OK, success
  • 201 — Created
  • 400 — Client error (invalid data, missing fields)
  • 404 — Not found
  • 500 — Server error

Never return 200 with "success": false. Use the correct status code.

Pagination

Offset/Limit — No Page Numbers

Always offset + limit, never page numbers. Default 20 items, max 100:

offset = int(request.GET.get("offset", 0))
limit = min(int(request.GET.get("limit", 20)), 100)

total = qs.count()
page = qs[offset:offset + limit]

return JsonResponse({
    "results": list(page.values("id", "name", "price")),
    "total": total,
    "has_more": (offset + limit) < total,
})

PDF Patterns

Pure functions, no classes. Take primitive data, return bytes. Internal helpers prefixed with _:

import io
from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle

def generate_restock_pdf(store_name: str, products_raw: list) -> bytes:
    buffer = io.BytesIO()
    doc = _make_doc(buffer)
    story = _build_story(store_name, products_raw)
    doc.build(story)
    buffer.seek(0)
    return buffer.read()

def _make_doc(buffer: io.BytesIO) -> SimpleDocTemplate:
    return SimpleDocTemplate(
        buffer,
        pagesize=A4,
        rightMargin=30,
        leftMargin=30,
        topMargin=30,
        bottomMargin=30,
    )

def _build_story(store_name: str, products_raw: list) -> list:
    data = [["SKU", "Product", "Stock", "Min"]]
    for p in products_raw:
        data.append([p["sku"], p["name"], p["stock"], p["min_stock"]])
    table = Table(data, colWidths=[60, 200, 50, 50])
    table.setStyle(TableStyle([...]))
    return [table]

The view wraps the result in HttpResponse:

def api_restock_pdf(request):
    products = Product.objects.filter(stock__lte=F("min_stock"))
    pdf_bytes = generate_restock_pdf(request.store.name, list(products.values()))
    response = HttpResponse(pdf_bytes, content_type="application/pdf")
    response["Content-Disposition"] = 'inline; filename="restock.pdf"'
    return response

Cart Patterns

Two independent carts coexist per request. Session key is the only separator:

CartSession KeyModule
Storefrontcartapps/cart/cart.py
POSpos_cartapps/pos/pos_cart.py

Both follow the same contract:

  • Initialize with request
  • Store as {str(product_id): {"quantity": int, "price": str}}
  • Price stored as str (Decimal is not JSON-serializable)
  • Always call self.session.modified = True in save()
class POSCart:
    def __init__(self, request):
        self.session = request.session.get("pos_cart", {})

    def add(self, product_id, quantity):
        key = str(product_id)
        if key in self.session:
            self.session[key]["quantity"] += quantity
        else:
            self.session[key] = {"quantity": quantity, "price": "0.00"}
        self.save()

    def save(self):
        self.session["pos_cart"] = self.session
        self.session.modified = True

Type Hints

Minimal: Only on public service/utility function signatures, not on views or helpers:

# Service — annotate
def generate_restock_pdf(store_name: str, products_raw: list) -> bytes:
    ...

# View — no annotations
def api_cart_add(request):
    ...

# Private helpers — no annotations
def _build_cart_item(product, quantity):
    ...

Access Decorators

require_pos_access

Verifies the employee has POS access:

def require_pos_access(view):
    def wrapper(request, *args, **kwargs):
        if not request.user.is_authenticated:
            return JsonResponse({"error": "Auth required"}, status=401)
        if not request.user.is_superuser:
            emp = EmployeeProfile.objects.get(user=request.user)
            if not emp.has_pos_access:
                return JsonResponse({"error": "No POS access"}, status=403)
        return view(request, *args, **kwargs)
    return wrapper

require_manager_access

Verifies the employee is a manager:

def require_manager_access(view):
    def wrapper(request, *args, **kwargs):
        if not request.user.is_authenticated:
            return JsonResponse({"error": "Auth required"}, status=401)
        if not request.user.is_superuser:
            emp = EmployeeProfile.objects.get(user=request.user)
            if not emp.is_manager:
                return JsonResponse({"error": "Manager required"}, status=403)
        return view(request, *args, **kwargs)
    return wrapper

URLs and Routing

URL Structure

# apps/catalog/urls.py
from django.urls import path

urlpatterns = [
    # API
    path("api/products/", api_catalog_products, name="api_catalog_products"),
    path("api/products/<int:pk>/", api_product_detail, name="api_product_detail"),
    # Web
    path("", product_list, name="catalog_product_list"),
    path("<slug:slug>/", product_detail, name="catalog_product_detail"),
]

Namespaces

# config/urls.py
urlpatterns = [
    path("catalog/", include("apps.catalog.urls", namespace="catalog")),
    path("pos/", include("apps.pos.urls", namespace="pos")),
]

Models — Best Practices

Model Fields

class Product(models.Model):
    name = models.CharField(max_length=255)
    slug = models.SlugField(max_length=255, unique=True)
    description = models.TextField(blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.PositiveIntegerField(default=0)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["name"]
        indexes = [
            models.Index(fields=["is_active", "stock"]),
            models.Index(fields=["slug"]),
        ]

    def __str__(self):
        return self.name

    @property
    def is_available(self):
        return self.is_active and (self.is_service or self.stock > 0)

Choices

class Order(models.Model):
    class Status(models.TextChoices):
        PENDING = "pending", "Pending"
        PAID = "paid", "Paid"
        SHIPPED = "shipped", "Shipped"
        CANCELLED = "cancelled", "Cancelled"

    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.PENDING,
    )

Code Quality

Line Length

88 characters (Black + Ruff). Run before every commit:

make format   # black + ruff --fix
make lint     # ruff check
uv run djlint . --reformat  # templates

Pre-commit hooks enforce Black and Ruff automatically.

Imports

# Standard library
import os
import json
from datetime import datetime

# Third party
from django.db import models
from django.http import JsonResponse
from django.views.decorators.http import require_POST

# Local
from apps.catalog.models import Product
from apps.pos.services import CartService

Commands

make run          # dev server on 0.0.0.0:8000
make lint         # ruff check
make format       # black + ruff fix
make tests        # pytest (--reuse-db --nomigrations by default)
make migrate      # migrate_schemas --shared + --tenant

Resources

  • FBV API examples: apps/pos/views.py
  • Service layer: apps/orders/services.py
  • PDF pattern: apps/catalog/reports.py
  • Cart pattern: apps/pos/pos_cart.py, apps/cart/cart.py
  • Pagination: apps/catalog/views.py (api_catalog_products)
  • Django docs: https://docs.djangoproject.com/en/5.2/

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use when adding monitoring, metrics, logging, or tracing to Spring AI or LLM integration code. Covers token tracking, latency measurement, cost estimation, and prompt/response logging. Use when user mentions AI monitoring, token costs, or LLM observability.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

android

無料

Provides expert knowledge for building modern native Android apps using Kotlin, Jetpack Compose, Material Design 3, MVI architecture, Hilt dependency injection, and Media3/ExoPlayer.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

Design, implement, review, and audit Android Jetpack Compose UI/UX for phone interfaces. Use when a request concerns concrete Compose layout, components, interaction, accessibility, or visual behavior. Do not activate for framework or dependency presence alone; non-UI Android work is excluded.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

Provides expert knowledge for designing, implementing, and managing REST API versioning strategies with deprecation workflows.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

astro

無料

Provides expert knowledge for building Astro 5+ sites with Islands Architecture, Content Collections, TypeScript, and performance-first rendering strategies.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

Audits token storage mechanisms to prevent XSS-based JWT theft.

日本語の概要は準備中です。原文の説明を表示しています。

lgzarturo/springboot-course32026年10月12日 更新

lgzarturo のスキルをすべて見る

このスキルの問題を報告する