Skip to content

Latest commit

 

History

History
248 lines (168 loc) · 11.3 KB

File metadata and controls

248 lines (168 loc) · 11.3 KB

Developer Guide

This page explains how microSYS fits into a Django project and how to think about its moving parts before you start extending it.

The Core Mental Model

microSYS is a Django app that combines six layers:

  1. runtime configuration
  2. generic discovery and generation
  3. global patches for translation and scope behavior
  4. reusable templates, views, and JavaScript for internal system workflows
  5. audit and governance infrastructure
  6. data-movement and productivity utilities

If you keep those layers in mind, the package becomes much easier to extend without fighting it.

Configuration Layers

The runtime configuration comes from get_system_config() and is merged in this order:

  1. package defaults
  2. settings.MICROSYS_CONFIG
  3. the database-backed SystemSettings singleton

Practical implications:

  • use MICROSYS_CONFIG for project-owned defaults checked into source control
  • use SystemSettings for live runtime edits from the UI
  • expect the final resolved configuration to be the merged view, not one single source

Core Models

The main system-level models are:

  • SystemSettings Stores branding, theme, language, home URL, translations override, and sidebar configuration.

  • Scope and ScopeSettings Represent the optional scope-isolation system and whether scoping is globally enabled. ScopeSettings.auto_create_user_scope enables automatic creation of a dedicated Scope for every newly registered user, providing automatic user isolation without manual assignment.

  • ScopedModel Gives inheriting models audit fields, actor tracking, soft-delete behavior, and automatic scope support.

  • Profile Extends the user model with phone, profile picture, preferences, and 2FA state. Profiles are created automatically.

Working with ScopedModel

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:

  • scope
  • created_at
  • updated_at
  • created_by
  • updated_by
  • deleted_at
  • deleted_by

Behavior to remember:

  • save() auto-assigns actor fields from the current request user
  • save() can also inherit scope from the current user's profile
  • delete() becomes a soft-delete
  • objects is scope-aware and hides soft-deleted rows
  • all_objects is the raw escape hatch
  • When ScopeSettings.auto_create_user_scope is enabled, new users automatically receive their own dedicated scope

Startup Patches and Zero-Boilerplate Behavior

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.

The ready() Phase: Behind the Magic

When MicrosysConfig.ready() runs at startup, it applies several global monkey-patches to Django's core components to enable the "Zero-Boilerplate" experience.

1. Automatic Scope Injection

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 scope field/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.

2. Universal Translation Patches

Through apply_global_translation_patches(), the system:

  • Monkey-patches gettext: Every call to Django's translation utilities (including _()) first checks the MS_TRANS dictionary and runtime overrides before falling back to local PO files.
  • Translates Model Meta: Automatically wraps verbose_name and verbose_name_plural of ALL models in the project with lazy translators that look up keys in microsys.
  • Translates Permissions: Dynamic labels in the user management UI are generated by translating the "Can add/view/delete" strings in real-time.

3. Auth Redirect Defaults

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 scope manually 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 scope in form metadata when needed

Discovery and Generated Components

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, and DepartmentFilter
  • 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 Permission Inference

Sidebar items are only visible to users who have the required view permission. The permission for each item is inferred in this order:

  1. Explicit decorator: sidebar_permissions or permission_required on the view callback — used as-is.
  2. System route meta: items in SYSTEM_ROUTE_META (e.g., manage_users, options_view) use their declared __ms_* permission tokens.
  3. Model-based inference: for class-based views with a model, the permission is app_label.view_model_name.
  4. 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_listdocuments.view_outgoing).
  5. 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.

Sections vs Dynamic Modals

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.

Users, Profiles, and Permissions

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.

Translation and Scope Behavior

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.

Audit, Interaction, and Utility Subsystems

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() and fetch_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.

vNext Tables and Filter Pages

If you are wiring a normal list screen today, the supported path is:

  • extend microsys/list_base.html
  • call setup_filter_helper() or advanced_filter_helper() in the view
  • render the table with {% render_table table %} and do not wrap it in another .table-responsive
  • prefer microsys.tables.MicrosysTable for handwritten tables

The current table contract is:

  • stock django_tables2 templates 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 -> request per_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_density intentionally hide the footer density switcher
  • default row actions are micro:record:view, micro:record:edit, and micro: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 = 50

Filter pages also have a clearer contract now:

  • setup_filter_helper() and advanced_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, include microsys/forms/filter_assets_head.html

For the full contract and more examples, use:

Where to Go Next

  • 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.