Skip to content

Commit 4ef4388

Browse files
Sponsorship - adding renewal option for contract generation (#2344)
* WIP renewal work * test fix * removing venv added files * tidy and fixup * test fixup after logic changes * Sponsorship renewal review (#2345) * add rewnewal to the admin view for sponsorship * use the sponsorship form directly rather than editing template * include previous effective date in context/review form for renewals * update to real renewal contract * missing migration --------- Co-authored-by: Ee Durbin <[email protected]>
1 parent 7ba7d36 commit 4ef4388

File tree

12 files changed

+149
-7
lines changed

12 files changed

+149
-7
lines changed

sponsors/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ class SponsorshipAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
402402
"end_date",
403403
"get_contract",
404404
"level_name",
405+
"renewal",
405406
"overlapped_by",
406407
),
407408
},

sponsors/forms.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,10 @@ class SponsorshipReviewAdminForm(forms.ModelForm):
392392
start_date = forms.DateField(widget=AdminDateWidget(), required=False)
393393
end_date = forms.DateField(widget=AdminDateWidget(), required=False)
394394
overlapped_by = forms.ModelChoiceField(queryset=Sponsorship.objects.select_related("sponsor", "package"), required=False)
395+
renewal = forms.BooleanField(
396+
help_text="If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting.",
397+
required=False,
398+
)
395399

396400
def __init__(self, *args, **kwargs):
397401
force_required = kwargs.pop("force_required", False)
@@ -403,10 +407,12 @@ def __init__(self, *args, **kwargs):
403407
self.fields.pop("overlapped_by") # overlapped should never be displayed on approval
404408
for field_name in self.fields:
405409
self.fields[field_name].required = True
410+
self.fields["renewal"].required = False
411+
406412

407413
class Meta:
408414
model = Sponsorship
409-
fields = ["start_date", "end_date", "package", "sponsorship_fee"]
415+
fields = ["start_date", "end_date", "package", "sponsorship_fee", "renewal"]
410416
widgets = {
411417
'year': SPONSORSHIP_YEAR_SELECT,
412418
}
@@ -415,6 +421,7 @@ def clean(self):
415421
cleaned_data = super().clean()
416422
start_date = cleaned_data.get("start_date")
417423
end_date = cleaned_data.get("end_date")
424+
renewal = cleaned_data.get("renewal")
418425

419426
if start_date and end_date and end_date <= start_date:
420427
raise forms.ValidationError("End date must be greater than start date")
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.2.24 on 2023-12-18 16:23
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('sponsors', '0096_auto_20231214_2108'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='sponsorship',
15+
name='renewal',
16+
field=models.BooleanField(blank=True, null=True),
17+
),
18+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.2.24 on 2023-12-19 19:10
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('sponsors', '0097_sponsorship_renewal'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='sponsorship',
15+
name='renewal',
16+
field=models.BooleanField(blank=True, help_text='If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting.', null=True),
17+
),
18+
]

sponsors/models/sponsorship.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class Meta(OrderedModel.Meta):
135135

136136
class Sponsorship(models.Model):
137137
"""
138-
Represente a sponsorship application by a sponsor.
138+
Represents a sponsorship application by a sponsor.
139139
It's responsible to group the set of selected benefits and
140140
link it to sponsor
141141
"""
@@ -182,6 +182,11 @@ class Sponsorship(models.Model):
182182
package = models.ForeignKey(SponsorshipPackage, null=True, on_delete=models.SET_NULL)
183183
sponsorship_fee = models.PositiveIntegerField(null=True, blank=True)
184184
overlapped_by = models.ForeignKey("self", null=True, on_delete=models.SET_NULL)
185+
renewal = models.BooleanField(
186+
null=True,
187+
blank=True,
188+
help_text="If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting."
189+
)
185190

186191
assets = GenericRelation(GenericAsset)
187192

@@ -378,6 +383,12 @@ def next_status(self):
378383
}
379384
return states_map[self.status]
380385

386+
@property
387+
def previous_effective_date(self):
388+
if len(self.sponsor.sponsorship_set.all().order_by('-year')) > 1:
389+
return self.sponsor.sponsorship_set.all().order_by('-year')[1].start_date
390+
return None
391+
381392

