Skip to main content

Django Admin backed by gRPC services — no ORM required.

Project description

django-admin-grpc

Django Admin backed by gRPC services — no ORM required.

PyPI version Python 3.12+ Django versions License: MIT CI


django-admin-grpc lets you expose remote gRPC microservices inside Django Admin with full list, create, update, delete and search support. You define a resource schema, wire a gRPC adapter, and register a single admin class — the package handles forms, pagination, filtering, and error mapping for you.

Installation

pip install django-admin-grpc

Add the app to INSTALLED_APPS:

INSTALLED_APPS = [
    # ...
    "django_admin_grpc",
]

Quick Start

# resources.py
from django_admin_grpc.resources import (
    BaseGrpcResource,
    BooleanFieldConfig,
    CharFieldConfig,
    FloatFieldConfig,
)

class Product(BaseGrpcResource):
    class Meta:
        app_label = "catalog"
        model_name = "product"
        verbose_name = "Product"
        pk_field = "id"

    fields = [
        CharFieldConfig(name="id"),
        CharFieldConfig(name="name", max_length=200),
        FloatFieldConfig(name="price"),
        BooleanFieldConfig(name="active", initial=True),
    ]

That is all. Django Admin now shows list, add, change, and delete screens powered by your gRPC service.


Package Architecture

Component Purpose
BaseGrpcResource Declarative schema for a remote entity. Defines fields, primary key, and metadata.
BaseFieldConfig Base class for field metadata. Use concrete subclasses like CharFieldConfig, IntegerFieldConfig, etc.
BaseGrpcServiceAdapter Abstract transport layer. Implement list, get, and optionally create, update, delete.
GrpcResourceAdmin ModelAdmin subclass that renders lists and forms using the adapter instead of the ORM.
BaseGrpcMapper Optional request/response mapper between Django forms and protobuf messages.
PagedResult Dataclass returned by adapter.list() carrying items, total count, and optional cursor.
AdapterRegistry Central registry mapping service names to adapter instances.

How to Define a New Resource

A resource is a Python class that tells Django Admin what columns exist, what types they are, and which one is the primary key.

from django_admin_grpc.resources import (
    BaseGrpcResource,
    BooleanFieldConfig,
    CharFieldConfig,
    FKFieldConfig,
    IntegerFieldConfig,
)

class NetworkRule(BaseGrpcResource):
    class Meta:
        app_label = "network"
        model_name = "networkrule"
        verbose_name = "Network Rule"
        verbose_name_plural = "Network Rules"
        pk_field = "rule_id"

    fields = [
        CharFieldConfig(name="rule_id", label="Rule ID"),
        CharFieldConfig(name="name", max_length=120),
        IntegerFieldConfig(name="priority"),
        BooleanFieldConfig(name="active", initial=True),
        FKFieldConfig(
            name="partner_id",
            label="Partner",
            model="contacts.Contact",      # resolves via Django ORM
            display_field="name",
            required=False,
        ),
    ]

Supported field types:

Type Form widget Notes
char TextInput Use max_length to limit input.
text Textarea Multi-line text.
integer NumberInput Whole numbers.
float NumberInput Decimal numbers.
boolean CheckboxInput Defaults to False unless initial=True.
choices Select Provide choices=[("a", "A"), ...].
fk Select Model FKs load options automatically; service/custom FKs use choices or choices_loader.
date / datetime DateInput / DateTimeInput Stored as string; validate in the adapter.

Common field options (all subclasses):

  • name — field identifier (required).
  • label — human label (default: auto-title-cased name).
  • required — whether the form field is required (default: True).
  • help_text — shown below the form field.
  • initial — default value.
  • source — name of the attribute in the gRPC response if it differs from name.

Type-specific options:

  • CharFieldConfig: max_length — max characters allowed.
  • ChoicesFieldConfig: choices — list of (value, label) tuples.
  • FKFieldConfig:
    • model"app_label.ModelName" for Django FK resolution.
    • to_field — model field to use as the FK value.
    • display_field — field to show in detail views; omitted means show the raw FK value.
    • service — service name in the adapter registry for gRPC FK resolution.
    • get_method — adapter method to call for gRPC FK resolution (default: get).
    • choices — static (value, label) select options for service/custom FK fields.
    • choices_loader — callable returning (value, label) options for service/custom FK fields.

How to Wire gRPC Stubs

