Expose a Proxy Model as a Separate CRUD Surface
A Django proxy model reads and writes its concrete model's database table. It has its own Content Type and permission codenames. You can give the same rows a second API surface with its own serializer, viewset, permissions, and Model Info.
This guide adds a proxy on top of an existing VUEDA Model. If you have not set up the concrete model yet, start with Create a CRUD Surface for a New Model.
Defining the Proxy Model
Subclass the concrete model and set proxy = True. A proxy adds no columns, so it declares no fields:
from .models import Distributor
class DistributorProxy(Distributor):
class Meta(Distributor.Meta):
proxy = True
verbose_name = "distributor proxy"
verbose_name_plural = "distributor proxies"Inheriting the concrete model's Meta carries over its options, such as ordering and VUEDA's default_permissions.
Do not declare class Vueda on the proxy. A proxy takes the Feature Policy of its concrete model, and a declaration on the proxy fails the vueda_core.E014 system check.
Generating the Migration
Run makemigrations. Django writes a CreateModel operation with an empty fields list and "proxy": True:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("myapp", "0003_distributor"),
]
operations = [
migrations.CreateModel(
name="DistributorProxy",
fields=[],
options={
"verbose_name": "distributor proxy",
"verbose_name_plural": "distributor proxies",
"abstract": False,
"proxy": True,
"default_permissions": ("create", "read", "update", "delete", "list"),
"indexes": [],
"constraints": [],
},
bases=("myapp.distributor",),
),
]Run migrate. After the migrations apply, Django creates the proxy's content type and its five permissions: create_, read_, update_, delete_, and list_distributorproxy. The names come from default_permissions through the Permission Mapping.
Defining the Serializer
Subclass VuedaSerializer and set Meta.model to the proxy. The field list can match the concrete model's serializer or be a subset:
from vueda.core.serializers import VuedaSerializer
from .models import DistributorProxy
class DistributorProxySerializer(VuedaSerializer):
class Meta(VuedaSerializer.Meta):
model = DistributorProxy
fields = [
"id",
"name",
"description",
] + VuedaSerializer.Meta.fieldsVuedaSerializer.Meta.fields adds formatted_name, available_actions, and object_revision.
Defining the FilterSet
Subclass VuedaFilterSet, which adds a hidden id filter. Declare the field filters that the proxy needs:
from django_filters import rest_framework
from vueda.core.filters import VuedaFilterSet
from .models import DistributorProxy
class DistributorProxyFilterSet(VuedaFilterSet):
name = rest_framework.CharFilter(field_name="name", lookup_expr="exact")
name_icontains = rest_framework.CharFilter(
field_name="name", label="Name (contains)", lookup_expr="icontains"
)
class Meta:
model = DistributorProxy
fields = ["name"]Defining the ViewSet
Subclass VuedaViewSet and point it at the proxy, its serializer, and its filterset:
from vueda.core.viewsets import VuedaViewSet
from .filtersets import DistributorProxyFilterSet
from .models import DistributorProxy
from .serializers import DistributorProxySerializer
class DistributorProxyViewSet(VuedaViewSet):
queryset = DistributorProxy.objects.all()
serializer_class = DistributorProxySerializer
filterset_class = DistributorProxyFilterSet
ordering_fields = ["name"]
ordering = ["name"]
def get_allowed_extra_actions(self, request, *, instance=None):
if request is not None and request.user.groups.filter(name="Customer").exists():
return frozenset()
return super().get_allowed_extra_actions(request, instance=instance)DistributorProxy.objects.all() returns every row of the shared table. To expose a subset of rows, filter queryset (for example, DistributorProxy.objects.filter(name__startswith="North")) or override get_queryset() and call super().
The get_allowed_extra_actions() override is optional. It gives the proxy different extra actions from the concrete model's viewset. Here, members of the Customer group get none. VUEDA also calls it with request=None when it builds metadata without a request, so check for None before you read request.user.
Routing
Register the viewset with VuedaRouter:
from vueda.core.routers import VuedaRouter
from .viewsets import DistributorProxyViewSet
router = VuedaRouter()
router.register("distributor_proxies", DistributorProxyViewSet)The router takes the basename from the queryset model's label_lower, here myapp.distributorproxy. The route names are myapp.distributorproxy-list and myapp.distributorproxy-detail.
Registration
Call register() for the proxy in your app's Django: AppConfig.ready:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "myapp"
def ready(self):
from vueda.info.registration import register
from .serializers import DistributorProxySerializer
from .viewsets import DistributorProxyViewSet
register(DistributorProxySerializer, DistributorProxyViewSet)The registry keys each registration by app_label.model_name, so the proxy and the concrete model register independently. The proxy gets its own model info, including its actions, filters, and ordering.
Permissions
Permission checks on the proxy's viewset use the proxy's codenames, such as myapp.read_distributorproxy. A grant on the concrete model, such as myapp.read_distributor, does not reach the proxy. Look up the permission through the proxy's content type and add it to a group:
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
content_type = ContentType.objects.get(app_label="myapp", model="distributorproxy")
read_perm = Permission.objects.get(content_type=content_type, codename="read_distributorproxy")
Group.objects.get(name="Staff").permissions.add(read_perm)To carry group grants to other environments, record them as a Group Permission Migration.
How History Tracking Works for Proxy Models
A proxy shares its concrete model's Model History. Saving through DistributorProxy writes the same DistributorEvent rows as saving through Distributor. The proxy's history-list endpoint returns those events, and each event's model names the concrete model, for example myapp.Distributor.
WARNING
Declare the History section of class Vueda on the concrete model. To exclude a field from history or turn history off, change the concrete model's policy, and the proxy follows it.
Workflow also resolves a proxy to its concrete model, so a proxy of a Workflow-Enabled Model shares each row's Object State. Model Feature Policy describes the rules for proxies.