22
33import io
44import zipfile
5+ from functools import wraps
56
67from django .contrib import messages
8+ from django .core .exceptions import PermissionDenied
79from django .db import transaction
810from django .http import HttpResponse
911from django .shortcuts import get_object_or_404 , redirect , render
2224from apps .sponsors .models import BenefitFeature , EmailTargetable , SponsorshipCurrentYear
2325
2426
27+ def require_change_permission (view ):
28+ """Require the model's change permission for a custom admin view.
29+
30+ ``AdminSite.admin_view`` only checks that the user is active staff, so the
31+ custom action URLs registered in ``admin.py`` must enforce the per-model
32+ permission themselves.
33+ """
34+
35+ @wraps (view )
36+ def wrapper (model_admin , request , * args , ** kwargs ):
37+ if not model_admin .has_change_permission (request ):
38+ raise PermissionDenied
39+ return view (model_admin , request , * args , ** kwargs )
40+
41+ return wrapper
42+
43+
44+ @require_change_permission
2545def preview_contract_view (model_admin , request , pk ):
2646 """Render a contract preview as PDF or DOCX based on the format query parameter."""
2747 contract = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
@@ -34,6 +54,7 @@ def preview_contract_view(model_admin, request, pk):
3454 return response
3555
3656
57+ @require_change_permission
3758def reject_sponsorship_view (model_admin , request , pk ):
3859 """Handle rejection of a sponsorship application with confirmation."""
3960 sponsorship = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
@@ -53,6 +74,7 @@ def reject_sponsorship_view(model_admin, request, pk):
5374 return render (request , "sponsors/admin/reject_application.html" , context = context )
5475
5576
77+ @require_change_permission
5678def approve_sponsorship_view (model_admin , request , pk ):
5779 """Approves a sponsorship and create an empty contract."""
5880 sponsorship = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
@@ -88,6 +110,7 @@ def approve_sponsorship_view(model_admin, request, pk):
88110 return render (request , "sponsors/admin/approve_application.html" , context = context )
89111
90112
113+ @require_change_permission
91114def approve_signed_sponsorship_view (model_admin , request , pk ):
92115 """Approves a sponsorship and execute contract for existing file."""
93116 sponsorship = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
@@ -123,6 +146,7 @@ def approve_signed_sponsorship_view(model_admin, request, pk):
123146 return render (request , "sponsors/admin/approve_application.html" , context = context )
124147
125148
149+ @require_change_permission
126150def send_contract_view (model_admin , request , pk ):
127151 """Send a finalized contract to the sponsor for signature."""
128152 contract = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
@@ -147,6 +171,7 @@ def send_contract_view(model_admin, request, pk):
147171 return render (request , "sponsors/admin/send_contract.html" , context = context )
148172
149173
174+ @require_change_permission
150175def rollback_to_editing_view (model_admin , request , pk ):
151176 """Roll back a sponsorship to editing status with confirmation."""
152177 sponsorship = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
@@ -170,6 +195,7 @@ def rollback_to_editing_view(model_admin, request, pk):
170195 )
171196
172197
198+ @require_change_permission
173199def unlock_view (model_admin , request , pk ):
174200 """Unlock a sponsorship to allow editing with confirmation."""
175201 sponsorship = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
@@ -193,17 +219,28 @@ def unlock_view(model_admin, request, pk):
193219 )
194220
195221
222+ @require_change_permission
196223def lock_view (model_admin , request , pk ):
197- """Lock a sponsorship to prevent further editing."""
224+ """Lock a sponsorship to prevent further editing with confirmation ."""
198225 sponsorship = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
199226
200- sponsorship .locked = True
201- sponsorship .save ()
227+ if request .method .upper () == "POST" and request .POST .get ("confirm" ) == "yes" :
228+ sponsorship .locked = True
229+ sponsorship .save (update_fields = ["locked" ])
230+ model_admin .message_user (request , "Sponsorship is now locked!" , messages .SUCCESS )
202231
203- redirect_url = reverse ("admin:sponsors_sponsorship_change" , args = [sponsorship .pk ])
204- return redirect (redirect_url )
232+ redirect_url = reverse ("admin:sponsors_sponsorship_change" , args = [sponsorship .pk ])
233+ return redirect (redirect_url )
234+
235+ context = {"sponsorship" : sponsorship }
236+ return render (
237+ request ,
238+ "sponsors/admin/lock.html" ,
239+ context = context ,
240+ )
205241
206242
243+ @require_change_permission
207244def execute_contract_view (model_admin , request , pk ):
208245 """Execute a contract by uploading the signed document."""
209246 contract = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
@@ -234,6 +271,7 @@ def execute_contract_view(model_admin, request, pk):
234271 return render (request , "sponsors/admin/execute_contract.html" , context = context )
235272
236273
274+ @require_change_permission
237275def nullify_contract_view (model_admin , request , pk ):
238276 """Nullify a contract with confirmation."""
239277 contract = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
@@ -258,6 +296,7 @@ def nullify_contract_view(model_admin, request, pk):
258296 return render (request , "sponsors/admin/nullify_contract.html" , context = context )
259297
260298
299+ @require_change_permission
261300@transaction .atomic
262301def update_related_sponsorships (model_admin , request , pk ):
263302 """Update all related SponsorBenefit from a SponsorshipBenefit.
@@ -288,6 +327,7 @@ def update_related_sponsorships(model_admin, request, pk):
288327 return render (request , "sponsors/admin/update_related_sponsorships.html" , context = context )
289328
290329
330+ @require_change_permission
291331def list_uploaded_assets (model_admin , request , pk ):
292332 """List and export assets uploaded by the user."""
293333 sponsorship = get_object_or_404 (model_admin .get_queryset (request ), pk = pk )
@@ -296,6 +336,7 @@ def list_uploaded_assets(model_admin, request, pk):
296336 return render (request , "sponsors/admin/list_uploaded_assets.html" , context = context )
297337
298338
339+ @require_change_permission
299340def clone_application_config (model_admin , request ):
300341 """Clone sponsorship application configuration from one year to another."""
301342 form = CloneApplicationConfigForm ()
0 commit comments