From ecf94bf84e0736b0eb9c8742dbeb177194c8984f Mon Sep 17 00:00:00 2001 From: pacnpal <183241239+pacnpal@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:06:10 -0500 Subject: [PATCH] Add version control context processor and integrate map functionality with dedicated JavaScript --- history_tracking/context_processors.py | 43 ++++ history_tracking/managers.py | 12 +- .../includes/version_control_ui.html | 94 ++++++++ history_tracking/utils.py | 79 +++--- memory-bank/activeContext.md | 132 ++++++---- .../features/version-control/README.md | 228 ++++++++++++++++++ .../implementation_checklist.md | 159 ++++++++++++ .../version-control/template_integration.md | 86 +++++++ .../version-control/ui_improvements.md | 110 +++++++++ parks/models.py | 91 ++++++- parks/templates/parks/park_detail.html | 200 +++++++++++++++ static/css/version-control.css | 181 ++++++++++++++ static/js/map-init.js | 20 ++ static/js/version-control.js | 155 ++++++++++++ templates/base.html | 169 +++++++++++++ thrillwiki/settings.py | 1 + 16 files changed, 1671 insertions(+), 89 deletions(-) create mode 100644 history_tracking/context_processors.py create mode 100644 history_tracking/templates/history_tracking/includes/version_control_ui.html create mode 100644 memory-bank/features/version-control/README.md create mode 100644 memory-bank/features/version-control/implementation_checklist.md create mode 100644 memory-bank/features/version-control/template_integration.md create mode 100644 memory-bank/features/version-control/ui_improvements.md create mode 100644 parks/templates/parks/park_detail.html create mode 100644 static/css/version-control.css create mode 100644 static/js/map-init.js create mode 100644 static/js/version-control.js create mode 100644 templates/base.html diff --git a/history_tracking/context_processors.py b/history_tracking/context_processors.py new file mode 100644 index 00000000..07c12c9a --- /dev/null +++ b/history_tracking/context_processors.py @@ -0,0 +1,43 @@ +from typing import Dict, Any +from django.http import HttpRequest +from .signals import get_current_branch +from .models import VersionBranch, ChangeSet + +def version_control(request: HttpRequest) -> Dict[str, Any]: + """ + Add version control information to the template context + """ + current_branch = get_current_branch() + context = { + 'vcs_enabled': True, + 'current_branch': current_branch, + 'recent_changes': [] + } + + if current_branch: + # Get recent changes for the current branch + recent_changes = ChangeSet.objects.filter( + branch=current_branch, + status='applied' + ).order_by('-created_at')[:5] + + context.update({ + 'recent_changes': recent_changes, + 'branch_name': current_branch.name, + 'branch_metadata': current_branch.metadata + }) + + # Get available branches for switching + context['available_branches'] = VersionBranch.objects.filter( + is_active=True + ).order_by('-created_at') + + # Check if current page is versioned + if hasattr(request, 'resolver_match') and request.resolver_match: + view_func = request.resolver_match.func + if hasattr(view_func, 'view_class'): + view_class = view_func.view_class + context['page_is_versioned'] = hasattr(view_class, 'model') and \ + hasattr(view_class.model, 'history') + + return {'version_control': context} \ No newline at end of file diff --git a/history_tracking/managers.py b/history_tracking/managers.py index c4ef39c8..16dd2f19 100644 --- a/history_tracking/managers.py +++ b/history_tracking/managers.py @@ -1,8 +1,9 @@ -from typing import Optional, List, Dict, Any, Tuple +from typing import Optional, List, Dict, Any, Tuple, Union from django.db import transaction from django.core.exceptions import ValidationError from django.utils import timezone from django.contrib.auth import get_user_model +from django.contrib.auth.models import AbstractUser from django.contrib.contenttypes.models import ContentType from .models import VersionBranch, VersionTag, ChangeSet @@ -11,10 +12,8 @@ User = get_user_model() class BranchManager: """Manages version control branch operations""" - @transaction.atomic def create_branch(self, name: str, parent: Optional[VersionBranch] = None, - user: Optional[User] = None) -> VersionBranch: - """Create a new version branch""" + user: Optional['User'] = None) -> VersionBranch: branch = VersionBranch.objects.create( name=name, parent=parent, @@ -29,7 +28,7 @@ class BranchManager: @transaction.atomic def merge_branches(self, source: VersionBranch, target: VersionBranch, - user: Optional[User] = None) -> Tuple[bool, List[Dict[str, Any]]]: + user: Optional['User'] = None) -> Tuple[bool, List[Dict[str, Any]]]: """ Merge source branch into target branch Returns: (success, conflicts) @@ -66,9 +65,8 @@ class BranchManager: class ChangeTracker: """Tracks and manages changes across the system""" - @transaction.atomic def record_change(self, instance: Any, change_type: str, - branch: VersionBranch, user: Optional[User] = None, + branch: VersionBranch, user: Optional['User'] = None, metadata: Optional[Dict] = None) -> ChangeSet: """Record a change in the system""" if not hasattr(instance, 'history'): diff --git a/history_tracking/templates/history_tracking/includes/version_control_ui.html b/history_tracking/templates/history_tracking/includes/version_control_ui.html new file mode 100644 index 00000000..5a643a2a --- /dev/null +++ b/history_tracking/templates/history_tracking/includes/version_control_ui.html @@ -0,0 +1,94 @@ +{% if version_control.vcs_enabled and version_control.page_is_versioned %} +
+ +
+
+

