Skip to content

Commit a7ad81a

Browse files
committed
configured Django static/media root, admin filtering, admin sorting, custom filters, ordering, DjangoQL, custom admin actions
1 parent 826d2ce commit a7ad81a

File tree

6 files changed

+124
-20
lines changed

6 files changed

+124
-20
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ media
1010

1111
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
1212
# in your Git repository. Update and uncomment the following line accordingly.
13-
# <django-project-name>/staticfiles/
13+
staticfiles/
14+
mediafiles/
1415

1516
### Django.Python Stack ###
1617
# Byte-compiled / optimized / DLL files

core/settings.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
"django.contrib.messages",
3939
"django.contrib.staticfiles",
4040
"tickets.apps.TicketsConfig",
41+
"djangoql",
42+
"import_export",
4143
]
4244

4345
MIDDLEWARE = [
@@ -117,6 +119,10 @@
117119
# https://docs.djangoproject.com/en/4.2/howto/static-files/
118120

119121
STATIC_URL = "static/"
122+
STATIC_ROOT = BASE_DIR / "staticfiles"
123+
124+
MEDIA_URL = "media/"
125+
MEDIA_ROOT = BASE_DIR / "mediafiles"
120126

121127
# Default primary key field type
122128
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

core/urls.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
from django.contrib import admin
1818
from django.urls import path
1919

20+
admin.site.site_title = "TicketsPlus site admin"
21+
admin.site.site_header = "TicketsPlus administration"
22+
admin.site.index_title = "Site administration"
23+
admin.site.site_url = "/"
24+
admin.site.enable_nav_sidebar = True
25+
admin.site.empty_value_display = "-"
26+
2027
urlpatterns = [
21-
path("admin/", admin.site.urls),
28+
path("secretadmin/", admin.site.urls),
2229
]

tickets/admin.py

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,92 @@
11
from django.contrib import admin
2+
from django.contrib.admin import SimpleListFilter
3+
from djangoql.admin import DjangoQLSearchMixin
4+
from import_export.admin import ExportActionModelAdmin, ImportExportActionModelAdmin
5+
26
from tickets.models import Venue, ConcertCategory, Concert, Ticket
37

48

9+
class ConcertInline(admin.TabularInline):
10+
model = Concert
11+
fields = ["name", "starts_at", "price", "tickets_left"]
12+
readonly_fields = ["name", "starts_at", "price", "tickets_left"]
13+
max_num = 0
14+
extra = 0
15+
can_delete = False
16+
show_change_link = True
17+
18+
19+
class ConcertThroughInline(admin.TabularInline):
20+
verbose_name = "concert"
21+
verbose_name_plural = "concerts"
22+
model = Concert.categories.through
23+
fields = ["concert"]
24+
readonly_fields = ["concert"]
25+
max_num = 0
26+
extra = 0
27+
can_delete = False
28+
show_change_link = True
29+
30+
531
class VenueAdmin(admin.ModelAdmin):
6-
pass
32+
list_display = ["name", "address", "capacity"]
33+
inlines = [ConcertInline]
734

835

936
class ConcertCategoryAdmin(admin.ModelAdmin):
10-
pass
37+
inlines = [ConcertThroughInline]
38+
39+
40+
class SoldOutFilter(SimpleListFilter):
41+
title = "Sold out"
42+
parameter_name = "sold_out"
43+
44+
def lookups(self, request, model_admin):
45+
return [
46+
("yes", "Yes"),
47+
("no", "No"),
48+
]
49+
50+
def queryset(self, request, queryset):
51+
if self.value() == "yes":
52+
return queryset.filter(tickets_left=0)
53+
else:
54+
return queryset.exclude(tickets_left=0)
55+
56+
57+
class ConcertAdmin(DjangoQLSearchMixin, admin.ModelAdmin):
58+
list_display = ["name", "venue", "starts_at", "display_price", "tickets_left", "display_sold_out"]
59+
list_filter = ["venue", SoldOutFilter]
60+
readonly_fields = ["tickets_left"]
61+
search_fields = ["name", "venue__name", "venue__address"]
62+
63+
def display_sold_out(self, obj):
64+
return obj.is_sold_out()
65+
66+
display_sold_out.short_description = "Sold out"
67+
display_sold_out.boolean = True
68+
69+
def display_price(self, obj):
70+
return f"${obj.price}"
71+
72+
display_price.short_description = "Price"
73+
display_price.admin_order_field = "price"
74+
75+
76+
@admin.action(description="Activate selected tickets")
77+
def activate_tickets(modeladmin, request, queryset):
78+
queryset.update(is_active=True)
1179

1280

13-
class ConcertAdmin(admin.ModelAdmin):
14-
pass
81+
@admin.action(description="Deactivate selected tickets")
82+
def deactivate_tickets(modeladmin, request, queryset):
83+
queryset.update(is_active=False)
1584

1685

17-
class TicketAdmin(admin.ModelAdmin):
18-
pass
86+
class TicketAdmin(ImportExportActionModelAdmin):
87+
list_display = ["customer_full_name", "concert", "payment_method", "paid_at", "is_active"]
88+
list_filter = ["payment_method", "paid_at", "is_active"]
89+
actions = [activate_tickets, deactivate_tickets]
1990

2091

2192
admin.site.register(Venue, VenueAdmin)

tickets/management/commands/populate_db.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,21 @@ class Command(BaseCommand):
1313
def handle(self, *args, **options):
1414
# populate the database with venues
1515
venues = [
16-
Venue.objects.get_or_create(name="The O2 Arena", address="Peninsula Square, London SE10 0DX, United Kingdom", capacity=20000),
17-
Venue.objects.get_or_create(name="Arena of Nîmes", address="Boulevard des Arènes, 30000 Nîmes, France", capacity=17000),
18-
Venue.objects.get_or_create(name="Red Rocks Amphitheatre", address="18300 W Alameda, Morrison, CO, United States", capacity=9525),
19-
Venue.objects.get_or_create(name="Dalhalla Amphitheatre", address="Dalhalla, 790 90 Rättvik, Sweden", capacity=4000),
20-
Venue.objects.get_or_create(name="The Fillmore", address="1805 Geary Blvd, San Francisco, CA, United States", capacity=1250),
16+
Venue.objects.get_or_create(
17+
name="The O2 Arena", address="Peninsula Square, London SE10 0DX, United Kingdom", capacity=960,
18+
),
19+
Venue.objects.get_or_create(
20+
name="Arena of Nîmes", address="Boulevard des Arènes, 30000 Nîmes, France", capacity=720,
21+
),
22+
Venue.objects.get_or_create(
23+
name="Red Rocks Amphitheatre", address="18300 W Alameda, Morrison, CO, United States", capacity=640,
24+
),
25+
Venue.objects.get_or_create(
26+
name="Dalhalla Amphitheatre", address="Dalhalla, 790 90 Rättvik, Sweden", capacity=800,
27+
),
28+
Venue.objects.get_or_create(
29+
name="The Fillmore", address="1805 Geary Blvd, San Francisco, CA, United States", capacity=620,
30+
),
2131
]
2232

2333
# populate the database with categories
@@ -26,7 +36,7 @@ def handle(self, *args, **options):
2636
ConcertCategory.objects.get_or_create(name=category)
2737

2838
# populate the database with concerts
29-
concert_prefix = ["Underground", "Midnight", "Late Night", "Secret", "", "", "", "", "", "", "", "", "", ""]
39+
concert_prefix = ["Underground", "Midnight", "Late Night", "Secret", "" * 10]
3040
concert_suffix = ["Party", "Rave", "Concert", "Gig", "Revolution", "Jam", "Tour"]
3141
for i in range(10):
3242
venue = random.choice(venues)[0]
@@ -35,23 +45,26 @@ def handle(self, *args, **options):
3545
name=f"{random.choice(concert_prefix)} {category.name} {random.choice(concert_suffix)}",
3646
description="",
3747
venue=venue,
38-
starts_at=datetime.now(pytz.utc) + timedelta(days=random.randint(1, 365)),
39-
price=random.randint(10, 100)
48+
starts_at=datetime.now(pytz.utc)
49+
+ timedelta(days=random.randint(1, 365)),
50+
price=random.randint(10, 100),
4051
)
4152
concert.categories.add(category)
4253
concert.save()
4354

