mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 14:51:08 -05:00
fix commas
This commit is contained in:
2
history_tracking/__init__.py
Normal file
2
history_tracking/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# history_tracking/__init__.py
|
||||||
|
default_app_config = "history_tracking.apps.HistoryTrackingConfig"
|
||||||
3
history_tracking/admin.py
Normal file
3
history_tracking/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
20
history_tracking/apps.py
Normal file
20
history_tracking/apps.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# history_tracking/apps.py
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryTrackingConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "history_tracking"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from .mixins import HistoricalChangeMixin
|
||||||
|
from .models import Park
|
||||||
|
|
||||||
|
models_with_history = [Park]
|
||||||
|
|
||||||
|
for model in models_with_history:
|
||||||
|
# Check if mixin is already applied
|
||||||
|
if HistoricalChangeMixin not in model.history.model.__bases__:
|
||||||
|
model.history.model.__bases__ = (
|
||||||
|
HistoricalChangeMixin,
|
||||||
|
) + model.history.model.__bases__
|
||||||
0
history_tracking/management/__init__.py
Normal file
0
history_tracking/management/__init__.py
Normal file
0
history_tracking/management/commands/__init__.py
Normal file
0
history_tracking/management/commands/__init__.py
Normal file
99
history_tracking/management/commands/initialize_history.py
Normal file
99
history_tracking/management/commands/initialize_history.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# history_tracking/management/commands/initialize_history.py
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.apps import apps
|
||||||
|
from django.db.models import Model
|
||||||
|
from simple_history.models import HistoricalRecords
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Initialize history records for existing objects with historical records"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--model",
|
||||||
|
type=str,
|
||||||
|
help="Specify model in format app_name.ModelName (e.g., history_tracking.Park)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--all",
|
||||||
|
action="store_true",
|
||||||
|
help="Initialize history for all models with historical records",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--force",
|
||||||
|
action="store_true",
|
||||||
|
help="Create history even if records already exist",
|
||||||
|
)
|
||||||
|
|
||||||
|
def initialize_model(self, model, force=False):
|
||||||
|
total = model.objects.count()
|
||||||
|
initialized = 0
|
||||||
|
model_name = f"{model._meta.app_label}.{model._meta.model_name}"
|
||||||
|
|
||||||
|
self.stdout.write(f"Processing {model_name}: Found {total} records")
|
||||||
|
|
||||||
|
for obj in model.objects.all():
|
||||||
|
try:
|
||||||
|
if force or not obj.history.exists():
|
||||||
|
obj.history.create(
|
||||||
|
history_date=timezone.now(),
|
||||||
|
history_type="+",
|
||||||
|
history_change_reason="Initial history record",
|
||||||
|
**{
|
||||||
|
field.name: getattr(obj, field.name)
|
||||||
|
for field in obj._meta.fields
|
||||||
|
if not isinstance(field, HistoricalRecords)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
initialized += 1
|
||||||
|
self.stdout.write(f"Created history for {model_name} id={obj.pk}")
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR(
|
||||||
|
f"Error creating history for {model_name} id={obj.pk}: {str(e)}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return initialized, total
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
if not options["model"] and not options["all"]:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR("Please specify either --model or --all")
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
force = options["force"]
|
||||||
|
total_initialized = 0
|
||||||
|
total_records = 0
|
||||||
|
|
||||||
|
if options["model"]:
|
||||||
|
try:
|
||||||
|
app_label, model_name = options["model"].split(".")
|
||||||
|
model = apps.get_model(app_label, model_name)
|
||||||
|
if hasattr(model, "history"):
|
||||||
|
initialized, total = self.initialize_model(model, force)
|
||||||
|
total_initialized += initialized
|
||||||
|
total_records += total
|
||||||
|
else:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR(
|
||||||
|
f'Model {options["model"]} does not have historical records'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(self.style.ERROR(str(e)))
|
||||||
|
else:
|
||||||
|
# Process all models with historical records
|
||||||
|
for model in apps.get_models():
|
||||||
|
if hasattr(model, "history"):
|
||||||
|
initialized, total = self.initialize_model(model, force)
|
||||||
|
total_initialized += initialized
|
||||||
|
total_records += total
|
||||||
|
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
f"Successfully initialized {total_initialized} of {total_records} total records"
|
||||||
|
)
|
||||||
|
)
|
||||||
76
history_tracking/migrations/0001_initial.py
Normal file
76
history_tracking/migrations/0001_initial.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2024-11-03 19:59
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import history_tracking.mixins
|
||||||
|
import simple_history.models
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Park",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=200)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="HistoricalPark",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigIntegerField(
|
||||||
|
auto_created=True, blank=True, db_index=True, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=200)),
|
||||||
|
("history_id", models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
("history_date", models.DateTimeField(db_index=True)),
|
||||||
|
("history_change_reason", models.CharField(max_length=100, null=True)),
|
||||||
|
(
|
||||||
|
"history_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
|
||||||
|
max_length=1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"history_user",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "historical park",
|
||||||
|
"verbose_name_plural": "historical parks",
|
||||||
|
"ordering": ("-history_date", "-history_id"),
|
||||||
|
"get_latest_by": ("history_date", "history_id"),
|
||||||
|
},
|
||||||
|
bases=(
|
||||||
|
history_tracking.mixins.HistoricalChangeMixin,
|
||||||
|
simple_history.models.HistoricalChanges,
|
||||||
|
models.Model,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
history_tracking/migrations/__init__.py
Normal file
0
history_tracking/migrations/__init__.py
Normal file
23
history_tracking/mixins.py
Normal file
23
history_tracking/mixins.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# history_tracking/mixins.py
|
||||||
|
|
||||||
|
|
||||||
|
class HistoricalChangeMixin:
|
||||||
|
@property
|
||||||
|
def diff_against_previous(self):
|
||||||
|
prev_record = self.prev_record
|
||||||
|
if not prev_record:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
changes = {}
|
||||||
|
for field in self.__dict__:
|
||||||
|
if field not in [
|
||||||
|
"history_date",
|
||||||
|
"history_id",
|
||||||
|
"history_type",
|
||||||
|
"history_user_id",
|
||||||
|
] and not field.startswith("_"):
|
||||||
|
old_value = getattr(prev_record, field)
|
||||||
|
new_value = getattr(self, field)
|
||||||
|
if old_value != new_value:
|
||||||
|
changes[field] = {"old": old_value, "new": new_value}
|
||||||
|
return changes
|
||||||
17
history_tracking/models.py
Normal file
17
history_tracking/models.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# history_tracking/models.py
|
||||||
|
from django.db import models
|
||||||
|
from simple_history.models import HistoricalRecords
|
||||||
|
from .mixins import HistoricalChangeMixin
|
||||||
|
|
||||||
|
|
||||||
|
class Park(models.Model):
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
# ... other fields ...
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _history_model(self):
|
||||||
|
return self.history.model
|
||||||
|
|
||||||
|
|
||||||
|
# Apply the mixin
|
||||||
3
history_tracking/tests.py
Normal file
3
history_tracking/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
history_tracking/views.py
Normal file
3
history_tracking/views.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
Binary file not shown.
@@ -0,0 +1,80 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2024-11-03 19:59
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
import parks.models
|
||||||
|
from decimal import Decimal
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("parks", "0005_normalize_coordinates"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="historicalpark",
|
||||||
|
name="latitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=6,
|
||||||
|
help_text="Latitude coordinate (-90 to 90)",
|
||||||
|
max_digits=9,
|
||||||
|
null=True,
|
||||||
|
validators=[
|
||||||
|
django.core.validators.MinValueValidator(Decimal("-90")),
|
||||||
|
django.core.validators.MaxValueValidator(Decimal("90")),
|
||||||
|
parks.models.validate_latitude_digits,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="historicalpark",
|
||||||
|
name="longitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=6,
|
||||||
|
help_text="Longitude coordinate (-180 to 180)",
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
validators=[
|
||||||
|
django.core.validators.MinValueValidator(Decimal("-180")),
|
||||||
|
django.core.validators.MaxValueValidator(Decimal("180")),
|
||||||
|
parks.models.validate_longitude_digits,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="park",
|
||||||
|
name="latitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=6,
|
||||||
|
help_text="Latitude coordinate (-90 to 90)",
|
||||||
|
max_digits=9,
|
||||||
|
null=True,
|
||||||
|
validators=[
|
||||||
|
django.core.validators.MinValueValidator(Decimal("-90")),
|
||||||
|
django.core.validators.MaxValueValidator(Decimal("90")),
|
||||||
|
parks.models.validate_latitude_digits,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="park",
|
||||||
|
name="longitude",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=6,
|
||||||
|
help_text="Longitude coordinate (-180 to 180)",
|
||||||
|
max_digits=10,
|
||||||
|
null=True,
|
||||||
|
validators=[
|
||||||
|
django.core.validators.MinValueValidator(Decimal("-180")),
|
||||||
|
django.core.validators.MaxValueValidator(Decimal("180")),
|
||||||
|
parks.models.validate_longitude_digits,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -20,7 +20,9 @@ def normalize_coordinate(value, max_digits, decimal_places):
|
|||||||
# Convert to Decimal for precise handling
|
# Convert to Decimal for precise handling
|
||||||
value = Decimal(str(value))
|
value = Decimal(str(value))
|
||||||
# Round to specified decimal places
|
# Round to specified decimal places
|
||||||
value = Decimal(value.quantize(Decimal('0.' + '0' * decimal_places), rounding=ROUND_DOWN))
|
value = Decimal(
|
||||||
|
value.quantize(Decimal("0." + "0" * decimal_places), rounding=ROUND_DOWN)
|
||||||
|
)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
except (TypeError, ValueError, InvalidOperation):
|
except (TypeError, ValueError, InvalidOperation):
|
||||||
@@ -34,10 +36,10 @@ def validate_coordinate_digits(value, max_digits, decimal_places):
|
|||||||
# Convert to Decimal for precise handling
|
# Convert to Decimal for precise handling
|
||||||
value = Decimal(str(value))
|
value = Decimal(str(value))
|
||||||
# Round to exactly 6 decimal places
|
# Round to exactly 6 decimal places
|
||||||
value = value.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
value = value.quantize(Decimal("0.000001"), rounding=ROUND_DOWN)
|
||||||
return value
|
return value
|
||||||
except (InvalidOperation, TypeError):
|
except (InvalidOperation, TypeError):
|
||||||
raise ValidationError('Invalid coordinate value.')
|
raise ValidationError("Invalid coordinate value.")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@@ -53,21 +55,19 @@ def validate_longitude_digits(value):
|
|||||||
|
|
||||||
class Park(models.Model):
|
class Park(models.Model):
|
||||||
STATUS_CHOICES = [
|
STATUS_CHOICES = [
|
||||||
('OPERATING', 'Operating'),
|
("OPERATING", "Operating"),
|
||||||
('CLOSED_TEMP', 'Temporarily Closed'),
|
("CLOSED_TEMP", "Temporarily Closed"),
|
||||||
('CLOSED_PERM', 'Permanently Closed'),
|
("CLOSED_PERM", "Permanently Closed"),
|
||||||
('UNDER_CONSTRUCTION', 'Under Construction'),
|
("UNDER_CONSTRUCTION", "Under Construction"),
|
||||||
('DEMOLISHED', 'Demolished'),
|
("DEMOLISHED", "Demolished"),
|
||||||
('RELOCATED', 'Relocated'),
|
("RELOCATED", "Relocated"),
|
||||||
]
|
]
|
||||||
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
slug = models.SlugField(max_length=255, unique=True)
|
slug = models.SlugField(max_length=255, unique=True)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20, choices=STATUS_CHOICES, default="OPERATING"
|
||||||
choices=STATUS_CHOICES,
|
|
||||||
default='OPERATING'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Location fields
|
# Location fields
|
||||||
@@ -76,24 +76,24 @@ class Park(models.Model):
|
|||||||
decimal_places=6,
|
decimal_places=6,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text='Latitude coordinate (-90 to 90)',
|
help_text="Latitude coordinate (-90 to 90)",
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(Decimal('-90')),
|
MinValueValidator(Decimal("-90")),
|
||||||
MaxValueValidator(Decimal('90')),
|
MaxValueValidator(Decimal("90")),
|
||||||
validate_latitude_digits,
|
validate_latitude_digits,
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
longitude = models.DecimalField(
|
longitude = models.DecimalField(
|
||||||
max_digits=10,
|
max_digits=10,
|
||||||
decimal_places=6,
|
decimal_places=6,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text='Longitude coordinate (-180 to 180)',
|
help_text="Longitude coordinate (-180 to 180)",
|
||||||
validators=[
|
validators=[
|
||||||
MinValueValidator(Decimal('-180')),
|
MinValueValidator(Decimal("-180")),
|
||||||
MaxValueValidator(Decimal('180')),
|
MaxValueValidator(Decimal("180")),
|
||||||
validate_longitude_digits,
|
validate_longitude_digits,
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
street_address = models.CharField(max_length=255, blank=True)
|
street_address = models.CharField(max_length=255, blank=True)
|
||||||
city = models.CharField(max_length=255, blank=True)
|
city = models.CharField(max_length=255, blank=True)
|
||||||
@@ -106,32 +106,22 @@ class Park(models.Model):
|
|||||||
closing_date = models.DateField(null=True, blank=True)
|
closing_date = models.DateField(null=True, blank=True)
|
||||||
operating_season = models.CharField(max_length=255, blank=True)
|
operating_season = models.CharField(max_length=255, blank=True)
|
||||||
size_acres = models.DecimalField(
|
size_acres = models.DecimalField(
|
||||||
max_digits=10,
|
max_digits=10, decimal_places=2, null=True, blank=True
|
||||||
decimal_places=2,
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
)
|
||||||
website = models.URLField(blank=True)
|
website = models.URLField(blank=True)
|
||||||
|
|
||||||
# Statistics
|
# Statistics
|
||||||
average_rating = models.DecimalField(
|
average_rating = models.DecimalField(
|
||||||
max_digits=3,
|
max_digits=3, decimal_places=2, null=True, blank=True
|
||||||
decimal_places=2,
|
|
||||||
null=True,
|
|
||||||
blank=True
|
|
||||||
)
|
)
|
||||||
total_rides = models.IntegerField(null=True, blank=True)
|
total_rides = models.IntegerField(null=True, blank=True)
|
||||||
total_roller_coasters = models.IntegerField(null=True, blank=True)
|
total_roller_coasters = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
owner = models.ForeignKey(
|
owner = models.ForeignKey(
|
||||||
Company,
|
Company, on_delete=models.SET_NULL, null=True, blank=True, related_name="parks"
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
related_name='parks'
|
|
||||||
)
|
)
|
||||||
photos = GenericRelation(Photo, related_query_name='park')
|
photos = GenericRelation(Photo, related_query_name="park")
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
created_at = models.DateTimeField(auto_now_add=True, null=True)
|
created_at = models.DateTimeField(auto_now_add=True, null=True)
|
||||||
@@ -139,7 +129,7 @@ class Park(models.Model):
|
|||||||
history = HistoricalRecords()
|
history = HistoricalRecords()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ["name"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@@ -157,7 +147,18 @@ class Park(models.Model):
|
|||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('parks:park_detail', kwargs={'slug': self.slug})
|
return reverse("parks:park_detail", kwargs={"slug": self.slug})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def formatted_location(self):
|
||||||
|
parts = []
|
||||||
|
if self.city:
|
||||||
|
parts.append(self.city)
|
||||||
|
if self.state:
|
||||||
|
parts.append(self.state)
|
||||||
|
if self.country:
|
||||||
|
parts.append(self.country)
|
||||||
|
return ", ".join(parts)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_slug(cls, slug):
|
def get_by_slug(cls, slug):
|
||||||
@@ -166,7 +167,7 @@ class Park(models.Model):
|
|||||||
return cls.objects.get(slug=slug), False
|
return cls.objects.get(slug=slug), False
|
||||||
except cls.DoesNotExist:
|
except cls.DoesNotExist:
|
||||||
# Check historical slugs
|
# Check historical slugs
|
||||||
history = cls.history.filter(slug=slug).order_by('-history_date').first()
|
history = cls.history.filter(slug=slug).order_by("-history_date").first()
|
||||||
if history:
|
if history:
|
||||||
try:
|
try:
|
||||||
return cls.objects.get(id=history.id), True
|
return cls.objects.get(id=history.id), True
|
||||||
@@ -176,11 +177,7 @@ class Park(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ParkArea(models.Model):
|
class ParkArea(models.Model):
|
||||||
park = models.ForeignKey(
|
park = models.ForeignKey(Park, on_delete=models.CASCADE, related_name="areas")
|
||||||
Park,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='areas'
|
|
||||||
)
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
slug = models.SlugField(max_length=255)
|
slug = models.SlugField(max_length=255)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
@@ -193,8 +190,8 @@ class ParkArea(models.Model):
|
|||||||
history = HistoricalRecords()
|
history = HistoricalRecords()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ["name"]
|
||||||
unique_together = ['park', 'slug']
|
unique_together = ["park", "slug"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name} at {self.park.name}"
|
return f"{self.name} at {self.park.name}"
|
||||||
@@ -205,10 +202,10 @@ class ParkArea(models.Model):
|
|||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('parks:area_detail', kwargs={
|
return reverse(
|
||||||
'park_slug': self.park.slug,
|
"parks:area_detail",
|
||||||
'area_slug': self.slug
|
kwargs={"park_slug": self.park.slug, "area_slug": self.slug},
|
||||||
})
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_slug(cls, slug):
|
def get_by_slug(cls, slug):
|
||||||
@@ -217,7 +214,7 @@ class ParkArea(models.Model):
|
|||||||
return cls.objects.get(slug=slug), False
|
return cls.objects.get(slug=slug), False
|
||||||
except cls.DoesNotExist:
|
except cls.DoesNotExist:
|
||||||
# Check historical slugs
|
# Check historical slugs
|
||||||
history = cls.history.filter(slug=slug).order_by('-history_date').first()
|
history = cls.history.filter(slug=slug).order_by("-history_date").first()
|
||||||
if history:
|
if history:
|
||||||
try:
|
try:
|
||||||
return cls.objects.get(id=history.id), True
|
return cls.objects.get(id=history.id), True
|
||||||
|
|||||||
@@ -2401,10 +2401,6 @@ select {
|
|||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-\[400px\] {
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-full {
|
.h-full {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -2860,11 +2856,6 @@ select {
|
|||||||
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-purple-100 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(243 232 255 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-opacity-50 {
|
.bg-opacity-50 {
|
||||||
--tw-bg-opacity: 0.5;
|
--tw-bg-opacity: 0.5;
|
||||||
}
|
}
|
||||||
@@ -3165,11 +3156,6 @@ select {
|
|||||||
color: rgb(133 77 14 / var(--tw-text-opacity));
|
color: rgb(133 77 14 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-purple-800 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(107 33 168 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.opacity-0 {
|
.opacity-0 {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@@ -3569,11 +3555,6 @@ select {
|
|||||||
background-color: rgb(113 63 18 / var(--tw-bg-opacity));
|
background-color: rgb(113 63 18 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:bg-purple-700:is(.dark *) {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(126 34 206 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:from-gray-950:is(.dark *) {
|
.dark\:from-gray-950:is(.dark *) {
|
||||||
--tw-gradient-from: #030712 var(--tw-gradient-from-position);
|
--tw-gradient-from: #030712 var(--tw-gradient-from-position);
|
||||||
--tw-gradient-to: rgb(3 7 18 / 0) var(--tw-gradient-to-position);
|
--tw-gradient-to: rgb(3 7 18 / 0) var(--tw-gradient-to-position);
|
||||||
@@ -3689,11 +3670,6 @@ select {
|
|||||||
color: rgb(254 252 232 / var(--tw-text-opacity));
|
color: rgb(254 252 232 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:text-purple-50:is(.dark *) {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(250 245 255 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:ring-1:is(.dark *) {
|
.dark\:ring-1:is(.dark *) {
|
||||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
@@ -3838,10 +3814,6 @@ select {
|
|||||||
margin-right: 1.5rem;
|
margin-right: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lg\:mt-0 {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lg\:flex {
|
.lg\:flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1 class="mb-2 text-3xl font-bold text-gray-900 dark:text-white">{{ park.name }}</h1>
|
<h1 class="mb-2 text-3xl font-bold text-gray-900 dark:text-white">{{ park.name }}</h1>
|
||||||
{% if park.city or park.state or park.country %}
|
{% if park.city or park.state or park.country %}
|
||||||
<p class="mb-2 text-gray-600 dark:text-gray-400">
|
{% spaceless %}
|
||||||
<i class="mr-1 fas fa-map-marker-alt"></i>
|
<p class="mb-2 text-gray-600 dark:text-gray-400">
|
||||||
{% if park.city %}{{ park.city }}{% endif %}
|
<i class="mr-1 fas fa-map-marker-alt"></i> {% if park.city %}{{ park.city }}{% endif %}{% if park.city and park.state %}, {% endif %}{% if park.state %}{{ park.state }}{% endif %}{% if park.country and park.state or park.city %}, {% endif %}{% if park.country %}{{ park.country }}{% endif %}
|
||||||
{% if park.city and park.state %}, {% endif %}
|
</p>
|
||||||
{% if park.state %}{{ park.state }}{% endif %}
|
{% endspaceless %}
|
||||||
{% if park.country and park.state or park.city %}, {% endif %}
|
|
||||||
{% if park.country %}{{ park.country }}{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
@@ -191,10 +188,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
{% for field, change in record.diff_against_previous %}
|
{% for field, changes in record.diff_against_previous.items %}
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span class="font-medium">{{ field }}:</span>
|
<span class="font-medium">{{ field }}:</span>
|
||||||
{{ change.old }} → {{ change.new }}
|
{{ changes.old }} → {{ changes.new }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,11 +19,10 @@
|
|||||||
{% if park.city or park.state or park.country %}
|
{% if park.city or park.state or park.country %}
|
||||||
<p class="mb-3 text-gray-600 dark:text-gray-400">
|
<p class="mb-3 text-gray-600 dark:text-gray-400">
|
||||||
<i class="mr-1 fas fa-map-marker-alt"></i>
|
<i class="mr-1 fas fa-map-marker-alt"></i>
|
||||||
{% if park.city %}{{ park.city }}{% endif %}
|
{% spaceless %}
|
||||||
{% if park.city and park.state %}, {% endif %}
|
{% if park.city %}{{ park.city }}{% endif %}{% if park.city and park.state %}, {% endif %}{% if park.state %}{{ park.state }}{% endif %}{% if park.country and park.state or park.city %}, {% endif %}{% if park.country %}{{ park.country }}{% endif %}
|
||||||
{% if park.state %}{{ park.state }}{% endif %}
|
</p>
|
||||||
{% if park.country and park.state or park.city %}, {% endif %}
|
{% endspaceless %}
|
||||||
{% if park.country %}{{ park.country }}{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
|
|||||||
@@ -205,10 +205,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
{% for field, change in record.diff_against_previous %}
|
{% for field, changes in record.diff_against_previous.items %}
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span class="font-medium">{{ field }}:</span>
|
<span class="font-medium">{{ field }}:</span>
|
||||||
{{ change.old }} → {{ change.new }}
|
{{ changes.old }} → {{ changes.new }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Binary file not shown.
@@ -44,6 +44,7 @@ INSTALLED_APPS = [
|
|||||||
"email_service",
|
"email_service",
|
||||||
"media.apps.MediaConfig",
|
"media.apps.MediaConfig",
|
||||||
"moderation",
|
"moderation",
|
||||||
|
"history_tracking",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
Reference in New Issue
Block a user