Skip to content

Commit

Permalink
Sponsorship - adding renewal option for contract generation (#2344)
Browse files Browse the repository at this point in the history
* 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 <ewdurbin@gmail.com>
  • Loading branch information
jessiebelle and ewdurbin authored Dec 19, 2023
1 parent 7ba7d36 commit 4ef4388
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 7 deletions.
1 change: 1 addition & 0 deletions sponsors/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ class SponsorshipAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
"end_date",
"get_contract",
"level_name",
"renewal",
"overlapped_by",
),
},
Expand Down
9 changes: 8 additions & 1 deletion sponsors/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,10 @@ class SponsorshipReviewAdminForm(forms.ModelForm):
start_date = forms.DateField(widget=AdminDateWidget(), required=False)
end_date = forms.DateField(widget=AdminDateWidget(), required=False)
overlapped_by = forms.ModelChoiceField(queryset=Sponsorship.objects.select_related("sponsor", "package"), required=False)
renewal = forms.BooleanField(
help_text="If true, it means the sponsorship is a renewal of a previous sponsorship and will use the renewal template for contracting.",
required=False,
)

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


class Meta:
model = Sponsorship
fields = ["start_date", "end_date", "package", "sponsorship_fee"]
fields = ["start_date", "end_date", "package", "sponsorship_fee", "renewal"]
widgets = {
'year': SPONSORSHIP_YEAR_SELECT,
}
Expand All @@ -415,6 +421,7 @@ def clean(self):
cleaned_data = super().clean()
start_date = cleaned_data.get("start_date")
end_date = cleaned_data.get("end_date")
renewal = cleaned_data.get("renewal")

if start_date and end_date and end_date <= start_date:
raise forms.ValidationError("End date must be greater than start date")
Expand Down
18 changes: 18 additions & 0 deletions sponsors/migrations/0097_sponsorship_renewal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.24 on 2023-12-18 16:23

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('sponsors', '0096_auto_20231214_2108'),
]

operations = [
migrations.AddField(
model_name='sponsorship',
name='renewal',
field=models.BooleanField(blank=True, null=True),
),
]
18 changes: 18 additions & 0 deletions sponsors/migrations/0098_auto_20231219_1910.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.24 on 2023-12-19 19:10

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('sponsors', '0097_sponsorship_renewal'),
]

operations = [
migrations.AlterField(
model_name='sponsorship',
name='renewal',
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),
),
]
13 changes: 12 additions & 1 deletion sponsors/models/sponsorship.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class Meta(OrderedModel.Meta):

class Sponsorship(models.Model):
"""
Represente a sponsorship application by a sponsor.
Represents a sponsorship application by a sponsor.
It's responsible to group the set of selected benefits and
link it to sponsor
"""
Expand Down Expand Up @@ -182,6 +182,11 @@ class Sponsorship(models.Model):
package = models.ForeignKey(SponsorshipPackage, null=True, on_delete=models.SET_NULL)
sponsorship_fee = models.PositiveIntegerField(null=True, blank=True)
overlapped_by = models.ForeignKey("self", null=True, on_delete=models.SET_NULL)
renewal = models.BooleanField(
null=True,
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."
)

assets = GenericRelation(GenericAsset)

Expand Down Expand Up @@ -378,6 +383,12 @@ def next_status(self):
}
return states_map[self.status]

@property
def previous_effective_date(self):
if len(self.sponsor.sponsorship_set.all().order_by('-year')) > 1:
return self.sponsor.sponsorship_set.all().order_by('-year')[1].start_date
return None