4455
# populate the database with ticket purchases
4556
names = ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Charles"]
4657
surname = ["Smith", "Jones", "Taylor", "Brown", "Williams", "Wilson", "Johnson", "Davies", "Patel", "Wright"]
47-
for i in range(200):
58+
for i in range(500):
4859
concert = Concert.objects.order_by("?").first()
4960
Ticket.objects.create(
5061
concert=concert,
5162
customer_full_name=f"{random.choice(names)} {random.choice(surname)}",
52-
payment_method=random.choice(["CC", "DC", "ET", "BC"]),
63+
payment_method=random.choice(["CC", "CC", "CC", "CC", "DC", "DC", "ET", "BC"]),
5364
paid_at=datetime.now(pytz.utc) - timedelta(days=random.randint(1, 365)),
54-
is_active=random.choice([True, False])
65+
is_active=random.choice([True, False]),
5566
)
67+
concert.tickets_left -= 1
68+
concert.save()
5669

57-
self.stdout.write(self.style.SUCCESS("Successfully populated the database."))
70+
self.stdout.write(self.style.SUCCESS("Successfully populated the database."))

tickets/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,18 @@ class Concert(models.Model):
3232
price = models.DecimalField(max_digits=6, decimal_places=2)
3333
tickets_left = models.IntegerField(default=0)
3434

35+
class Meta:
36+
ordering = ["starts_at"]
37+
3538
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
3639
if self.pk is None:
3740
self.tickets_left = self.venue.capacity
3841

3942
super().save(force_insert, force_update, using, update_fields)
4043

44+
def is_sold_out(self):
45+
return self.tickets_left == 0
46+
4147
def __str__(self):
4248
return f"{self.venue}: {self.name}"
4349

0 commit comments

Comments
 (0)