mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 13:31:18 -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}")
|
||||
|
||||
# Create new historical record with manual content
|
||||
base_record = source_change.historical_records.first()
|
||||
if base_record:
|
||||
new_record = base_record.__class__(
|
||||
**{
|
||||
**base_record.__dict__,
|
||||
'id': base_record.id,
|
||||
'history_date': timezone.now(),
|
||||
'history_user': user,
|
||||
'history_change_reason': 'Manual conflict resolution',
|
||||
'history_type': '~'
|
||||
}
|
||||
)
|
||||
# Apply manual changes
|
||||
for field, value in manual_content.items():
|
||||
setattr(new_record, field, value)
|
||||
resolved_content[f"{new_record.instance_type}_{new_record.instance_pk}"] = new_record
|
||||
resolved_content.update(_handle_manual_resolution(
|
||||
conflict_id, source_change, manual_resolutions, user
|
||||
))
|
||||
|
||||
# Create resolution changeset
|
||||
resolution_changeset = ChangeSet.objects.create(
|
||||
branch=target_branch,
|
||||
created_by=user,
|
||||
@@ -83,7 +93,6 @@ def resolve_conflicts(
|
||||
status='applied'
|
||||
)
|
||||
|
||||
# Add resolved records to changeset
|
||||
for record in resolved_content.values():
|
||||
resolution_changeset.historical_records.add(record)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user