Implement wiki and parks plugin architecture: add initial app configurations, models, and update dependencies

This commit is contained in:
pacnpal
2025-02-22 20:55:00 -05:00
parent 02e4b82beb
commit 2faf0368cf
30 changed files with 2973 additions and 316 deletions

View File

@@ -0,0 +1,84 @@
{% load wiki_tags %}
{% if user.is_authenticated %}
<div class="flex justify-end gap-2 mb-2">
<!-- Wiki Article Actions -->
{% if article|can_write:user %}
<a href="{% url 'wiki:edit' article.id %}"
class="transition-transform btn-secondary hover:scale-105">
<i class="mr-1 fas fa-pencil-alt"></i>Edit Article
</a>
{% endif %}
<!-- Park Metadata Actions -->
{% if park_metadata or article|can_write:user %}
<a href="{% url 'wiki:parks_metadata' article.id %}"
class="transition-transform btn-secondary hover:scale-105">
<i class="mr-1 fas fa-info-circle"></i>Park Info
</a>
{% endif %}
<!-- Statistics Management -->
{% if park_metadata and article|can_write:user %}
<a href="{% url 'wiki:parks_statistics' article.id %}"
class="transition-transform btn-secondary hover:scale-105">
<i class="mr-1 fas fa-chart-bar"></i>Statistics
</a>
{% endif %}
<!-- Media Management -->
{% if article|can_write:user %}
<button class="transition-transform btn-secondary hover:scale-105"
@click="$dispatch('show-wiki-media-upload')">
<i class="mr-1 fas fa-camera"></i>Add Media
</button>
{% endif %}
<!-- Article Tools -->
<div class="dropdown relative inline-block">
<button class="transition-transform btn-secondary hover:scale-105">
<i class="mr-1 fas fa-ellipsis-v"></i>More
</button>
<div class="dropdown-content hidden absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg">
<!-- History -->
<a href="{% url 'wiki:history' article.id %}"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="mr-1 fas fa-history"></i>History
</a>
<!-- Discussion -->
<a href="{% url 'wiki:discussion' article.id %}"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="mr-1 fas fa-comments"></i>Discussion
</a>
<!-- Settings -->
{% if article|can_moderate:user %}
<a href="{% url 'wiki:settings' article.id %}"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="mr-1 fas fa-cog"></i>Settings
</a>
{% endif %}
<!-- Permissions -->
{% if article|can_moderate:user %}
<a href="{% url 'wiki:permissions' article.id %}"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="mr-1 fas fa-lock"></i>Permissions
</a>
{% endif %}
</div>
</div>
</div>
<!-- Notification Area -->
{% if messages %}
<div class="mt-4">
{% for message in messages %}
<div class="p-4 mb-4 rounded-lg {% if message.tags == 'error' %}bg-red-100 text-red-700{% else %}bg-green-100 text-green-700{% endif %}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endif %}

View File

