Coverage for website/photos/admin.py: 82.14%
52 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1from django.contrib import admin, messages
2from django.db import transaction
3from django.db.models import Count
4from django.utils.translation import gettext_lazy as _
6from .forms import AlbumForm
7from .models import Album, Like, Photo
8from .tasks import process_album_upload
11@admin.register(Album)
12class AlbumAdmin(admin.ModelAdmin):
13 """Model for Album admin page."""
15 list_display = (
16 "title",
17 "date",
18 "num_photos",
19 "hidden",
20 "is_processing",
21 "shareable",
22 )
23 fields = (
24 "title",
25 "slug",
26 "date",
27 "event",
28 "hidden",
29 "is_processing",
30 "shareable",
31 "album_archive",
32 "_cover",
33 )
34 readonly_fields = ("is_processing",)
35 search_fields = ("title", "date")
36 list_filter = ("hidden", "shareable")
37 date_hierarchy = "date"
38 prepopulated_fields = {
39 "slug": (
40 "date",
41 "title",
42 )
43 }
44 form = AlbumForm
46 def get_fields(self, request, obj=None):
47 fields = list(super().get_fields(request, obj))
48 if obj is None: 48 ↛ 51line 48 didn't jump to line 51 because the condition on line 48 was always true
49 fields.remove("_cover")
51 return fields
53 def get_queryset(self, request):
54 """Get Albums and add the amount of photos as an annotation."""
55 return Album.objects.annotate(photos_count=Count("photo"))
57 def num_photos(self, obj):
58 """Pretty-print the number of photos."""
59 return obj.photos_count
61 num_photos.short_description = _("Number of photos")
62 num_photos.admin_order_field = "photos_count"
64 def save_model(self, request, obj, form, change):
65 """Save the new Album by extracting the archive."""
66 super().save_model(request, obj, form, change)
68 archive = form.cleaned_data.get("album_archive")
69 if archive is not None: 69 ↛ 70line 69 didn't jump to line 70 because the condition on line 69 was never true
70 obj.is_processing = True
71 obj.save()
72 # Schedule a celery task to unpack the upload in the background. In local development
73 # (when to Redis queue is set up) these tasks are run immediately as if it is a normal
74 # function call. In a real deployment, it has to be called only when the current db
75 # transaction is committed,
76 transaction.on_commit(
77 process_album_upload.s(
78 archive.temporary_upload.upload_id,
79 obj.id,
80 uploader_id=request.user.id,
81 ).delay
82 )
84 self.message_user(
85 request,
86 "Album is being processed, is_processing will become False when it is ready.",
87 messages.INFO,
88 )
90 def get_deleted_objects(self, objs, request):
91 (
92 deleted_objects,
93 model_count,
94 perms_needed,
95 protected,
96 ) = super().get_deleted_objects(objs, request)
98 # Drop any missing delete permissions. If the user has `delete_album` permission,
99 # they should automatically be allowed to cascade e.g. related pushnotifications.
100 return deleted_objects, model_count, set(), protected
103class LikeInline(admin.StackedInline):
104 model = Like
105 extra = 0
108@admin.register(Photo)
109class PhotoAdmin(admin.ModelAdmin):
110 """Model for Photo admin page."""
112 list_display = (
113 "__str__",
114 "album",
115 "num_likes",
116 )
117 search_fields = ("file",)
118 list_filter = ("album",)
119 exclude = ("_digest",)
121 inlines = [
122 LikeInline,
123 ]
125 def get_deleted_objects(self, objs, request):
126 (
127 deleted_objects,
128 model_count,
129 perms_needed,
130 protected,
131 ) = super().get_deleted_objects(objs, request)
133 return deleted_objects, model_count, set(), protected