如何在Django RestFramework中实现一对多关系?附示例需求
Alright, let's get your one-to-many relationship set up to match that exact JSON structure you're aiming for. Here's how to tweak your existing model.py and serializers.py step by step:
models.py We'll define two models: Product (the "one" side of the relationship) and TaxDetail (the "many" side, each linked to a single product). The foreign key goes on the TaxDetail model, and we'll set a related_name to make it easy to pull all tax details for a product later.
from django.db import models class Product(models.Model): product_name = models.CharField(max_length=255) description = models.TextField() sales = models.DecimalField(max_digits=10, decimal_places=2) cost = models.DecimalField(max_digits=10, decimal_places=2) def __str__(self): return self.product_name class TaxDetail(models.Model): # Link each tax detail to a single product product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='tax_details') tax_name = models.CharField(max_length=255) percentage = models.DecimalField(max_digits=5, decimal_places=2) def __str__(self): return f"{self.tax_name} - {self.percentage}%"
The related_name='tax_details' is key here—it lets us access all linked tax records directly from a Product instance, which is critical for the nested JSON output.
serializers.py We'll create two serializers: one for TaxDetail (to format each tax entry correctly, including the URL) and one for Product that nests the tax details serializer.
from rest_framework import serializers from .models import Product, TaxDetail from django.urls import reverse class TaxDetailSerializer(serializers.ModelSerializer): # Dynamically generate the tax detail URL tax_name = serializers.SerializerMethodField() class Meta: model = TaxDetail fields = ['id', 'tax_name', 'percentage'] def get_tax_name(self, obj): # Build the absolute URL using the request context (if available) request = self.context.get('request') if request: return request.build_absolute_uri(reverse('tax-detail', kwargs={'pk': obj.pk})) # Fallback to relative URL if request isn't passed in return f"/tax/{obj.pk}/" class ProductSerializer(serializers.ModelSerializer): # Nest the tax details serializer to show all related entries tax_details = TaxDetailSerializer(many=True, read_only=True) class Meta: model = Product fields = ['id', 'product_name', 'description', 'sales', 'cost', 'tax_details']
Quick breakdown:
SerializerMethodFieldfortax_namegenerates the exact URL format you need, using Django'sreverseto match your route structure.TaxDetailSerializer(many=True)tells the product serializer to expect and format multiple tax entries for a single product.
To get the full working endpoint, you'll need to set up basic views and routes:
Example views.py
from rest_framework import viewsets from .models import Product, TaxDetail from .serializers import ProductSerializer, TaxDetailSerializer class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer class TaxDetailViewSet(viewsets.ModelViewSet): queryset = TaxDetail.objects.all() serializer_class = TaxDetailSerializer
Example urls.py
from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import ProductViewSet, TaxDetailViewSet router = DefaultRouter() router.register(r'products', ProductViewSet) # Set basename to ensure the route name matches what we used in the serializer router.register(r'tax', TaxDetailViewSet, basename='tax') urlpatterns = [ path('', include(router.urls)), ]
Once you create a Product and link some TaxDetail entries to it, hitting the product detail endpoint (e.g., http://127.0.0.1:8000/products/1/) will return exactly the JSON structure you wanted:
{ "id": 1, "product_name": "Rice", "description": "expired on 13-04-2018", "sales": "55.00", "cost": "55.00", "tax_details": [ { "id": 1, "tax_name": "http://127.0.0.1:8000/tax/1/", "percentage": 10.00 }, { "id": 2, "tax_name": "http://127.0.0.1:8000/tax/3/", "percentage": 2.00 } ] }
内容的提问来源于stack exchange,提问作者Sudharsan Venkatraj

