如何在Django Oscar 1.5及后续版本中实现多租户(Multitenancy)?
Hey there! I've worked on setting up multi-tenancy in Django Oscar 1.5+ before, since the old 0.6 multi-dealer feature was removed. Let me walk you through the most practical approaches to make this work:
This approach adds a tenant identifier to core Oscar models and filters data automatically, so each shop owner only sees their own content.
Step 1: Add a Tenant Model & Link to Oscar Core Models
First, create aShopmodel to represent each tenant (shop owner), then extend Oscar's key models (like Product, Partner, Order) with a foreign key toShop. Use Oscar's model overriding mechanism to avoid modifying core code:# myshop/shops/models.py from django.db import models from django.contrib.auth.models import User from oscar.apps.catalogue.abstract_models import AbstractProduct from oscar.apps.partner.abstract_models import AbstractPartner class Shop(models.Model): name = models.CharField(max_length=255, unique=True) owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="shops") # Add fields like domain, logo, or custom settings here # Override Oscar's Product model class Product(AbstractProduct): shop = models.ForeignKey(Shop, on_delete=models.CASCADE) # Override Oscar's Partner model class Partner(AbstractPartner): shop = models.ForeignKey(Shop, on_delete=models.CASCADE) # Don't forget to import Oscar's abstract models at the end from oscar.apps.catalogue.models import * from oscar.apps.partner.models import *Update your
INSTALLED_APPSto replace Oscar's default apps with your overridden versions.Step 2: Auto-Filter Data with Custom Managers
Create a tenant-aware manager that automatically filters queries to the current shop:# myshop/core/managers.py from django.db import models class TenantManager(models.Manager): def get_queryset(self): qs = super().get_queryset() # Access the current shop from the request (set via middleware) if hasattr(self.model, '_current_shop'): return qs.filter(shop=self.model._current_shop) return qs # Attach this manager to your overridden models # e.g., in Product: # objects = TenantManager()You'll need a way to set
_current_shopon models, which ties into the next step.Step 3: Identify Tenants with Middleware
Add middleware to detect the current shop (via subdomain, URL path, or user session) and attach it to the request:# myshop/core/middleware.py class TenantMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # Example: Detect shop via subdomain (e.g., shop1.yourdomain.com) host_parts = request.get_host().split('.') if len(host_parts) >= 3: shop_slug = host_parts[0] try: request.shop = Shop.objects.get(name=shop_slug) # Set the current shop for tenant-aware managers from myshop.shops.models import Product, Partner Product._current_shop = request.shop Partner._current_shop = request.shop except Shop.DoesNotExist: # Handle invalid shop (e.g., redirect to 404 or default page) pass response = self.get_response(request) return responseAdd this middleware to
settings.MIDDLEWARE(place it early in the stack).Step 4: Enforce Permissions
Ensure only the shop owner can modify their shop's data with a custom decorator:# myshop/core/decorators.py from django.core.exceptions import PermissionDenied def shop_owner_required(view_func): def wrapper(request, *args, **kwargs): if not hasattr(request, 'shop') or request.user != request.shop.owner: raise PermissionDenied return view_func(request, *args, **kwargs) return wrapperApply this decorator to your shop management views.
If you need complete data isolation between shops (no shared tables), use a multi-tenancy library like django-tenants (PostgreSQL-only, since it uses database schemas).
Key Steps:
- Install
django-tenantsand configure your settings to use a tenant-aware database router. - Set your
Shopmodel as the tenant model. - Migrate Oscar's models into tenant schemas (instead of the public schema).
- Use the library's middleware to switch schemas based on the current shop.
This approach is more heavyweight but ensures zero data leakage between tenants.
- Install
For basic multi-shop setups where some data (e.g., categories) can be shared, use Django's built-in sites framework:
- Add a
siteforeign key to Oscar models (similar to theShopfield in approach 1). - Filter queries using
Site.objects.get_current()to return only data for the current site. - Use Django's
sitesmiddleware to detect the current site via domain.
This is simpler but less flexible than row-level isolation, as it's designed for shared content across domains.
- Oscar Overrides: Always use Oscar's model/app overriding system instead of modifying core code—this ensures compatibility with future updates.
- Search: If using Haystack or Elasticsearch, update your search indexes to include the
shop/sitefield and filter results accordingly. - Media/Static Files: Store tenant-specific media (like product images) in separate directories (e.g.,
media/shop1/) to avoid conflicts.
内容的提问来源于stack exchange,提问作者kurtgn