382393
class SponsorshipBenefit(OrderedModel):
383394
"""

sponsors/pdf.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ def _contract_context(contract, **context):
3232
"sponsorship": contract.sponsorship,
3333
"benefits": _clean_split(contract.benefits_list.raw),
3434
"legal_clauses": _clean_split(contract.legal_clauses.raw),
35+
"renewal": contract.sponsorship.renewal,
3536
})
37+
context["previous_effective"] = contract.sponsorship.previous_effective_date if contract.sponsorship.previous_effective_date else "UNKNOWN"
3638
return context
3739

3840

@@ -49,9 +51,13 @@ def render_contract_to_pdf_file(contract, **context):
4951

5052

5153
def _gen_docx_contract(output, contract, **context):
52-
template = os.path.join(settings.TEMPLATES_DIR, "sponsors", "admin", "contract-template.docx")
53-
doc = DocxTemplate(template)
5454
context = _contract_context(contract, **context)
55+
renewal = context["renewal"]
56+
if renewal:
57+
template = os.path.join(settings.TEMPLATES_DIR, "sponsors", "admin", "renewal-contract-template.docx")
58+
else:
59+
template = os.path.join(settings.TEMPLATES_DIR, "sponsors", "admin", "contract-template.docx")
60+
doc = DocxTemplate(template)
5561
doc.render(context)
5662
doc.save(output)
5763
return output

sponsors/tests/test_pdf.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ def setUp(self):
2828
"sponsorship": self.contract.sponsorship,
2929
"benefits": [],
3030
"legal_clauses": [],
31+
"renewal": None,
32+
"previous_effective": "UNKNOWN",
3133
}
3234
self.template = "sponsors/admin/preview-contract.html"
3335

@@ -71,3 +73,39 @@ def test_render_response_with_docx_attachment(self, MockDocxTemplate):
7173
response.get("Content-Type"),
7274
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
7375
)
76+
77+
@patch("sponsors.pdf.DocxTemplate")
78+
def test_render_response_with_docx_attachment__renewal(self, MockDocxTemplate):
79+
renewal_contract = baker.make_recipe("sponsors.tests.empty_contract", sponsorship__start_date=date.today(),
80+
sponsorship__renewal=True)
81+
text = f"{renewal_contract.benefits_list.raw}\n\n**Legal Clauses**\n{renewal_contract.legal_clauses.raw}"
82+
html = render_md(text)
83+
renewal_context = {
84+
"contract": renewal_contract,
85+
"start_date": renewal_contract.sponsorship.start_date,
86+
"start_day_english_suffix": format(self.contract.sponsorship.start_date, "S"),
87+
"sponsor": renewal_contract.sponsorship.sponsor,
88+
"sponsorship": renewal_contract.sponsorship,
89+
"benefits": [],
90+
"legal_clauses": [],
91+
"renewal": True,
92+
"previous_effective": "UNKNOWN",
93+
}
94+
renewal_template = "sponsors/admin/preview-contract.html"
95+
96+
template = Path(settings.TEMPLATES_DIR) / "sponsors" / "admin" / "renewal-contract-template.docx"
97+
self.assertTrue(template.exists())
98+
mocked_doc = Mock(DocxTemplate)
99+
MockDocxTemplate.return_value = mocked_doc
100+
101+
request = Mock(HttpRequest)
102+
response = render_contract_to_docx_response(request, renewal_contract)
103+
104+
MockDocxTemplate.assert_called_once_with(str(template.resolve()))
105+
mocked_doc.render.assert_called_once_with(renewal_context)
106+
mocked_doc.save.assert_called_once_with(response)
107+
self.assertEqual(response.get("Content-Disposition"), "attachment; filename=contract.docx")
108+
self.assertEqual(
109+
response.get("Content-Type"),
110+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
111+
)

sponsors/tests/test_use_cases.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,24 @@ def test_update_sponsorship_as_approved_and_create_contract(self):
118118
self.assertEqual(self.sponsorship.sponsorship_fee, 100)
119119
self.assertEqual(self.sponsorship.package, self.package)
120120
self.assertEqual(self.sponsorship.level_name, self.package.name)
121+
self.assertFalse(self.sponsorship.renewal)
122+
123+
124+
def test_update_renewal_sponsorship_as_approved_and_create_contract(self):
125+
self.data.update({"renewal": True})
126+
self.use_case.execute(self.sponsorship, **self.data)
127+
self.sponsorship.refresh_from_db()
128+
129+
today = timezone.now().date()
130+
self.assertEqual(self.sponsorship.approved_on, today)
131+
self.assertEqual(self.sponsorship.status, Sponsorship.APPROVED)
132+
self.assertTrue(self.sponsorship.contract.pk)
133+
self.assertTrue(self.sponsorship.start_date)
134+
self.assertTrue(self.sponsorship.end_date)
135+
self.assertEqual(self.sponsorship.sponsorship_fee, 100)
136+
self.assertEqual(self.sponsorship.package, self.package)
137+
self.assertEqual(self.sponsorship.level_name, self.package.name)
138+
self.assertEqual(self.sponsorship.renewal, True)
121139

122140
def test_send_notifications_using_sponsorship(self):
123141
self.use_case.execute(self.sponsorship, **self.data)

sponsors/use_cases.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,14 @@ def execute(self, sponsorship, start_date, end_date, **kwargs):
5555
sponsorship.approve(start_date, end_date)
5656
package = kwargs.get("package")
5757
fee = kwargs.get("sponsorship_fee")
58+
renewal = kwargs.get("renewal", False)
5859
if package:
5960
sponsorship.package = package
6061
sponsorship.level_name = package.name
6162
if fee:
6263
sponsorship.sponsorship_fee = fee
64+
if renewal:
65+
sponsorship.renewal = True
6366

6467
sponsorship.save()
6568
contract = Contract.new(sponsorship)

sponsors/views_admin.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ def approve_sponsorship_view(ModelAdmin, request, pk):
8585
)
8686
return redirect(redirect_url)
8787

88-
context = {"sponsorship": sponsorship, "form": form}
88+
context = {
89+
"sponsorship": sponsorship,
90+
"form": form,
91+
"previous_effective": sponsorship.previous_effective_date if sponsorship.previous_effective_date else "UNKNOWN",
92+
}
8993
return render(request, "sponsors/admin/approve_application.html", context=context)
9094

9195

0 commit comments

Comments
 (0)