An adapter bridges Django Admin and your gRPC service. Subclass BaseGrpcServiceAdapter and implement at least list and get. Implement create, update, and delete only if you need write access.

from django_admin_grpc.adapters import BaseGrpcServiceAdapter
from django_admin_grpc.paginator import PagedResult
import grpc

class NetworkRulesAdapter(BaseGrpcServiceAdapter):
    service_name = "network_rules"

    def __init__(self):
        self._channel = None

    @property
    def channel(self):
        if self._channel is None:
            raw = grpc.insecure_channel("network-service:50051")
            self._channel = self._wrap_channel(raw)
        return self._channel

    def list(self, resource_class, page=1, page_size=25, filters=None):
        stub = NetworkRulesStub(self.channel)
        request = ListRulesRequest(page=page, page_size=page_size)
        response = stub.ListRules(request)
        items = [resource_class.from_response(r) for r in response.rules]
        return PagedResult(items=items, total=response.total)

    def get(self, resource_class, pk):
        stub = NetworkRulesStub(self.channel)
        response = stub.GetRule(GetRuleRequest(rule_id=pk))
        return resource_class.from_response(response)

    def create(self, resource_class, data):
        stub = NetworkRulesStub(self.channel)
        request = CreateRuleRequest(**data)
        response = stub.CreateRule(request)
        return resource_class.from_response(response)

    def update(self, resource_class, pk, data):
        stub = NetworkRulesStub(self.channel)
        request = UpdateRuleRequest(rule_id=pk, **data)
        response = stub.UpdateRule(request)
        return resource_class.from_response(response)

    def delete(self, resource_class, pk):
        stub = NetworkRulesStub(self.channel)
        stub.DeleteRule(DeleteRuleRequest(rule_id=pk))
        return True

Registering via the adapter registry (optional):

from django_admin_grpc.registry import adapter_registry

adapter = NetworkRulesAdapter()
adapter_registry.register("network_rules", adapter)

When registered by name, the admin class can reference it with service_name = "network_rules" instead of adapter_class.


How to Register in Django Admin

Register the resource's fake model with a GrpcResourceAdmin subclass:

from django.contrib import admin
from django_admin_grpc.admin import GrpcResourceAdmin

from .resources import NetworkRule
from .adapters import NetworkRulesAdapter

@admin.register(NetworkRule.admin_model())
class NetworkRuleAdmin(GrpcResourceAdmin):
    resource_class = NetworkRule
    adapter_class = NetworkRulesAdapter   # or service_name = "network_rules"

    list_display = ["rule_id", "name", "active"]
    list_filter = ["active"]
    search_fields = ["name"]

    grpc_enable_create = True
    grpc_enable_update = True
    grpc_enable_delete = True
    grpc_form_fields = ["name", "priority", "active", "partner_id"]

Resource.admin_model() builds a lightweight compatible class with _meta, objects, DoesNotExist, and MultipleObjectsReturned so that Django's ModelAdmin machinery works without a real ORM model.


How to Customize Forms

By default, forms are built automatically from grpc_form_fields and the resource's field config list. You can override widgets or build the form class yourself.

Custom widgets per field:

class ProductAdmin(GrpcResourceAdmin):
    resource_class = Product
    adapter_class = CatalogAdapter
    grpc_form_fields = ["name", "description", "price"]

    def _build_form_class(self):
        return self.resource_class.build_form_class(widgets={
            "description": forms.Textarea(attrs={"rows": 8}),
            "price": forms.NumberInput(attrs={"step": "0.01"}),
        })

Using build_form_class() on the resource:

from django import forms

form_class = Product.build_form_class(widgets={
    "name": forms.TextInput(attrs={"class": "vTextField"}),
})

Filtering which fields appear:

Set grpc_form_fields to a subset of the resource's fields. Only those fields will be rendered in add/change views.

Customizing create/update payloads:

Override get_grpc_create_data and get_grpc_update_data on the admin class to transform cleaned_data before it reaches the adapter:

class ProductAdmin(GrpcResourceAdmin):
    def get_grpc_create_data(self, cleaned_data):
        data = dict(cleaned_data)
        data["created_by"] = self.request.user.username
        return data

How to Customize Permissions

Permissions are controlled by three flags on the admin class:

class ProductAdmin(GrpcResourceAdmin):
    grpc_enable_create = True   # show "Add" button
    grpc_enable_update = True   # allow editing in change view
    grpc_enable_delete = True   # show delete button and bulk delete action

