Skip to content

Commit 2ce2442

Browse files
authored
sponsorship: allow admins to temporarily unlock finalized sponsorships for editing (#2249)
1 parent 240865e commit 2ce2442

File tree

7 files changed

+150
-3
lines changed

7 files changed

+150
-3
lines changed

sponsors/admin.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ def get_readonly_fields(self, request, obj):
489489
"get_custom_benefits_removed_by_user",
490490
]
491491

492-
if obj and obj.status != Sponsorship.APPLIED:
492+
if obj and not obj.open_for_editing:
493493
extra = ["start_date", "end_date", "package", "level_name", "sponsorship_fee"]
494494
readonly_fields.extend(extra)
495495

@@ -554,6 +554,16 @@ def get_urls(self):
554554
self.admin_site.admin_view(self.list_uploaded_assets_view),
555555
name=f"{base_name}_list_uploaded_assets",
556556
),
557+
path(
558+
"<int:pk>/unlock",
559+
self.admin_site.admin_view(self.unlock_view),
560+
name=f"{base_name}_unlock",
561+
),
562+
path(
563+
"<int:pk>/lock",
564+
self.admin_site.admin_view(self.lock_view),
565+
name=f"{base_name}_lock",
566+
),
557567
]
558568
return my_urls + urls
559569

@@ -677,6 +687,12 @@ def approve_signed_sponsorship_view(self, request, pk):
677687
def list_uploaded_assets_view(self, request, pk):
678688
return views_admin.list_uploaded_assets(self, request, pk)
679689

690+
def unlock_view(self, request, pk):
691+
return views_admin.unlock_view(self, request, pk)
692+
693+
def lock_view(self, request, pk):
694+
return views_admin.lock_view(self, request, pk)
695+
680696

681697
@admin.register(SponsorshipCurrentYear)
682698
class SponsorshipCurrentYearAdmin(admin.ModelAdmin):
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Generated by Django 2.2.24 on 2023-02-16 13:55
2+
3+
from django.db import migrations, models
4+
5+
from sponsors.models.sponsorship import Sponsorship as _Sponsorship
6+
7+
def forwards_func(apps, schema_editor):
8+
Sponsorship = apps.get_model('sponsors', 'Sponsorship')
9+
db_alias = schema_editor.connection.alias
10+
11+
for sponsorship in Sponsorship.objects.all():
12+
sponsorship.locked = not (sponsorship.status == _Sponsorship.APPLIED)
13+
sponsorship.save()
14+
15+
def reverse_func(apps, schema_editor):
16+
pass
17+
18+
19+
class Migration(migrations.Migration):
20+
21+
dependencies = [
22+
('sponsors', '0093_auto_20230214_2113'),
23+
]
24+
25+
operations = [
26+
migrations.AddField(
27+
model_name='sponsorship',
28+
name='locked',
29+
field=models.BooleanField(default=False),
30+
),
31+
migrations.RunPython(forwards_func, reverse_func)
32+
]

sponsors/models/contract.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ def execute(self, commit=True, force=False):
248248

249249
self.status = self.EXECUTED
250250
self.sponsorship.status = Sponsorship.FINALIZED
251+
self.sponsorship.locked = True
251252
self.sponsorship.finalized_on = timezone.now().date()
252253
if commit:
253254
self.sponsorship.save()

sponsors/models/sponsorship.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ class Sponsorship(models.Model):
161161
status = models.CharField(
162162
max_length=20, choices=STATUS_CHOICES, default=APPLIED, db_index=True
163163
)
164+
locked = models.BooleanField(default=False)
164165

165166
start_date = models.DateField(null=True, blank=True)
166167
end_date = models.DateField(null=True, blank=True)
@@ -211,6 +212,12 @@ def __str__(self):
211212
repr += f" [{start} - {end}]"
212213
return repr
213214

215+
def save(self, *args, **kwargs):
216+
if "locked" not in kwargs.get("update_fields", []):
217+
if self.status != self.APPLIED:
218+
self.locked = True
219+
return super().save(*args, **kwargs)
220+
214221
@classmethod
215222
@transaction.atomic
216223
def new(cls, sponsor, benefits, package=None, submited_by=None):
@@ -287,6 +294,7 @@ def reject(self):
287294
msg = f"Can't reject a {self.get_status_display()} sponsorship."
288295
raise InvalidStatusException(msg)
289296
self.status = self.REJECTED
297+
self.locked = True
290298
self.rejected_on = timezone.now().date()
291299

292300
def approve(self, start_date, end_date):
@@ -297,6 +305,7 @@ def approve(self, start_date, end_date):
297305
msg = f"Start date greater or equal than end date"
298306
raise SponsorshipInvalidDateRangeException(msg)
299307
self.status = self.APPROVED
308+
self.locked = True
300309
self.start_date = start_date
301310
self.end_date = end_date
302311
self.approved_on = timezone.now().date()
@@ -320,6 +329,10 @@ def rollback_to_editing(self):
320329
self.approved_on = None
321330
self.rejected_on = None
322331

