Skip to content

SystemConfig App Reference

The SystemConfig app provides database-driven configurable dropdown choices for forms, allowing administrators to customize options without code changes. Choices are organization-specific with utility functions, login validation, template filters, and deletion protection.

Overview

Location: app/systemconfig/

Purpose: Configurable dropdown choices with utility functions and login validation

URL Namespace: /system/

Key Models: ChoiceCategory, ConfigurableChoice

Used By: Hives (hive styles, honey types, operation types), Breeding (methods, statuses), Warehouse (item types, units, payment methods), Organizations (settings form)


File Structure

systemconfig/
├── models.py              # 2 models (ChoiceCategory, ConfigurableChoice)
├── views.py               # 6 view classes + 1 permission mixin
├── forms.py               # ConfigurableChoiceForm
├── urls.py                # 6 URL patterns
├── utils.py               # 8 utility functions for forms/views
├── services.py            # Login validation service
├── signals.py             # 1 signal handler (user_logged_in)
├── apps.py                # AppConfig with post_migrate auto-category creation
├── admin.py               # Admin with inline editing + bulk delete protection
├── templatetags/
│   └── choice_filters.py  # 2 template filters (get_choice_display, translate)
├── management/commands/
│   └── load_default_categories.py  # CLI to load default categories + choices
├── tests/                 # Model, view, signal, and service tests
└── templates/systemconfig/
    ├── category_list.html
    ├── choice_list.html
    ├── choice_form.html
    └── choice_confirm_delete.html

Models

See Database Models - SystemConfig App for complete field reference.

ChoiceCategory

Represents a configurable category (e.g., "Hive Style", "Honey Type").

Fields: name, code (unique slug), description, order, is_active, created_at, updated_at

27 Default Categories (auto-created via post_migrate signal in apps.py): honey_type, hive_style, health_status, queen_origin, temperament, inspection_status, drone_presence, operation_status, operation_priority, feed_type, treatment_type, treatment_effectiveness, breeding_method, breeding_status, breeding_quality, combine_method, split_type, split_status, queen_replacement_method, queen_replacement_reason, maintenance_type, unit_type, equipment_type, supply_type, product_type, payment_method, transaction_type

Note: queen_marking_color is no longer configurable; it uses a fixed set of choices (White, Yellow, Red, Green, Blue, Unmarked) following the international beekeeping standard.

ConfigurableChoice

Individual choices within a category, belonging to a specific organization.

Fields: category (FK), organization (FK, nullable), label, value (auto-slug from label, not editable), description, order, is_active, is_default, brood_chamber_weight_kg, honey_chamber_weight_kg, honeycomb_weight_kg, created_at, updated_at

Constraint: Unique (category, value, organization)

Key Methods: - can_be_deleted() -- Checks all reverse FK relations with on_delete=PROTECT; returns (bool, blocking_objects_list) - delete() -- Overridden to call can_be_deleted() first; raises ValidationError if referenced

Save Hook: Auto-generates value slug from label with uniqueness check. When is_default=True, clears the default flag from other choices in the same category/organization scope.


Organization-Specific Choices

All choices belong to a specific organization. The organization field is required for non-staff users.

When querying choices for a form, use the utility functions which handle organization filtering:

from systemconfig.utils import populate_choice_field

# In a form's __init__ method:
populate_choice_field(
    self, 'feed_type', 'feed_type',
    organization=current_org
)

Views

OrganizationManagerOrStaffMixin

Permission control mixin restricting access to staff, org owners, and org admins. Includes user_can_edit_choice() for per-choice permission checks.

CRUD Views

View Purpose
CategoryListView List active categories with choice counts per organization
ChoiceListView List choices for a category with sorting (SortableListMixin)
ChoiceCreateView Create new choice with category from URL
ChoiceUpdateView Update choice with per-choice permission check
ChoiceDeleteView Delete choice; shows blocking objects if referenced
LoadDefaultCategoriesView POST-only; loads default categories and choices

Forms

ConfigurableChoiceForm

Fields: label, description, order, is_active, is_default, brood_chamber_weight_kg, honey_chamber_weight_kg, honeycomb_weight_kg, organization

Conditional Fields: Chamber weight fields (brood, honey, honeycomb) are hidden when category is not hive_style.

Dynamic Organization Field:

  • Superusers: Required, can select any organization
  • Org owners/admins (multiple orgs): Required dropdown of managed organizations
  • Org owners/admins (single org): Hidden, auto-set to managed organization

Validation: clean_organization() ensures the user has owner/admin role for the selected organization.


URL Patterns

system/                                  -> CategoryListView (systemconfig-home)
system/load-defaults/                    -> LoadDefaultCategoriesView (systemconfig-load-defaults)
system/<slug:category_code>/             -> ChoiceListView (systemconfig-choice-list)
system/<slug:category_code>/new/         -> ChoiceCreateView (systemconfig-choice-create)
system/choice/<int:pk>/edit/             -> ChoiceUpdateView (systemconfig-choice-update)
system/choice/<int:pk>/delete/           -> ChoiceDeleteView (systemconfig-choice-delete)

Utility Functions

The utils.py module provides helper functions for forms and views:

Function Purpose
get_queryset_for_category() QuerySet of choices for ModelChoiceField
get_default_choice() Get the default choice for a category
get_choice_value() Get slug value by primary key
get_choice_by_value() Reverse lookup: find choice by category and value
validate_choice_value() Check if a value exists in a category
category_exists() Check if an active category exists
get_choices_for_category() Tuple list for ChoiceField with warnings for empty categories
populate_choice_field() Set field choices and add configuration link to help text
get_config_link_help_text() Generate HTML link to the configuration page
add_config_link_help_text() Append configuration link to a field's help text

Usage in Forms

from systemconfig.utils import populate_choice_field

class MyForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        organization = kwargs.pop('organization', None)
        super().__init__(*args, **kwargs)

        # Populate field with choices and add config link
        populate_choice_field(
            self, 'unit', 'unit_type',
            organization=organization,
            include_blank=True,
            blank_label=_('Select unit')
        )

Services

Login Validation

On user login, the system checks all active categories for missing configuration:

  • validate_system_categories_for_user(user) -- Checks orgs where user is owner/admin
  • Creates system notifications for unconfigured categories
  • Uses 24-hour deduplication to prevent repeated notifications

Signals

handle_user_logged_in

  • Trigger: user_logged_in signal
  • Action: Calls validate_system_categories_for_user()
  • Error handling: All exceptions caught and logged; never blocks login

Template Tags

Two template filters in templatetags/choice_filters.py:

Filter Usage Purpose
get_choice_display {{ value\|get_choice_display:"category_code" }} Look up display label for a stored value
translate {{ category.name\|translate }} Apply Django translation to database strings

Management Commands

load_default_categories

Load default choice categories and sample choices:

python manage.py load_default_categories

Delegates to the sample data service. Safe to run multiple times (uses get_or_create).


When to Use ConfigurableChoice

Use ConfigurableChoice when:

  • Options should be user-customizable
  • Organizations need different options
  • The list of options may grow over time
  • No code logic depends on specific values

Use hardcoded choices when:

  • Options are fixed (e.g., yes/no, status values)
  • Code branches based on values
  • Only 2-3 options exist
  • Performance is critical (avoids extra queries)

See Also