@@ -0,0 +1,200 @@
{% extends "wiki/article.html" %}
{% load i18n %}
{% load wiki_tags %}
{% load static %}
{% block wiki_contents_tab %}
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold mb-6">{% trans "Park Metadata" %}</h2>
<form method="POST" class="space-y-6">
{% csrf_token %}
<!-- Basic Information -->
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4">{% trans "Basic Information" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-group">
{{ form.operator.label_tag }}
{{ form.operator }}
{{ form.operator.errors }}
</div>
<div class="form-group">
{{ form.owner.label_tag }}
{{ form.owner }}
{{ form.owner.errors }}
</div>
<div class="form-group">
{{ form.opened_date.label_tag }}
{{ form.opened_date }}
{{ form.opened_date.errors }}
</div>
<div class="form-group">
{{ form.park_size.label_tag }}
{{ form.park_size }}
{{ form.park_size.errors }}
</div>
</div>
</div>
<!-- Location Information -->
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4">{% trans "Location" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-group">
{{ form.latitude.label_tag }}
{{ form.latitude }}
{{ form.latitude.errors }}
</div>
<div class="form-group">
{{ form.longitude.label_tag }}
{{ form.longitude }}
{{ form.longitude.errors }}
</div>
<div class="form-group col-span-2">
{{ form.address.label_tag }}
{{ form.address }}
{{ form.address.errors }}
</div>
</div>
</div>
<!-- Operating Information -->
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4">{% trans "Operating Information" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="form-group">
{{ form.seasonal.label_tag }}
{{ form.seasonal }}
{{ form.seasonal.errors }}
</div>
<div class="form-group">
{{ form.season_start.label_tag }}
{{ form.season_start }}
{{ form.season_start.errors }}
</div>
<div class="form-group">
{{ form.season_end.label_tag }}
{{ form.season_end }}
{{ form.season_end.errors }}
</div>
</div>
</div>
<!-- Attractions -->
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4">{% trans "Attractions" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-group">
{{ form.total_rides.label_tag }}
{{ form.total_rides }}
{{ form.total_rides.errors }}
</div>
<div class="form-group">
{{ form.total_roller_coasters.label_tag }}
{{ form.total_roller_coasters }}
{{ form.total_roller_coasters.errors }}
</div>
</div>
</div>
<!-- Contact Information -->
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4">{% trans "Contact Information" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-group">
{{ form.phone.label_tag }}
{{ form.phone }}
{{ form.phone.errors }}
</div>
<div class="form-group">
{{ form.email.label_tag }}
{{ form.email }}
{{ form.email.errors }}
</div>
<div class="form-group">
{{ form.website.label_tag }}
{{ form.website }}
{{ form.website.errors }}
</div>
</div>
</div>
<!-- Social Media -->
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4">{% trans "Social Media" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="form-group">
{{ form.facebook.label_tag }}
{{ form.facebook }}
{{ form.facebook.errors }}
</div>
<div class="form-group">
{{ form.twitter.label_tag }}
{{ form.twitter }}
{{ form.twitter.errors }}
</div>
<div class="form-group">
{{ form.instagram.label_tag }}
{{ form.instagram }}
{{ form.instagram.errors }}
</div>
</div>
</div>
<!-- Additional Information -->
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4">{% trans "Additional Information" %}</h3>
<div class="space-y-4">
<div class="form-group">
{{ form.amenities_text.label_tag }}
{{ form.amenities_text }}
{{ form.amenities_text.errors }}
<p class="text-sm text-gray-600 mt-1">{{ form.amenities_text.help_text }}</p>
</div>
<div class="form-group">
{{ form.ticket_info_text.label_tag }}
{{ form.ticket_info_text }}
{{ form.ticket_info_text.errors }}
<p class="text-sm text-gray-600 mt-1">{{ form.ticket_info_text.help_text }}</p>
</div>
</div>
</div>
<!-- Submit Button -->
<div class="flex justify-end space-x-4">
<a href="{% url 'wiki:get' path=article.get_absolute_url %}"
class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50">
{% trans "Cancel" %}
</a>
<button type="submit"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
{% trans "Save Changes" %}
</button>
</div>
</form>
</div>
{% endblock %}
{% block wiki_footer_script %}
{{ block.super }}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle seasonal checkbox toggling season dates
const seasonalCheckbox = document.getElementById('id_seasonal');
const seasonStartInput = document.getElementById('id_season_start');
const seasonEndInput = document.getElementById('id_season_end');
function toggleSeasonDates() {
const isDisabled = !seasonalCheckbox.checked;
seasonStartInput.disabled = isDisabled;
seasonEndInput.disabled = isDisabled;
}
if (seasonalCheckbox) {
toggleSeasonDates();
seasonalCheckbox.addEventListener('change', toggleSeasonDates);
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,146 @@
{% extends "wiki/article.html" %}
{% load i18n %}
{% load wiki_tags %}
{% load static %}
{% block wiki_contents_tab %}
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold mb-6">{% trans "Park Statistics" %}</h2>
<!-- Add New Statistics -->
<div class="mb-8">
<h3 class="text-lg font-semibold mb-4">{% trans "Add New Statistics" %}</h3>
<form method="POST" class="bg-gray-50 p-4 rounded-lg">
{% csrf_token %}
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="form-group">
{{ form.year.label_tag }}
{{ form.year }}
{{ form.year.errors }}
</div>
<div class="form-group">
{{ form.attendance.label_tag }}
{{ form.attendance }}
{{ form.attendance.errors }}
</div>
<div class="form-group">
{{ form.revenue.label_tag }}
{{ form.revenue }}
{{ form.revenue.errors }}
</div>
<div class="form-group">
{{ form.investment.label_tag }}
{{ form.investment }}
{{ form.investment.errors }}
</div>
</div>
<div class="mt-4 flex justify-end">
<button type="submit"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
{% trans "Add Statistics" %}
</button>
</div>
</form>
</div>
<!-- Statistics History -->
<div>
<h3 class="text-lg font-semibold mb-4">{% trans "Historical Statistics" %}</h3>
{% if statistics %}
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{% trans "Year" %}
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{% trans "Attendance" %}
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{% trans "Revenue" %}
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{% trans "Investment" %}
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{% trans "Actions" %}
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{% for stat in statistics %}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{{ stat.year }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ stat.attendance|default:"-" }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{% if stat.revenue %}
${{ stat.revenue }}
{% else %}
-
{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{% if stat.investment %}
${{ stat.investment }}
{% else %}
-
{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<form method="POST" action="{% url 'wiki:parks_delete_statistic' article.id stat.id %}"
class="inline-block">
{% csrf_token %}
<button type="submit"
class="text-red-600 hover:text-red-900"
onclick="return confirm('{% trans "Are you sure you want to delete this statistic?" %}')">
{% trans "Delete" %}
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-gray-500 italic">{% trans "No statistics available." %}</p>
{% endif %}
</div>
<!-- Back to Article -->
<div class="mt-8">
<a href="{% url 'wiki:get' path=article.get_absolute_url %}"
class="inline-block px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50">
{% trans "Back to Article" %}
</a>
</div>
</div>
{% endblock %}
{% block wiki_footer_script %}
{{ block.super }}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Auto-fill current year if empty
const yearInput = document.getElementById('id_year');
if (yearInput && !yearInput.value) {
yearInput.value = new Date().getFullYear();
}
// Format number inputs
const numberInputs = document.querySelectorAll('input[type="number"]');
numberInputs.forEach(input => {
input.addEventListener('blur', function() {
if (this.value) {
this.value = parseInt(this.value).toLocaleString();
}
});
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,146 @@
{% load i18n %}
{% load static %}
<div class="park-sidebar">
<!-- Quick Stats -->
<div class="bg-gray-50 p-4 rounded-lg mb-4">
{% if article.park_metadata %}
<div class="space-y-3">
{% if article.park_metadata.operator %}
<div class="flex justify-between">
<span class="text-sm text-gray-600">{% trans "Operator" %}</span>
<span class="text-sm font-medium">{{ article.park_metadata.operator }}</span>
</div>
{% endif %}
{% if article.park_metadata.opened_date %}
<div class="flex justify-between">
<span class="text-sm text-gray-600">{% trans "Opened" %}</span>
<span class="text-sm font-medium">{{ article.park_metadata.opened_date|date:"Y" }}</span>
</div>
{% endif %}
{% if article.park_metadata.total_rides %}
<div class="flex justify-between">
<span class="text-sm text-gray-600">{% trans "Total Rides" %}</span>
<span class="text-sm font-medium">{{ article.park_metadata.total_rides }}</span>
</div>
{% endif %}
{% if article.park_metadata.total_roller_coasters %}
<div class="flex justify-between">
<span class="text-sm text-gray-600">{% trans "Roller Coasters" %}</span>
<span class="text-sm font-medium">{{ article.park_metadata.total_roller_coasters }}</span>
</div>
{% endif %}
{% if article.park_metadata.park_size %}
<div class="flex justify-between">
<span class="text-sm text-gray-600">{% trans "Size" %}</span>
<span class="text-sm font-medium">{{ article.park_metadata.park_size }} {% trans "acres" %}</span>
</div>
{% endif %}
</div>
<!-- Season Info -->
{% if article.park_metadata.seasonal %}
<div class="mt-4 pt-4 border-t border-gray-200">
<h4 class="text-sm font-medium text-gray-900 mb-2">{% trans "Season" %}</h4>
<div class="text-sm text-gray-600">
{% if article.park_metadata.season_start and article.park_metadata.season_end %}
{{ article.park_metadata.season_start|date:"M j" }} - {{ article.park_metadata.season_end|date:"M j" }}
{% else %}
{% trans "Seasonal operation" %}
{% endif %}
</div>
</div>
{% endif %}
<!-- Contact -->
{% if article.park_metadata.phone or article.park_metadata.email or article.park_metadata.website %}
<div class="mt-4 pt-4 border-t border-gray-200">
<h4 class="text-sm font-medium text-gray-900 mb-2">{% trans "Contact" %}</h4>
<div class="space-y-2">
{% if article.park_metadata.phone %}
<div class="text-sm">
<a href="tel:{{ article.park_metadata.phone }}"
class="text-blue-600 hover:text-blue-800">
{{ article.park_metadata.phone }}
</a>
</div>
{% endif %}
{% if article.park_metadata.website %}
<div class="text-sm">
<a href="{{ article.park_metadata.website }}"
class="text-blue-600 hover:text-blue-800"
target="_blank" rel="noopener">
{% trans "Official Website" %}
</a>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Social Media -->
{% if article.park_metadata.facebook or article.park_metadata.twitter or article.park_metadata.instagram %}
<div class="mt-4 pt-4 border-t border-gray-200">
<h4 class="text-sm font-medium text-gray-900 mb-2">{% trans "Social Media" %}</h4>
<div class="flex space-x-4">
{% if article.park_metadata.facebook %}
<a href="{{ article.park_metadata.facebook }}"
class="text-gray-400 hover:text-blue-600"
target="_blank" rel="noopener">
<i class="fab fa-facebook"></i>
</a>
{% endif %}
{% if article.park_metadata.twitter %}
<a href="{{ article.park_metadata.twitter }}"
class="text-gray-400 hover:text-blue-400"
target="_blank" rel="noopener">
<i class="fab fa-twitter"></i>
</a>
{% endif %}
{% if article.park_metadata.instagram %}
<a href="{{ article.park_metadata.instagram }}"
class="text-gray-400 hover:text-pink-600"
target="_blank" rel="noopener">
<i class="fab fa-instagram"></i>
</a>
{% endif %}
</div>
</div>
{% endif %}
{% else %}
<p class="text-sm text-gray-500 italic">
{% trans "No park metadata available." %}
{% if article|can_write:user %}
<a href="{% url 'wiki:parks_metadata' article.id %}"
class="text-blue-600 hover:text-blue-800">
{% trans "Add metadata" %}
</a>
{% endif %}
</p>
{% endif %}
</div>
<!-- Admin Actions -->
{% if article|can_write:user %}
<div class="space-y-2">
<a href="{% url 'wiki:parks_metadata' article.id %}"
class="block w-full px-4 py-2 text-sm text-center bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200">
{% trans "Edit Park Information" %}
</a>
{% if article.park_metadata %}
<a href="{% url 'wiki:parks_statistics' article.id %}"
class="block w-full px-4 py-2 text-sm text-center bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200">
{% trans "Manage Statistics" %}
</a>
{% endif %}
</div>
{% endif %}
</div>