This page explains how microSYS fits into a Django project and how to think about its moving parts before you start extending it.
microSYS is a Django app that combines six layers:
- runtime configuration
- generic discovery and generation
- global patches for translation and scope behavior
- reusable templates, views, and JavaScript for internal system workflows
- audit and governance infrastructure
- data-movement and productivity utilities
If you keep those layers in mind, the package becomes much easier to extend without fighting it.
The runtime configuration comes from get_system_config() and is merged in this order:
- package defaults
settings.MICROSYS_CONFIG- the database-backed
SystemSettingssingleton
Practical implications:
- use
MICROSYS_CONFIGfor project-owned defaults checked into source control - use
SystemSettingsfor live runtime edits from the UI - expect the final resolved configuration to be the merged view, not one single source
The main system-level models are:
-
SystemSettingsStores branding, theme, language, home URL, translations override, and sidebar configuration. -
ScopeandScopeSettingsRepresent the optional scope-isolation system and whether scoping is globally enabled.ScopeSettings.auto_create_user_scopeenables automatic creation of a dedicatedScopefor every newly registered user, providing automatic user isolation without manual assignment. -
ScopedModelGives inheriting models audit fields, actor tracking, soft-delete behavior, and automatic scope support. -
ProfileExtends the user model with phone, profile picture, preferences, and 2FA state. Profiles are created automatically.
Inheriting from ScopedModel is the main way to make a model feel native inside microSYS.
from django.db import models
from microsys.models import ScopedModel
class Department(ScopedModel):
name = models.CharField(max_length=100)What you get automatically:
scopecreated_atupdated_atcreated_byupdated_bydeleted_atdeleted_by
Behavior to remember:
save()auto-assigns actor fields from the current request usersave()can also inherit scope from the current user's profiledelete()becomes a soft-deleteobjectsis scope-aware and hides soft-deleted rowsall_objectsis the raw escape hatch- When
ScopeSettings.auto_create_user_scopeis enabled, new users automatically receive their own dedicated scope
MicrosysConfig.ready() applies the package's global behavior at startup. That includes:
- permission-label translation
- automatic scope handling for forms, filters, and tables
- automatic translation patches for forms, filters, tables, and some context-menu labels
That is why many microsys features feel "automatic" even when your model or form code looks ordinary.
When MicrosysConfig.ready() runs at startup, it applies several global monkey-patches to Django's core components to enable the "Zero-Boilerplate" experience.
Through microsys.patches.apply_scoped_patches(), the system intercepts the __init__ methods of ModelForm, FilterSet, and Table (from django-tables2).
- Discovery: It checks if the underlying model inherits from
ScopedModel. - Injection: If so, it automatically adds a
scopefield/filter/column if one hasn't been explicitly defined or excluded. - Access Control: It locks the field for non-superusers, ensuring they cannot change their assigned scope.
Through apply_global_translation_patches(), the system:
- Monkey-patches
gettext: Every call to Django's translation utilities (including_()) first checks theMS_TRANSdictionary and runtime overrides before falling back to local PO files. - Translates Model Meta: Automatically wraps
verbose_nameandverbose_name_pluralof ALL models in the project with lazy translators that look up keys inmicrosys. - Translates Permissions: Dynamic labels in the user management UI are generated by translating the "Can add/view/delete" strings in real-time.
If LOGIN_REDIRECT_URL or LOGOUT_REDIRECT_URL are missing from your settings.py, microSYS injects its own defaults to ensure a smooth out-of-the-box flow.
A few practical consequences:
- you usually do not add
scopemanually to every form and filter - translated labels often come from verbose names or translation keys without manual wiring
- opting out is explicit, such as excluding
scopein form metadata when needed
microSYS leans heavily on naming conventions and runtime discovery.
The generic class resolver looks for model-adjacent classes in this order:
- convention-based imports such as
DepartmentForm,DepartmentTable, andDepartmentFilter - explicit model methods that return classes
- explicit dotted-path model methods
- runtime auto-generation
That same discovery model powers both sections and dynamic modal flows.
It also connects to the surrounding UI systems:
- sidebar discovery for runtime navigation
- sidebar permission inference (see below)
- context-menu actions attached to generated tables
- autofill metadata attached to generated or patched form fields
- generic modal/list/detail views that expect convention-friendly classes
Sidebar items are only visible to users who have the required view permission. The permission for each item is inferred in this order:
- Explicit decorator:
sidebar_permissionsorpermission_requiredon the view callback — used as-is. - System route meta: items in
SYSTEM_ROUTE_META(e.g.,manage_users,options_view) use their declared__ms_*permission tokens. - Model-based inference: for class-based views with a model, the permission is
app_label.view_model_name. - URL pattern inference: for function-based views without a model, the app label comes from the URL namespace (or callback module) and the model name from the URL name prefix (e.g.,
documents:outgoing_list→documents.view_outgoing). - No inference: if none of the above produce a permission, the item is hidden from non-superusers.
This means staff users will only see sidebar items for models they have explicit app.view_model permissions for. Ensure users are granted the appropriate permissions.
Use sections when:
- the model is a simple auxiliary or lookup dataset
- you want a list + filter + modal CRUD flow with minimal code
- the model belongs in the system navigation automatically
Use dynamic modals when:
- the CRUD flow should be embedded inside another screen
- you need form-only, list-only, or read-only modal behavior
- the model should be managed from a custom trigger instead of the sections page
In both cases, the discovery system is the same. What changes is the entry point and the surrounding UI.
The user side of microSYS has a few important defaults:
- every user gets a
Profile - preferences and 2FA state live on that profile
- the user-management flow uses the interactive modal wizard
- permission labels are translated dynamically instead of staying in raw Django English
If you extend user-facing workflows, treat Profile as part of the normal user contract rather than an optional extra.
Two recent themes in microSYS are worth treating as first-class features, not side effects:
- translations are resolved with layered fallbacks, including user preferences, session state, config defaults, and runtime overrides
- scope behavior is auto-injected when enabled and removed when disabled
That means you should usually extend the system by leaning into those mechanisms rather than rebuilding them locally in each form, table, or view.
microSYS also includes a few subsystems that make it feel larger than a normal "app with some templates":
- Activity logging: the system captures login/logout, CRUD, merged User/Profile changes, diffs, and download/export events through signals and shared logging helpers.
- Context menus:
generated or custom tables can emit URL actions or decoupled
micro:record:*events, which lets the UI layer stay interactive without hard-wiring custom JavaScript everywhere. - Fetch/export helpers:
fetch_file()andfetch_excel()provide package-level download/export behavior instead of every project reimplementing file handling and Excel generation. - Productivity helpers: autofill, sticky forms, filter setup, and reusable list templates push common back-office UX patterns into shared infrastructure.
That cluster of subsystems is a big part of why microSYS should be treated like an internal platform layer, not just a widget library.
If you are wiring a normal list screen today, the supported path is:
- extend
microsys/list_base.html - call
setup_filter_helper()oradvanced_filter_helper()in the view - render the table with
{% render_table table %}and do not wrap it in another.table-responsive - prefer
microsys.tables.MicrosysTablefor handwritten tables
The current table contract is:
- stock
django_tables2templates and no-template tables are auto-adopted into the Microsys renderer - explicit non-stock custom templates are left alone unless you point them at the Microsys template yourself
- density precedence is
Table.Meta.microsys_density->Profile.preferences["table_density"]->SystemSettings.default_table_density->balanced - page-size precedence is
Table.Meta.microsys_per_page-> requestper_page->Profile.preferences["table_page_size"]->20 - built-in per-page controls and the density switcher live in the framework-owned footer
- tables with forced
microsys_densityintentionally hide the footer density switcher - default row actions are
micro:record:view,micro:record:edit, andmicro:record:delete - disable default row actions with
Meta.microsys_actions = False - customize the action list with
get_microsys_row_actions(self, record, base_actions)
Typical handwritten table:
from microsys.tables import MicrosysTable
class InvoiceTable(MicrosysTable):
class Meta(MicrosysTable.Meta):
model = Invoice
fields = ("number", "customer", "status", "created_at")
microsys_density = "balanced"
microsys_per_page = 50Filter pages also have a clearer contract now:
setup_filter_helper()andadvanced_filter_helper()default to inline placeholder labels for filter bars- if you want normal external labels instead, pass
inline_labels=False - if a page cannot extend
microsys/list_base.html, includemicrosys/forms/filter_assets_head.html
For the full contract and more examples, use:
- Use the Customization Guide when you are ready to wire your own translations, sections, modals, or template overrides.
- Use the Reference when you need commands, endpoints, template tags, or helper names quickly.