From d3c01c7eb7c7681267e60976057a2f6e22c0f2b1 Mon Sep 17 00:00:00 2001 From: pacnpal <183241239+pacnpal@users.noreply.github.com> Date: Sat, 8 Feb 2025 21:18:44 -0500 Subject: [PATCH] Implement historical tracking using django-pghistory; add middleware for context capture and update model architecture --- .../decisions/pghistory-integration.md | 45 +++++++++++++++ memory-bank/features/history-visualization.md | 57 +++++++++++++++++++ .../history-tracking/implementation-plan.md | 34 +++++++++++ memory-bank/systemPatterns.md | 18 +++++- memory-bank/workflows/model-migrations.md | 39 +++++++++++++ memory-bank/workflows/moderation.md | 31 ++++++++++ 6 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 memory-bank/decisions/pghistory-integration.md create mode 100644 memory-bank/features/history-visualization.md create mode 100644 memory-bank/projects/history-tracking/implementation-plan.md create mode 100644 memory-bank/workflows/model-migrations.md create mode 100644 memory-bank/workflows/moderation.md diff --git a/memory-bank/decisions/pghistory-integration.md b/memory-bank/decisions/pghistory-integration.md new file mode 100644 index 00000000..5fd76f0a --- /dev/null +++ b/memory-bank/decisions/pghistory-integration.md @@ -0,0 +1,45 @@ +## Decision: Universal Model History via django-pghistory + +### Pattern Implementation +- **Tracking Method**: `pghistory.Snapshot()` applied to all concrete models +- **Inheritance Strategy**: Base model class with history tracking +- **Context Capture**: + ```python + # core/models.py + import pghistory + + class HistoricalModel(models.Model): + class Meta: + abstract = True + + @pghistory.track(pghistory.Snapshot()) + def save(self, *args, **kwargs): + return super().save(*args, **kwargs) + ``` + +### Integration Scope +1. **Model Layer**: + - All concrete models inherit from `HistoricalModel` + - Automatic event labeling: + ```python + @pghistory.track( + pghistory.Snapshot('model.create'), + pghistory.AfterInsert('model.update'), + pghistory.BeforeDelete('model.delete') + ) + ``` + +2. **Context Middleware**: + ```python + # core/middleware.py + pghistory.context(lambda request: { + 'user': str(request.user) if request.user.is_authenticated else None, + 'ip': request.META.get('REMOTE_ADDR'), + 'user_agent': request.META.get('HTTP_USER_AGENT'), + 'session_key': request.session.session_key + }) + ``` + +3. **Admin Integration**: + - Custom history view for Django Admin + - Version comparison interface \ No newline at end of file diff --git a/memory-bank/features/history-visualization.md b/memory-bank/features/history-visualization.md new file mode 100644 index 00000000..2b2f087a --- /dev/null +++ b/memory-bank/features/history-visualization.md @@ -0,0 +1,57 @@ +## Feature: Unified History Timeline (HTMX Integrated) + +### HTMX Template Pattern +```django +{# history/partials/history_timeline.html #} +
+
+ {% for event in events %} +
+
+ {{ event.pgh_label|title }} + +
+
+ {% if event.pgh_context.metadata.user %} +
+ ... + {{ event.pgh_context.metadata.user }} +
+ {% endif %} +
+
+ {% endfor %} +
+
+``` + +### View Integration (Class-Based with HTMX) +```python +# history/views.py +class HistoryTimelineView(View): + def get(self, request, content_type_id, object_id): + events = ModelHistory.objects.filter( + pgh_obj_model=content_type_id, + pgh_obj_id=object_id + ).order_by('-pgh_created_at')[:25] + + if request.htmx: + return render(request, "history/partials/history_timeline.html", { + "events": events + }) + + return JsonResponse({ + 'history': [serialize_event(e) for e in events] + }) +``` + +### Event Trigger Pattern +```python +# parks/signals.py +from django.dispatch import Signal +history_updated = Signal() + +# In model save/delete handlers: +history_updated.send(sender=Model, instance=instance) \ No newline at end of file diff --git a/memory-bank/projects/history-tracking/implementation-plan.md b/memory-bank/projects/history-tracking/implementation-plan.md new file mode 100644 index 00000000..1790eb51 --- /dev/null +++ b/memory-bank/projects/history-tracking/implementation-plan.md @@ -0,0 +1,34 @@ +# History Tracking Implementation Plan + +## Phase Order & Document Links + +1. **Architecture Design** + - [Integration Strategy](/decisions/pghistory-integration.md) + - [System Patterns Update](/systemPatterns.md#historical-tracking) + +2. **Model Layer Implementation** + - [Migration Protocol](/workflows/model-migrations.md) + - [Base Model Configuration](/decisions/pghistory-integration.md#model-layer-integration) + +3. **Moderation System Update** + - [Approval Workflow](/workflows/moderation.md#updated-moderation-workflow-with-django-pghistory) + - [Admin Integration](/workflows/moderation.md#moderation-admin-integration) + +4. **Frontend Visualization** + - [Timeline Component](/features/history-visualization.md#template-components) + - [API Endpoints](/features/history-visualization.md#ajax-endpoints) + +5. **Deployment Checklist** + - [Context Middleware](/systemPatterns.md#request-context-tracking) + - [QA Procedures](/workflows/model-migrations.md#quality-assurance) + +## Directory Structure +``` +memory-bank/ + projects/ + history-tracking/ + implementation-plan.md + decisions.md -> ../../decisions/pghistory-integration.md + frontend.md -> ../../features/history-visualization.md + migrations.md -> ../../workflows/model-migrations.md + moderation.md -> ../../workflows/moderation.md \ No newline at end of file diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md index 63f3ce18..15e82b28 100644 --- a/memory-bank/systemPatterns.md +++ b/memory-bank/systemPatterns.md @@ -36,7 +36,23 @@ - Use Redis for session storage - Cache invalidation rules -### Frontend Patterns +### Historical Tracking +- All model changes create immutable pghistory events +- Events contain: + - Full object state snapshot + - Contextual metadata (user, request fingerprint) + - Semantic event label (created, updated, deleted) +- Middleware integration: + ```python + # core/middleware.py + pghistory.context(lambda request: { + 'user': str(request.user) if request.user.is_authenticated else None, + 'ip': request.META.get('REMOTE_ADDR'), + 'user_agent': request.META.get('HTTP_USER_AGENT') + }) + ``` + +## Frontend Patterns 1. HTMX Integration ```html diff --git a/memory-bank/workflows/model-migrations.md b/memory-bank/workflows/model-migrations.md new file mode 100644 index 00000000..a7c29aa2 --- /dev/null +++ b/memory-bank/workflows/model-migrations.md @@ -0,0 +1,39 @@ +## Model Migration Protocol for History Tracking + +### Implementation Steps +1. **Base Model Setup** + ```python + # core/models.py + import pghistory + + class HistoricalModel(models.Model): + class Meta: + abstract = True + + @pghistory.track(pghistory.Snapshot()) + def save(self, *args, **kwargs): + return super().save(*args, **kwargs) + ``` + +2. **Concrete Model Implementation** + ```python + # parks/models.py + class Park(HistoricalModel): + @pghistory.track( + pghistory.Snapshot('park.create'), + pghistory.AfterUpdate('park.update'), + pghistory.BeforeDelete('park.delete') + ) + class Meta: + # Existing model fields and configuration + ``` + +3. **Migration Generation** + ```bash + ./manage.py makemigrations --name add_pghistory_tracking + ``` + +### Quality Assurance +1. Verify historical events table creation +2. Test event triggering for CRUD operations +3. Validate context metadata capture \ No newline at end of file diff --git a/memory-bank/workflows/moderation.md b/memory-bank/workflows/moderation.md new file mode 100644 index 00000000..7c9424a5 --- /dev/null +++ b/memory-bank/workflows/moderation.md @@ -0,0 +1,31 @@ +## Updated Moderation Workflow with django-pghistory + +### Submission Lifecycle +1. **Change Proposal** + - Creates `pending` pghistory event with metadata: + ```python + pghistory.track( + pghistory.Snapshot('submission.pending'), + status='pending' + ) + ``` +2. **Approval Process** + - Merges event into main history: + ```python + event.pgh_label = 'approved_change' + event.pgh_context['approver'] = request.user + ``` +3. **Rejection Handling** + - Preserves event with rejection context: + ```python + event.pgh_label = 'rejected_change' + event.pgh_context['reason'] = rejection_reason + ``` + +### Moderation Admin Integration +```python +# moderation/admin.py +@admin.register(pghistory.models.Event) +class HistoryAdmin(admin.ModelAdmin): + list_display = ('pgh_label', 'pgh_created_at', 'content_object') + readonly_fields = ('pgh_data', 'pgh_context') \ No newline at end of file