Skip to content

Parent/Child Table Editing - Reusing Components

Exploring how powercrud's existing table components can be reused for parent/child relationship editing within forms, if we already had inline editing for the main list.

The Idea

I'm thinking of a feature to make it easy to edit parent and child objects on the same object_form, because this is a very common requirement. We could use inline formsets, which is feasible. But a different pattern could be to have an editable htmx-enabled table of the child objects under the parent form. This article explores how much re-use we could achieve from existing powercrud elements.

Also see Inline Table Editing - What's Already Available.

Component Reusability Analysis

The existing table infrastructure is well-designed for reuse in parent/child editing scenarios. The modular template structure makes this approach both feasible and elegant.

Template Structure Modularity

1. Existing Separation of Concerns

The current template architecture demonstrates good separation:

  • object_list.html contains the main page structure (filters, pagination, modals)
  • partial/list.html is the core table renderer that can be embedded anywhere
  • The {% object_list object_list view %} template tag is context-agnostic

2. Proven Reuse Pattern

The filtered_results partial in object_list.html already demonstrates this pattern:

{% partialdef filtered_results %}
    {% if object_list %}
        {% object_list object_list view %}  <!-- Reusing the template tag -->
        {% partial pagination %}
    {% else %}
        <p class="mt-4">There are no {{ object_verbose_name_plural }}. Create one now?</p>
    {% endif %}
{% endpartialdef filtered_results %}

Implementation Approaches for Parent/Child Tables

Option 1: Template Tag Reuse (Simplest)

Add a section to object_form.html after the main form:

<!-- Child Objects Section -->
{% if child_objects %}
<div class="mt-6">
    <h2 class="text-lg font-semibold mb-4">Related {{ child_verbose_name_plural|capfirst }}</h2>
    <div id="child-table-container">
        {% object_list child_objects child_view %}
    </div>
</div>
{% endif %}

Option 2: Dedicated Child Table Partial

Create a new partial that wraps the list component with child-specific features:

{% partialdef child_table %}
<div class="border border-base-300 rounded-lg p-4">
    <div class="flex justify-between items-center mb-4">
        <h3 class="text-md font-medium">{{ child_model_name|title }} Records</h3>
        <button class="btn btn-sm btn-primary" 
                hx-get="{{ child_create_url }}" 
                hx-target="#child-form-modal"
                onclick="childModal.showModal()">
            Add {{ child_model_name|title }}
        </button>
    </div>
    {% object_list child_objects child_view %}
</div>
{% endpartialdef child_table %}

Technical Implementation Details

1. Context Requirements

The object_list template tag needs:

  • object_list - the child objects queryset (filtered by parent FK)
  • view - a view-like object with necessary attributes

2. View Object Adaptation

Create a lightweight view proxy for the child model:

class ChildTableView:
    def __init__(self, child_model, parent_view, parent_instance=None):
        self.model = child_model
        self.fields = self._get_child_display_fields()
        self.parent_instance = parent_instance

        # Inherit styling/framework methods from parent view
        self.get_framework_styles = parent_view.get_framework_styles
        self.get_action_button_classes = parent_view.get_action_button_classes
        self.get_use_htmx = parent_view.get_use_htmx

    def _get_child_display_fields(self):
        # Exclude the parent FK from display
        return [f.name for f in self.model._meta.get_fields() 
                if f.name != f'{self.parent_instance._meta.model_name}_id']

3. HTMX Integration Patterns

The existing HTMX infrastructure integrates seamlessly:

Child Table Updates:

<div id="child-table-{{ parent_object.pk }}">
    {% object_list child_objects child_view %}
</div>

Inline Editing for Child Records:

  • Child row edits target: #child-row-{{ child.pk }}
  • Child form submissions refresh: #child-table-{{ parent.pk }}
  • Parent form can also trigger child table refresh

HTMX Attribute Examples:

<!-- Child record edit button -->
<a hx-get="{{ child_edit_url }}" 
   hx-target="#child-row-{{ child.pk }}"
   hx-swap="outerHTML">Edit</a>