These flags are ANDed with adapter capability: if the adapter does not implement create(), the add view is disabled automatically even when grpc_enable_create = True.

Hook methods for dynamic permission checks:

class ProductAdmin(GrpcResourceAdmin):
    def has_add_permission(self, request):
        return request.user.is_superuser and super().has_add_permission(request)

    def has_delete_permission(self, request, obj=None):
        return request.user.groups.filter(name="Admins").exists()

How to Customize List Pages

GrpcResourceAdmin supports the standard ModelAdmin list options:

class ProductAdmin(GrpcResourceAdmin):
    list_display = ["id", "name", "price", "active"]
    list_filter = ["active", "category_id"]
    search_fields = ["name", "description"]
    list_per_page = 25

Filter configuration:

For list filters to work, declare grpc_filter_config as a list or dict:

class ProductAdmin(GrpcResourceAdmin):
    # Simple list — fields are auto-detected by type
    grpc_filter_config = ["active", "category_id"]

    # Dict form — override type or choices per field
    grpc_filter_config = {
        "active": {"type": "boolean"},
        "status": {"type": "choices", "choices": [("draft", "Draft"), ("live", "Live")]},
        "name": {"type": "text", "label": "Product Name"},
    }

Supported filter types: boolean, choices, text. The admin renders a sidebar filter panel just like standard Django Admin.

Pagination:

By default the adapter receives page (1-indexed) and page_size. Return a PagedResult from adapter.list():

PagedResult(items=instances, total=total_count, page=page, page_size=page_size)

For cursor-based pagination, set grpc_cursor_pagination = True on the admin. The adapter will receive page_size and filters["cursor"]. Return the next cursor in PagedResult.next_cursor.


How to Customize Actions

Bulk delete is built in. When grpc_enable_delete = True and the adapter supports delete, a "Delete selected records" action appears in the dropdown.

Adding custom actions:

from django.contrib import messages

class ProductAdmin(GrpcResourceAdmin):
    actions = ["activate_selected"]

    @admin.action(description="Activate selected products")
    def activate_selected(self, request, queryset):
        adapter = self.get_adapter()
        for obj in queryset:
            adapter.update(self.resource_class, obj.pk, {"active": True})
        messages.success(request, "Selected products activated.")

Because queryset is a GrpcFakeQuerySet, iterate over it to access the wrapped resource instances.


Error Handling

gRPC errors are caught at the adapter boundary and mapped to typed exceptions. The admin displays them as Django messages.

gRPC Status Code Mapped Exception Admin Behavior
NOT_FOUND GrpcNotFoundError Redirects to "object does not exist" page.
PERMISSION_DENIED / UNAUTHENTICATED GrpcPermissionDeniedError Shown as red error message; user stays on the page.
INVALID_ARGUMENT GrpcInvalidArgumentError Shown as red error message (validation failed).
UNAVAILABLE GrpcUnavailableError Shown as red error message (service down).
DEADLINE_EXCEEDED GrpcDeadlineExceededError Shown as red error message (timeout).
Other GrpcAdminError Shown as generic error message.

Mapping errors in your adapter:

from django_admin_grpc.exceptions import map_grpc_error

class MyAdapter(BaseGrpcServiceAdapter):
    def get(self, resource_class, pk):
        try:
            return self.stub.Get(...)
        except grpc.RpcError as exc:
            raise self._map_rpc_error(exc)

You can also catch the typed exceptions in custom admin methods if you need special handling.


Customizing Appearance

Custom Widgets

You can override widgets per field when building the form class:

from django import forms
from django_admin_grpc.admin import GrpcResourceAdmin

class ProductAdmin(GrpcResourceAdmin):
    resource_class = Product
    adapter_class = CatalogAdapter
    grpc_form_fields = ["name", "description", "price", "active"]

    def _build_form_class(self):
        return self.resource_class.build_form_class(
            widgets={
                "description": forms.Textarea(attrs={"rows": 8}),
                "price": forms.NumberInput(attrs={"step": "0.01"}),
            }
        )

Or set defaults globally in settings.py:

GRPC_ADMIN = {
    "DEFAULT_WIDGETS": {
        "char": forms.TextInput,
        "text": forms.Textarea,
        "boolean": forms.CheckboxInput,
    },
}

Custom Admin Base Class

GrpcResourceAdmin inherits from Django's ModelAdmin. If you use a custom admin theme (e.g. django-unfold, django-jazzmin, etc.), subclass with the theme's ModelAdmin after GrpcResourceAdmin:

