mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 11:31:07 -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.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'):
|
||||
|
||||
@@ -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 .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}")
|
||||
resolved_content.update(_handle_manual_resolution(
|
||||
conflict_id, source_change, manual_resolutions, user
|
||||
))
|
||||
|
||||
# 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(
|
||||
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)
|
||||
|
||||
|
||||
@@ -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
|
||||
<!-- 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
|
||||
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
|
||||
- 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:
|
||||
if not self.slug:
|
||||
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)
|
||||
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)
|
||||
|
||||
# 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(
|
||||
|
||||
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.messages.context_processors.messages",
|
||||
"moderation.context_processors.moderation_access",
|
||||
"history_tracking.context_processors.version_control",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user