class SponsorshipBenefit(OrderedModel):
"""
Expand Down
10 changes: 8 additions & 2 deletions sponsors/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ def _contract_context(contract, **context):
"sponsorship": contract.sponsorship,
"benefits": _clean_split(contract.benefits_list.raw),
"legal_clauses": _clean_split(contract.legal_clauses.raw),
"renewal": contract.sponsorship.renewal,
})
context["previous_effective"] = contract.sponsorship.previous_effective_date if contract.sponsorship.previous_effective_date else "UNKNOWN"
return context


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


def _gen_docx_contract(output, contract, **context):
template = os.path.join(settings.TEMPLATES_DIR, "sponsors", "admin", "contract-template.docx")
doc = DocxTemplate(template)
context = _contract_context(contract, **context)
renewal = context["renewal"]
if renewal:
template = os.path.join(settings.TEMPLATES_DIR, "sponsors", "admin", "renewal-contract-template.docx")
else:
template = os.path.join(settings.TEMPLATES_DIR, "sponsors", "admin", "contract-template.docx")
doc = DocxTemplate(template)
doc.render(context)
doc.save(output)
return output
Expand Down
38 changes: 38 additions & 0 deletions sponsors/tests/test_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def setUp(self):
"sponsorship": self.contract.sponsorship,
"benefits": [],
"legal_clauses": [],
"renewal": None,
"previous_effective": "UNKNOWN",
}
self.template = "sponsors/admin/preview-contract.html"

Expand Down Expand Up @@ -71,3 +73,39 @@ def test_render_response_with_docx_attachment(self, MockDocxTemplate):
response.get("Content-Type"),
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
)

@patch("sponsors.pdf.DocxTemplate")
def test_render_response_with_docx_attachment__renewal(self, MockDocxTemplate):
renewal_contract = baker.make_recipe("sponsors.tests.empty_contract", sponsorship__start_date=date.today(),
sponsorship__renewal=True)
text = f"{renewal_contract.benefits_list.raw}\n\n**Legal Clauses**\n{renewal_contract.legal_clauses.raw}"
html = render_md(text)
renewal_context = {
"contract": renewal_contract,
"start_date": renewal_contract.sponsorship.start_date,
"start_day_english_suffix": format(self.contract.sponsorship.start_date, "S"),
"sponsor": renewal_contract.sponsorship.sponsor,
"sponsorship": renewal_contract.sponsorship,
"benefits": [],
"legal_clauses": [],
"renewal": True,
"previous_effective": "UNKNOWN",
}
renewal_template = "sponsors/admin/preview-contract.html"

template = Path(settings.TEMPLATES_DIR) / "sponsors" / "admin" / "renewal-contract-template.docx"
self.assertTrue(template.exists())
mocked_doc = Mock(DocxTemplate)
MockDocxTemplate.return_value = mocked_doc

request = Mock(HttpRequest)
response = render_contract_to_docx_response(request, renewal_contract)

MockDocxTemplate.assert_called_once_with(str(template.resolve()))
mocked_doc.render.assert_called_once_with(renewal_context)
mocked_doc.save.assert_called_once_with(response)
self.assertEqual(response.get("Content-Disposition"), "attachment; filename=contract.docx")
self.assertEqual(
response.get("Content-Type"),
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
)
18 changes: 18 additions & 0 deletions sponsors/tests/test_use_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ def test_update_sponsorship_as_approved_and_create_contract(self):
self.assertEqual(self.sponsorship.sponsorship_fee, 100)
self.assertEqual(self.sponsorship.package, self.package)
self.assertEqual(self.sponsorship.level_name, self.package.name)
self.assertFalse(self.sponsorship.renewal)


def test_update_renewal_sponsorship_as_approved_and_create_contract(self):
self.data.update({"renewal": True})
self.use_case.execute(self.sponsorship, **self.data)
self.sponsorship.refresh_from_db()

today = timezone.now().date()
self.assertEqual(self.sponsorship.approved_on, today)
self.assertEqual(self.sponsorship.status, Sponsorship.APPROVED)
self.assertTrue(self.sponsorship.contract.pk)
self.assertTrue(self.sponsorship.start_date)
self.assertTrue(self.sponsorship.end_date)
self.assertEqual(self.sponsorship.sponsorship_fee, 100)
self.assertEqual(self.sponsorship.package, self.package)
self.assertEqual(self.sponsorship.level_name, self.package.name)
self.assertEqual(self.sponsorship.renewal, True)

def test_send_notifications_using_sponsorship(self):
self.use_case.execute(self.sponsorship, **self.data)
Expand Down
3 changes: 3 additions & 0 deletions sponsors/use_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,14 @@ def execute(self, sponsorship, start_date, end_date, **kwargs):
sponsorship.approve(start_date, end_date)
package = kwargs.get("package")
fee = kwargs.get("sponsorship_fee")
renewal = kwargs.get("renewal", False)
if package:
sponsorship.package = package
sponsorship.level_name = package.name
if fee:
sponsorship.sponsorship_fee = fee
if renewal:
sponsorship.renewal = True

sponsorship.save()
contract = Contract.new(sponsorship)
Expand Down
6 changes: 5 additions & 1 deletion sponsors/views_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ def approve_sponsorship_view(ModelAdmin, request, pk):
)
return redirect(redirect_url)

context = {"sponsorship": sponsorship, "form": form}
context = {
"sponsorship": sponsorship,
"form": form,
"previous_effective": sponsorship.previous_effective_date if sponsorship.previous_effective_date else "UNKNOWN",
}
return render(request, "sponsors/admin/approve_application.html", context=context)


Expand Down
22 changes: 20 additions & 2 deletions templates/sponsors/admin/approve_application.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@
{% extends 'admin/change_form.html' %}
{% load i18n static sponsors %}

{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">{% endblock %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}">
<style>
.helptext {
font-size: smaller;
display: block;
padding-top: .5em;
color: #999;
}
</style>
{% endblock %}

{% block title %}Accept {{ sponsorship }} | python.org{% endblock %}

Expand Down Expand Up @@ -33,8 +44,15 @@ <h1>Generate Contract for Signing</h1>
{{ form.media }}
{{ form.as_p }}

<input name="confirm" value="yes" style="display:none">
<p>
<label for="id_renewal_previous_effective_date">
Previous Effective Contract:
</label>
<span><b>{{ previous_effective }}</b></span>
<span class="helptext">The last known contract effective date for this sponsor. This will <i>only</i> impact renewals. If UNKNOWN, you <strong>MUST</strong> update the resulting docx with the correct effective date.</span>
</p>

<input name="confirm" value="yes" style="display:none">
<div class="submit-row">
<input type="submit" value="Approve" class="default">
</div>
Expand Down
Binary file not shown.

0 comments on commit 4ef4388

Please sign in to comment.