Files
home-information/docs/dev/shared/coding-patterns.md
Tony C 06bddccc6a Updated docs, claude agents and commands (#193)
* Working on improving AI agent docs.

* Update dev/docs and claude agent configs.

* Updated claude commands.
2025-09-17 17:19:51 +00:00

14 KiB

Coding Patterns

  • New modal dialogs extend one of the standard base classes in hi/templates/modals.
  • New model dialogs extend the HiModalView class.
  • The antinode.js framework and pattern is used for async/ajax content updates and modals.
  • Javascript uses jQuery for DOM manipulations.
  • Minimal business logic in templates: views prepare the template context needed
  • Minimal business logic in Django views: use helepr classes
  • No ORM calls in template tags
  • Only internal system icons are used (no font-awesome icon).
  • Appropriate icons are selected, or new icons created.

zzzzzzzz

Django Patterns

Model Design Patterns

Abstract Base Models

Use abstract models for common functionality:

class TimestampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        abstract = True

class Entity(IntegrationKeyMixin, TimestampedModel):
    name = models.CharField(max_length=100)
    # Inherits integration keys and timestamps

Custom Model Managers

Create custom managers for common query patterns:

class EntityManager(models.Manager):
    def active(self):
        """Get only active entities"""
        return self.filter(is_active=True)
    
    def with_sensors(self):
        """Get entities that have sensors"""
        return self.filter(sensors__isnull=False).distinct()
    
    def for_location_view(self, location_view):
        """Get entities visible in location view"""
        return self.filter(
            location=location_view.location,
            entityposition__isnull=False
        ).select_related('entity_type', 'location')

class Entity(models.Model):
    objects = EntityManager()
    # ... model fields ...

Model Property Patterns

Use properties for computed fields and business logic:

class Entity(models.Model):
    @property
    def has_recent_activity(self):
        """Check if entity had activity in last hour"""
        if not hasattr(self, '_recent_activity'):
            cutoff = timezone.now() - timedelta(hours=1)
            self._recent_activity = self.sensor_responses.filter(
                timestamp__gte=cutoff
            ).exists()
        return self._recent_activity
    
    @property
    def display_status(self):
        """Get human-readable status"""
        return self.get_current_state_display()
    
    def get_current_state_display(self):
        """Business logic for state display"""
        latest_response = self.get_latest_sensor_response()
        if not latest_response:
            return 'Unknown'
        return latest_response.get_display_value()

Model Method Patterns

Encapsulate business logic in model methods:

class Alert(models.Model):
    def get_first_visual_content(self):
        """Get first image or visual content for display"""
        for alarm in self.alarm_list:
            for source_detail in alarm.source_details_list:
                if source_detail.image_url:
                    return {
                        'type': 'image',
                        'url': source_detail.image_url,
                        'description': source_detail.description
                    }
        return None
    
    def get_priority_level(self):
        """Calculate priority based on alarm levels"""
        if not self.alarm_list:
            return 'low'
        
        max_level = max(alarm.level for alarm in self.alarm_list)
        if max_level >= AlarmLevel.CRITICAL:
            return 'critical'
        elif max_level >= AlarmLevel.WARNING:
            return 'high'
        return 'medium'

View Patterns

Class-Based View Mixins

Create reusable mixins for common view functionality:

class LocationViewContextMixin:
    """Add location view context to all views"""
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['current_location_view'] = self.get_current_location_view()
        context['available_location_views'] = LocationView.objects.all()
        return context
    
    def get_current_location_view(self):
        """Get location view from session or default"""
        location_view_id = self.request.session.get('location_view_id')
        if location_view_id:
            try:
                return LocationView.objects.get(id=location_view_id)
            except LocationView.DoesNotExist:
                pass
        return LocationView.objects.filter(is_default=True).first()

class EntityListView(LocationViewContextMixin, ListView):
    model = Entity
    template_name = 'entity/pages/entity_list.html'
    
    def get_queryset(self):
        location_view = self.get_current_location_view()
        if location_view:
            return Entity.objects.for_location_view(location_view)
        return Entity.objects.active()

Form Processing Patterns

Standard form handling with proper error handling:

class EntityUpdateView(UpdateView):
    model = Entity
    form_class = EntityForm
    template_name = 'entity/pages/entity_edit.html'
    
    def form_valid(self, form):
        """Handle successful form submission"""
        try:
            # Perform any additional business logic
            entity = form.save(commit=False)
            entity.updated_by = self.request.user
            entity.save()
            
            # Add success message
            messages.success(
                self.request,
                f"Entity '{entity.name}' updated successfully."
            )
            
            return super().form_valid(form)
            
        except ValidationError as e:
            form.add_error(None, e.message)
            return self.form_invalid(form)
        except Exception as e:
            form.add_error(None, "An unexpected error occurred.")
            logger.exception(f"Error updating entity {entity.id}: {e}")
            return self.form_invalid(form)
    
    def get_success_url(self):
        """Redirect based on user action"""
        if 'save_and_continue' in self.request.POST:
            return reverse('entity_edit', kwargs={'pk': self.object.pk})
        return reverse('entity_list')

AJAX View Patterns

Handle both regular and AJAX requests:

class EntityActionView(View):
    def post(self, request, *args, **kwargs):
        entity = get_object_or_404(Entity, id=kwargs['entity_id'])
        action = request.POST.get('action')
        
        try:
            result = self.perform_action(entity, action)
            
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return JsonResponse({
                    'status': 'success',
                    'message': f"Action '{action}' completed successfully",
                    'result': result,
                    'entity_id': entity.id
                })
            else:
                messages.success(request, f"Action '{action}' completed successfully")
                return redirect('entity_detail', pk=entity.id)
                
        except Exception as e:
            error_msg = f"Failed to perform action '{action}': {str(e)}"
            
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return JsonResponse({
                    'status': 'error',
                    'message': error_msg
                }, status=400)
            else:
                messages.error(request, error_msg)
                return redirect('entity_detail', pk=entity.id)
    
    def perform_action(self, entity, action):
        """Override in subclasses for specific actions"""
        raise NotImplementedError("Subclasses must implement perform_action")

Custom Template Tags and Filters

Inclusion Tags for Reusable Components

# In templatetags/entity_tags.py
from django import template
from django.template.loader import render_to_string

register = template.Library()

@register.inclusion_tag('entity/tags/entity_status_badge.html')
def entity_status_badge(entity, size='normal'):
    """Render entity status badge with appropriate styling"""
    status = entity.get_current_status()
    css_class = f'badge-{status.get_css_class()}'
    
    return {
        'entity': entity,
        'status': status,
        'css_class': css_class,
        'size': size,
    }

@register.simple_tag
def entity_action_url(entity, action):
    """Generate URL for entity actions"""
    return reverse('entity_action', kwargs={
        'entity_id': entity.id,
        'action': action
    })

Custom Filters

@register.filter
def time_since_activity(entity):
    """Get human-readable time since last activity"""
    latest_response = entity.get_latest_sensor_response()
    if not latest_response:
        return "Never"
    
    return timesince(latest_response.timestamp)

@register.filter
def status_css_class(entity_state):
    """Get CSS class for entity state"""
    # For complete status display implementation including CSS classes,
    # colors, and visual progression, see [Entity Status Display](../frontend/entity-status-display.md)
    status_map = {
        'active': 'status-active',
        'recent': 'status-recent',
        'past': 'status-past',
        'idle': 'status-idle',
        'unknown': 'status-unknown',
    }
    return status_map.get(entity_state.lower(), 'status-unknown')

Django Admin Customization

Custom Admin Classes

# In admin.py
@admin.register(Entity)
class EntityAdmin(admin.ModelAdmin):
    list_display = ['name', 'entity_type', 'location', 'is_active', 'last_updated']
    list_filter = ['entity_type', 'location', 'is_active', 'created_at']
    search_fields = ['name', 'integration_id']
    readonly_fields = ['created_at', 'updated_at', 'integration_key']
    
    fieldsets = (
        ('Basic Information', {
            'fields': ('name', 'entity_type', 'location', 'is_active')
        }),
        ('Integration', {
            'fields': ('integration_name', 'integration_id', 'integration_key'),
            'classes': ('collapse',)
        }),
        ('Metadata', {
            'fields': ('created_at', 'updated_at'),
            'classes': ('collapse',)
        }),
    )
    
    def get_queryset(self, request):
        """Optimize queries for admin list view"""
        return super().get_queryset(request).select_related(
            'entity_type', 'location'
        )
    
    def last_updated(self, obj):
        """Custom column for last update time"""
        return obj.updated_at.strftime('%Y-%m-%d %H:%M')
    last_updated.short_description = 'Last Updated'
    last_updated.admin_order_field = 'updated_at'

Signal Patterns

Model Signals for Business Logic

# In models.py or signals.py
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver

@receiver(post_save, sender=Entity)
def entity_post_save(sender, instance, created, **kwargs):
    """Handle entity creation/update"""
    if created:
        # Initialize default entity states
        for state_type in EntityStateType.get_defaults_for_entity_type(instance.entity_type):
            EntityState.objects.get_or_create(
                entity=instance,
                entity_state_type=state_type,
                defaults={'current_value': state_type.default_value}
            )
    
    # Update search index or cache
    search_index.update_entity(instance)
    cache.delete(f'entity:{instance.id}')

@receiver(pre_delete, sender=EntityImage)
def cleanup_entity_image(sender, instance, **kwargs):
    """Clean up image files before deleting model"""
    if instance.image:
        if os.path.isfile(instance.image.path):
            os.remove(instance.image.path)

Migration Patterns

Data Migrations

# In migration file
from django.db import migrations

def populate_entity_states(apps, schema_editor):
    """Populate entity states for existing entities"""
    Entity = apps.get_model('entity', 'Entity')
    EntityState = apps.get_model('entity', 'EntityState')
    EntityStateType = apps.get_model('entity', 'EntityStateType')
    
    default_state_type = EntityStateType.objects.get(name='status')
    
    for entity in Entity.objects.filter(states__isnull=True):
        EntityState.objects.create(
            entity=entity,
            entity_state_type=default_state_type,
            current_value='unknown'
        )

def reverse_populate_entity_states(apps, schema_editor):
    """Reverse migration - remove populated states"""
    EntityState = apps.get_model('entity', 'EntityState')
    EntityState.objects.filter(current_value='unknown').delete()

class Migration(migrations.Migration):
    dependencies = [
        ('entity', '0005_add_entity_state_model'),
    ]
    
    operations = [
        migrations.RunPython(
            populate_entity_states,
            reverse_populate_entity_states
        ),
    ]

Schema Migrations with Indexes

class Migration(migrations.Migration):
    atomic = False  # For PostgreSQL index creation
    
    operations = [
        migrations.AddIndex(
            model_name='sensorresponse',
            index=models.Index(
                fields=['sensor', '-timestamp'],
                name='sensor_response_sensor_time_idx'
            ),
        ),
        migrations.RunSQL(
            "CREATE INDEX CONCURRENTLY IF NOT EXISTS entity_integration_key_idx ON entity_entity (integration_name, integration_id);",
            reverse_sql="DROP INDEX IF EXISTS entity_integration_key_idx;"
        ),
    ]