From 209e6add1cc6a75b7f9a3dcc1d8dc5eac6ba53e6 Mon Sep 17 00:00:00 2001 From: pacnpal <183241239+pacnpal@users.noreply.github.com> Date: Mon, 4 Nov 2024 00:33:19 +0000 Subject: [PATCH] add category views for each type of ride, add ride designers --- companies/__pycache__/views.cpython-312.pyc | Bin 15574 -> 16169 bytes companies/views.py | 14 +- designers/__init__.py | 0 designers/admin.py | 10 + designers/apps.py | 6 + designers/migrations/0001_initial.py | 88 ++++ designers/migrations/__init__.py | 0 designers/models.py | 37 ++ designers/tests.py | 3 + designers/urls.py | 8 + designers/views.py | 29 ++ ...lpark_history_user_delete_park_and_more.py | 23 + parks/__pycache__/urls.cpython-312.pyc | Bin 1356 -> 2299 bytes parks/urls.py | 9 + rides/__pycache__/models.cpython-312.pyc | Bin 6718 -> 7906 bytes rides/__pycache__/urls.cpython-312.pyc | Bin 967 -> 1837 bytes rides/__pycache__/views.cpython-312.pyc | Bin 17787 -> 21313 bytes rides/forms.py | 5 +- ...oasterstats_max_drop_height_ft_and_more.py | 65 +++ ...asterstats_roller_coaster_type_and_more.py | 69 +++ ...5_historicalride_designer_ride_designer.py | 40 ++ rides/models.py | 63 ++- rides/urls.py | 11 +- rides/views.py | 68 ++- static/css/tailwind.css | 279 ++++++++++++ templates/companies/company_detail.html | 135 +++--- templates/companies/manufacturer_detail.html | 130 +++--- templates/designers/designer_detail.html | 121 +++++ templates/parks/park_detail.html | 93 ++-- templates/rides/ride_category_list.html | 154 +++++++ templates/rides/ride_detail.html | 420 +++++++++++++----- .../__pycache__/settings.cpython-312.pyc | Bin 5645 -> 5657 bytes thrillwiki/__pycache__/urls.cpython-312.pyc | Bin 2885 -> 2974 bytes thrillwiki/settings.py | 1 + thrillwiki/urls.py | 1 + 35 files changed, 1607 insertions(+), 275 deletions(-) create mode 100644 designers/__init__.py create mode 100644 designers/admin.py create mode 100644 designers/apps.py create mode 100644 designers/migrations/0001_initial.py create mode 100644 designers/migrations/__init__.py create mode 100644 designers/models.py create mode 100644 designers/tests.py create mode 100644 designers/urls.py create mode 100644 designers/views.py create mode 100644 history_tracking/migrations/0002_remove_historicalpark_history_user_delete_park_and_more.py create mode 100644 rides/migrations/0003_historicalrollercoasterstats_max_drop_height_ft_and_more.py create mode 100644 rides/migrations/0004_historicalrollercoasterstats_roller_coaster_type_and_more.py create mode 100644 rides/migrations/0005_historicalride_designer_ride_designer.py create mode 100644 templates/designers/designer_detail.html create mode 100644 templates/rides/ride_category_list.html diff --git a/companies/__pycache__/views.cpython-312.pyc b/companies/__pycache__/views.cpython-312.pyc index cd843db9e8c407c5081f8e1a3a434f2fc1fd9289..9be12c9abd555f33a113c7eb37a186a74f1fd985 100644 GIT binary patch delta 4004 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 diff --git a/companies/views.py b/companies/views.py index db27238a..6d6d1e5e 100644 --- a/companies/views.py +++ b/companies/views.py @@ -5,6 +5,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType from django.contrib import messages from django.http import HttpResponseRedirect +from django.db.models import Count, Sum from .models import Company, Manufacturer from .forms import CompanyForm, ManufacturerForm from rides.models import Ride @@ -173,9 +174,13 @@ class CompanyDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionM def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['parks'] = Park.objects.filter( + parks = Park.objects.filter( owner=self.object ).select_related('owner') + + context['parks'] = parks + context['total_rides'] = Ride.objects.filter(park__in=parks).count() + return context def get_redirect_url_pattern(self): @@ -195,9 +200,14 @@ class ManufacturerDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmis def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['rides'] = Ride.objects.filter( + rides = Ride.objects.filter( manufacturer=self.object ).select_related('park', 'coaster_stats') + + context['rides'] = rides + context['coaster_count'] = rides.filter(category='ROLLER_COASTER').count() + context['parks_count'] = rides.values('park').distinct().count() + return context def get_redirect_url_pattern(self): diff --git a/designers/__init__.py b/designers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/designers/admin.py b/designers/admin.py new file mode 100644 index 00000000..26f980e0 --- /dev/null +++ b/designers/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin +from simple_history.admin import SimpleHistoryAdmin +from .models import Designer + +@admin.register(Designer) +class DesignerAdmin(SimpleHistoryAdmin): + list_display = ('name', 'headquarters', 'founded_date', 'website') + search_fields = ('name', 'headquarters') + list_filter = ('founded_date',) + prepopulated_fields = {'slug': ('name',)} diff --git a/designers/apps.py b/designers/apps.py new file mode 100644 index 00000000..bffb09e3 --- /dev/null +++ b/designers/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DesignersConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "designers" diff --git a/designers/migrations/0001_initial.py b/designers/migrations/0001_initial.py new file mode 100644 index 00000000..afb8916f --- /dev/null +++ b/designers/migrations/0001_initial.py @@ -0,0 +1,88 @@ +# Generated by Django 5.1.2 on 2024-11-04 00:28 + +import django.db.models.deletion +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="Designer", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ("slug", models.SlugField(max_length=255, unique=True)), + ("description", models.TextField(blank=True)), + ("website", models.URLField(blank=True)), + ("founded_date", models.DateField(blank=True, null=True)), + ("headquarters", models.CharField(blank=True, max_length=255)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ], + options={ + "ordering": ["name"], + }, + ), + migrations.CreateModel( + name="HistoricalDesigner", + fields=[ + ( + "id", + models.BigIntegerField( + auto_created=True, blank=True, db_index=True, verbose_name="ID" + ), + ), + ("name", models.CharField(max_length=255)), + ("slug", models.SlugField(max_length=255)), + ("description", models.TextField(blank=True)), + ("website", models.URLField(blank=True)), + ("founded_date", models.DateField(blank=True, null=True)), + ("headquarters", models.CharField(blank=True, max_length=255)), + ("created_at", models.DateTimeField(blank=True, editable=False)), + ("updated_at", models.DateTimeField(blank=True, editable=False)), + ("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 designer", + "verbose_name_plural": "historical designers", + "ordering": ("-history_date", "-history_id"), + "get_latest_by": ("history_date", "history_id"), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/designers/migrations/__init__.py b/designers/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/designers/models.py b/designers/models.py new file mode 100644 index 00000000..912503c4 --- /dev/null +++ b/designers/models.py @@ -0,0 +1,37 @@ +from django.db import models +from django.utils.text import slugify +from simple_history.models import HistoricalRecords + +class Designer(models.Model): + name = models.CharField(max_length=255) + slug = models.SlugField(max_length=255, unique=True) + description = models.TextField(blank=True) + website = models.URLField(blank=True) + founded_date = models.DateField(null=True, blank=True) + headquarters = models.CharField(max_length=255, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + history = HistoricalRecords() + + class Meta: + ordering = ['name'] + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) + + @classmethod + def get_by_slug(cls, slug): + """Get designer by current or historical slug""" + try: + return cls.objects.get(slug=slug), False + except cls.DoesNotExist: + # Check historical slugs + history = cls.history.filter(slug=slug).order_by('-history_date').first() + if history: + return cls.objects.get(id=history.id), True + raise cls.DoesNotExist("No designer found with this slug") diff --git a/designers/tests.py b/designers/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/designers/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/designers/urls.py b/designers/urls.py new file mode 100644 index 00000000..c71f8420 --- /dev/null +++ b/designers/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from . import views + +app_name = 'designers' + +urlpatterns = [ + path('/', views.DesignerDetailView.as_view(), name='designer_detail'), +] diff --git a/designers/views.py b/designers/views.py new file mode 100644 index 00000000..d0b133b5 --- /dev/null +++ b/designers/views.py @@ -0,0 +1,29 @@ +from django.views.generic import DetailView +from .models import Designer +from django.db.models import Count + +class DesignerDetailView(DetailView): + model = Designer + template_name = 'designers/designer_detail.html' + context_object_name = 'designer' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Get all rides by this designer + context['rides'] = self.object.rides.select_related( + 'park', + 'manufacturer', + 'coaster_stats' + ).order_by('-opening_date') + + # Get stats + context['stats'] = { + 'total_rides': self.object.rides.count(), + 'total_parks': self.object.rides.values('park').distinct().count(), + 'total_coasters': self.object.rides.filter(category='RC').count(), + 'total_countries': self.object.rides.values( + 'park__location__country' + ).distinct().count(), + } + + return context diff --git a/history_tracking/migrations/0002_remove_historicalpark_history_user_delete_park_and_more.py b/history_tracking/migrations/0002_remove_historicalpark_history_user_delete_park_and_more.py new file mode 100644 index 00000000..a5941190 --- /dev/null +++ b/history_tracking/migrations/0002_remove_historicalpark_history_user_delete_park_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.2 on 2024-11-04 00:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("history_tracking", "0001_initial"), + ] + + operations = [ + migrations.RemoveField( + model_name="historicalpark", + name="history_user", + ), + migrations.DeleteModel( + name="Park", + ), + migrations.DeleteModel( + name="HistoricalPark", + ), + ] diff --git a/parks/__pycache__/urls.cpython-312.pyc b/parks/__pycache__/urls.cpython-312.pyc index f4c37adf7979e3c28d3b178812af2d492eb3ac0a..b285ca98c1775fe850ab3b6994c0c3c0b2b695ad 100644 GIT binary patch literal 2299 zcmb7FPfXip6#s0Te}oVUX(1HCO35G@Bvqzp0?}?0uyiG~fsu8}+GSqsCI%7F|Q$6tB0NynRZ@LHGb>Pi7c+nns zZvt=D!J7jf&BeI*ledm(yI<{ld9W~aFdtiZN!&5oWa_M1F+D=9B<^wiONw|Y)Ad;3 z_Z2Y4bF94kkx(m({0gh`Wl28B3X1wMgiTi+Vv31OA2Es|_<_OH*eb7iN-_`lr0KU} z_D&mER>ZyX5?OAwLmaQNf~Z|O8{#=ZZO8ZO90+Mc3eU==N>Y?ctSU&gq#10RMLVR8 zwjeV9n3olPq0CDqiGvz~El;uK(dO)AR%v!IER?GxZ8x!t?Ha93tHx_xG+7cwUM`j- zR#72m()2=gR+mhAYsCy%85|&OqDzJ&{gVZb2L*a{%M4n)j`tkOBau~4DIc^c9j`Xo zm1Q4FlTSJKx6C0c%^CMOc~qILDRoI!Nom{dJRPq#)|E$6D^@S)d)uwYZ+o=SE~tHG zt>3yQ_GD2pU6u`l0Pc$+@8qak`tgt-c99KSWo&Wiuw~pU!ai}DD{_r_#5vlFLL<~hY#~cU`h{68G%_n zFxv>kp8A{2@*j-%F#P?R5t`FOb4KWf9=g#8rJhYTnG6XERBi@Ll^hnBxEa{P@Mr8T<%_g(?$_?qT@9I(Bm`v%DDZF^6$*fz9=^xjO znMHkO5x=+DWVTw!x1Z*X^oE|^Fw%KFoo}S?kG%qg~0}xm|QA_t_W?*;>;=lkOl=1lu<3tTbMV2U5CWchT6xLLhEFPE| z1f9h{*?~z~kZle7YM?am$&IF(2dG*ESsjE8 zQJu((rkW3^S`1Tl3O|}U0iZewEb0W&)Co<_V-#b8SY|AYCMg2+DN~AQir5 zV01c16knx;rsU)jW|hg?m_JQ^!D=PXe~UXMD={xUU$3+%r??0dTtzG(f&)abO%7%Y zWdj9%5&z_MY^4(NKqey)7lUHw12ZEd<9!Czy9~mU?bwe/areas//", views.ParkAreaDetailView.as_view(), name="area_detail"), + # Park-specific category URLs + path("/roller_coasters/", ParkSingleCategoryListView.as_view(), {'category': 'RC'}, name="park_roller_coasters"), + path("/dark_rides/", ParkSingleCategoryListView.as_view(), {'category': 'DR'}, name="park_dark_rides"), + path("/flat_rides/", ParkSingleCategoryListView.as_view(), {'category': 'FR'}, name="park_flat_rides"), + path("/water_rides/", ParkSingleCategoryListView.as_view(), {'category': 'WR'}, name="park_water_rides"), + path("/transports/", ParkSingleCategoryListView.as_view(), {'category': 'TR'}, name="park_transports"), + path("/others/", ParkSingleCategoryListView.as_view(), {'category': 'OT'}, name="park_others"), + # Include rides URLs path("/rides/", include("rides.urls", namespace="rides")), ] diff --git a/rides/__pycache__/models.cpython-312.pyc b/rides/__pycache__/models.cpython-312.pyc index fa2c2d12fa5c0ef6d42d92c8fd80de27867f9658..f5cd228a645c03d5d1cd6ed0e891475a36cd4707 100644 GIT binary patch delta 2809 zcmZ`*OH3ol8SeJ84Yu*y_%YB7kHIrMW_DS!S!R*}YzAljAhv{`$NAs_w7+yNSQwbARS?bue)KD=@tNZsdvki%yPo;re{ca_nYzS^Z~h!0JQR z%^v-`KEM8<&tkWV{-eO5P5+gtugff2kAjD$_Ly=&|FtO+4k3#eK5`$L&)A@ef$<|` ze9#K<-9~))N~QZsB|?>nS7eJ_sl=%gpcVYa?6hCIGP37NrI#xANsdTFbc&>s=%MAT z#y+YhiR!l=#SYmszW;*XwI0O{egODE@tV;eqWvDDe*^l%hwUUxIbmWRFk zW685tW3se?_;Y?7)z+mNLRhM;^J@~W@))UgrKU;`WyG&3nAad0L&k&^_rer0PoR%L z9H8acKyqEs&?D`s3CGUqe+EeF_&>HXYrO-_Mg1KvZkrupl&?q?EC<-iqGuNU&3S)}i-Kkl|JWBvH$%Zq*TnM3rrnu;Bap z{~SX>k_smQ8V;Ibi~J02WUY_=f)2HR+`fcNFEV^*IU2BA(Enx$dy9>n8UdJJ(*~J3slytu?PXp#8BgEA_VX_Oq>FEIaS}8BWV{)SH(lPQ+t>7c z=hS8Q*!JdMc#a#M@3GhE){yk8@G~y^S?lw^nHb&6#s=0I(fkQpw!#Z*+jJXoMgwuk zrssVj-;vdDgbo@UqTiBDw2Pd`9XX6|$$3fb_?qgvq;`HybyJmkmEU>fGQKCz1s^0& za~t20mns8fF2OEPJci=GP$FLNG#{vbW0rvnHReTDF#uHrk3xs!6@;ryT+g;iYmA-M z2(H*|=%I`8VJ|X^5pa`p$0WXJ+kDlWh%egOUi_6hh%dfciC!pEU#^`vX54TLa7A#L z7`RN_Sc4w%8n{gFPEVp&p9*}s`_$C3=1cRb0`@?W2}=U{#ZoGjft!Yq41LRTKA!|s z6cKv1`IVVMItllkhu|6b12W75g1wk7CG*R<23M4{(WLTz4M;AXTS^s5sU);&+X!oj z=9tT@q;n5iwz*w-2V9AcE=}i>iwiJXtJK8N#X69dWfEmsZihIEi^YXhE(ysT#Vxgt zY9h^MpUKY@Gr-zsl!vNJIc_1nkjkWUsTQ{&)sYPT2l*V?d?{a;Nxz+frlKkM;jZ)^ z73MPed?Ax67F&)vSy8ZzRFzC1nO;f(O5izA`rLFjomqiyj|C9zy_A)U*S>2t-pRI9a1@XX~G3#EA>na-wi#dJRBZMY`14L&JV5%{rE z!yoAX4fM0O^xj}}HvvnFqXcdc7$&d>0QN^+cz_Ut1c*)J9{@CFi1TYZbu_MOJ7Dk1 z8jlrOMwnk!DyjzeVKlZr#-H!e2!FmWBm7)@GvE}U>6gBee~9EC$W|Un)vYQ|Hd9)y z$h?S^I=_LW^$m@eYWx~P8e|wDGa4meoFkYd!0+q1V7>FQ%l;*JhA!(AHGT$a)7pDt#;6}7S3!iP*4X>4^o}V*+ z)&CUg%aG^g68vS$2d3rDOfTkU=Y`q%e0nxjY=ugN>Djl1>~twrNKa=BJ}{e~hEJs+ zlvWl}x;cEkvkb%WQv$MnJ3Pp)=}Y1M^dd>xq2bEvdji>+mb=}qR$x1%irn(IcStzf zp>~k3T6rXpwH4ZHE39ey<8Zd~(lMj|JLn(XCXXXfg`fIIScHA%rHKpK_Hr)+)*jn4 zOZ27N8;5)Miy__H{oBpp;D<}kgM%l*!KOdh^b9pUVK^_~SRrHJSc%>GXy|$L=1KG> z)d!lM2t7}hPCIQe$P<3yW}>4Xg`P)7P9h^sf1nxeK0kBbgqSGAgdoP>uRrVle)}K6 zw*F3JZpwnSay~@nEc`0$9GQ|+BwacS6LPw Q{Bisz@h=$CrZ2+(0Z`_v#sB~S delta 1697 zcmZ`(UuauZ7(eIU-2BgNlbf3?f10MLZI{l@^)IZAwOZ^7t5MfYG>D<=x$RmrR?bai z9m47eT0xL;eCR+B-y8`jggwb*z6m}FK8!#?1i>jtaoLLx)AOBVH(O^e+~2+D_xsN8 ze&^ipJHI~v-Gut1&*w(`x_%+GKCfL@A9x%viVt;E!X1?(LK*uXw`fNtCX^t|VgJfrN4#TX zPe&yolv(hIgou~GO2QReYmG^vz67eKRF70}i%+%rt4cLu@%@~ChD5FLfEdTD@gN@$ zRhlL!cT_^B5Q&!&>9O`MO%i348{S20nDmyNxnst&Bw0q*oRM908Rw2KqjX>0c4mVZ zwFTujT-$RLuyTSMFEN)ZZlC6FG{cU$N)w~-I>zB9uG>tbux7#{l5~b+-I33mMVilF z@q*Ry*620%vn!3K*+UoW9f#=(02tL3v$SPUZnW~Ak+#tn*f;J$9jxd)hbt~&1L89K z(>;NwYe~=N7>~2t-tFE+9+8&o@@m1P!gh%hCX`b$$@5|7d@1}UyX?E=dyY2*DCF~LOcyvSHe^n02zA0_06PdH?0okf?W={avp2JM^dF3dECt;=O9$@S zdH*|@@6h)F-3^g&-JfN@1azn;+otHTCjAHvJLgrXB)?RZtuAsxJnPd!AzpfRVy`-^ z;&ZnoJ?wl+^E}cbuBSAwNFgeU)?M{j#izD;9lqqXimpp2NtmJgxT0E$zpaG344HIS z{Z{IMwsvTi*6~YlXxKJIZijNv+se$7*}-uVwi-Ewi!Yy{-Rxnw|HLr=3HRWjyC*6c&Ncq;2l#^?1&9I+0t^9^m_M@RdAtNa zi40Ai1W7cOsCQ9o*EshXzfB*9GP)Zc+x6Z}oczfdYer_+?~#p=Xu(+sYquVqW9!jg zyvII@_9ehIn~ptR^_;1zOD~?w&M&e?H061V6ZAuXP1YA1z=WNO^>?3zFn5uztiGEs zZf_Z^7JFspP%9kDy9gctgx}_Ao>3Y7EjdH>o$Nu7osQp@0x3p&t~Jk)BKxOjXHCp` z(x>jsT)ad|1!I=p;6|`8bBe=(j4|Fv@qIMAj|TS9c*FKK#`Ac0>eI}}nFbPr#{1GF S9@z_h5x)_CfcUG}t^WY8jbIi4 diff --git a/rides/__pycache__/urls.cpython-312.pyc b/rides/__pycache__/urls.cpython-312.pyc index 967d0df51a4d17c2d6628ab2afca180bacac497f..bd07a329ae6e062909ffabf1a7ea28080d0f936a 100644 GIT binary patch literal 1837 zcmbV~PfXKL7{J@DV;iN6LBUPw5T^k&T0TbH(o^rQz9 zj^55QwjCdQP8yLL-vsE712P^lew9Ekswm^>GO>ak}1d#gLNXd>! z{XiOMBk>)P27xrxMmp9JDFvi-8|ipQq!U0IZX*dm>I-h5RUUbF8DO-&p%Gy8w^j%G zk&KgVj8S0l|H(MjWR%$gyOo_d{f!_dXw1|ltMZKwjK~vJ*)(y)P-Vr$6pj<6sHvt! zGHX&*u^CCzL>vcM^t0?fL)SFLD3)}|v=qY>ajYa+N?A9ya45fs6K!#ul_leGP%Prm zwLI=_I&5}LldQww4G1a9S}SYH;{GBJ6M9Md zqTjS%;M*&88ShgxB%o$@qBM--X3i5lHrs>br@Mmh^3)}xJ_9I3` z;@kb(D;_t3xDk&VL)@6lO}u9tOzsmC+a7v3=q0l#ne~$AQ1YCcJpcYygSiSJCw^jQ z#S^X|;fg0LAYs837I)X3{Hk;PzVk?LFm+Nd>_oiO6iQ8bsT@k>+|=Ce@x5i|YTkLE zG?=x2Ov&sFdg*DDp7zqSC_U?@=XcNU$%0E)lFRy$2MZ{n9_{)gD?D8`o z3VXbhyW^}pY%q_0bdg>7N#lqg4^mxz@`KP|Zu$d{zMl57mr(YSmz_u1c{jVT2YXuf kW5YX&mzhMFNiQ>lGBa)_w>#Tl?tK>$iO_S_I0HBE8}8+_;{X5v delta 279 zcmZ3>cbr}0G%qg~0}$*!q@MnRk%8echyw$hP{!v9#)%q_MAopcW`>9`F{Co4@T4+l z2~5so)EDJNli>r(2!UjT`4KYdtWjK*0-A!86PT6Ms#p?pa`bO;0?GKI%#_q(O~zX+ zd5O8Hw-|15f&}7oGK)(lr?5nea^K=k$x6&i&(|w0$|)`axuA%1@)Z`X$zrU%N|Hb> z(Af;di-5!jW=2NFy9_dS8H68j3pH?jWMg3Eocw@QTDn81BlJ42{6${*>%8h0dDSoT dYHr}Z!eVoig=6wXwm@!fHh!i??jmuZApoo)M1}wW diff --git a/rides/__pycache__/views.cpython-312.pyc b/rides/__pycache__/views.cpython-312.pyc index fe42bfbfa58abce1861984688ac38cff25894022..73623e5c9b4593249e2acf55ac049c55ca45e7f7 100644 GIT binary patch delta 6131 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 diff --git a/rides/forms.py b/rides/forms.py index 94a27918..4d7d177f 100644 --- a/rides/forms.py +++ b/rides/forms.py @@ -4,7 +4,7 @@ from .models import Ride class RideForm(forms.ModelForm): class Meta: model = Ride - fields = ['name', 'park_area', 'category', 'manufacturer', 'model_name', 'status', + fields = ['name', 'park_area', 'category', 'manufacturer', 'designer', 'model_name', 'status', 'opening_date', 'closing_date', 'status_since', 'min_height_in', 'max_height_in', 'accessibility_options', 'capacity_per_hour', 'ride_duration_seconds', 'description'] widgets = { @@ -20,6 +20,9 @@ class RideForm(forms.ModelForm): 'manufacturer': forms.Select(attrs={ 'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white' }), + 'designer': forms.Select(attrs={ + 'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white' + }), 'model_name': forms.TextInput(attrs={ 'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white' }), diff --git a/rides/migrations/0003_historicalrollercoasterstats_max_drop_height_ft_and_more.py b/rides/migrations/0003_historicalrollercoasterstats_max_drop_height_ft_and_more.py new file mode 100644 index 00000000..4994ce94 --- /dev/null +++ b/rides/migrations/0003_historicalrollercoasterstats_max_drop_height_ft_and_more.py @@ -0,0 +1,65 @@ +# Generated by Django 5.1.2 on 2024-11-04 00:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("rides", "0002_alter_ride_manufacturer"), + ] + + operations = [ + migrations.AddField( + model_name="historicalrollercoasterstats", + name="max_drop_height_ft", + field=models.DecimalField( + blank=True, + decimal_places=2, + help_text="Maximum vertical drop height in feet", + max_digits=6, + null=True, + ), + ), + migrations.AddField( + model_name="historicalrollercoasterstats", + name="track_material", + field=models.CharField( + blank=True, + choices=[ + ("STEEL", "Steel"), + ("WOOD", "Wood"), + ("HYBRID", "Hybrid"), + ("OTHER", "Other"), + ], + default="STEEL", + max_length=20, + ), + ), + migrations.AddField( + model_name="rollercoasterstats", + name="max_drop_height_ft", + field=models.DecimalField( + blank=True, + decimal_places=2, + help_text="Maximum vertical drop height in feet", + max_digits=6, + null=True, + ), + ), + migrations.AddField( + model_name="rollercoasterstats", + name="track_material", + field=models.CharField( + blank=True, + choices=[ + ("STEEL", "Steel"), + ("WOOD", "Wood"), + ("HYBRID", "Hybrid"), + ("OTHER", "Other"), + ], + default="STEEL", + max_length=20, + ), + ), + ] diff --git a/rides/migrations/0004_historicalrollercoasterstats_roller_coaster_type_and_more.py b/rides/migrations/0004_historicalrollercoasterstats_roller_coaster_type_and_more.py new file mode 100644 index 00000000..11003d24 --- /dev/null +++ b/rides/migrations/0004_historicalrollercoasterstats_roller_coaster_type_and_more.py @@ -0,0 +1,69 @@ +# Generated by Django 5.1.2 on 2024-11-04 00:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("rides", "0003_historicalrollercoasterstats_max_drop_height_ft_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="historicalrollercoasterstats", + name="roller_coaster_type", + field=models.CharField( + blank=True, + choices=[ + ("SITDOWN", "Sit-Down"), + ("INVERTED", "Inverted"), + ("FLYING", "Flying"), + ("STANDUP", "Stand-Up"), + ("WING", "Wing"), + ("SUSPENDED", "Suspended"), + ("BOBSLED", "Bobsled"), + ("PIPELINE", "Pipeline"), + ("MOTORBIKE", "Motorbike"), + ("FLOORLESS", "Floorless"), + ("DIVE", "Dive"), + ("FAMILY", "Family"), + ("WILD_MOUSE", "Wild Mouse"), + ("SPINNING", "Spinning"), + ("FOURTH_DIMENSION", "4th Dimension"), + ("OTHER", "Other"), + ], + default="SITDOWN", + help_text="The type/style of roller coaster (e.g. Sit-Down, Inverted, Flying)", + max_length=20, + ), + ), + migrations.AddField( + model_name="rollercoasterstats", + name="roller_coaster_type", + field=models.CharField( + blank=True, + choices=[ + ("SITDOWN", "Sit-Down"), + ("INVERTED", "Inverted"), + ("FLYING", "Flying"), + ("STANDUP", "Stand-Up"), + ("WING", "Wing"), + ("SUSPENDED", "Suspended"), + ("BOBSLED", "Bobsled"), + ("PIPELINE", "Pipeline"), + ("MOTORBIKE", "Motorbike"), + ("FLOORLESS", "Floorless"), + ("DIVE", "Dive"), + ("FAMILY", "Family"), + ("WILD_MOUSE", "Wild Mouse"), + ("SPINNING", "Spinning"), + ("FOURTH_DIMENSION", "4th Dimension"), + ("OTHER", "Other"), + ], + default="SITDOWN", + help_text="The type/style of roller coaster (e.g. Sit-Down, Inverted, Flying)", + max_length=20, + ), + ), + ] diff --git a/rides/migrations/0005_historicalride_designer_ride_designer.py b/rides/migrations/0005_historicalride_designer_ride_designer.py new file mode 100644 index 00000000..4255cf13 --- /dev/null +++ b/rides/migrations/0005_historicalride_designer_ride_designer.py @@ -0,0 +1,40 @@ +# Generated by Django 5.1.2 on 2024-11-04 00:28 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("designers", "0001_initial"), + ("rides", "0004_historicalrollercoasterstats_roller_coaster_type_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="historicalride", + name="designer", + field=models.ForeignKey( + blank=True, + db_constraint=False, + help_text="The designer/engineering firm responsible for the ride", + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="designers.designer", + ), + ), + migrations.AddField( + model_name="ride", + name="designer", + field=models.ForeignKey( + blank=True, + help_text="The designer/engineering firm responsible for the ride", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="rides", + to="designers.designer", + ), + ), + ] diff --git a/rides/models.py b/rides/models.py index 6fdebbfc..c3577616 100644 --- a/rides/models.py +++ b/rides/models.py @@ -43,10 +43,19 @@ class Ride(models.Model): default='OT' ) manufacturer = models.ForeignKey( - 'companies.manufacturer', on_delete=models.CASCADE, null=False, blank=False - ) - # other fields... - + 'companies.manufacturer', + on_delete=models.CASCADE, + null=False, + blank=False + ) + designer = models.ForeignKey( + 'designers.Designer', + on_delete=models.SET_NULL, + related_name='rides', + null=True, + blank=True, + help_text='The designer/engineering firm responsible for the ride' + ) model_name = models.CharField(max_length=255, blank=True) status = models.CharField( max_length=20, @@ -108,6 +117,32 @@ class RollerCoasterStats(models.Model): ('OTHER', 'Other'), ] + TRACK_MATERIAL_CHOICES = [ + ('STEEL', 'Steel'), + ('WOOD', 'Wood'), + ('HYBRID', 'Hybrid'), + ('OTHER', 'Other'), + ] + + COASTER_TYPE_CHOICES = [ + ('SITDOWN', 'Sit-Down'), + ('INVERTED', 'Inverted'), + ('FLYING', 'Flying'), + ('STANDUP', 'Stand-Up'), + ('WING', 'Wing'), + ('SUSPENDED', 'Suspended'), + ('BOBSLED', 'Bobsled'), + ('PIPELINE', 'Pipeline'), + ('MOTORBIKE', 'Motorbike'), + ('FLOORLESS', 'Floorless'), + ('DIVE', 'Dive'), + ('FAMILY', 'Family'), + ('WILD_MOUSE', 'Wild Mouse'), + ('SPINNING', 'Spinning'), + ('FOURTH_DIMENSION', '4th Dimension'), + ('OTHER', 'Other'), + ] + ride = models.OneToOneField( Ride, on_delete=models.CASCADE, @@ -134,6 +169,26 @@ class RollerCoasterStats(models.Model): inversions = models.PositiveIntegerField(default=0) ride_time_seconds = models.PositiveIntegerField(null=True, blank=True) track_type = models.CharField(max_length=255, blank=True) + track_material = models.CharField( + max_length=20, + choices=TRACK_MATERIAL_CHOICES, + default='STEEL', + blank=True + ) + roller_coaster_type = models.CharField( + max_length=20, + choices=COASTER_TYPE_CHOICES, + default='SITDOWN', + blank=True, + help_text='The type/style of roller coaster (e.g. Sit-Down, Inverted, Flying)' + ) + max_drop_height_ft = models.DecimalField( + max_digits=6, + decimal_places=2, + null=True, + blank=True, + help_text='Maximum vertical drop height in feet' + ) launch_type = models.CharField( max_length=20, choices=LAUNCH_CHOICES, diff --git a/rides/urls.py b/rides/urls.py index 77db4f29..d8358aac 100644 --- a/rides/urls.py +++ b/rides/urls.py @@ -4,8 +4,17 @@ from . import views app_name = 'rides' # Add namespace urlpatterns = [ - path('all/', views.RideListView.as_view(), name='all_rides'), # New pattern for all rides + # Global category URLs path('', views.RideListView.as_view(), name='ride_list'), + path('all/', views.RideListView.as_view(), name='all_rides'), + path('roller_coasters/', views.SingleCategoryListView.as_view(), {'category': 'RC'}, name='roller_coasters'), + path('dark_rides/', views.SingleCategoryListView.as_view(), {'category': 'DR'}, name='dark_rides'), + path('flat_rides/', views.SingleCategoryListView.as_view(), {'category': 'FR'}, name='flat_rides'), + path('water_rides/', views.SingleCategoryListView.as_view(), {'category': 'WR'}, name='water_rides'), + path('transports/', views.SingleCategoryListView.as_view(), {'category': 'TR'}, name='transports'), + path('others/', views.SingleCategoryListView.as_view(), {'category': 'OT'}, name='others'), + + # Basic ride URLs path('create/', views.RideCreateView.as_view(), name='ride_create'), path('/edit/', views.RideUpdateView.as_view(), name='ride_edit'), path('/', views.RideDetailView.as_view(), name='ride_detail'), diff --git a/rides/views.py b/rides/views.py index 95973d89..1734783a 100644 --- a/rides/views.py +++ b/rides/views.py @@ -6,15 +6,79 @@ from django.db.models import Q from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType from django.contrib import messages -from django.http import JsonResponse, HttpResponseRedirect +from django.http import JsonResponse, HttpResponseRedirect, Http404 +from django.db.models import Count from .models import Ride, RollerCoasterStats from .forms import RideForm from parks.models import Park from core.views import SlugRedirectMixin from moderation.mixins import EditSubmissionMixin, PhotoSubmissionMixin, HistoryMixin -from moderation.models import EditSubmission from media.models import Photo +class SingleCategoryListView(ListView): + model = Ride + template_name = 'rides/ride_category_list.html' + context_object_name = 'categories' + + def get_category_code(self): + category = self.kwargs.get('category') + if not category: + raise Http404("Category not found") + return category + + def get_queryset(self): + category_code = self.get_category_code() + category_name = dict(Ride.CATEGORY_CHOICES)[category_code] + + rides = Ride.objects.filter(category=category_code).select_related( + 'park', 'manufacturer' + ).order_by('name') + + return {category_name: rides} if rides.exists() else {} + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + category_code = self.get_category_code() + category_name = dict(Ride.CATEGORY_CHOICES)[category_code] + context['title'] = f'All {category_name}s' + context['category_code'] = category_code + return context + +class ParkSingleCategoryListView(ListView): + model = Ride + template_name = 'rides/ride_category_list.html' + context_object_name = 'categories' + + def setup(self, request, *args, **kwargs): + super().setup(request, *args, **kwargs) + self.park = get_object_or_404(Park, slug=self.kwargs['park_slug']) + + def get_category_code(self): + category = self.kwargs.get('category') + if not category: + raise Http404("Category not found") + return category + + def get_queryset(self): + category_code = self.get_category_code() + category_name = dict(Ride.CATEGORY_CHOICES)[category_code] + + rides = Ride.objects.filter( + park=self.park, + category=category_code + ).select_related('manufacturer').order_by('name') + + return {category_name: rides} if rides.exists() else {} + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['park'] = self.park + category_code = self.get_category_code() + category_name = dict(Ride.CATEGORY_CHOICES)[category_code] + context['title'] = f'{category_name}s at {self.park.name}' + context['category_code'] = category_code + return context + class RideCreateView(LoginRequiredMixin, CreateView): model = Ride form_class = RideForm diff --git a/static/css/tailwind.css b/static/css/tailwind.css index 2a0d290b..2bb1a0f0 100644 --- a/static/css/tailwind.css +++ b/static/css/tailwind.css @@ -2253,6 +2253,10 @@ select { grid-column: span 1 / span 1; } +.col-span-12 { + grid-column: span 12 / span 12; +} + .mx-1 { margin-left: 0.25rem; margin-right: 0.25rem; @@ -2361,6 +2365,30 @@ select { margin-top: 2rem; } +.mr-1\.5 { + margin-right: 0.375rem; +} + +.mb-0\.5 { + margin-bottom: 0.125rem; +} + +.ml-0\.5 { + margin-left: 0.125rem; +} + +.mr-0\.5 { + margin-right: 0.125rem; +} + +.mt-1\.5 { + margin-top: 0.375rem; +} + +.mb-10 { + margin-bottom: 2.5rem; +} + .block { display: block; } @@ -2433,6 +2461,10 @@ select { height: 340px; } +.h-auto { + height: auto; +} + .max-h-60 { max-height: 15rem; } @@ -2441,6 +2473,10 @@ select { max-height: 90vh; } +.max-h-\[340px\] { + max-height: 340px; +} + .min-h-\[calc\(100vh-16rem\)\] { min-height: calc(100vh - 16rem); } @@ -2453,6 +2489,10 @@ select { min-height: 0px; } +.min-h-\[200px\] { + min-height: 200px; +} + .w-16 { width: 4rem; } @@ -2526,6 +2566,10 @@ select { flex: 1 1 0%; } +.flex-shrink-0 { + flex-shrink: 0; +} + .flex-grow { flex-grow: 1; } @@ -2659,6 +2703,14 @@ select { gap: 2rem; } +.gap-1\.5 { + gap: 0.375rem; +} + +.gap-1 { + gap: 0.25rem; +} + .gap-x-8 { -moz-column-gap: 2rem; column-gap: 2rem; @@ -2732,6 +2784,10 @@ select { overflow: hidden; } +.overflow-y-auto { + overflow-y: auto; +} + .rounded { border-radius: 0.25rem; } @@ -2935,6 +2991,11 @@ select { background-color: rgb(202 138 4 / var(--tw-bg-opacity)); } +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + .bg-opacity-50 { --tw-bg-opacity: 0.5; } @@ -3006,6 +3067,18 @@ select { padding: 2rem; } +.p-2\.5 { + padding: 0.625rem; +} + +.p-0\.5 { + padding: 0.125rem; +} + +.p-1\.5 { + padding: 0.375rem; +} + .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -3071,6 +3144,11 @@ select { padding-bottom: 2rem; } +.py-0\.5 { + padding-top: 0.125rem; + padding-bottom: 0.125rem; +} + .pb-4 { padding-bottom: 1rem; } @@ -3079,6 +3157,10 @@ select { text-align: center; } +.align-middle { + vertical-align: middle; +} + .text-2xl { font-size: 1.5rem; line-height: 2rem; @@ -3114,6 +3196,11 @@ select { line-height: 1rem; } +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + .font-bold { font-weight: 700; } @@ -3126,6 +3213,10 @@ select { font-weight: 600; } +.leading-tight { + line-height: 1.25; +} + .text-blue-500 { --tw-text-opacity: 1; color: rgb(59 130 246 / var(--tw-text-opacity)); @@ -3240,6 +3331,16 @@ select { color: rgb(22 163 74 / var(--tw-text-opacity)); } +.text-sky-400 { + --tw-text-opacity: 1; + color: rgb(56 189 248 / var(--tw-text-opacity)); +} + +.text-sky-900 { + --tw-text-opacity: 1; + color: rgb(12 74 110 / var(--tw-text-opacity)); +} + .opacity-0 { opacity: 0; } @@ -3335,6 +3436,12 @@ select { transition-duration: 150ms; } +.transition-shadow { + transition-property: box-shadow; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + .duration-100 { transition-duration: 100ms; } @@ -3455,6 +3562,11 @@ select { background-color: rgb(202 138 4 / var(--tw-bg-opacity)); } +.hover\:bg-gray-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + .hover\:text-blue-500:hover { --tw-text-opacity: 1; color: rgb(59 130 246 / var(--tw-text-opacity)); @@ -3494,10 +3606,41 @@ select { color: rgb(79 70 229 / 0.8); } +.hover\:text-sky-300:hover { + --tw-text-opacity: 1; + color: rgb(125 211 252 / var(--tw-text-opacity)); +} + +.hover\:text-sky-900:hover { + --tw-text-opacity: 1; + color: rgb(12 74 110 / var(--tw-text-opacity)); +} + +.hover\:text-sky-950:hover { + --tw-text-opacity: 1; + color: rgb(8 47 73 / var(--tw-text-opacity)); +} + +.hover\:text-sky-800:hover { + --tw-text-opacity: 1; + color: rgb(7 89 133 / var(--tw-text-opacity)); +} + +.hover\:text-blue-800:hover { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity)); +} + .hover\:underline:hover { text-decoration-line: underline; } +.hover\:shadow-md:hover { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + .focus\:border-blue-500:focus { --tw-border-opacity: 1; border-color: rgb(59 130 246 / var(--tw-border-opacity)); @@ -3765,6 +3908,11 @@ select { color: rgb(74 222 128 / var(--tw-text-opacity)); } +.dark\:text-sky-400:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(56 189 248 / var(--tw-text-opacity)); +} + .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-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); @@ -3838,6 +3986,26 @@ select { color: rgb(79 70 229 / var(--tw-text-opacity)); } +.dark\:hover\:text-sky-400:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(56 189 248 / var(--tw-text-opacity)); +} + +.dark\:hover\:text-sky-600:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(2 132 199 / var(--tw-text-opacity)); +} + +.dark\:hover\:text-sky-200:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(186 230 253 / var(--tw-text-opacity)); +} + +.dark\:hover\:text-sky-300:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(125 211 252 / var(--tw-text-opacity)); +} + @media (min-width: 640px) { .sm\:col-span-2 { grid-column: span 2 / span 2; @@ -3847,6 +4015,54 @@ select { grid-column: span 4 / span 4; } + .sm\:col-span-3 { + grid-column: span 3 / span 3; + } + + .sm\:col-span-8 { + grid-column: span 8 / span 8; + } + + .sm\:col-span-9 { + grid-column: span 9 / span 9; + } + + .sm\:mb-8 { + margin-bottom: 2rem; + } + + .sm\:mb-16 { + margin-bottom: 4rem; + } + + .sm\:flex { + display: flex; + } + + .sm\:h-\[340px\] { + height: 340px; + } + + .sm\:h-\[300px\] { + height: 300px; + } + + .sm\:h-\[200px\] { + height: 200px; + } + + .sm\:h-\[160px\] { + height: 160px; + } + + .sm\:h-\[140px\] { + height: 140px; + } + + .sm\:h-auto { + height: auto; + } + .sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } @@ -3855,6 +4071,26 @@ select { grid-template-columns: repeat(6, minmax(0, 1fr)); } + .sm\:grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + + .sm\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .sm\:grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + + .sm\:flex-col { + flex-direction: column; + } + + .sm\:gap-4 { + gap: 1rem; + } + .sm\:space-x-4 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(1rem * var(--tw-space-x-reverse)); @@ -3866,6 +4102,41 @@ select { margin-right: calc(1.5rem * var(--tw-space-x-reverse)); margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse))); } + + .sm\:text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; + } + + .sm\:text-base { + font-size: 1rem; + line-height: 1.5rem; + } + + .sm\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } + + .sm\:text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; + } + + .sm\:text-lg { + font-size: 1.125rem; + line-height: 1.75rem; + } + + .sm\:text-xs { + font-size: 0.75rem; + line-height: 1rem; + } + + .sm\:text-2xl { + font-size: 1.5rem; + line-height: 2rem; + } } @media (min-width: 768px) { @@ -3893,6 +4164,14 @@ select { margin-top: 0px; } + .md\:mb-8 { + margin-bottom: 2rem; + } + + .md\:h-\[140px\] { + height: 140px; + } + .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } diff --git a/templates/companies/company_detail.html b/templates/companies/company_detail.html index 58a68233..118ca122 100644 --- a/templates/companies/company_detail.html +++ b/templates/companies/company_detail.html @@ -5,75 +5,100 @@ {% block content %}
- -
-
-
-

{{ company.name }}

- {% if company.headquarters %} -

- {{ company.headquarters }} -

- {% endif %} -
-
- {% if company.website %} - - Visit Website - - {% endif %} - {% if user.is_authenticated %} - - Edit - - {% endif %} -
-
- - {% if company.description %} -
- {{ company.description|linebreaks }} -
+ +
+ {% if company.website %} + + Visit Website + + {% endif %} + {% if user.is_authenticated %} + + Edit + {% endif %}
- -
-
-
- {{ parks.count }} + +
+ +
+

{{ company.name }}

+ + {% if company.headquarters %} +
+ +

{{ company.headquarters }}

-
Theme Parks
+ {% endif %}
- -
-
- {{ parks|length }} + + +
+ +
+
+
Total Parks
+
{{ parks.count }}
+
+
+
Active Parks
+
{{ parks|length }}
+
-
Active Parks
-
- -
-
- {% with total_rides=0 %} - {% for park in parks %} - {% with total_rides=total_rides|add:park.rides.count %}{% endwith %} - {% endfor %} - {{ total_rides }} - {% endwith %} + + +
+
+ +
Total Attractions
+
{{ total_rides }}
+
+ + {% if company.founded_date %} +
+ +
Founded
+
{{ company.founded_date }}
+
+ {% endif %} + + {% if company.website %} +
+ +
Website
+
+ + Visit + + +
+
+ {% endif %}
-
Total Attractions
+ {% if company.description %} +
+

About

+
+ {{ company.description|linebreaks }} +
+
+ {% endif %} +
-

Theme Parks

+

Theme Parks

{% for park in parks %} -
+
{% if park.photos.exists %} {{ park.name }}

+ class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300"> {{ park.name }}

diff --git a/templates/companies/manufacturer_detail.html b/templates/companies/manufacturer_detail.html index dac5026a..6431c9fc 100644 --- a/templates/companies/manufacturer_detail.html +++ b/templates/companies/manufacturer_detail.html @@ -5,70 +5,100 @@ {% block content %}
- -
-
-
-

{{ manufacturer.name }}

- {% if manufacturer.headquarters %} -

- {{ manufacturer.headquarters }} -

- {% endif %} -
-
- {% if manufacturer.website %} - - Visit Website - - {% endif %} - {% if user.is_authenticated %} - - Edit - - {% endif %} -
-
- - {% if manufacturer.description %} -
- {{ manufacturer.description|linebreaks }} -
+ +
+ {% if manufacturer.website %} + + Visit Website + + {% endif %} + {% if user.is_authenticated %} + + Edit + {% endif %}
- -
-
-
- {{ rides.count }} + +
+ +
+

{{ manufacturer.name }}

+ + {% if manufacturer.headquarters %} +
+ +

{{ manufacturer.headquarters }}

-
Total Rides
+ {% endif %}
- -
-
- {{ rides|filter:"type='ROLLER_COASTER'"|length }} + + +
+ +
+
+
Total Rides
+
{{ rides.count }}
+
+
+
Coasters
+
{{ coaster_count }}
+
-
Roller Coasters
-
- -
-
- {{ rides|regroup:"park"|length }} + + +
+
+ +
Parks Served
+
{{ parks_count }}
+
+ + {% if manufacturer.founded_date %} +
+ +
Founded
+
{{ manufacturer.founded_date }}
+
+ {% endif %} + + {% if manufacturer.website %} +
+ +
Website
+
+ + Visit + + +
+
+ {% endif %}
-
Parks with Rides
+ {% if manufacturer.description %} +
+

About

+
+ {{ manufacturer.description|linebreaks }} +
+
+ {% endif %} +
-

Rides

+

Rides

{% for ride in rides %} -
+
{% if ride.photos.exists %} {{ ride.name }}

+ class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300"> {{ ride.name }}

diff --git a/templates/designers/designer_detail.html b/templates/designers/designer_detail.html new file mode 100644 index 00000000..3b58e621 --- /dev/null +++ b/templates/designers/designer_detail.html @@ -0,0 +1,121 @@ +{% extends 'base/base.html' %} +{% load static %} + +{% block title %}{{ designer.name }} - ThrillWiki{% endblock %} + +{% block content %} +
+ +
+ +
+
+

{{ designer.name }}

+ {% if designer.description %} +
+ {{ designer.description|linebreaks }} +
+ {% endif %} +
+
+ + +
+
+

Quick Stats

+
+
+
Total Rides
+
{{ stats.total_rides }}
+
+
+
Roller Coasters
+
{{ stats.total_coasters }}
+
+
+
Parks
+
{{ stats.total_parks }}
+
+
+
Countries
+
{{ stats.total_countries }}
+
+
+ {% if designer.website %} + + {% endif %} +
+
+
+ + +
+

Designed Rides

+ + {% if rides %} +
+ {% for ride in rides %} +
+
+ + + {{ ride.get_category_display }} + +
+ +
+ {% if ride.opening_date %} +
+
Opened
+
{{ ride.opening_date }}
+
+ {% endif %} + {% if ride.manufacturer %} +
+
Manufacturer
+
{{ ride.manufacturer.name }}
+
+ {% endif %} + {% if ride.category == 'RC' and ride.coaster_stats %} + {% if ride.coaster_stats.height_ft %} +
+
Height
+
{{ ride.coaster_stats.height_ft }} ft
+
+ {% endif %} + {% if ride.coaster_stats.speed_mph %} +
+
Speed
+
{{ ride.coaster_stats.speed_mph }} mph
+
+ {% endif %} + {% endif %} +
+
+ {% endfor %} +
+ {% else %} +

No rides found.

+ {% endif %} +
+
+{% endblock %} diff --git a/templates/parks/park_detail.html b/templates/parks/park_detail.html index cb969f03..df15234c 100644 --- a/templates/parks/park_detail.html +++ b/templates/parks/park_detail.html @@ -22,35 +22,35 @@
{% if user.is_authenticated %} -
+
- Edit + Edit {% if perms.media.add_photo %} {% endif %}
{% endif %} -
+
-
-

{{ park.name }}

+
+

{{ park.name }}

{% if park.formatted_location %} -
- +
+

{{ park.formatted_location }}

{% endif %} -
- + - + + {{ park.average_rating|floatformat:1 }}/10 {% endif %} @@ -67,36 +67,36 @@
-
+
-
+
{% if park.total_rides %} -
Total Rides
-
{{ park.total_rides }}
+ class="flex flex-col items-center justify-center p-2 text-center transition-transform bg-white rounded-lg shadow-lg hover:scale-[1.02] dark:bg-gray-800"> +
Total Rides
+
{{ park.total_rides }}
{% endif %} {% if park.total_roller_coasters %} -
-
Total Roller Coasters
-
{{ park.total_roller_coasters }}
+
+
Roller Coasters
+
{{ park.total_roller_coasters }}
{% endif %}
-
+
{% if park.owner %} -
- -
Owner/Operator
-
+
+ +
Owner
+
+ class="text-blue-600 text-2xs sm:text-xs hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"> {{ park.owner.name }}
@@ -104,39 +104,23 @@ {% endif %} {% if park.opening_date %} -
- -
Opening Date
-
{{ park.opening_date }}
-
- {% endif %} - - {% if park.operating_season %} -
- -
Operating Season
-
{{ park.operating_season }}
-
- {% endif %} - - {% if park.size_acres %} -
- -
Size
-
{{ park.size_acres }} acres
+
+ +
Opened
+
{{ park.opening_date }}
{% endif %} {% if park.website %} -
- -
Website
-
+
+ +
Website
+
- Official Website - + Visit +
@@ -144,7 +128,8 @@
- + + {% if park.photos.exists %}

Photos

diff --git a/templates/rides/ride_category_list.html b/templates/rides/ride_category_list.html new file mode 100644 index 00000000..a21b670d --- /dev/null +++ b/templates/rides/ride_category_list.html @@ -0,0 +1,154 @@ +{% extends "base/base.html" %} +{% load static %} +{% load ride_tags %} + +{% block title %}{{ title }}{% endblock %} + +{% block content %} +
+
+
+

{{ title }}

+ {% if park %} + + Back to {{ park.name }} + + {% endif %} +
+
+ + + + + {% if not categories %} +

No rides found.

+ {% endif %} + + {% for category_name, rides in categories.items %} +
+

{{ category_name }}s

+
+ {% for ride in rides %} +
+
+ {% if ride.photos.exists %} + {{ ride.name }} + {% else %} + {{ ride.name }} + {% endif %} +
+ +
+

+ + {{ ride.name }} + +

+ {% if not park %} +

+ at + {{ ride.park.name }} + +

+ {% endif %} + {% if ride.manufacturer %} +

{{ ride.manufacturer.name }}

+ {% endif %} +
+ + {{ ride.get_category_display }} + + + {{ ride.get_status_display }} + + {% if ride.average_rating %} + + + {{ ride.average_rating|floatformat:1 }}/10 + + {% endif %} +
+ {% if ride.coaster_stats %} +
+ {% if ride.coaster_stats.height_ft %} +
+ Height: {{ ride.coaster_stats.height_ft }}ft +
+ {% endif %} + {% if ride.coaster_stats.speed_mph %} +
+ Speed: {{ ride.coaster_stats.speed_mph }}mph +
+ {% endif %} +
+ {% endif %} +
+
+ {% endfor %} +
+
+ {% endfor %} +
+{% endblock %} diff --git a/templates/rides/ride_detail.html b/templates/rides/ride_detail.html index 7c406a56..47741f94 100644 --- a/templates/rides/ride_detail.html +++ b/templates/rides/ride_detail.html @@ -5,54 +5,161 @@ {% block content %}
- -
-
-
-

{{ ride.name }}

-

- at - {{ ride.park.name }} - - {% if ride.park_area %} - - {{ ride.park_area.name }} - {% endif %} -

-
- - {{ ride.get_status_display }} - - - {{ ride.get_category_display }} - - {% if ride.average_rating %} - - - {{ ride.average_rating|floatformat:1 }}/10 - - {% endif %} -
+ + {% if user.is_authenticated %} +
+ + Edit + + {% if perms.media.add_photo %} + + {% endif %} +
+ {% endif %} + + +
+ +
+

{{ ride.name }}

+ +
+ at + {{ ride.park.name }} + + {% if ride.park_area %} + - {{ ride.park_area.name }} + {% endif %}
- {% if user.is_authenticated %} -
- - Edit - - {% if perms.media.add_photo %} - + +
+ + {{ ride.get_status_display }} + + + {{ ride.get_category_display }} + + {% if ride.average_rating %} + + + {{ ride.average_rating|floatformat:1 }}/10 + + {% endif %} +
+
+ + +
+ +
+ {% if coaster_stats %} + {% if coaster_stats.height_ft %} +
+
Height
+
{{ coaster_stats.height_ft }} ft
+
{% endif %} + {% if coaster_stats.speed_mph %} +
+
Speed
+
{{ coaster_stats.speed_mph }} mph
+
+ {% endif %} + {% if coaster_stats.inversions %} +
+
Inversions
+
{{ coaster_stats.inversions }}
+
+ {% endif %} + {% if coaster_stats.length_ft %} +
+
Length
+
{{ coaster_stats.length_ft }} ft
+
+ {% endif %} + {% endif %} +
+ + +
+ {% if ride.manufacturer %} +
+ +
Manufacturer
+
+ + {{ ride.manufacturer.name }} + +
- {% endif %} + {% endif %} + + {% if ride.designer %} +
+ +
Designer
+
+ + {{ ride.designer.name }} + +
+
+ {% endif %} + + {% if coaster_stats.roller_coaster_type %} +
+ +
Coaster Type
+
{{ coaster_stats.get_roller_coaster_type_display }}
+
+ {% endif %} + + {% if coaster_stats.track_material %} +
+ +
Track Material
+
{{ coaster_stats.get_track_material_display }}
+
+ {% endif %} + + {% if ride.opening_date %} +
+ +
Opened
+
{{ ride.opening_date }}
+
+ {% endif %} + + {% if ride.capacity_per_hour %} +
+ +
Capacity
+
{{ ride.capacity_per_hour }}/hr
+
+ {% endif %} + + {% if coaster_stats.launch_type %} +
+ +
Launch Type
+
{{ coaster_stats.get_launch_type_display }}
+
+ {% endif %} +
- + {% if ride.photos.exists %}

Photos

@@ -60,13 +167,50 @@
{% endif %} + +
+
+

Reviews

+ {% if user.is_authenticated %} + + {% endif %} +
+ + {% if ride.reviews.exists %} +
+ {% for review in ride.reviews.all %} +
+
+
+

{{ review.title }}

+

+ by {{ review.user.username }} on {{ review.created_at|date }} +

+
+
+ + {{ review.rating }}/10 +
+
+

{{ review.content }}

+
+ {% endfor %} +
+ {% else %} +

No reviews yet. Be the first to review this ride!

+ {% endif %} +
+
{% if ride.description %}
-

About

+

Trivia

{{ ride.description|linebreaks }}
@@ -79,8 +223,8 @@
{% for name_history in ride.previous_names %}
- {{ name_history.name }} - {{ name_history.period }} + {{ name_history.name }} + {{ name_history.period }}
{% endfor %}
@@ -91,44 +235,129 @@

Roller Coaster Statistics

+ + {% if coaster_stats.roller_coaster_type %} +
+ Coaster Type + + {{ coaster_stats.get_roller_coaster_type_display }} + +
+ {% endif %} + + {% if coaster_stats.height_ft %}
- Height + Maximum Height {{ coaster_stats.height_ft }} ft
{% endif %} + {% if coaster_stats.max_drop_height_ft %} +
+ Drop Height + + {{ coaster_stats.max_drop_height_ft }} ft + +
+ {% endif %} + + {% if coaster_stats.length_ft %}
- Length + Track Length {{ coaster_stats.length_ft }} ft
{% endif %} + {% if coaster_stats.track_type %} +
+ Track Layout + + {{ coaster_stats.track_type }} + +
+ {% endif %} + {% if coaster_stats.track_material %} +
+ Track Material + + {{ coaster_stats.get_track_material_display }} + +
+ {% endif %} + + {% if coaster_stats.speed_mph %}
- Speed + Maximum Speed {{ coaster_stats.speed_mph }} mph
{% endif %} -
- Inversions - - {{ coaster_stats.inversions }} - -
{% if coaster_stats.ride_time_seconds %}
- Ride Duration + Ride Duration {{ coaster_stats.ride_time_seconds }} sec
{% endif %} + + + {% if coaster_stats.train_style %} +
+ Train Style + + {{ coaster_stats.train_style }} + +
+ {% endif %} + {% if coaster_stats.trains_count %} +
+ Number of Trains + + {{ coaster_stats.trains_count }} + +
+ {% endif %} + {% if coaster_stats.cars_per_train %} +
+ Cars per Train + + {{ coaster_stats.cars_per_train }} + +
+ {% endif %} + {% if coaster_stats.seats_per_car %} +
+ Seats per Car + + {{ coaster_stats.seats_per_car }} + +
+ {% endif %} + + + {% if coaster_stats.inversions %} +
+ Inversions + + {{ coaster_stats.inversions }} + +
+ {% endif %} + {% if coaster_stats.launch_type %} +
+ Launch Type + + {{ coaster_stats.get_launch_type_display }} + +
+ {% endif %}
{% endif %} @@ -140,18 +369,29 @@

Quick Facts

-
Manufacturer
+
Manufacturer
{{ ride.manufacturer }}
+ {% if ride.designer %} + + {% endif %} {% if ride.model_name %}
-
Model
+
Model
{{ ride.model_name }}
{% endif %} {% if ride.opening_date %}
-
Opening Date
+
Opening Date
{{ ride.opening_date }}
@@ -159,7 +399,7 @@ {% endif %} {% if ride.status_since %}
-
Status Since
+
Status Since
{{ ride.status_since }}
@@ -167,7 +407,7 @@ {% endif %} {% if ride.closing_date %}
-
Closing Date
+
Closing Date
{{ ride.closing_date }}
@@ -175,7 +415,7 @@ {% endif %} {% if ride.capacity_per_hour %}
-
Capacity
+
Capacity
{{ ride.capacity_per_hour }} riders/hour
@@ -183,7 +423,7 @@ {% endif %} {% if ride.min_height_in %}
-
Minimum Height
+
Minimum Height
{{ ride.min_height_in }} inches
@@ -206,65 +446,33 @@
{% for field, changes in record.diff_against_previous.items %} -
- {{ field }}: - {{ changes.old }} → {{ changes.new }} -
+ {% if field != "updated_at" %} +
+ {{ field|title }}: + {{ changes.old }} + + {{ changes.new }} +
+ {% endif %} {% endfor %}
{% empty %} -

No history available.

+

No history available.

{% endfor %}
- - -
-
-

Reviews

- {% if user.is_authenticated %} - - {% endif %} -
- - {% if ride.reviews.exists %} -
- {% for review in ride.reviews.all %} -
-
-
-

{{ review.title }}

-

- by {{ review.user.username }} on {{ review.created_at|date }} -

-
-
- - {{ review.rating }}/10 -
-
-

{{ review.content }}

-
- {% endfor %} -
- {% else %} -

No reviews yet. Be the first to review this ride!

- {% endif %} -
{% if perms.media.add_photo %} -
diff --git a/thrillwiki/__pycache__/settings.cpython-312.pyc b/thrillwiki/__pycache__/settings.cpython-312.pyc index 0de634859a82c2863f026c2ffa45d0c6cca94019..d24daa643eefd756e2bdf93c9940ae115898e055 100644 GIT binary patch delta 115 zcmeCxnW@8jnwOW00SM$oHPSz9cw88u&ihZQdyy!^Bbr6bAqyqHcwprG%qg~0}$wnX{0~nWMFs<;=q6;l<~P>qk09SEK7<^itHMJ)l3j!7|q0x z%A6uMc@CqZp!^!1)yxoCu&Bc1bBvPAiffc6D=^7UzQ`!1thz>NH6uhtiW-_~b)X)W z6phIaOrq=%wkC+Jsg=r|r2%yc5|yHjreA0BJtkv8T{KZWkf^9Wny3NLOr6Q|nDiqJ z(PWH(GI~hXK$$7VXzEPBt~E_D!x19plh-ke^I9NurHf3y#O%vxIaz_lQQ8s~dR4p( z3{jFPR#EaP)=>(THk!7RYgm4(a;2meXQt<+78UDP@xj@8rA0Z#w>V+^&84i1m>4BD zOK>nVa#;c$3JTd`t;wpK6ZvE&mS2$9-(h`4-2F0($L39(mW-U?j0XHn4cs5nCV%1b JWibaT1^}=|WpV%j delta 430 zcmbOyepF2TG%qg~0}#BqtCl{8lY!wehyw!>P{!wgjp`MQllL>q3CgVDSP+6pq#vn=CZi9O(L>k*W2G3NsWSvS z&?v=tjlgOqh$!jnHS`!bqMc3^RoHiN0H;$>inl1wp=l25US zQmC}lw3