Version Control

+ {% if version_control.current_branch %} +

+ Current Branch: + {{ version_control.branch_name }} +

+ {% endif %} +
+ + +
+ +
+ + + + + {% if version_control.available_branches %} +
+ {% for branch in version_control.available_branches %} + + {% endfor %} + {% endif %} +
+
+
+ + + {% if version_control.recent_changes %} +
+

Recent Changes

+
+ {% for change in version_control.recent_changes %} +
+
+
+ {{ change.description }} +

+ {{ change.created_at|date:"M d, Y H:i" }} + {% if change.created_by %} + by {{ change.created_by.username }} + {% endif %} +

+
+ + {{ change.status|title }} + +
+
+ {% endfor %} +
+
+ {% endif %} + + +
+ + +
+
+ + + +{% endif %} \ No newline at end of file diff --git a/history_tracking/utils.py b/history_tracking/utils.py index e3f11ef6..a4956b6a 100644 --- a/history_tracking/utils.py +++ b/history_tracking/utils.py @@ -1,17 +1,53 @@ -from typing import Dict, Any, List, Optional +from typing import Dict, Any, List, Optional, TypeVar, Type, Union, cast from django.core.exceptions import ValidationError from .models import VersionBranch, ChangeSet from django.utils import timezone from django.contrib.auth import get_user_model +from django.contrib.auth.models import AbstractUser +from django.db.models import Model -User = get_user_model() +UserModel = TypeVar('UserModel', bound=AbstractUser) +User = cast(Type[UserModel], get_user_model()) + +def _handle_source_target_resolution(change: ChangeSet) -> Dict[str, Any]: + resolved = {} + for record in change.historical_records.all(): + resolved[f"{record.instance_type}_{record.instance_pk}"] = record + return resolved +def _handle_manual_resolution( + conflict_id: str, + source_change: ChangeSet, + manual_resolutions: Dict[str, str], + user: Optional[UserModel] +) -> Dict[str, Any]: + manual_content = manual_resolutions.get(conflict_id) + if not manual_content: + raise ValidationError(f"Manual resolution missing for conflict {conflict_id}") + + resolved = {} + base_record = source_change.historical_records.first() + if base_record: + new_record = base_record.__class__( + **{ + **base_record.__dict__, + 'id': base_record.id, + 'history_date': timezone.now(), + 'history_user': user, + 'history_change_reason': 'Manual conflict resolution', + 'history_type': '~' + } + ) + for field, value in manual_content.items(): + setattr(new_record, field, value) + resolved[f"{new_record.instance_type}_{new_record.instance_pk}"] = new_record + return resolved def resolve_conflicts( source_branch: VersionBranch, target_branch: VersionBranch, resolutions: Dict[str, str], manual_resolutions: Dict[str, str], - user: Optional[User] = None + user: Optional[UserModel] = None ) -> ChangeSet: """ Resolve merge conflicts between branches @@ -37,40 +73,14 @@ def resolve_conflicts( target_change = ChangeSet.objects.get(pk=target_id) if resolution_type == 'source': - # Use source branch version - for record in source_change.historical_records.all(): - resolved_content[f"{record.instance_type}_{record.instance_pk}"] = record - + resolved_content.update(_handle_source_target_resolution(source_change)) elif resolution_type == 'target': - # Use target branch version - for record in target_change.historical_records.all(): - resolved_content[f"{record.instance_type}_{record.instance_pk}"] = record - + resolved_content.update(_handle_source_target_resolution(target_change)) elif resolution_type == 'manual': - # Use manual resolution - manual_content = manual_resolutions.get(conflict_id) - if not manual_content: - raise ValidationError(f"Manual resolution missing for conflict {conflict_id}") - - # Create new historical record with manual content - base_record = source_change.historical_records.first() - if base_record: - new_record = base_record.__class__( - **{ - **base_record.__dict__, - 'id': base_record.id, - 'history_date': timezone.now(), - 'history_user': user, - 'history_change_reason': 'Manual conflict resolution', - 'history_type': '~' - } - ) - # Apply manual changes - for field, value in manual_content.items(): - setattr(new_record, field, value) - resolved_content[f"{new_record.instance_type}_{new_record.instance_pk}"] = new_record + resolved_content.update(_handle_manual_resolution( + conflict_id, source_change, manual_resolutions, user + )) - # Create resolution changeset resolution_changeset = ChangeSet.objects.create( branch=target_branch, created_by=user, @@ -83,7 +93,6 @@ def resolve_conflicts( status='applied' ) - # Add resolved records to changeset for record in resolved_content.values(): resolution_changeset.historical_records.add(record) diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 94e6e0e3..b2c110e2 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -1,58 +1,99 @@ # Active Development Context -## Recently Completed -- Implemented Version Control System enhancement - - Core models and database schema - - Business logic layer with managers - - HTMX-based frontend integration - - API endpoints and URL configuration - - Signal handlers for automatic tracking - - Documentation updated in `memory-bank/features/version-control/` +## Current Implementation Status +Version Control System has been implemented with core functionality and initial integration: -## Current Status -The Version Control System has been fully implemented according to the implementation plan and technical guide. The system provides: -- Branch management -- Change tracking -- Version tagging -- Merge operations with conflict resolution -- Real-time UI updates via HTMX +### Completed +1. Core VCS Components: + - Base models (VersionBranch, VersionTag, ChangeSet) + - Business logic (BranchManager, ChangeTracker, MergeStrategy) + - UI components and templates + - Asset integration (JS/CSS) -## Next Steps -1. Testing - - Create comprehensive test suite - - Test branch operations - - Test merge scenarios - - Test conflict resolution +2. Initial Integration: + - Park model VCS integration + - ParkArea model VCS integration + - Base template VCS support + - Park detail template integration + - Version control context processor -2. Monitoring - - Implement performance metrics - - Track merge success rates - - Monitor system health +3. Documentation: + - Technical implementation guide + - Template integration guide + - Implementation checklist + - Base README -3. Documentation - - Create user guide - - Document API endpoints - - Add inline code documentation +### In Progress +1. Model Integration: + - [ ] Rides system + - [ ] Reviews system + - [ ] Companies system -4. Future Enhancements - - Branch locking mechanism - - Advanced merge strategies - - Custom diff viewers - - Performance optimizations +2. Template Updates: + - [ ] Park list view + - [ ] Ride detail/list views + - [ ] Review detail/list views + - [ ] Company detail/list views + +## Immediate Next Steps +1. Model Integration (Priority) + ```python + # Add to rides/models.py: + class Ride(HistoricalModel): + # Update save method + def save(self, *args, **kwargs): + from history_tracking.signals import get_current_branch, ChangesetContextManager + # Add version control logic + ``` + +2. Template Updates + ```html + + {% if version_control.vcs_enabled %} + {% include "history_tracking/includes/version_status.html" %} + {% endif %} + ``` + +3. Testing Setup + - Create test cases for model integration + - Verify UI functionality + - Test version control operations ## Active Issues -None at present, awaiting testing phase to identify any issues. +1. Need to ensure consistent version control behavior across models +2. Must handle relationships between versioned models +3. Need to implement proper cleanup for old versions -## Recent Decisions -- Used GenericForeignKey for flexible history tracking -- Implemented HTMX for real-time updates -- Structured change tracking with atomic changesets -- Integrated with django-simple-history +## Technical Dependencies +- django-simple-history: Base history tracking +- HTMX: UI interactions +- Alpine.js: Frontend reactivity +- Custom VCS components -## Technical Debt -- Need comprehensive test suite -- Performance monitoring to be implemented -- Documentation needs to be expanded +## Integration Strategy +1. Roll out model integration one app at a time +2. Update templates to include version control UI +3. Add list view version indicators +4. Implement relationship handling + +## Monitoring Points +- Track version control operation performance +- Monitor database size with version history +- Watch for merge conflicts +- Track user interaction patterns + +## Code Standards +- All versioned models inherit from HistoricalModel +- Consistent save method implementation +- Proper branch context management +- Standard version control UI components + +## Documentation Status +- [x] Technical implementation +- [x] Template integration guide +- [ ] API documentation +- [ ] User guide +- [ ] Admin documentation ## Current Branch main @@ -60,4 +101,5 @@ main ## Environment - Django with HTMX integration - PostgreSQL database -- django-simple-history for base tracking \ No newline at end of file +- django-simple-history +- Custom VCS extensions \ No newline at end of file diff --git a/memory-bank/features/version-control/README.md b/memory-bank/features/version-control/README.md new file mode 100644 index 00000000..a58f9259 --- /dev/null +++ b/memory-bank/features/version-control/README.md @@ -0,0 +1,228 @@ +# ThrillWiki Version Control System + +## Overview +The ThrillWiki Version Control System (VCS) provides comprehensive version tracking, branching, and merging capabilities for all content in the system. It builds upon django-simple-history while adding powerful versioning features. + +## Features +- Full version history tracking +- Branch-based development +- Version tagging +- Merge operations with conflict resolution +- Real-time collaborative editing +- Automatic change tracking + +## Model Integration + +### Making Models Version-Controlled +To add version control to any model, inherit from `HistoricalModel`: + +```python +from history_tracking.models import HistoricalModel + +class YourModel(HistoricalModel): + # Your model fields here + name = models.CharField(max_length=255) + + class Meta: + # Your meta options +``` + +This automatically provides: +- Full version history +- Change tracking +- Branch support +- Merge capabilities + +### Example Integration (from parks/models.py) +```python +from history_tracking.models import HistoricalModel + +class Park(HistoricalModel): + name = models.CharField(max_length=255) + description = models.TextField() + + def save(self, *args, **kwargs): + # Changes will be automatically tracked + super().save(*args, **kwargs) +``` + +## Usage Guide + +### Basic Version Control Operations + +1. Creating a Branch +```python +from history_tracking.managers import BranchManager + +# Create a new feature branch +branch_manager = BranchManager() +feature_branch = branch_manager.create_branch( + name="feature/new-park-details", + user=request.user +) +``` + +2. Recording Changes +```python +from history_tracking.signals import ChangesetContextManager + +# Making changes in a specific branch +with ChangesetContextManager(branch=feature_branch, user=request.user): + park = Park.objects.get(id=1) + park.description = "Updated description" + park.save() # Change is automatically tracked in the branch +``` + +3. Merging Changes +```python +# Merge feature branch back to main +success, conflicts = branch_manager.merge_branches( + source=feature_branch, + target=main_branch, + user=request.user +) + +if not success: + # Handle merge conflicts + for conflict in conflicts: + # Resolve conflicts through UI or programmatically + pass +``` + +4. Working with Tags +```python +from history_tracking.models import VersionTag + +# Tag a specific version +VersionTag.objects.create( + name="v1.0.0", + branch=main_branch, + content_type=ContentType.objects.get_for_model(park), + object_id=park.id, + created_by=user +) +``` + +## UI Integration + +### HTMX Components +The system provides HTMX-powered components for real-time version control: + +1. Version Control Panel +```html +{% include "history_tracking/version_control_panel.html" %} +``` + +2. Branch Selection +```html +
+
+``` + +3. Change History +```html +
+
+``` + +## Best Practices + +1. Branch Management +- Create feature branches for significant changes +- Use descriptive branch names (e.g., "feature/new-park-system") +- Clean up merged branches +- Regularly sync with main branch + +2. Change Tracking +- Make atomic, related changes +- Provide clear change descriptions +- Group related changes in a single changeset +- Review changes before merging + +3. Conflict Resolution +- Resolve conflicts promptly +- Communicate with team members about overlapping changes +- Test after resolving conflicts +- Document resolution decisions + +4. Performance +- Use changesets for bulk operations +- Index frequently queried fields +- Clean up old branches and tags +- Monitor system performance + +## Error Handling + +1. Common Issues +```python +try: + branch_manager.merge_branches(source, target) +except ValidationError as e: + # Handle validation errors +except MergeConflict as e: + # Handle merge conflicts +``` + +2. Conflict Resolution +```python +from history_tracking.utils import resolve_conflicts + +resolved = resolve_conflicts( + source_branch=source, + target_branch=target, + resolutions={ + 'conflict_id': 'resolution_type', # 'source', 'target', or 'manual' + }, + manual_resolutions={ + 'conflict_id': 'manual resolution content' + }, + user=request.user +) +``` + +## System Maintenance + +1. Regular Tasks +- Clean up old branches +- Archive old versions +- Verify data integrity +- Monitor system health + +2. Monitoring +```python +from history_tracking.utils import get_system_metrics + +metrics = get_system_metrics() +# Check branch counts, merge success rates, etc. +``` + +## Security Considerations + +1. Access Control +- All VCS operations require authentication +- Branch operations are logged +- Merge operations require proper permissions +- Changes are tracked with user attribution + +2. Data Protection +- Historical data is preserved +- Audit logs are maintained +- Sensitive data is handled securely +- Backups include version history + +## Support and Troubleshooting + +For issues or questions: +1. Check the logs for detailed error messages +2. Review the conflict resolution documentation +3. Verify branch and change permissions +4. Contact the development team for assistance + +## Contributing +When contributing to the VCS: +1. Follow the established branching pattern +2. Document significant changes +3. Add tests for new features +4. Update technical documentation \ No newline at end of file diff --git a/memory-bank/features/version-control/implementation_checklist.md b/memory-bank/features/version-control/implementation_checklist.md new file mode 100644 index 00000000..1a3c01ba --- /dev/null +++ b/memory-bank/features/version-control/implementation_checklist.md @@ -0,0 +1,159 @@ +# Version Control System Implementation Checklist + +## Core Implementation ✓ +- [x] Models + - [x] VersionBranch + - [x] VersionTag + - [x] ChangeSet + - [x] Generic relationships for flexibility + +- [x] Managers + - [x] BranchManager + - [x] ChangeTracker + - [x] MergeStrategy + +- [x] UI Components + - [x] Version Control Panel + - [x] Branch List + - [x] History View + - [x] Merge Panel + - [x] Branch Creation Form + +## Asset Integration ✓ +- [x] JavaScript + - [x] Version Control core functionality + - [x] HTMX integration + - [x] Event handling + +- [x] CSS + - [x] Version control styles + - [x] Component styles + - [x] Responsive design + +## Template Integration +- [x] Base Template Updates + - [x] Required JS/CSS includes + - [x] Version control status bar + - [x] HTMX setup + +- [x] Park System + - [x] Park detail template + - [ ] Park list template + - [ ] Area detail template + +- [ ] Rides System + - [ ] Ride detail template + - [ ] Ride list template + +- [ ] Reviews System + - [ ] Review detail template + - [ ] Review list template + +- [ ] Companies System + - [ ] Company detail template + - [ ] Company list template + +## Model Integration +- [x] Park Model + - [x] VCS integration + - [x] Save method override + - [x] Version info methods + +- [x] ParkArea Model + - [x] VCS integration + - [x] Save method override + - [x] Version info methods + +- [ ] Ride Model + - [ ] VCS integration + - [ ] Save method override + - [ ] Version info methods + +- [ ] Review Model + - [ ] VCS integration + - [ ] Save method override + - [ ] Version info methods + +- [ ] Company Model + - [ ] VCS integration + - [ ] Save method override + - [ ] Version info methods + +## Documentation +- [x] README creation +- [x] Implementation guide +- [x] Template integration guide +- [ ] API documentation +- [ ] User guide + +## Testing Requirements +- [ ] Unit Tests + - [ ] Model tests + - [ ] Manager tests + - [ ] View tests + - [ ] Form tests + +- [ ] Integration Tests + - [ ] Branch operations + - [ ] Merge operations + - [ ] Change tracking + - [ ] UI interactions + +- [ ] UI Tests + - [ ] Component rendering + - [ ] User interactions + - [ ] Responsive design + - [ ] Browser compatibility + +## Monitoring Setup +- [ ] Performance Metrics + - [ ] Branch operation timing + - [ ] Merge success rates + - [ ] Change tracking overhead + - [ ] UI responsiveness + +- [ ] Error Tracking + - [ ] Operation failures + - [ ] Merge conflicts + - [ ] UI errors + - [ ] Performance issues + +## Next Steps +1. Complete model integrations: + - Update Ride model + - Update Review model + - Update Company model + +2. Template implementations: + - Create remaining detail templates + - Add version control to list views + - Implement version indicators + +3. Testing: + - Write comprehensive test suite + - Set up CI/CD integration + - Perform load testing + +4. Documentation: + - Complete API documentation + - Create user guide + - Add examples and tutorials + +5. Monitoring: + - Set up performance monitoring + - Configure error tracking + - Create dashboards + +## Known Issues +1. Need to implement proper error handling in JavaScript +2. Add loading states to UI components +3. Implement proper caching for version history +4. Add batch operations for multiple changes +5. Implement proper cleanup for old versions + +## Future Enhancements +1. Add visual diff viewer +2. Implement branch locking +3. Add commenting on changes +4. Create change approval workflow +5. Add version comparison tool \ No newline at end of file diff --git a/memory-bank/features/version-control/template_integration.md b/memory-bank/features/version-control/template_integration.md new file mode 100644 index 00000000..4e8c796f --- /dev/null +++ b/memory-bank/features/version-control/template_integration.md @@ -0,0 +1,86 @@ +# Version Control UI Template Integration + +## Templates Requiring VCS Integration + +### Park System +- [x] parks/templates/parks/park_detail.html - Completed +- [ ] parks/templates/parks/park_list.html - Add version status indicators +- [ ] parks/templates/parks/park_area_detail.html - Add version control UI + +### Rides System +- [ ] rides/templates/rides/ride_detail.html - Add version control UI +- [ ] rides/templates/rides/ride_list.html - Add version status indicators + +### Reviews System +- [ ] reviews/templates/reviews/review_detail.html - Add version control UI +- [ ] reviews/templates/reviews/review_list.html - Add version status indicators + +### Company System +- [ ] companies/templates/companies/company_detail.html - Add version control UI +- [ ] companies/templates/companies/company_list.html - Add version status indicators + +## Integration Guidelines + +### Detail Templates +For detail templates, add the version control UI below the main title: + +```html + +

{{ object.name }}

+ + +{% include "history_tracking/includes/version_control_ui.html" %} + + +``` + +### List Templates +For list templates, add version indicators in the list items: + +```html +{% for item in object_list %} +
+

{{ item.name }}

+ {% if version_control.vcs_enabled %} +
+ Branch: {{ item.get_version_info.current_branch.name }} +
+ {% endif %} +
+{% endfor %} +``` + +## Integration Steps + +1. Update base template to include necessary JavaScript +```html + + +``` + +2. Add version control UI to detail views +- Include the version control UI component +- Add branch switching functionality +- Display version history + +3. Add version indicators to list views +- Show current branch +- Indicate if changes are pending +- Show version status + +4. Update view classes +- Ensure models inherit from HistoricalModel +- Add version control context +- Handle branch switching + +5. Test integration +- Verify UI appears correctly +- Test branch switching +- Verify history tracking +- Test merge functionality + +## Next Steps +1. Create park area detail template with version control +2. Update ride detail template +3. Add version control to review system +4. Integrate with company templates \ No newline at end of file diff --git a/memory-bank/features/version-control/ui_improvements.md b/memory-bank/features/version-control/ui_improvements.md new file mode 100644 index 00000000..0ab8899f --- /dev/null +++ b/memory-bank/features/version-control/ui_improvements.md @@ -0,0 +1,110 @@ +# Version Control System UI Improvements + +## Recent Improvements + +### 1. Template Structure Enhancement +- Moved map initialization to dedicated JavaScript file +- Implemented data attribute pattern for passing data to JavaScript +- Improved template organization and maintainability + +### 2. JavaScript Organization +- Created separate `map-init.js` for map functionality +- Established pattern for external JavaScript files +- Improved error handling and script loading + +### 3. Asset Management +```javascript +// Static Asset Organization +/static/ + /js/ + version-control.js // Core VCS functionality + map-init.js // Map initialization logic + /css/ + version-control.css // VCS styles +``` + +## Best Practices Established + +### 1. Data Passing Pattern +```html + +
+
+``` + +### 2. JavaScript Separation +```javascript +// Modular JavaScript organization +document.addEventListener('DOMContentLoaded', function() { + // Initialize components + const mapContainer = document.getElementById('map'); + if (mapContainer) { + // Component-specific logic + } +}); +``` + +### 3. Template Structure +```html +{% block content %} + +{% endblock %} + +{% block extra_js %} + {{ block.super }} + + +{% endblock %} +``` + +## Integration Guidelines + +### 1. Adding New Components +1. Create dedicated JavaScript file in `/static/js/` +2. Use data attributes for configuration +3. Follow established loading pattern +4. Update base template if needed + +### 2. Version Control UI +1. Include version control UI component +2. Add necessary data attributes +3. Ensure proper script loading +4. Follow established patterns + +### 3. Static Asset Management +1. Keep JavaScript files modular +2. Use proper static file organization +3. Follow naming conventions +4. Maintain clear dependencies + +## Next Steps + +1. Apply this pattern to other templates: + - Ride detail template + - Review detail template + - Company detail template + +2. Implement consistent error handling: + ```javascript + function handleError(error) { + console.error('Component error:', error); + // Handle error appropriately + } + ``` + +3. Add performance monitoring: + ```javascript + // Add timing measurements + const startTime = performance.now(); + // Component initialization + const endTime = performance.now(); + console.debug(`Component initialized in ${endTime - startTime}ms`); + ``` + +4. Documentation updates: + - Add JavaScript patterns to technical guide + - Update template integration guide + - Document asset organization \ No newline at end of file diff --git a/parks/models.py b/parks/models.py index d6ed5903..d15fffc1 100644 --- a/parks/models.py +++ b/parks/models.py @@ -73,7 +73,50 @@ class Park(HistoricalModel): def save(self, *args: Any, **kwargs: Any) -> None: if not self.slug: self.slug = slugify(self.name) - super().save(*args, **kwargs) + + # Get the branch from context or use default + from history_tracking.signals import get_current_branch + current_branch = get_current_branch() + + if current_branch: + # Save in the context of the current branch + super().save(*args, **kwargs) + else: + # If no branch context, save in main branch + from history_tracking.models import VersionBranch + main_branch, _ = VersionBranch.objects.get_or_create( + name='main', + defaults={'metadata': {'type': 'default_branch'}} + ) + + from history_tracking.signals import ChangesetContextManager + with ChangesetContextManager(branch=main_branch): + super().save(*args, **kwargs) + + def get_version_info(self) -> dict: + """Get version control information for this park""" + from history_tracking.models import VersionBranch, ChangeSet + from django.contrib.contenttypes.models import ContentType + + content_type = ContentType.objects.get_for_model(self) + latest_changes = ChangeSet.objects.filter( + content_type=content_type, + object_id=self.pk, + status='applied' + ).order_by('-created_at')[:5] + + active_branches = VersionBranch.objects.filter( + changesets__content_type=content_type, + changesets__object_id=self.pk, + is_active=True + ).distinct() + + return { + 'latest_changes': latest_changes, + 'active_branches': active_branches, + 'current_branch': get_current_branch(), + 'total_changes': latest_changes.count() + } def get_absolute_url(self) -> str: return reverse("parks:park_detail", kwargs={"slug": self.slug}) @@ -134,7 +177,51 @@ class ParkArea(HistoricalModel): def save(self, *args: Any, **kwargs: Any) -> None: if not self.slug: self.slug = slugify(self.name) - super().save(*args, **kwargs) + + # Get the branch from context or use default + from history_tracking.signals import get_current_branch + current_branch = get_current_branch() + + if current_branch: + # Save in the context of the current branch + super().save(*args, **kwargs) + else: + # If no branch context, save in main branch + from history_tracking.models import VersionBranch + main_branch, _ = VersionBranch.objects.get_or_create( + name='main', + defaults={'metadata': {'type': 'default_branch'}} + ) + + from history_tracking.signals import ChangesetContextManager + with ChangesetContextManager(branch=main_branch): + super().save(*args, **kwargs) + + def get_version_info(self) -> dict: + """Get version control information for this park area""" + from history_tracking.models import VersionBranch, ChangeSet + from django.contrib.contenttypes.models import ContentType + + content_type = ContentType.objects.get_for_model(self) + latest_changes = ChangeSet.objects.filter( + content_type=content_type, + object_id=self.pk, + status='applied' + ).order_by('-created_at')[:5] + + active_branches = VersionBranch.objects.filter( + changesets__content_type=content_type, + changesets__object_id=self.pk, + is_active=True + ).distinct() + + return { + 'latest_changes': latest_changes, + 'active_branches': active_branches, + 'current_branch': get_current_branch(), + 'total_changes': latest_changes.count(), + 'parent_park_branch': self.park.get_version_info()['current_branch'] + } def get_absolute_url(self) -> str: return reverse( diff --git a/parks/templates/parks/park_detail.html b/parks/templates/parks/park_detail.html new file mode 100644 index 00000000..fbcfee01 --- /dev/null +++ b/parks/templates/parks/park_detail.html @@ -0,0 +1,200 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}{{ park.name }} - ThrillWiki{% endblock %} + +{% block content %} +
+
+ +
+ + {% include "history_tracking/includes/version_control_ui.html" %} + + +
+
+

{{ park.name }}

+ + {{ park.get_status_display }} + +
+ + {% if park.description %} +
+ {{ park.description|linebreaks }} +
+ {% endif %} + + +
+ {% if park.opening_date %} +
+

Opening Date

+

{{ park.opening_date }}

+
+ {% endif %} + + {% if park.size_acres %} +
+

Size

+

{{ park.size_acres }} acres

+
+ {% endif %} + + {% if park.operating_season %} +
+

Operating Season

+

{{ park.operating_season }}

+
+ {% endif %} + + {% if park.owner %} + + {% endif %} +
+
+ + +
+

Rides

+ {% if park.rides.all %} +
+ {% for ride in park.rides.all %} +
+

+ + {{ ride.name }} + +

+

{{ ride.type }}

+
+ {% endfor %} +
+ {% else %} +

No rides listed yet.

+ {% endif %} +
+ + + {% if park.areas.exists %} +
+

Areas

+
+ {% for area in park.areas.all %} +
+

+ + {{ area.name }} + +

+ {% if area.description %} +

{{ area.description|truncatewords:20 }}

+ {% endif %} +
+ {% endfor %} +
+
+ {% endif %} +
+ + +
+ + {% if park.formatted_location %} +
+

Location

+

{{ park.formatted_location }}

+ {% if park.coordinates %} +
+
+ {% endif %} +
+ {% endif %} + + +
+

Statistics

+
+ {% if park.average_rating %} +
+ Average Rating: + {{ park.average_rating }}/5 +
+ {% endif %} + + {% if park.ride_count %} +
+ Total Rides: + {{ park.ride_count }} +
+ {% endif %} + + {% if park.coaster_count %} +
+ Roller Coasters: + {{ park.coaster_count }} +
+ {% endif %} +
+
+ + + {% if park.photos.exists %} +
+ +
    + {% for photo in park.photos.all|slice:":4" %} +
  • + {% if photo.title %}{{ photo.title }} - {% endif %}{{ park.name }} +
  • + {% endfor %} +
+ {% if park.photos.count > 4 %} + + View all {{ park.photos.count }} photos + + {% endif %} +
+ {% endif %} +
+
+
+{% endblock %} + +{% block extra_js %} +{{ block.super }} +{% if park.coordinates %} + +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/static/css/version-control.css b/static/css/version-control.css new file mode 100644 index 00000000..674d3afd --- /dev/null +++ b/static/css/version-control.css @@ -0,0 +1,181 @@ +/* Version Control System Styles */ + +.version-control-ui { + --vcs-primary: #3b82f6; + --vcs-success: #10b981; + --vcs-warning: #f59e0b; + --vcs-error: #ef4444; + --vcs-gray: #6b7280; +} + +/* Branch Status Indicators */ +.branch-indicator { + display: inline-flex; + align-items: center; + padding: 0.25rem 0.75rem; + border-radius: 9999px; + font-size: 0.875rem; + font-weight: 500; +} + +.branch-indicator.active { + background-color: rgba(16, 185, 129, 0.1); + color: var(--vcs-success); +} + +.branch-indicator.inactive { + background-color: rgba(107, 114, 128, 0.1); + color: var(--vcs-gray); +} + +/* Change Status Tags */ +.change-status { + display: inline-flex; + align-items: center; + padding: 0.25rem 0.75rem; + border-radius: 0.375rem; + font-size: 0.75rem; + font-weight: 500; +} + +.change-status.applied { + background-color: rgba(16, 185, 129, 0.1); + color: var(--vcs-success); +} + +.change-status.pending { + background-color: rgba(245, 158, 11, 0.1); + color: var(--vcs-warning); +} + +.change-status.failed { + background-color: rgba(239, 68, 68, 0.1); + color: var(--vcs-error); +} + +/* Change History */ +.change-history { + border-left: 2px solid #e5e7eb; + margin-left: 1rem; + padding-left: 1rem; +} + +.change-history-item { + position: relative; + padding: 1rem 0; +} + +.change-history-item::before { + content: ''; + position: absolute; + left: -1.25rem; + top: 1.5rem; + height: 0.75rem; + width: 0.75rem; + border-radius: 9999px; + background-color: white; + border: 2px solid var(--vcs-primary); +} + +/* Merge Interface */ +.merge-conflict { + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + margin: 1rem 0; + padding: 1rem; +} + +.merge-conflict-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.merge-conflict-content { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; +} + +.merge-version { + background-color: #f9fafb; + padding: 1rem; + border-radius: 0.375rem; +} + +/* Branch Selection */ +.branch-selector { + position: relative; +} + +.branch-list { + max-height: 24rem; + overflow-y: auto; +} + +.branch-item { + display: flex; + align-items: center; + padding: 0.5rem; + border-radius: 0.375rem; + transition: background-color 0.2s; +} + +.branch-item:hover { + background-color: #f9fafb; +} + +.branch-item.active { + background-color: rgba(59, 130, 246, 0.1); +} + +/* Version Tags */ +.version-tag { + display: inline-flex; + align-items: center; + padding: 0.25rem 0.75rem; + border-radius: 9999px; + background-color: #f3f4f6; + color: var(--vcs-gray); + font-size: 0.875rem; + margin: 0.25rem; +} + +/* Loading States */ +.vcs-loading { + position: relative; + pointer-events: none; + opacity: 0.7; +} + +.vcs-loading::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 1.5rem; + height: 1.5rem; + border: 2px solid #e5e7eb; + border-top-color: var(--vcs-primary); + border-radius: 50%; + animation: vcs-spin 1s linear infinite; +} + +@keyframes vcs-spin { + to { + transform: translate(-50%, -50%) rotate(360deg); + } +} + +/* Responsive Design */ +@media (max-width: 640px) { + .merge-conflict-content { + grid-template-columns: 1fr; + } + + .branch-selector { + width: 100%; + } +} \ No newline at end of file diff --git a/static/js/map-init.js b/static/js/map-init.js new file mode 100644 index 00000000..1a92afb2 --- /dev/null +++ b/static/js/map-init.js @@ -0,0 +1,20 @@ +document.addEventListener('DOMContentLoaded', function() { + const mapContainer = document.getElementById('map'); + if (!mapContainer) return; + + const lat = parseFloat(mapContainer.dataset.lat); + const lng = parseFloat(mapContainer.dataset.lng); + const name = mapContainer.dataset.name; + + if (isNaN(lat) || isNaN(lng)) return; + + const map = L.map('map').setView([lat, lng], 13); + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(map); + + L.marker([lat, lng]) + .addTo(map) + .bindPopup(name); +}); \ No newline at end of file diff --git a/static/js/version-control.js b/static/js/version-control.js new file mode 100644 index 00000000..5c8fc0d7 --- /dev/null +++ b/static/js/version-control.js @@ -0,0 +1,155 @@ +// Version Control System Functionality + +class VersionControl { + constructor() { + this.setupEventListeners(); + } + + setupEventListeners() { + // Branch switching + document.addEventListener('htmx:afterRequest', (event) => { + if (event.detail.target.id === 'branch-form-container') { + this.handleBranchFormResponse(event); + } + }); + + // Listen for branch switches + document.addEventListener('branch-switched', () => { + this.refreshContent(); + }); + + // Handle merge operations + document.addEventListener('htmx:afterRequest', (event) => { + if (event.detail.target.id === 'merge-panel') { + this.handleMergeResponse(event); + } + }); + } + + handleBranchFormResponse(event) { + if (event.detail.successful) { + // Clear the branch form container + document.getElementById('branch-form-container').innerHTML = ''; + // Trigger branch list refresh + document.body.dispatchEvent(new CustomEvent('branch-updated')); + } + } + + handleMergeResponse(event) { + if (event.detail.successful) { + const mergePanel = document.getElementById('merge-panel'); + if (mergePanel.innerHTML.includes('Merge Successful')) { + // Trigger content refresh after successful merge + setTimeout(() => { + this.refreshContent(); + }, 1500); + } + } + } + + refreshContent() { + // Reload the page to show content from new branch + window.location.reload(); + } + + // Branch operations + createBranch(name, parentBranch = null) { + const formData = new FormData(); + formData.append('name', name); + if (parentBranch) { + formData.append('parent', parentBranch); + } + + return fetch('/vcs/branches/create/', { + method: 'POST', + body: formData, + headers: { + 'X-CSRFToken': this.getCsrfToken() + } + }).then(response => response.json()); + } + + switchBranch(branchName) { + const formData = new FormData(); + formData.append('branch', branchName); + + return fetch('/vcs/branches/switch/', { + method: 'POST', + body: formData, + headers: { + 'X-CSRFToken': this.getCsrfToken() + } + }).then(response => { + if (response.ok) { + document.body.dispatchEvent(new CustomEvent('branch-switched')); + } + return response.json(); + }); + } + + // Merge operations + initiateMerge(sourceBranch, targetBranch) { + const formData = new FormData(); + formData.append('source', sourceBranch); + formData.append('target', targetBranch); + + return fetch('/vcs/merge/', { + method: 'POST', + body: formData, + headers: { + 'X-CSRFToken': this.getCsrfToken() + } + }).then(response => response.json()); + } + + resolveConflicts(resolutions) { + return fetch('/vcs/resolve-conflicts/', { + method: 'POST', + body: JSON.stringify(resolutions), + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': this.getCsrfToken() + } + }).then(response => response.json()); + } + + // History operations + getHistory(branch = null) { + let url = '/vcs/history/'; + if (branch) { + url += `?branch=${encodeURIComponent(branch)}`; + } + + return fetch(url) + .then(response => response.json()); + } + + // Utility functions + getCsrfToken() { + return document.querySelector('[name=csrfmiddlewaretoken]').value; + } + + showError(message) { + const errorDiv = document.createElement('div'); + errorDiv.className = 'bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4'; + errorDiv.innerHTML = ` +
+
+ + + +
+
+

${message}

+
+
+ `; + document.querySelector('.version-control-ui').prepend(errorDiv); + setTimeout(() => errorDiv.remove(), 5000); + } +} + +// Initialize version control +document.addEventListener('DOMContentLoaded', () => { + window.versionControl = new VersionControl(); +}); \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 00000000..82556675 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,169 @@ + + + + + + {% block title %}ThrillWiki{% endblock %} + + + {% csrf_token %} + + + + + + + + + + + + + + + + + + + + {% block extra_css %}{% endblock %} + + + +
+ +
+ + +
+ {% if messages %} +
+ {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} +
+ {% endif %} + + {% block content %}{% endblock %} +
+ + + + + + {% block extra_js %}{% endblock %} + + + {% if version_control.current_branch %} +
+
+ Branch: + {{ version_control.branch_name }} + {% if version_control.recent_changes %} + + {{ version_control.recent_changes|length }} changes + + {% endif %} +
+
+ {% endif %} + + \ No newline at end of file diff --git a/thrillwiki/settings.py b/thrillwiki/settings.py index c430ec9e..8e32c037 100644 --- a/thrillwiki/settings.py +++ b/thrillwiki/settings.py @@ -86,6 +86,7 @@ TEMPLATES = [ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "moderation.context_processors.moderation_access", + "history_tracking.context_processors.version_control", ], }, },