from django.contrib import admin
from django_admin_grpc.admin import GrpcResourceAdmin
from unfold.admin import ModelAdmin as UnfoldModelAdmin

class MyGrpcAdmin(GrpcResourceAdmin, UnfoldModelAdmin):
    pass

@admin.register(Product.admin_model())
class ProductAdmin(MyGrpcAdmin):
    resource_class = Product
    adapter_class = CatalogAdapter

Alternatively, use the factory helper:

MyGrpcAdmin = GrpcResourceAdmin.with_base(UnfoldModelAdmin)

Custom Templates

You can override templates per-resource via the resource Meta class:

class Product(BaseGrpcResource):
    class Meta:
        app_label = "catalog"
        change_form_template = "myapp/product_change_form.html"
        delete_confirm_template = "myapp/product_delete_confirm.html"

Or globally via settings.py:

GRPC_ADMIN = {
    "DEFAULT_CHANGE_FORM_TEMPLATE": "myapp/change_form.html",
    "DEFAULT_DELETE_CONFIRM_TEMPLATE": "myapp/delete_confirm.html",
    "DEFAULT_CURSOR_PAGINATION_TEMPLATE": "myapp/cursor_pagination.html",
}

Configuration

Set these in your Django settings.py:

Setting Default Description
GRPC_ADMIN_TRACE_CONTEXT_PROVIDER None Callable or dotted path that returns a dict of trace headers injected into every gRPC call.
GRPC_ADMIN_DEFAULT_PAGE_SIZE 25 Default items per page for list views.
GRPC_ADMIN_MAX_PAGE_SIZE 100 Maximum items per page.
GRPC_ADMIN_CURSOR_PAGINATION False Enable cursor-based pagination globally.
GRPC_ADMIN_LOG_LEVEL "INFO" Log level for the package logger.
DEFAULT_WIDGETS None Dict mapping field type to widget class or dotted path.
DEFAULT_ADMIN_CLASS django.contrib.admin.ModelAdmin Dotted path to the base ModelAdmin subclass.
DEFAULT_CHANGE_FORM_TEMPLATE django_admin_grpc/change_form.html Template for add/change views.
DEFAULT_DELETE_CONFIRM_TEMPLATE django_admin_grpc/delete_confirm.html Template for delete confirmation.
DEFAULT_CURSOR_PAGINATION_TEMPLATE django_admin_grpc/cursor_pagination.html Template for cursor pagination controls.

Example Project

An example Django project is included in the example/ directory. It demonstrates a catalog microservice with in-memory adapters so you can run the admin locally without a real gRPC server.

cd example
python manage.py migrate
python manage.py runserver

Browse to /admin/ to see Products and Categories backed by gRPC-style adapters.

Contributing

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/my-change.
  3. Install dev dependencies: pip install -e '.[dev]'.
  4. Run tests: pytest.
  5. Open a pull request.

Please include tests for new functionality and keep line coverage at 80% or higher.


License

MIT License. See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_admin_grpc-0.2.2.tar.gz (38.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_admin_grpc-0.2.2-py3-none-any.whl (44.7 kB view details)

Uploaded Python 3

File details

Details for the file django_admin_grpc-0.2.2.tar.gz.

File metadata

  • Download URL: django_admin_grpc-0.2.2.tar.gz
  • Upload date:
  • Size: 38.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_admin_grpc-0.2.2.tar.gz
Algorithm Hash digest
SHA256 fac9dd54aede29a2efaed255515e4d642adad9b26401b2761ed139bfbc162f84
MD5 e5530d1f822175a157f237fa5ea61e20
BLAKE2b-256 0b0acadd733a7e69b6f488f8e18a93bf4e8fc5f17ff5891ae0ee9f3496e177e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_admin_grpc-0.2.2.tar.gz:

Publisher: release.yml on DVNghiem/django-admin-grpc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_admin_grpc-0.2.2-py3-none-any.whl.

File metadata

File hashes

Hashes for django_admin_grpc-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7183abce951fc2c98171f66b6b8e03941dda3ce486bf62fff888158f6ff229a5
MD5 58135d6abc1a8df7653b72bdcd1eedfc
BLAKE2b-256 26061c6ed4721b743ad9310db72bbc9b3471342f70070f2d54e11386653a569f

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_admin_grpc-0.2.2-py3-none-any.whl:

Publisher: release.yml on DVNghiem/django-admin-grpc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page