<!-- Child record save -->
<form hx-post="{{ child_update_url }}"
      hx-target="#child-table-{{ parent.pk }}"
      hx-swap="innerHTML">

4. Session Management for Bulk Operations

If using bulk operations on child records, implement separate session keys:

  • Parent bulk selections: parent_model_{{ parent.pk }}_bulk_selected
  • Child bulk selections: child_model_parent_{{ parent.pk }}_bulk_selected

This prevents conflicts between parent and child bulk operations.

Advantages of Component Reuse

1. Code Reuse Benefits

  • Leverage existing table rendering, sorting, pagination logic
  • Inherit all current and future table features automatically
  • Consistent styling and behavior patterns

2. Feature Inheritance

All existing table features work in child tables:

  • Bulk operations for child records
  • Inline editing capabilities
  • Sorting and filtering (if needed)
  • Column width management
  • Responsive design

3. Maintainability

  • Changes to core table rendering automatically apply to child tables
  • Single source of truth for table behavior
  • Consistent debugging and testing patterns

Potential Challenges and Solutions

1. Context Complexity

Challenge: Managing multiple view contexts in one template

Solution: Create context processors or view mixins that handle child context setup:

class ParentChildFormMixin:
    child_models = []  # Define child relationships

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.object:  # Editing existing parent
            for child_config in self.child_models:
                child_objects = self._get_child_objects(child_config)
                child_view = self._create_child_view(child_config)
                context[f'{child_config["name"]}_objects'] = child_objects
                context[f'{child_config["name"]}_view'] = child_view
        return context

2. HTMX Target Management

Challenge: Ensuring child table updates don't interfere with parent form

Solution: Use distinct target IDs and careful response handling:

def child_form_valid(self, form):
    # Save child object
    child = form.save()

    # Return child table partial, not full page
    response = render(request, 'child_table_partial.html', {
        'child_objects': self.get_child_objects(),
        'child_view': self.get_child_view()
    })

    response['HX-Trigger'] = json.dumps({
        'childSaved': True,
        'parentFormStayOpen': True
    })
    return response

3. Styling Conflicts

Challenge: Nested tables might have styling conflicts

Solution: Scoped CSS or child-specific table classes:

.child-table .table {
    font-size: 0.875rem; /* Smaller text for child tables */
}

.child-table .table th {
    background-color: theme('colors.base-200'); /* Lighter header */
}

1. Parent Form Enhancement

class AuthorUpdateView(ParentChildFormMixin, PowerCRUDMixin, CRUDView):
    model = Author
    child_models = [
        {
            'name': 'books',
            'model': Book,
            'fields': ['title', 'isbn', 'publication_date'],
            'fk_field': 'author'
        }
    ]

2. Template Structure

<!-- In object_form.html -->
{% partialdef normal_content %}
<!-- Parent form here -->

<!-- Child tables section -->
{% if books_objects %}
    {% include 'powercrud/daisyUI/partials/child_table.html' with child_objects=books_objects child_view=books_view child_name='books' %}
{% endif %}
{% endpartialdef normal_content %}

3. URL Structure

# Parent URLs
path('authors/', AuthorListView.as_view(), name='author-list'),
path('authors/<int:pk>/edit/', AuthorUpdateView.as_view(), name='author-update'),

# Child URLs (nested under parent)
path('authors/<int:parent_pk>/books/create/', BookCreateView.as_view(), name='author-book-create'),
path('authors/<int:parent_pk>/books/<int:pk>/edit/', BookUpdateView.as_view(), name='author-book-update'),

Conclusion

Reusing powercrud's existing table components for parent/child editing is not only feasible but represents excellent architectural practice. The modular design specifically enables this kind of component reuse, and the HTMX infrastructure handles the complexity of nested interactive elements.

The main implementation work involves:

  1. Creating child view proxy classes
  2. Adding child object context to parent form views
  3. Including child table sections in form templates
  4. Implementing proper HTMX targeting for nested components

This approach provides a powerful parent/child editing experience while maintaining code reuse and consistency with the existing powercrud design patterns.