Existing Model Interfaces¶
ExistingModelInterface (general_manager.interface.ExistingModelInterface) lets a manager wrap an existing Django model without generating new tables. It keeps the GeneralManager API intact—create, update, delete, factories, and history tracking all work the same—while pointing at the tables you already manage elsewhere. When the legacy model exposes an is_active column the interface enables soft delete automatically (delete() toggles the flag).
When to use it¶
- You have legacy Django models and want GeneralManager orchestration without migrating data into freshly generated tables.
- Multiple apps already depend on a model definition and you need the manager layer to stay backward compatible.
- You want to incrementally adopt managers before refactoring model code.
If you control the schema and prefer GeneralManager to build models for you, stick with DatabaseInterface.
Pointing a manager at a legacy model¶
Reference the model with either an import, settings.AUTH_USER_MODEL, or an app label string. Annotate the manager attributes you plan to read—GeneralManager derives field accessors from the legacy model instead of from interface field declarations.
from django.conf import settings
from general_manager.interface import ExistingModelInterface
from general_manager.manager import GeneralManager
class User(GeneralManager):
id: int
username: str
email: str | None
class Interface(ExistingModelInterface):
model = settings.AUTH_USER_MODEL
For wrappers that use the same class name as your Django model, keep the Django model in models.py and define the GeneralManager wrapper in managers.py. GeneralManager auto-imports <app>.managers for every installed app during startup before it initializes manager classes, which avoids import cycles and ensures the wrapper class is available for registration and _general_manager_class linking. Import the wrapper from myapp.managers in shells and app code so you do not confuse it with myapp.models.User; if the model is already imported, assign the wrapper class directly to _general_manager_class instead of relying on a string reference.
Auditing and validation¶
createandupdateassignchanged_by_idwhen the model exposes that column and recordhistory_commentvalues usingdjango-simple-history.deletetogglesis_active(when the column exists) and appends" (deactivated)"to the provided history comment; if your legacy model lacks that field the interface performs a hard delete instead. Usefilter(include_inactive=True)when you need to surface soft-deleted rows explicitly.- Define
Meta.ruleson the interface to add GeneralManager validation alongside any rules already declared on the model. The interface merges both sets and replacesfull_clean()so the rule set runs consistently everywhere.
Writing data through the manager¶
ExistingModelInterface reuses the same write helpers as DatabaseInterface:
update()refreshes the current manager in place and returns that same manager instance. Reassigning the return value is optional now, socustomer.update(name="New")immediately makescustomer.namereflect the saved value.-
delete()invalidates the current manager instance for subsequent field reads. This applies to both hard deletes and soft deletes on models withis_active; the row may still exist forinclude_inactive=Truelookups, but the deleted manager object itself should be treated as spent. -
Pass many-to-many identifiers with the
<relation>_id_listconvention or provide GeneralManager instances; the interface normalises and applies them after saving the main record. - Foreign key assignments accept manager instances or raw IDs.
filter,exclude, andallreturn manager instances backed by the legacy rows. You can query historical states by passingsearch_date=...tofilter()/exclude()or by callingManagerInterface(search_date=...)the same way you would with generated models.
customer.update(name="Acme International", history_comment="rename")
assert customer.name == "Acme International"
customer.delete(history_comment="manual block")
# `customer` is now invalid for attribute reads even when the row was soft-deleted.
No schema changes are generated by this interface—keep running your migrations and model definitions in the owning app.
Factory support¶
An AutoFactory subclass is built automatically for each manager. It reuses any attributes you place on an inner Factory definition and populates missing fields using the legacy model metadata. Calling User.Factory.create() returns manager instances, so your tests keep existing fixtures while benefiting from the richer interface surface. Factory-created managers follow the same lifecycle contract as any other manager instance: updates mutate the current manager object in place, and deletes invalidate that object for later field reads.
Because history tracking is still attached to the underlying legacy row, in-place updates do not change audit behavior. You still get the same changed_by_id and history_comment entries as before; only the in-memory manager identity contract changed.
Looking for sample code? See the Existing model interface cookbook recipes for end-to-end snippets you can drop into your project.