Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions docs/source/development_manual/customization.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
Customization Guide
=================

This guide explains how to customize the Kompass application using configuration files and templates.

Comment on lines +1 to +5
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add this guide to the table of contents for the developer manual?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW: the documentation got deployed at https://chrisflav.github.io/kompass/2/merge/development_manual/customization. Unfortunately, I have no idea why it is deployed at this strange url 2/merge instead of MK/conditional_fields.

Configuration Files
-----------------

The application uses two main configuration files:

* ``settings.toml``: Contains core application settings
* ``text.toml``: Contains customizable text content

An example `settings.toml` can be found in `deploy/config/settings.toml`. For more details, see the following section.
settings.toml
~~~~~~~~~~~~

The ``settings.toml`` file contains all core configuration settings organized in sections:

.. code-block:: toml

[section]
name = "Your Section Name"
street = "Street Address"
town = "12345 Town"
# ... other section details

[LJP]
contribution_per_day = 25
tax = 0.1

[finance]
allowance_per_day = 22
max_night_cost = 11

Key sections include:

* ``[section]``: Organization details
* ``[LJP]``: Youth leadership program settings
* ``[finance]``: Financial configurations
* ``[misc]``: Miscellaneous application settings
* ``[mail]``: Email configuration
* ``[database]``: Database connection details
* ``[custom_model_fields]``: Customize visible model fields in admin interface (see below)

Customizing Model Fields
~~~~~~~~~~~~~~~~~~~~~~~

The ``[custom_model_fields]`` section in ``settings.toml`` allows you to customize which fields are visible in the admin interface:

.. code-block:: toml

[custom_model_fields]
# Format: applabel_modelname.fields = ['field1', 'field2']
# applabel_modelname.exclude = ['field3', 'field4']

# Example: Show only specific fields
members_emergencycontact.fields = ['prename', 'lastname', 'phone_number']

# Example: Exclude specific fields
members_member.exclude = ['ticket_no', 'dav_badge_no']

There are two ways to customize fields:

1. Using ``fields``: Explicitly specify which fields should be shown
- Only listed fields will be visible
- Overrides any existing field configuration
- Order of fields is preserved as specified

2. Using ``exclude``: Specify which fields should be hidden
- All fields except the listed ones will be visible
- Adds to any existing exclusions
- Original field order is maintained

Field customization applies to:
- Django admin views
- Admin forms
- Model admin fieldsets

.. note::
Custom forms must be modified manually as they are not affected by this configuration.

Text Content
-----------

The ``text.toml`` file allows customization of application text content:

.. code-block:: toml

[emails]
welcome_subject = "Welcome to {section_name}"
welcome_body = """
Dear {name},
Welcome to our organization...
"""

[messages]
success_registration = "Registration successful!"

Templates
---------

Template Customization
~~~~~~~~~~~~~~~~~~~~

You can override any template by placing a custom version in your project's templates directory:

1. Create a directory structure matching the original template path
2. Place your custom template file with the same name
3. Django will use your custom template instead of the default

Example directory structure::

templates/
└── members/
└── registration_form.tex
└── startpage/
└── contact.html
└── impressum_content.html


72 changes: 70 additions & 2 deletions jdav_web/contrib/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.urls import path, reverse
from django.db import models
from django.contrib.admin import helpers, widgets
from django.conf import settings
import rules.contrib.admin
from rules.permissions import perm_exists

Expand Down Expand Up @@ -107,7 +108,74 @@ def get_queryset(self, request):

#class ObjectPermissionsInlineModelAdminMixin(rules.contrib.admin.ObjectPermissionsInlineModelAdminMixin):

class CommonAdminMixin(FieldPermissionsAdminMixin, ChangeViewAdminMixin, FilteredQuerysetAdminMixin):
class FieldCustomizationMixin:
@property
def field_key(self):
"""returns the key to look if model has custom fields in settings"""
return f"{self.model._meta.app_label}_{self.model.__name__}".lower()

def get_excluded_fields(self):
"""if model has custom excluded fields in settings, return them as list"""
return settings.CUSTOM_MODEL_FIELDS.get(self.field_key, {}).get("exclude", [])

def get_included_fields(self):
"""if model has an entire fieldset in settings, return them as list"""
return settings.CUSTOM_MODEL_FIELDS.get(self.field_key, {}).get("fields", [])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return settings.CUSTOM_MODEL_FIELDS.get(self.field_key, {}).get("fields", [])
return settings.CUSTOM_MODEL_FIELDS.get(self.field_key, {}).get("fields", None)

Required for the change below. The same should be done for the exclude variant.


def get_fieldsets(self, request, obj=None):
"""filter fieldsets according to included and excluded fields in settings"""

# get original fields and user-defined included and excluded fields
original_fieldsets = super().get_fieldsets(request, obj)
included = self.get_included_fields()
excluded = self.get_excluded_fields()

new_fieldsets = []

for title, attrs in original_fieldsets:
fields = attrs.get("fields", [])

# custom fields take precedence over exclude
filtered_fields = [
f
for f in fields
if ((not included or f in included) and (included or f not in excluded))
]

if filtered_fields:
# only add fieldset if it has any fields left
new_fieldsets.append(
(title, dict(attrs, **{"fields": filtered_fields}))
)

return new_fieldsets

def get_fields(self, request, obj=None):
"""filter fields according to included and excluded fields in settings"""
fields = super().get_fields(request, obj) or []
excluded = super().get_exclude(request, obj) or []
custom_included = self.get_included_fields()
custom_excluded = self.get_excluded_fields()

if custom_included:
# custom included fields take precedence over exclude
return custom_included
Comment on lines +160 to +162
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if custom_included:
# custom included fields take precedence over exclude
return custom_included
if not custom_included is None:
# custom included fields take precedence over exclude
return custom_included

Otherwise, setting fields = [] will be ignored. Similarly elsewhere.

return [f for f in fields if f not in custom_excluded and f not in excluded]

def get_exclude(self, request, obj=None):
"""filter excluded fields according to included and excluded fields in settings"""
excluded = super().get_exclude(request, obj) or []
custom_included = self.get_included_fields()
custom_excluded = self.get_excluded_fields()

if custom_included:
# custom included fields take precedence over exclude
return list((set(excluded) | set(custom_excluded)) - set(custom_included))
return list(set(excluded) | set(custom_excluded))



class CommonAdminMixin(FieldPermissionsAdminMixin, ChangeViewAdminMixin, FilteredQuerysetAdminMixin, FieldCustomizationMixin):
def has_add_permission(self, request, obj=None):
assert obj is None
opts = self.opts
Expand Down Expand Up @@ -194,7 +262,7 @@ def formfield_for_dbfield(self, db_field, request, **kwargs):

# For any other type of field, just call its formfield() method.
return db_field.formfield(**kwargs)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change


class CommonAdminInlineMixin(CommonAdminMixin):
def has_add_permission(self, request, obj):
Expand Down
Loading