Skip to content

Existing Model Interface Recipes

These examples show how to adopt ExistingModelInterface for legacy tables while keeping the GeneralManager ergonomics.

Wrapping a swappable Django User model

Keep the Django auth model in models.py and expose the GeneralManager wrapper from managers.py. That lets both classes be named User while giving shells and application code a canonical manager import path.

from django.conf import settings

from general_manager.interface import ExistingModelInterface
from general_manager.manager import GeneralManager, graph_ql_property


class User(GeneralManager):
    id: int
    username: str
    email: str | None

    class Interface(ExistingModelInterface):
        model = settings.AUTH_USER_MODEL

    class Factory:
        username = "legacy-user"
        email = "legacy@example.com"
        password = "not-hashed"

    @graph_ql_property
    def display_name(self) -> str:
        if self.email:
            return f"{self.username} ({self.email})"
        return self.username

Import the wrapper with from myapp.managers import User; keep myapp.models.User for direct ORM usage only. GeneralManager auto-imports myapp.managers during startup so foreign keys pointing at settings.AUTH_USER_MODEL resolve to the wrapper once the app is loaded.

User.create and User.update call through to the configured auth table, User.filter(is_active=True) returns manager instances, and User.Factory.create() seeds users for tests without duplicating schema.

Adding validation to a legacy model

Combine existing business rules with new ones by declaring Meta.rules. The interface merges them and rewires full_clean() so the complete rule set runs everywhere.

from django.db import models

from general_manager.interface import ExistingModelInterface
from general_manager.manager import GeneralManager
from general_manager.rule import Rule


class LegacyProject(models.Model):
    name = models.CharField(max_length=100, unique=True)
    is_active = models.BooleanField(default=True)

    class Meta:
        app_label = "projects"


class UniqueNameRule(Rule):
    """Reject placeholder project names."""

    def evaluate(self, obj: models.Model) -> bool:
        return obj.name != "TBD"

    def get_error_message(self) -> dict[str, list[str]]:
        return {"name": ["tbd_not_allowed"]}


class Project(GeneralManager):
    name: str
    is_active: bool

    class Interface(ExistingModelInterface):
        model = LegacyProject

        class Meta:
            rules = [UniqueNameRule()]

Project.create(name="TBD") now raises a validation error, while historical tracking and activation semantics continue to flow through to LegacyProject.