332+
@property
333+
def unlocked(self):
334+
return not self.locked
335+
323336
@property
324337
def verified_emails(self):
325338
emails = [self.submited_by.email]
@@ -353,7 +366,7 @@ def added_benefits(self):
353366

354367
@property
355368
def open_for_editing(self):
356-
return self.status == self.APPLIED
369+
return (self.status == self.APPLIED) or (self.unlocked)
357370

358371
@property
359372
def next_status(self):

sponsors/views_admin.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,44 @@ def rollback_to_editing_view(ModelAdmin, request, pk):
182182
)
183183

184184

185+
def unlock_view(ModelAdmin, request, pk):
186+
sponsorship = get_object_or_404(ModelAdmin.get_queryset(request), pk=pk)
187+
188+
if request.method.upper() == "POST" and request.POST.get("confirm") == "yes":
189+
try:
190+
sponsorship.locked = False
191+
sponsorship.save(update_fields=['locked'])
192+
ModelAdmin.message_user(
193+
request, "Sponsorship is now unlocked!", messages.SUCCESS
194+
)
195+
except InvalidStatusException as e:
196+
ModelAdmin.message_user(request, str(e), messages.ERROR)
197+
198+
redirect_url = reverse(
199+
"admin:sponsors_sponsorship_change", args=[sponsorship.pk]
200+
)
201+
return redirect(redirect_url)
202+
203+
context = {"sponsorship": sponsorship}
204+
return render(
205+
request,
206+
"sponsors/admin/unlock.html",
207+
context=context,
208+
)
209+
210+
211+
def lock_view(ModelAdmin, request, pk):
212+
sponsorship = get_object_or_404(ModelAdmin.get_queryset(request), pk=pk)
213+
214+
sponsorship.locked = True
215+
sponsorship.save()
216+
217+
redirect_url = reverse(
218+
"admin:sponsors_sponsorship_change", args=[sponsorship.pk]
219+
)
220+
return redirect(redirect_url)
221+
222+
185223
def execute_contract_view(ModelAdmin, request, pk):
186224
contract = get_object_or_404(ModelAdmin.get_queryset(request), pk=pk)
187225

templates/sponsors/admin/sponsorship_change_form.html

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends "admin/change_form.html" %}
2-
{% load i18n admin_urls %}
2+
{% load i18n admin_urls admin_modify %}
33

44
{% block object-tools-items %}
55
{% with original as sp %}
@@ -39,6 +39,18 @@
3939
<a href="{% url 'admin:sponsors_sponsorship_list_uploaded_assets' sp.pk %}">Uploaded Assets</a>
4040
</li>
4141

42+
{% if sp.unlocked and sp.status != sp.APPLIED %}
43+
<li>
44+
<a href="{% url 'admin:sponsors_sponsorship_lock' sp.pk %}" style="background: #70bf2b">Lock</a>
45+
</li>
46+
{% endif %}
47+
48+
{% if sp.locked and sp.status == sp.FINALIZED %}
49+
<li>
50+
<a href="{% url 'admin:sponsors_sponsorship_unlock' sp.pk %}">Unlock</a>
51+
</li>
52+
{% endif %}
53+
4254
{% endwith %}
4355

4456
{{ block.super }}

templates/sponsors/admin/unlock.html

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{% extends 'admin/base_site.html' %}
2+
{% load i18n static sponsors %}
3+
4+
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">{% endblock %}
5+
6+
{% block title %}Unlock Finalized Sponsorship {{ sponsorship }} | python.org{% endblock %}
7+
8+
{% block breadcrumbs %}
9+
<div class="breadcrumbs">
10+
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a> &gt
11+
<a href="{% url 'admin:app_list' app_label='sponsors' %}">{% trans 'Sponsors' %}</a> &gt
12+
<a href="{% url 'admin:sponsors_sponsorship_changelist' %}">{% trans 'Sponsorship' %}</a> &gt
13+
<a href="{% url 'admin:sponsors_sponsorship_change' sponsorship.pk %}">{{ sponsorship }}</a> &gt
14+
{% trans 'Unlock Finalized Sponsorship' %}
15+
</div>
16+
{% endblock %}
17+
18+
{% block content %}
19+
<h1>Unlock Finalized Sponsorship</h1>
20+
<p>Please review the sponsorship application and click in the Unlock button if you want to proceed.</p>
21+
<div id="content-main">
22+
<form action="" method="post" id="new_psf_board_meeting_form">
23+
{% csrf_token %}
24+
25+
<pre>{% full_sponsorship sponsorship display_fee=True %}</pre>
26+
27+
<input name="confirm" value="yes" style="display:none">
28+
29+
<div class="submit-row">
30+
<input type="submit" value="Unlock" class="default">
31+
</div>
32+
33+
</form>
34+
<div>
35+
</div>{% endblock %}

0 commit comments

Comments
 (0)