mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 13:11:08 -05:00
Add version control context processor and integrate map functionality with dedicated JavaScript
This commit is contained in:
43
history_tracking/context_processors.py
Normal file
43
history_tracking/context_processors.py
Normal file
@@ -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}
|
||||||
@@ -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.db import transaction
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from .models import VersionBranch, VersionTag, ChangeSet
|
from .models import VersionBranch, VersionTag, ChangeSet
|
||||||
|
|
||||||
@@ -11,10 +12,8 @@ User = get_user_model()
|
|||||||
class BranchManager:
|
class BranchManager:
|
||||||
"""Manages version control branch operations"""
|
"""Manages version control branch operations"""
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def create_branch(self, name: str, parent: Optional[VersionBranch] = None,
|
def create_branch(self, name: str, parent: Optional[VersionBranch] = None,
|
||||||
user: Optional[User] = None) -> VersionBranch:
|
user: Optional['User'] = None) -> VersionBranch:
|
||||||
"""Create a new version branch"""
|
|
||||||
branch = VersionBranch.objects.create(
|
branch = VersionBranch.objects.create(
|
||||||
name=name,
|
name=name,
|
||||||
parent=parent,
|
parent=parent,
|
||||||
@@ -29,7 +28,7 @@ class BranchManager:
|
|||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def merge_branches(self, source: VersionBranch, target: VersionBranch,
|
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
|
Merge source branch into target branch
|
||||||
Returns: (success, conflicts)
|
Returns: (success, conflicts)
|
||||||
@@ -66,9 +65,8 @@ class BranchManager:
|
|||||||
class ChangeTracker:
|
class ChangeTracker:
|
||||||
"""Tracks and manages changes across the system"""
|
"""Tracks and manages changes across the system"""
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def record_change(self, instance: Any, change_type: str,
|
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:
|
metadata: Optional[Dict] = None) -> ChangeSet:
|
||||||
"""Record a change in the system"""
|
"""Record a change in the system"""
|
||||||
if not hasattr(instance, 'history'):
|
if not hasattr(instance, 'history'):
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
{% if version_control.vcs_enabled and version_control.page_is_versioned %}
|
||||||
|
<div class="version-control-ui bg-white shadow-sm rounded-lg p-4 mb-4">
|
||||||
|
<!-- Branch Information -->
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold">Version Control</h3>
|
||||||
|
{% if version_control.current_branch %}
|
||||||
|
<p class="text-sm text-gray-600">
|
||||||
|
Current Branch:
|
||||||
|
<span class="font-medium">{{ version_control.branch_name }}</span>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Branch Selection -->
|
||||||
|
<div class="relative" x-data="{ open: false }">
|
||||||
|
<button @click="open = !open"
|
||||||
|
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
|
||||||
|
Branch Actions
|
||||||
|
</button>
|
||||||
|
<div x-show="open"
|
||||||
|
@click.away="open = false"
|
||||||
|
class="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl z-50">
|
||||||
|
<!-- Create Branch -->
|
||||||
|
<button class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full text-left"
|
||||||
|
hx-get="{% url 'history:branch-create' %}"
|
||||||
|
hx-target="#branch-form-container">
|
||||||
|
Create Branch
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Switch Branch -->
|
||||||
|
{% if version_control.available_branches %}
|
||||||
|
<div class="border-t border-gray-100 my-2"></div>
|
||||||
|
{% for branch in version_control.available_branches %}
|
||||||
|
<button class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 w-full text-left"
|
||||||
|
hx-post="{% url 'history:switch-branch' %}"
|
||||||
|
hx-vals='{"branch": "{{ branch.name }}"}'
|
||||||
|
hx-target="body">
|
||||||
|
Switch to {{ branch.name }}
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recent Changes -->
|
||||||
|
{% if version_control.recent_changes %}
|
||||||
|
<div class="mt-4">
|
||||||
|
<h4 class="text-sm font-semibold text-gray-700 mb-2">Recent Changes</h4>
|
||||||
|
<div class="space-y-2">
|
||||||
|
{% for change in version_control.recent_changes %}
|
||||||
|
<div class="bg-gray-50 p-2 rounded text-sm">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<span class="font-medium">{{ change.description }}</span>
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
{{ change.created_at|date:"M d, Y H:i" }}
|
||||||
|
{% if change.created_by %}
|
||||||
|
by {{ change.created_by.username }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span class="px-2 py-1 text-xs rounded
|
||||||
|
{% if change.status == 'applied' %}
|
||||||
|
bg-green-100 text-green-800
|
||||||
|
{% elif change.status == 'pending' %}
|
||||||
|
bg-yellow-100 text-yellow-800
|
||||||
|
{% elif change.status == 'failed' %}
|
||||||
|
bg-red-100 text-red-800
|
||||||
|
{% endif %}">
|
||||||
|
{{ change.status|title }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Branch Form Container -->
|
||||||
|
<div id="branch-form-container"></div>
|
||||||
|
|
||||||
|
<!-- Merge Panel -->
|
||||||
|
<div id="merge-panel"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script>
|
||||||
|
document.body.addEventListener('branch-switched', function(e) {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
@@ -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 django.core.exceptions import ValidationError
|
||||||
from .models import VersionBranch, ChangeSet
|
from .models import VersionBranch, ChangeSet
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.auth import get_user_model
|
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(
|
def resolve_conflicts(
|
||||||
source_branch: VersionBranch,
|
source_branch: VersionBranch,
|
||||||
target_branch: VersionBranch,
|
target_branch: VersionBranch,
|
||||||
resolutions: Dict[str, str],
|
resolutions: Dict[str, str],
|
||||||
manual_resolutions: Dict[str, str],
|
manual_resolutions: Dict[str, str],
|
||||||
user: Optional[User] = None
|
user: Optional[UserModel] = None
|
||||||
) -> ChangeSet:
|
) -> ChangeSet:
|
||||||
"""
|
"""
|
||||||
Resolve merge conflicts between branches
|
Resolve merge conflicts between branches
|
||||||
@@ -37,40 +73,14 @@ def resolve_conflicts(
|
|||||||
target_change = ChangeSet.objects.get(pk=target_id)
|
target_change = ChangeSet.objects.get(pk=target_id)
|
||||||
|
|
||||||
if resolution_type == 'source':
|
if resolution_type == 'source':
|
||||||
# Use source branch version
|
resolved_content.update(_handle_source_target_resolution(source_change))
|
||||||
for record in source_change.historical_records.all():
|
|
||||||
resolved_content[f"{record.instance_type}_{record.instance_pk}"] = record
|
|
||||||
|
|
||||||
elif resolution_type == 'target':
|
elif resolution_type == 'target':
|
||||||
# Use target branch version
|
resolved_content.update(_handle_source_target_resolution(target_change))
|
||||||
for record in target_change.historical_records.all():
|
|
||||||
resolved_content[f"{record.instance_type}_{record.instance_pk}"] = record
|
|
||||||
|
|
||||||
elif resolution_type == 'manual':
|
elif resolution_type == 'manual':
|
||||||
# Use manual resolution
|
resolved_content.update(_handle_manual_resolution(
|
||||||
manual_content = manual_resolutions.get(conflict_id)
|
conflict_id, source_change, manual_resolutions, user
|
||||||
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
|
|
||||||
|
|
||||||
# Create resolution changeset
|
|
||||||
resolution_changeset = ChangeSet.objects.create(
|
resolution_changeset = ChangeSet.objects.create(
|
||||||
branch=target_branch,
|
branch=target_branch,
|
||||||
created_by=user,
|
created_by=user,
|
||||||
@@ -83,7 +93,6 @@ def resolve_conflicts(
|
|||||||
status='applied'
|
status='applied'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add resolved records to changeset
|
|
||||||
for record in resolved_content.values():
|
for record in resolved_content.values():
|
||||||
resolution_changeset.historical_records.add(record)
|
resolution_changeset.historical_records.add(record)
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +1,99 @@
|
|||||||
# Active Development Context
|
# Active Development Context
|
||||||
|
|
||||||
## Recently Completed
|
## Current Implementation Status
|
||||||
- Implemented Version Control System enhancement
|
Version Control System has been implemented with core functionality and initial integration:
|
||||||
- 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 Status
|
### Completed
|
||||||
The Version Control System has been fully implemented according to the implementation plan and technical guide. The system provides:
|
1. Core VCS Components:
|
||||||
- Branch management
|
- Base models (VersionBranch, VersionTag, ChangeSet)
|
||||||
- Change tracking
|
- Business logic (BranchManager, ChangeTracker, MergeStrategy)
|
||||||
- Version tagging
|
- UI components and templates
|
||||||
- Merge operations with conflict resolution
|
- Asset integration (JS/CSS)
|
||||||
- Real-time UI updates via HTMX
|
|
||||||
|
|
||||||
## Next Steps
|
2. Initial Integration:
|
||||||
1. Testing
|
- Park model VCS integration
|
||||||
- Create comprehensive test suite
|
- ParkArea model VCS integration
|
||||||
- Test branch operations
|
- Base template VCS support
|
||||||
- Test merge scenarios
|
- Park detail template integration
|
||||||
- Test conflict resolution
|
- Version control context processor
|
||||||
|
|
||||||
2. Monitoring
|
3. Documentation:
|
||||||
- Implement performance metrics
|
- Technical implementation guide
|
||||||
- Track merge success rates
|
- Template integration guide
|
||||||
- Monitor system health
|
- Implementation checklist
|
||||||
|
- Base README
|
||||||
|
|
||||||
3. Documentation
|
### In Progress
|
||||||
- Create user guide
|
1. Model Integration:
|
||||||
- Document API endpoints
|
- [ ] Rides system
|
||||||
- Add inline code documentation
|
- [ ] Reviews system
|
||||||
|
- [ ] Companies system
|
||||||
|
|
||||||
4. Future Enhancements
|
2. Template Updates:
|
||||||
- Branch locking mechanism
|
- [ ] Park list view
|
||||||
- Advanced merge strategies
|
- [ ] Ride detail/list views
|
||||||
- Custom diff viewers
|
- [ ] Review detail/list views
|
||||||
- Performance optimizations
|
- [ ] 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
|
||||||
|
<!-- Add to each list template -->
|
||||||
|
{% 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
|
## 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
|
## Technical Dependencies
|
||||||
- Used GenericForeignKey for flexible history tracking
|
- django-simple-history: Base history tracking
|
||||||
- Implemented HTMX for real-time updates
|
- HTMX: UI interactions
|
||||||
- Structured change tracking with atomic changesets
|
- Alpine.js: Frontend reactivity
|
||||||
- Integrated with django-simple-history
|
- Custom VCS components
|
||||||
|
|
||||||
## Technical Debt
|
## Integration Strategy
|
||||||
- Need comprehensive test suite
|
1. Roll out model integration one app at a time
|
||||||
- Performance monitoring to be implemented
|
2. Update templates to include version control UI
|
||||||
- Documentation needs to be expanded
|
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
|
## Current Branch
|
||||||
main
|
main
|
||||||
@@ -60,4 +101,5 @@ main
|
|||||||
## Environment
|
## Environment
|
||||||
- Django with HTMX integration
|
- Django with HTMX integration
|
||||||
- PostgreSQL database
|
- PostgreSQL database
|
||||||
- django-simple-history for base tracking
|
- django-simple-history
|
||||||
|
- Custom VCS extensions
|
||||||
228
memory-bank/features/version-control/README.md
Normal file
228
memory-bank/features/version-control/README.md
Normal file
@@ -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
|
||||||
|
<div hx-get="{% url 'history:branch-list' %}"
|
||||||
|
hx-trigger="load, branch-updated from:body">
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Change History
|
||||||
|
```html
|
||||||
|
<div hx-get="{% url 'history:history-view' %}?branch={{ branch.name }}"
|
||||||
|
hx-trigger="load, branch-selected from:body">
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
159
memory-bank/features/version-control/implementation_checklist.md
Normal file
159
memory-bank/features/version-control/implementation_checklist.md
Normal file
@@ -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
|
||||||
86
memory-bank/features/version-control/template_integration.md
Normal file
86
memory-bank/features/version-control/template_integration.md
Normal file
@@ -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
|
||||||
|
<!-- Title Section -->
|
||||||
|
<h1>{{ object.name }}</h1>
|
||||||
|
|
||||||
|
<!-- Version Control UI -->
|
||||||
|
{% include "history_tracking/includes/version_control_ui.html" %}
|
||||||
|
|
||||||
|
<!-- Rest of the content -->
|
||||||
|
```
|
||||||
|
|
||||||
|
### List Templates
|
||||||
|
For list templates, add version indicators in the list items:
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% for item in object_list %}
|
||||||
|
<div class="item">
|
||||||
|
<h2>{{ item.name }}</h2>
|
||||||
|
{% if version_control.vcs_enabled %}
|
||||||
|
<div class="version-info text-sm text-gray-600">
|
||||||
|
Branch: {{ item.get_version_info.current_branch.name }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Steps
|
||||||
|
|
||||||
|
1. Update base template to include necessary JavaScript
|
||||||
|
```html
|
||||||
|
<!-- In base.html -->
|
||||||
|
<script src="{% static 'js/version-control.js' %}"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
110
memory-bank/features/version-control/ui_improvements.md
Normal file
110
memory-bank/features/version-control/ui_improvements.md
Normal file
@@ -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
|
||||||
|
<!-- Using data attributes for JavaScript configuration -->
|
||||||
|
<div id="map"
|
||||||
|
data-lat="{{ coordinates.lat }}"
|
||||||
|
data-lng="{{ coordinates.lng }}"
|
||||||
|
data-name="{{ name }}">
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 %}
|
||||||
|
<!-- Main content -->
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
{{ block.super }}
|
||||||
|
<!-- Component-specific scripts -->
|
||||||
|
<script src="{% static 'js/component-script.js' %}"></script>
|
||||||
|
{% 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
|
||||||
@@ -73,7 +73,50 @@ class Park(HistoricalModel):
|
|||||||
def save(self, *args: Any, **kwargs: Any) -> None:
|
def save(self, *args: Any, **kwargs: Any) -> None:
|
||||||
if not self.slug:
|
if not self.slug:
|
||||||
self.slug = slugify(self.name)
|
self.slug = slugify(self.name)
|
||||||
|
|
||||||
|
# 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)
|
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:
|
def get_absolute_url(self) -> str:
|
||||||
return reverse("parks:park_detail", kwargs={"slug": self.slug})
|
return reverse("parks:park_detail", kwargs={"slug": self.slug})
|
||||||
@@ -134,7 +177,51 @@ class ParkArea(HistoricalModel):
|
|||||||
def save(self, *args: Any, **kwargs: Any) -> None:
|
def save(self, *args: Any, **kwargs: Any) -> None:
|
||||||
if not self.slug:
|
if not self.slug:
|
||||||
self.slug = slugify(self.name)
|
self.slug = slugify(self.name)
|
||||||
|
|
||||||
|
# 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)
|
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:
|
def get_absolute_url(self) -> str:
|
||||||
return reverse(
|
return reverse(
|
||||||
|
|||||||
200
parks/templates/parks/park_detail.html
Normal file
200
parks/templates/parks/park_detail.html
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{ park.name }} - ThrillWiki{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
<!-- Main Content Column -->
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<!-- Version Control UI -->
|
||||||
|
{% include "history_tracking/includes/version_control_ui.html" %}
|
||||||
|
|
||||||
|
<!-- Park Information -->
|
||||||
|
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900">{{ park.name }}</h1>
|
||||||
|
<span class="px-3 py-1 rounded text-sm
|
||||||
|
{% if park.status == 'OPERATING' %}
|
||||||
|
bg-green-100 text-green-800
|
||||||
|
{% elif park.status == 'CLOSED_TEMP' %}
|
||||||
|
bg-yellow-100 text-yellow-800
|
||||||
|
{% elif park.status == 'UNDER_CONSTRUCTION' %}
|
||||||
|
bg-blue-100 text-blue-800
|
||||||
|
{% else %}
|
||||||
|
bg-red-100 text-red-800
|
||||||
|
{% endif %}">
|
||||||
|
{{ park.get_status_display }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if park.description %}
|
||||||
|
<div class="mt-4 prose">
|
||||||
|
{{ park.description|linebreaks }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Park Details -->
|
||||||
|
<div class="mt-6 grid grid-cols-2 gap-4">
|
||||||
|
{% if park.opening_date %}
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium text-gray-500">Opening Date</h3>
|
||||||
|
<p class="mt-1">{{ park.opening_date }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if park.size_acres %}
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium text-gray-500">Size</h3>
|
||||||
|
<p class="mt-1">{{ park.size_acres }} acres</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if park.operating_season %}
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium text-gray-500">Operating Season</h3>
|
||||||
|
<p class="mt-1">{{ park.operating_season }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if park.owner %}
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium text-gray-500">Owner</h3>
|
||||||
|
<p class="mt-1">
|
||||||
|
<a href="{{ park.owner.get_absolute_url }}" class="text-blue-600 hover:underline">
|
||||||
|
{{ park.owner.name }}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rides Section -->
|
||||||
|
<div class="mt-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-900 mb-4">Rides</h2>
|
||||||
|
{% if park.rides.all %}
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{% for ride in park.rides.all %}
|
||||||
|
<div class="bg-white rounded-lg shadow-sm p-4">
|
||||||
|
<h3 class="text-lg font-semibold">
|
||||||
|
<a href="{{ ride.get_absolute_url }}" class="text-blue-600 hover:underline">
|
||||||
|
{{ ride.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-gray-600 mt-1">{{ ride.type }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-gray-600">No rides listed yet.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Areas Section -->
|
||||||
|
{% if park.areas.exists %}
|
||||||
|
<div class="mt-8">
|
||||||
|
<h2 class="text-2xl font-bold text-gray-900 mb-4">Areas</h2>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{% for area in park.areas.all %}
|
||||||
|
<div class="bg-white rounded-lg shadow-sm p-4">
|
||||||
|
<h3 class="text-lg font-semibold">
|
||||||
|
<a href="{{ area.get_absolute_url }}" class="text-blue-600 hover:underline">
|
||||||
|
{{ area.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
{% if area.description %}
|
||||||
|
<p class="text-sm text-gray-600 mt-1">{{ area.description|truncatewords:20 }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div class="lg:col-span-1">
|
||||||
|
<!-- Location -->
|
||||||
|
{% if park.formatted_location %}
|
||||||
|
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
||||||
|
<h2 class="text-lg font-semibold mb-3">Location</h2>
|
||||||
|
<p>{{ park.formatted_location }}</p>
|
||||||
|
{% if park.coordinates %}
|
||||||
|
<div id="map"
|
||||||
|
class="h-64 mt-4 rounded-lg"
|
||||||
|
data-lat="{{ park.coordinates.0|stringformat:'f' }}"
|
||||||
|
data-lng="{{ park.coordinates.1|stringformat:'f' }}"
|
||||||
|
data-name="{{ park.name|escapejs }}">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Statistics -->
|
||||||
|
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
||||||
|
<h2 class="text-lg font-semibold mb-3">Statistics</h2>
|
||||||
|
<div class="space-y-3">
|
||||||
|
{% if park.average_rating %}
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-600">Average Rating:</span>
|
||||||
|
<span class="font-medium">{{ park.average_rating }}/5</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if park.ride_count %}
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-600">Total Rides:</span>
|
||||||
|
<span class="font-medium">{{ park.ride_count }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if park.coaster_count %}
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-600">Roller Coasters:</span>
|
||||||
|
<span class="font-medium">{{ park.coaster_count }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Photo Gallery -->
|
||||||
|
{% if park.photos.exists %}
|
||||||
|
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||||
|
<h2 class="text-lg font-semibold mb-3" id="photo-gallery">Photo Gallery</h2>
|
||||||
|
<ul class="grid grid-cols-2 gap-2 list-none p-0"
|
||||||
|
aria-labelledby="photo-gallery">
|
||||||
|
{% for photo in park.photos.all|slice:":4" %}
|
||||||
|
<li class="aspect-w-1 aspect-h-1">
|
||||||
|
<img src="{{ photo.image.url }}"
|
||||||
|
alt="{% if photo.title %}{{ photo.title }} - {% endif %}{{ park.name }}"
|
||||||
|
class="object-cover rounded"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
fetchpriority="low"
|
||||||
|
width="300"
|
||||||
|
height="300">
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% if park.photos.count > 4 %}
|
||||||
|
<a href="{% url 'photos:park-gallery' park.slug %}"
|
||||||
|
class="text-blue-600 hover:underline text-sm block mt-3"
|
||||||
|
aria-label="View full photo gallery of {{ park.name }}">
|
||||||
|
View all {{ park.photos.count }} photos
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% if park.coordinates %}
|
||||||
|
<script src="{% static 'js/map-init.js' %}"></script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
181
static/css/version-control.css
Normal file
181
static/css/version-control.css
Normal file
@@ -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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
static/js/map-init.js
Normal file
20
static/js/map-init.js
Normal file
@@ -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);
|
||||||
|
});
|
||||||
155
static/js/version-control.js
Normal file
155
static/js/version-control.js
Normal file
@@ -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 = `
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<svg class="h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<p>${message}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.querySelector('.version-control-ui').prepend(errorDiv);
|
||||||
|
setTimeout(() => errorDiv.remove(), 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize version control
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
window.versionControl = new VersionControl();
|
||||||
|
});
|
||||||
169
templates/base.html
Normal file
169
templates/base.html
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}ThrillWiki{% endblock %}</title>
|
||||||
|
|
||||||
|
<!-- CSRF Token -->
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<!-- Tailwind CSS -->
|
||||||
|
<link href="{% static 'css/tailwind.css' %}" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- HTMX -->
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||||
|
|
||||||
|
<!-- Alpine.js -->
|
||||||
|
<script defer src="https://unpkg.com/alpinejs@3.13.5/dist/cdn.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Leaflet for maps -->
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
|
||||||
|
<!-- Version Control Assets -->
|
||||||
|
<link href="{% static 'css/version-control.css' %}" rel="stylesheet">
|
||||||
|
<script src="{% static 'js/version-control.js' %}" defer></script>
|
||||||
|
|
||||||
|
<!-- Custom Styles -->
|
||||||
|
{% block extra_css %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50 min-h-screen">
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="bg-white shadow-sm">
|
||||||
|
<nav class="container mx-auto px-4 py-4">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<a href="{% url 'home' %}" class="text-2xl font-bold text-blue-600">
|
||||||
|
ThrillWiki
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<div class="hidden md:flex space-x-6">
|
||||||
|
<a href="{% url 'parks:park_list' %}" class="text-gray-600 hover:text-gray-900">Parks</a>
|
||||||
|
<a href="{% url 'rides:ride_list' %}" class="text-gray-600 hover:text-gray-900">Rides</a>
|
||||||
|
<a href="{% url 'companies:company_list' %}" class="text-gray-600 hover:text-gray-900">Companies</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- User Menu -->
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<div x-data="{ open: false }" class="relative">
|
||||||
|
<button @click="open = !open" class="flex items-center space-x-2">
|
||||||
|
<img src="{{ user.get_avatar_url }}"
|
||||||
|
alt="{{ user.username }}"
|
||||||
|
class="h-8 w-8 rounded-full">
|
||||||
|
<span class="text-gray-700">{{ user.username }}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div x-show="open"
|
||||||
|
@click.away="open = false"
|
||||||
|
class="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
|
||||||
|
<a href="{% url 'profile' user.username %}"
|
||||||
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||||
|
Profile
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'settings' %}"
|
||||||
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||||
|
Settings
|
||||||
|
</a>
|
||||||
|
<form method="post" action="{% url 'account_logout' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit"
|
||||||
|
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||||
|
Sign Out
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'account_login' %}"
|
||||||
|
class="text-gray-600 hover:text-gray-900">
|
||||||
|
Sign In
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'account_signup' %}"
|
||||||
|
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
|
||||||
|
Sign Up
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="py-8">
|
||||||
|
{% if messages %}
|
||||||
|
<div class="container mx-auto px-4 mb-8">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="p-4 {% if message.tags == 'success' %}bg-green-100 text-green-700{% elif message.tags == 'error' %}bg-red-100 text-red-700{% else %}bg-blue-100 text-blue-700{% endif %} rounded-lg">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-white border-t mt-auto">
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold mb-4">About</h3>
|
||||||
|
<p class="text-gray-600">
|
||||||
|
ThrillWiki is your source for theme park and attraction information.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Explore</h3>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
<li><a href="{% url 'parks:park_list' %}" class="text-gray-600 hover:text-gray-900">Parks</a></li>
|
||||||
|
<li><a href="{% url 'rides:ride_list' %}" class="text-gray-600 hover:text-gray-900">Rides</a></li>
|
||||||
|
<li><a href="{% url 'companies:company_list' %}" class="text-gray-600 hover:text-gray-900">Companies</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Legal</h3>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
<li><a href="{% url 'terms' %}" class="text-gray-600 hover:text-gray-900">Terms of Service</a></li>
|
||||||
|
<li><a href="{% url 'privacy' %}" class="text-gray-600 hover:text-gray-900">Privacy Policy</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Connect</h3>
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<a href="#" class="text-gray-400 hover:text-gray-500">
|
||||||
|
<span class="sr-only">Twitter</span>
|
||||||
|
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-8 border-t pt-8 text-center text-gray-400">
|
||||||
|
<p>© {% now "Y" %} ThrillWiki. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- Extra Scripts -->
|
||||||
|
{% block extra_js %}{% endblock %}
|
||||||
|
|
||||||
|
<!-- Version Control Status -->
|
||||||
|
{% if version_control.current_branch %}
|
||||||
|
<div class="fixed bottom-4 right-4 bg-white rounded-lg shadow-lg p-3 text-sm">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<span class="text-gray-600">Branch:</span>
|
||||||
|
<span class="font-medium">{{ version_control.branch_name }}</span>
|
||||||
|
{% if version_control.recent_changes %}
|
||||||
|
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs">
|
||||||
|
{{ version_control.recent_changes|length }} changes
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -86,6 +86,7 @@ TEMPLATES = [
|
|||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"moderation.context_processors.moderation_access",
|
"moderation.context_processors.moderation_access",
|
||||||
|
"history_tracking.context_processors.version_control",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user