mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 07:11:09 -05:00
Add OWASP compliance mapping and security test case templates, and document version control implementation phases
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
from django.db import models
|
||||
from django.utils.text import slugify
|
||||
from django.urls import reverse
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from typing import Tuple, Optional, ClassVar, TYPE_CHECKING
|
||||
from history_tracking.models import HistoricalModel, VersionBranch, ChangeSet
|
||||
from history_tracking.signals import get_current_branch, ChangesetContextManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from history_tracking.models import HistoricalSlug
|
||||
|
||||
class Company(models.Model):
|
||||
class Company(HistoricalModel):
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(max_length=255, unique=True)
|
||||
website = models.URLField(blank=True)
|
||||
@@ -29,7 +32,47 @@ class Company(models.Model):
|
||||
def save(self, *args, **kwargs) -> None:
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# Get the branch from context or use default
|
||||
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
|
||||
main_branch, _ = VersionBranch.objects.get_or_create(
|
||||
name='main',
|
||||
defaults={'metadata': {'type': 'default_branch'}}
|
||||
)
|
||||
|
||||
with ChangesetContextManager(branch=main_branch):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_version_info(self) -> dict:
|
||||
"""Get version control information for this company"""
|
||||
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("companies:company_detail", kwargs={"slug": self.slug})
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, slug: str) -> Tuple['Company', bool]:
|
||||
@@ -48,7 +91,7 @@ class Company(models.Model):
|
||||
except (HistoricalSlug.DoesNotExist, cls.DoesNotExist):
|
||||
raise cls.DoesNotExist()
|
||||
|
||||
class Manufacturer(models.Model):
|
||||
class Manufacturer(HistoricalModel):
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(max_length=255, unique=True)
|
||||
website = models.URLField(blank=True)
|
||||
@@ -70,7 +113,47 @@ class Manufacturer(models.Model):
|
||||
def save(self, *args, **kwargs) -> None:
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# Get the branch from context or use default
|
||||
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
|
||||
main_branch, _ = VersionBranch.objects.get_or_create(
|
||||
name='main',
|
||||
defaults={'metadata': {'type': 'default_branch'}}
|
||||
)
|
||||
|
||||
with ChangesetContextManager(branch=main_branch):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_version_info(self) -> dict:
|
||||
"""Get version control information for this manufacturer"""
|
||||
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("companies:manufacturer_detail", kwargs={"slug": self.slug})
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, slug: str) -> Tuple['Manufacturer', bool]:
|
||||
@@ -89,7 +172,7 @@ class Manufacturer(models.Model):
|
||||
except (HistoricalSlug.DoesNotExist, cls.DoesNotExist):
|
||||
raise cls.DoesNotExist()
|
||||
|
||||
class Designer(models.Model):
|
||||
class Designer(HistoricalModel):
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(max_length=255, unique=True)
|
||||
website = models.URLField(blank=True)
|
||||
@@ -110,7 +193,47 @@ class Designer(models.Model):
|
||||
def save(self, *args, **kwargs) -> None:
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# Get the branch from context or use default
|
||||
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
|
||||
main_branch, _ = VersionBranch.objects.get_or_create(
|
||||
name='main',
|
||||
defaults={'metadata': {'type': 'default_branch'}}
|
||||
)
|
||||
|
||||
with ChangesetContextManager(branch=main_branch):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_version_info(self) -> dict:
|
||||
"""Get version control information for this designer"""
|
||||
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("companies:designer_detail", kwargs={"slug": self.slug})
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, slug: str) -> Tuple['Designer', bool]:
|
||||
|
||||
137
companies/templates/companies/company_detail.html
Normal file
137
companies/templates/companies/company_detail.html
Normal file
@@ -0,0 +1,137 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{ company.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" %}
|
||||
|
||||
<!-- Company Information -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">{{ company.name }}</h1>
|
||||
|
||||
{% if company.description %}
|
||||
<div class="prose max-w-none mb-6">
|
||||
{{ company.description|linebreaks }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Company Details -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
{% if company.headquarters %}
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-500">Headquarters</h3>
|
||||
<p class="mt-1">{{ company.headquarters }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if company.website %}
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-500">Website</h3>
|
||||
<p class="mt-1">
|
||||
<a href="{{ company.website }}" class="text-blue-600 hover:underline" target="_blank" rel="noopener noreferrer">
|
||||
{{ company.website }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Parks Section -->
|
||||
{% if company.parks.exists %}
|
||||
<div class="mt-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Theme Parks</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{% for park in company.parks.all %}
|
||||
<div class="bg-white rounded-lg shadow-sm p-4">
|
||||
<h3 class="text-lg font-semibold">
|
||||
<a href="{{ park.get_absolute_url }}" class="text-blue-600 hover:underline">
|
||||
{{ park.name }}
|
||||
</a>
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 mt-1">{{ park.get_status_display }}</p>
|
||||
{% if park.formatted_location %}
|
||||
<p class="text-sm text-gray-500 mt-1">{{ park.formatted_location }}</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Version Control Status -->
|
||||
{% with version_info=park.get_version_info %}
|
||||
{% if version_info.active_branches.count > 1 %}
|
||||
<div class="mt-2 text-sm">
|
||||
<span class="text-yellow-600">
|
||||
{{ version_info.active_branches.count }} active branches
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="lg:col-span-1">
|
||||
<!-- 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">
|
||||
<div>
|
||||
<span class="text-gray-600">Total Parks:</span>
|
||||
<span class="font-medium">{{ company.total_parks }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600">Total Rides:</span>
|
||||
<span class="font-medium">{{ company.total_rides }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Version Control Info -->
|
||||
{% with version_info=company.get_version_info %}
|
||||
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold mb-3">Version Control</h2>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-600">Active Branches:</span>
|
||||
<span class="font-medium">{{ version_info.active_branches.count }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600">Total Changes:</span>
|
||||
<span class="font-medium">{{ version_info.total_changes }}</span>
|
||||
</div>
|
||||
{% if version_info.latest_changes %}
|
||||
<div>
|
||||
<span class="text-gray-600 block mb-2">Recent Changes:</span>
|
||||
<ul class="space-y-2">
|
||||
{% for change in version_info.latest_changes|slice:":3" %}
|
||||
<li class="text-gray-700">{{ change.description }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h2 class="text-lg font-semibold mb-3">Details</h2>
|
||||
<div class="space-y-2 text-sm">
|
||||
<p><span class="text-gray-600">Created:</span> {{ company.created_at|date:"F j, Y" }}</p>
|
||||
{% if company.created_at != company.updated_at %}
|
||||
<p><span class="text-gray-600">Last updated:</span> {{ company.updated_at|date:"F j, Y" }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
136
companies/templates/companies/company_list.html
Normal file
136
companies/templates/companies/company_list.html
Normal file
@@ -0,0 +1,136 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Companies - ThrillWiki{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Version Control UI -->
|
||||
{% include "history_tracking/includes/version_control_ui.html" %}
|
||||
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Companies</h1>
|
||||
<p class="text-gray-600 mt-2">Theme park owners and operators</p>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
|
||||
<form method="get" class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="min_parks" class="block text-sm font-medium text-gray-700">Minimum Parks</label>
|
||||
<select name="min_parks" id="min_parks" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
|
||||
<option value="">Any</option>
|
||||
{% for i in "12345" %}
|
||||
<option value="{{ i }}" {% if min_parks == i %}selected{% endif %}>{{ i }}+ parks</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="order" class="block text-sm font-medium text-gray-700">Sort By</label>
|
||||
<select name="order" id="order" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
|
||||
<option value="name" {% if order == 'name' %}selected{% endif %}>Name (A-Z)</option>
|
||||
<option value="-name" {% if order == '-name' %}selected{% endif %}>Name (Z-A)</option>
|
||||
<option value="-total_parks" {% if order == '-total_parks' %}selected{% endif %}>Most Parks</option>
|
||||
<option value="-total_rides" {% if order == '-total_rides' %}selected{% endif %}>Most Rides</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded">
|
||||
Apply Filters
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Companies Grid -->
|
||||
{% if companies %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{% for company in companies %}
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<div class="p-6">
|
||||
<h2 class="text-xl font-semibold mb-2">
|
||||
<a href="{{ company.get_absolute_url }}" class="text-blue-600 hover:underline">
|
||||
{{ company.name }}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
{% if company.description %}
|
||||
<p class="text-gray-600 text-sm mb-4">{{ company.description|truncatewords:30 }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-4 text-sm">
|
||||
{% if company.headquarters %}
|
||||
<div>
|
||||
<span class="text-gray-500">Headquarters</span>
|
||||
<p class="font-medium">{{ company.headquarters }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if company.website %}
|
||||
<div>
|
||||
<span class="text-gray-500">Website</span>
|
||||
<p>
|
||||
<a href="{{ company.website }}"
|
||||
class="text-blue-600 hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Visit Site
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<span class="text-gray-500">Total Parks</span>
|
||||
<p class="font-medium">{{ company.total_parks }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="text-gray-500">Total Rides</span>
|
||||
<p class="font-medium">{{ company.total_rides }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Version Control Status -->
|
||||
{% with version_info=company.get_version_info %}
|
||||
{% if version_info.active_branches.count > 1 %}
|
||||
<div class="text-sm">
|
||||
<span class="text-yellow-600">
|
||||
{{ version_info.active_branches.count }} active branches
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
<div class="mt-8 flex justify-center">
|
||||
<nav class="inline-flex rounded-md shadow">
|
||||
{% if page_obj.has_previous %}
|
||||
<a href="?page={{ page_obj.previous_page_number }}{% if request.GET.min_parks %}&min_parks={{ request.GET.min_parks }}{% endif %}{% if request.GET.order %}&order={{ request.GET.order }}{% endif %}"
|
||||
class="px-3 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
|
||||
Previous
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?page={{ page_obj.next_page_number }}{% if request.GET.min_parks %}&min_parks={{ request.GET.min_parks }}{% endif %}{% if request.GET.order %}&order={{ request.GET.order }}{% endif %}"
|
||||
class="px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
|
||||
Next
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<div class="text-center py-12">
|
||||
<p class="text-gray-600">No companies found matching your criteria.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
154
companies/templates/companies/designer_detail.html
Normal file
154
companies/templates/companies/designer_detail.html
Normal file
@@ -0,0 +1,154 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{ designer.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" %}
|
||||
|
||||
<!-- Designer Information -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">{{ designer.name }}</h1>
|
||||
|
||||
{% if designer.description %}
|
||||
<div class="prose max-w-none mb-6">
|
||||
{{ designer.description|linebreaks }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Designer Details -->
|
||||
{% if designer.website %}
|
||||
<div class="mb-4">
|
||||
<h3 class="text-sm font-medium text-gray-500">Website</h3>
|
||||
<p class="mt-1">
|
||||
<a href="{{ designer.website }}" class="text-blue-600 hover:underline" target="_blank" rel="noopener noreferrer">
|
||||
{{ designer.website }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Designed Rides Section -->
|
||||
{% if designer.rides.exists %}
|
||||
<div class="mt-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Designed Rides</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{% for ride in designer.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">
|
||||
at
|
||||
<a href="{{ ride.park.get_absolute_url }}" class="hover:underline">
|
||||
{{ ride.park.name }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
{% if ride.manufacturer %}
|
||||
<p class="text-sm text-gray-600 mt-1">
|
||||
Built by
|
||||
<a href="{{ ride.manufacturer.get_absolute_url }}" class="hover:underline">
|
||||
{{ ride.manufacturer.name }}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-2">
|
||||
<span class="px-2 py-1 text-xs rounded
|
||||
{% if ride.status == 'OPERATING' %}
|
||||
bg-green-100 text-green-800
|
||||
{% elif ride.status == 'SBNO' %}
|
||||
bg-yellow-100 text-yellow-800
|
||||
{% elif ride.status == 'UNDER_CONSTRUCTION' %}
|
||||
bg-blue-100 text-blue-800
|
||||
{% else %}
|
||||
bg-red-100 text-red-800
|
||||
{% endif %}">
|
||||
{{ ride.get_status_display }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Version Control Status -->
|
||||
{% with version_info=ride.get_version_info %}
|
||||
{% if version_info.active_branches.count > 1 %}
|
||||
<div class="mt-2 text-sm">
|
||||
<span class="text-yellow-600">
|
||||
{{ version_info.active_branches.count }} active branches
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="lg:col-span-1">
|
||||
<!-- 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">
|
||||
<div>
|
||||
<span class="text-gray-600">Total Rides:</span>
|
||||
<span class="font-medium">{{ designer.total_rides }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600">Roller Coasters:</span>
|
||||
<span class="font-medium">{{ designer.total_roller_coasters }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Version Control Info -->
|
||||
{% with version_info=designer.get_version_info %}
|
||||
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold mb-3">Version Control</h2>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-600">Active Branches:</span>
|
||||
<span class="font-medium">{{ version_info.active_branches.count }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600">Total Changes:</span>
|
||||
<span class="font-medium">{{ version_info.total_changes }}</span>
|
||||
</div>
|
||||
{% if version_info.latest_changes %}
|
||||
<div>
|
||||
<span class="text-gray-600 block mb-2">Recent Changes:</span>
|
||||
<ul class="space-y-2">
|
||||
{% for change in version_info.latest_changes|slice:":3" %}
|
||||
<li class="text-gray-700">{{ change.description }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h2 class="text-lg font-semibold mb-3">Details</h2>
|
||||
<div class="space-y-2 text-sm">
|
||||
<p><span class="text-gray-600">Created:</span> {{ designer.created_at|date:"F j, Y" }}</p>
|
||||
{% if designer.created_at != designer.updated_at %}
|
||||
<p><span class="text-gray-600">Last updated:</span> {{ designer.updated_at|date:"F j, Y" }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
146
companies/templates/companies/designer_list.html
Normal file
146
companies/templates/companies/designer_list.html
Normal file
@@ -0,0 +1,146 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Designers - ThrillWiki{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Version Control UI -->
|
||||
{% include "history_tracking/includes/version_control_ui.html" %}
|
||||
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Ride Designers</h1>
|
||||
<p class="text-gray-600 mt-2">Ride and attraction designers and engineers</p>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
|
||||
<form method="get" class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="min_rides" class="block text-sm font-medium text-gray-700">Minimum Rides</label>
|
||||
<select name="min_rides" id="min_rides" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
|
||||
<option value="">Any</option>
|
||||
{% for i in "12345" %}
|
||||
<option value="{{ i }}" {% if min_rides == i %}selected{% endif %}>{{ i }}+ rides</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="order" class="block text-sm font-medium text-gray-700">Sort By</label>
|
||||
<select name="order" id="order" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
|
||||
<option value="name" {% if order == 'name' %}selected{% endif %}>Name (A-Z)</option>
|
||||
<option value="-name" {% if order == '-name' %}selected{% endif %}>Name (Z-A)</option>
|
||||
<option value="-total_rides" {% if order == '-total_rides' %}selected{% endif %}>Most Rides</option>
|
||||
<option value="-total_roller_coasters" {% if order == '-total_roller_coasters' %}selected{% endif %}>Most Roller Coasters</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded">
|
||||
Apply Filters
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Designers Grid -->
|
||||
{% if designers %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{% for designer in designers %}
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<div class="p-6">
|
||||
<h2 class="text-xl font-semibold mb-2">
|
||||
<a href="{{ designer.get_absolute_url }}" class="text-blue-600 hover:underline">
|
||||
{{ designer.name }}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
{% if designer.description %}
|
||||
<p class="text-gray-600 text-sm mb-4">{{ designer.description|truncatewords:30 }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-4 text-sm">
|
||||
{% if designer.website %}
|
||||
<div>
|
||||
<span class="text-gray-500">Website</span>
|
||||
<p>
|
||||
<a href="{{ designer.website }}"
|
||||
class="text-blue-600 hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Visit Site
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<span class="text-gray-500">Total Rides</span>
|
||||
<p class="font-medium">{{ designer.total_rides }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="text-gray-500">Roller Coasters</span>
|
||||
<p class="font-medium">{{ designer.total_roller_coasters }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notable Rides Preview -->
|
||||
{% if designer.rides.exists %}
|
||||
<div class="text-sm text-gray-600 mb-4">
|
||||
<span class="font-medium">Notable Works:</span>
|
||||
<div class="mt-1">
|
||||
{% for ride in designer.rides.all|slice:":3" %}
|
||||
<span class="inline-block bg-gray-100 rounded-full px-3 py-1 text-xs font-medium text-gray-700 mr-2 mb-2">
|
||||
{{ ride.name }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% if designer.rides.count > 3 %}
|
||||
<span class="text-blue-600">+{{ designer.rides.count|add:"-3" }} more</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Version Control Status -->
|
||||
{% with version_info=designer.get_version_info %}
|
||||
{% if version_info.active_branches.count > 1 %}
|
||||
<div class="text-sm">
|
||||
<span class="text-yellow-600">
|
||||
{{ version_info.active_branches.count }} active branches
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
<div class="mt-8 flex justify-center">
|
||||
<nav class="inline-flex rounded-md shadow">
|
||||
{% if page_obj.has_previous %}
|
||||
<a href="?page={{ page_obj.previous_page_number }}{% if request.GET.min_rides %}&min_rides={{ request.GET.min_rides }}{% endif %}{% if request.GET.order %}&order={{ request.GET.order }}{% endif %}"
|
||||
class="px-3 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
|
||||
Previous
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?page={{ page_obj.next_page_number }}{% if request.GET.min_rides %}&min_rides={{ request.GET.min_rides }}{% endif %}{% if request.GET.order %}&order={{ request.GET.order }}{% endif %}"
|
||||
class="px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
|
||||
Next
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<div class="text-center py-12">
|
||||
<p class="text-gray-600">No designers found matching your criteria.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
188
companies/templates/companies/manufacturer_detail.html
Normal file
188
companies/templates/companies/manufacturer_detail.html
Normal file
@@ -0,0 +1,188 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{ manufacturer.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" %}
|
||||
|
||||
<!-- Manufacturer Information -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-4">{{ manufacturer.name }}</h1>
|
||||
|
||||
{% if manufacturer.description %}
|
||||
<div class="prose max-w-none mb-6">
|
||||
{{ manufacturer.description|linebreaks }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Manufacturer Details -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
{% if manufacturer.headquarters %}
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-500">Headquarters</h3>
|
||||
<p class="mt-1">{{ manufacturer.headquarters }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if manufacturer.website %}
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-gray-500">Website</h3>
|
||||
<p class="mt-1">
|
||||
<a href="{{ manufacturer.website }}" class="text-blue-600 hover:underline" target="_blank" rel="noopener noreferrer">
|
||||
{{ manufacturer.website }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ride Models Section -->
|
||||
{% if manufacturer.ride_models.exists %}
|
||||
<div class="mt-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Ride Models</h2>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
{% for model in manufacturer.ride_models.all %}
|
||||
<div class="bg-white rounded-lg shadow-sm p-4">
|
||||
<h3 class="text-lg font-semibold">
|
||||
<a href="{{ model.get_absolute_url }}" class="text-blue-600 hover:underline">
|
||||
{{ model.name }}
|
||||
</a>
|
||||
</h3>
|
||||
{% if model.category %}
|
||||
<p class="text-sm text-gray-600 mt-1">{{ model.get_category_display }}</p>
|
||||
{% endif %}
|
||||
{% if model.description %}
|
||||
<p class="text-sm text-gray-600 mt-2">{{ model.description|truncatewords:50 }}</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Version Control Status -->
|
||||
{% with version_info=model.get_version_info %}
|
||||
{% if version_info.active_branches.count > 1 %}
|
||||
<div class="mt-2 text-sm">
|
||||
<span class="text-yellow-600">
|
||||
{{ version_info.active_branches.count }} active branches
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Installed Rides Section -->
|
||||
{% if manufacturer.rides.exists %}
|
||||
<div class="mt-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-4">Installed Rides</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{% for ride in manufacturer.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">
|
||||
at
|
||||
<a href="{{ ride.park.get_absolute_url }}" class="hover:underline">
|
||||
{{ ride.park.name }}
|
||||
</a>
|
||||
</p>
|
||||
<div class="mt-2">
|
||||
<span class="px-2 py-1 text-xs rounded
|
||||
{% if ride.status == 'OPERATING' %}
|
||||
bg-green-100 text-green-800
|
||||
{% elif ride.status == 'SBNO' %}
|
||||
bg-yellow-100 text-yellow-800
|
||||
{% elif ride.status == 'UNDER_CONSTRUCTION' %}
|
||||
bg-blue-100 text-blue-800
|
||||
{% else %}
|
||||
bg-red-100 text-red-800
|
||||
{% endif %}">
|
||||
{{ ride.get_status_display }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Version Control Status -->
|
||||
{% with version_info=ride.get_version_info %}
|
||||
{% if version_info.active_branches.count > 1 %}
|
||||
<div class="mt-2 text-sm">
|
||||
<span class="text-yellow-600">
|
||||
{{ version_info.active_branches.count }} active branches
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="lg:col-span-1">
|
||||
<!-- 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">
|
||||
<div>
|
||||
<span class="text-gray-600">Total Rides:</span>
|
||||
<span class="font-medium">{{ manufacturer.total_rides }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600">Roller Coasters:</span>
|
||||
<span class="font-medium">{{ manufacturer.total_roller_coasters }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Version Control Info -->
|
||||
{% with version_info=manufacturer.get_version_info %}
|
||||
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
||||
<h2 class="text-lg font-semibold mb-3">Version Control</h2>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-600">Active Branches:</span>
|
||||
<span class="font-medium">{{ version_info.active_branches.count }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600">Total Changes:</span>
|
||||
<span class="font-medium">{{ version_info.total_changes }}</span>
|
||||
</div>
|
||||
{% if version_info.latest_changes %}
|
||||
<div>
|
||||
<span class="text-gray-600 block mb-2">Recent Changes:</span>
|
||||
<ul class="space-y-2">
|
||||
{% for change in version_info.latest_changes|slice:":3" %}
|
||||
<li class="text-gray-700">{{ change.description }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-6">
|
||||
<h2 class="text-lg font-semibold mb-3">Details</h2>
|
||||
<div class="space-y-2 text-sm">
|
||||
<p><span class="text-gray-600">Created:</span> {{ manufacturer.created_at|date:"F j, Y" }}</p>
|
||||
{% if manufacturer.created_at != manufacturer.updated_at %}
|
||||
<p><span class="text-gray-600">Last updated:</span> {{ manufacturer.updated_at|date:"F j, Y" }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
162
companies/templates/companies/manufacturer_list.html
Normal file
162
companies/templates/companies/manufacturer_list.html
Normal file
@@ -0,0 +1,162 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Manufacturers - ThrillWiki{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Version Control UI -->
|
||||
{% include "history_tracking/includes/version_control_ui.html" %}
|
||||
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">Manufacturers</h1>
|
||||
<p class="text-gray-600 mt-2">Ride and attraction manufacturers</p>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
|
||||
<form method="get" class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<label for="category" class="block text-sm font-medium text-gray-700">Ride Type</label>
|
||||
<select name="category" id="category" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
|
||||
<option value="">All Types</option>
|
||||
{% for code, name in category_choices %}
|
||||
<option value="{{ code }}" {% if category == code %}selected{% endif %}>{{ name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="min_rides" class="block text-sm font-medium text-gray-700">Minimum Rides</label>
|
||||
<select name="min_rides" id="min_rides" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
|
||||
<option value="">Any</option>
|
||||
{% for i in "12345" %}
|
||||
<option value="{{ i }}" {% if min_rides == i %}selected{% endif %}>{{ i }}+ rides</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="order" class="block text-sm font-medium text-gray-700">Sort By</label>
|
||||
<select name="order" id="order" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm">
|
||||
<option value="name" {% if order == 'name' %}selected{% endif %}>Name (A-Z)</option>
|
||||
<option value="-name" {% if order == '-name' %}selected{% endif %}>Name (Z-A)</option>
|
||||
<option value="-total_rides" {% if order == '-total_rides' %}selected{% endif %}>Most Rides</option>
|
||||
<option value="-total_roller_coasters" {% if order == '-total_roller_coasters' %}selected{% endif %}>Most Roller Coasters</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded">
|
||||
Apply Filters
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Manufacturers Grid -->
|
||||
{% if manufacturers %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{% for manufacturer in manufacturers %}
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<div class="p-6">
|
||||
<h2 class="text-xl font-semibold mb-2">
|
||||
<a href="{{ manufacturer.get_absolute_url }}" class="text-blue-600 hover:underline">
|
||||
{{ manufacturer.name }}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
{% if manufacturer.description %}
|
||||
<p class="text-gray-600 text-sm mb-4">{{ manufacturer.description|truncatewords:30 }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-4 text-sm">
|
||||
{% if manufacturer.headquarters %}
|
||||
<div>
|
||||
<span class="text-gray-500">Headquarters</span>
|
||||
<p class="font-medium">{{ manufacturer.headquarters }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if manufacturer.website %}
|
||||
<div>
|
||||
<span class="text-gray-500">Website</span>
|
||||
<p>
|
||||
<a href="{{ manufacturer.website }}"
|
||||
class="text-blue-600 hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Visit Site
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<span class="text-gray-500">Total Rides</span>
|
||||
<p class="font-medium">{{ manufacturer.total_rides }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="text-gray-500">Roller Coasters</span>
|
||||
<p class="font-medium">{{ manufacturer.total_roller_coasters }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ride Models Preview -->
|
||||
{% if manufacturer.ride_models.exists %}
|
||||
<div class="text-sm text-gray-600 mb-4">
|
||||
<span class="font-medium">Popular Models:</span>
|
||||
<div class="mt-1">
|
||||
{% for model in manufacturer.ride_models.all|slice:":3" %}
|
||||
<span class="inline-block bg-gray-100 rounded-full px-3 py-1 text-xs font-medium text-gray-700 mr-2 mb-2">
|
||||
{{ model.name }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% if manufacturer.ride_models.count > 3 %}
|
||||
<span class="text-blue-600">+{{ manufacturer.ride_models.count|add:"-3" }} more</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Version Control Status -->
|
||||
{% with version_info=manufacturer.get_version_info %}
|
||||
{% if version_info.active_branches.count > 1 %}
|
||||
<div class="text-sm">
|
||||
<span class="text-yellow-600">
|
||||
{{ version_info.active_branches.count }} active branches
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
<div class="mt-8 flex justify-center">
|
||||
<nav class="inline-flex rounded-md shadow">
|
||||
{% if page_obj.has_previous %}
|
||||
<a href="?page={{ page_obj.previous_page_number }}{% if request.GET.category %}&category={{ request.GET.category }}{% endif %}{% if request.GET.min_rides %}&min_rides={{ request.GET.min_rides }}{% endif %}{% if request.GET.order %}&order={{ request.GET.order }}{% endif %}"
|
||||
class="px-3 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
|
||||
Previous
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?page={{ page_obj.next_page_number }}{% if request.GET.category %}&category={{ request.GET.category }}{% endif %}{% if request.GET.min_rides %}&min_rides={{ request.GET.min_rides }}{% endif %}{% if request.GET.order %}&order={{ request.GET.order }}{% endif %}"
|
||||
class="px-3 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
|
||||
Next
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<div class="text-center py-12">
|
||||
<p class="text-gray-600">No manufacturers found matching your criteria.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user