Skip to content

Commit 6c5a004

Browse files
authored
Bugfix: enable to execute draft contracts (#1916)
* Update contrac't Execute use case to require the signed document as well * Display an input for the user to upload the final signed document before executing it * Change signed_document to readonly and display link to execute draft contract * Remove broken import
1 parent 365233e commit 6c5a004

File tree

9 files changed

+75
-27
lines changed

9 files changed

+75
-27
lines changed

sponsors/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ def get_readonly_fields(self, request, obj):
536536
"revision",
537537
"document",
538538
"document_docx",
539+
"signed_document",
539540
"get_sponsorship_url",
540541
]
541542

sponsors/models/contract.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def awaiting_signature(self):
193193
@property
194194
def next_status(self):
195195
states_map = {
196-
self.DRAFT: [self.AWAITING_SIGNATURE],
196+
self.DRAFT: [self.AWAITING_SIGNATURE, self.EXECUTED],
197197
self.OUTDATED: [],
198198
self.AWAITING_SIGNATURE: [self.EXECUTED, self.NULLIFIED],
199199
self.EXECUTED: [],

sponsors/tests/test_models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ def test_format_benefits_with_legal_clauses(self):
485485
def test_control_contract_next_status(self):
486486
SOW = Contract
487487
states_map = {
488-
SOW.DRAFT: [SOW.AWAITING_SIGNATURE],
488+
SOW.DRAFT: [SOW.AWAITING_SIGNATURE, SOW.EXECUTED],
489489
SOW.OUTDATED: [],
490490
SOW.AWAITING_SIGNATURE: [SOW.EXECUTED, SOW.NULLIFIED],
491491
SOW.EXECUTED: [],
@@ -547,7 +547,7 @@ def test_execute_contract(self):
547547

548548
def test_raise_invalid_status_when_trying_to_execute_contract_if_not_awaiting_signature(self):
549549
contract = baker.make_recipe(
550-
"sponsors.tests.empty_contract", status=Contract.DRAFT
550+
"sponsors.tests.empty_contract", status=Contract.OUTDATED
551551
)
552552

553553
with self.assertRaises(InvalidStatusException):

sponsors/tests/test_use_cases.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,22 @@ def setUp(self):
168168
self.notifications = [Mock()]
169169
self.use_case = use_cases.ExecuteContractUseCase(self.notifications)
170170
self.user = baker.make(settings.AUTH_USER_MODEL)
171-
self.contract = baker.make_recipe("sponsors.tests.empty_contract", status=Contract.AWAITING_SIGNATURE)
171+
self.file = SimpleUploadedFile("contract.txt", b"Contract content")
172+
self.contract = baker.make_recipe(
173+
"sponsors.tests.empty_contract",
174+
status=Contract.AWAITING_SIGNATURE,
175+
)
176+
177+
def tearDown(self):
178+
self.contract.refresh_from_db()
179+
if self.contract.signed_document:
180+
self.contract.signed_document.delete()
172181

173182
def test_execute_and_update_database_object(self):
174-
self.use_case.execute(self.contract)
183+
self.use_case.execute(self.contract, self.file)
175184
self.contract.refresh_from_db()
176185
self.assertEqual(self.contract.status, Contract.EXECUTED)
186+
self.assertTrue(self.contract.signed_document.url)
177187

178188
def test_build_use_case_with_default_notificationss(self):
179189
uc = use_cases.ExecuteContractUseCase.build()

sponsors/tests/test_views_admin.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import io
22
import json
3+
4+
from django.core.files.uploadedfile import SimpleUploadedFile
35
from model_bakery import baker
46
from datetime import date, timedelta
57
from unittest.mock import patch, PropertyMock, Mock
@@ -569,8 +571,17 @@ def setUp(self):
569571
)
570572
self.data = {
571573
"confirm": "yes",
574+
"signed_document": SimpleUploadedFile("contract.txt", b"Contract content"),
572575
}
573576

577+
def tearDown(self):
578+
try:
579+
self.contract.refresh_from_db()
580+
if self.contract.signed_document:
581+
self.contract.signed_document.delete()
582+
except Contract.DoesNotExist:
583+
pass
584+
574585
def test_display_confirmation_form_on_get(self):
575586
response = self.client.get(self.url)
576587
context = response.context
@@ -591,7 +602,7 @@ def test_execute_sponsorship_on_post(self):
591602
assertMessage(msg, "Contract was executed!", messages.SUCCESS)
592603

593604
def test_display_error_message_to_user_if_invalid_status(self):
594-
self.contract.status = Contract.DRAFT
605+
self.contract.status = Contract.OUTDATED
595606
self.contract.save()
596607
expected_url = reverse(
597608
"admin:sponsors_contract_change", args=[self.contract.pk]
@@ -602,10 +613,10 @@ def test_display_error_message_to_user_if_invalid_status(self):
602613
msg = list(get_messages(response.wsgi_request))[0]
603614

604615
self.assertRedirects(response, expected_url, fetch_redirect_response=True)
605-
self.assertEqual(self.contract.status, Contract.DRAFT)
616+
self.assertEqual(self.contract.status, Contract.OUTDATED)
606617
assertMessage(
607618
msg,
608-
"Contract with status Draft can't be executed.",
619+
"Contract with status Outdated can't be executed.",
609620
messages.ERROR,
610621
)
611622

@@ -622,6 +633,15 @@ def test_do_not_execute_contract_if_no_confirmation_in_the_post(self):
622633
self.contract.refresh_from_db()
623634
self.assertEqual(self.contract.status, Contract.AWAITING_SIGNATURE)
624635

636+
def test_display_error_message_to_user_if_no_signed_document(self):
637+
self.data.pop("signed_document")
638+
response = self.client.post(self.url, data=self.data)
639+
context = response.context
640+
641+
self.assertTemplateUsed(response, "sponsors/admin/execute_contract.html")
642+
self.assertEqual(context["contract"], self.contract)
643+
self.assertTrue(context["error_msg"])
644+
625645
def test_404_if_contract_does_not_exist(self):
626646
self.contract.delete()
627647
response = self.client.get(self.url)

sponsors/use_cases.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,33 +91,28 @@ def execute(self, contract, **kwargs):
9191
)
9292

9393

94-
class ExecuteContractUseCase(BaseUseCaseWithNotifications):
95-
notifications = [
96-
notifications.ExecutedContractLogger(),
97-
]
98-
99-
def execute(self, contract, **kwargs):
100-
contract.execute()
101-
self.notify(
102-
request=kwargs.get("request"),
103-
contract=contract,
104-
)
105-
106-
10794
class ExecuteExistingContractUseCase(BaseUseCaseWithNotifications):
10895
notifications = [
10996
notifications.ExecutedExistingContractLogger(),
11097
]
98+
force_execute = True
11199

112100
def execute(self, contract, contract_file, **kwargs):
113101
contract.signed_document = contract_file
114-
contract.execute(force=True)
102+
contract.execute(force=self.force_execute)
115103
self.notify(
116104
request=kwargs.get("request"),
117105
contract=contract,
118106
)
119107

120108

109+
class ExecuteContractUseCase(ExecuteExistingContractUseCase):
110+
notifications = [
111+
notifications.ExecutedContractLogger(),
112+
]
113+
force_execute = False
114+
115+
121116
class NullifyContractUseCase(BaseUseCaseWithNotifications):
122117
notifications = [
123118
notifications.NullifiedContractLogger(),

sponsors/views_admin.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,13 @@ def rollback_to_editing_view(ModelAdmin, request, pk):
179179
def execute_contract_view(ModelAdmin, request, pk):
180180
contract = get_object_or_404(ModelAdmin.get_queryset(request), pk=pk)
181181

182-
if request.method.upper() == "POST" and request.POST.get("confirm") == "yes":
182+
is_post = request.method.upper() == "POST"
183+
signed_document = request.FILES.get("signed_document")
184+
if is_post and request.POST.get("confirm") == "yes" and signed_document:
183185

184186
use_case = use_cases.ExecuteContractUseCase.build()
185187
try:
186-
use_case.execute(contract, request=request)
188+
use_case.execute(contract, signed_document, request=request)
187189
ModelAdmin.message_user(
188190
request, "Contract was executed!", messages.SUCCESS
189191
)
@@ -198,7 +200,11 @@ def execute_contract_view(ModelAdmin, request, pk):
198200
redirect_url = reverse("admin:sponsors_contract_change", args=[contract.pk])
199201
return redirect(redirect_url)
200202

201-
context = {"contract": contract}
203+
error_msg = ""
204+
if is_post and not signed_document:
205+
error_msg = "You must submit the signed contract document to execute it."
206+
207+
context = {"contract": contract, "error_msg": error_msg}
202208
return render(request, "sponsors/admin/execute_contract.html", context=context)
203209

204210

templates/sponsors/admin/contract_change_form.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@
1414
<li>
1515
<a href="{% url 'admin:sponsors_contract_send' contract.pk %}" style="background: #70bf2b">Send document</a>
1616
</li>
17-
{% elif contract.status == contract.AWAITING_SIGNATURE %}
17+
{% endif %}
18+
19+
{% if contract.EXECUTED in contract.next_status %}
1820
<li>
1921
<a href="{% url 'admin:sponsors_contract_execute' contract.pk %}" style="background: #70bf2b">Execute</a>
2022
</li>
23+
{% endif %}
24+
{% if contract.NULLIFY in contract.next_status %}
2125
<li>
2226
<a href="{% url 'admin:sponsors_contract_nullify' contract.pk %}" style="background: #ba2121" >Nullify</a>
2327
</li>

templates/sponsors/admin/execute_contract.html

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,22 @@ <h1>Execute Contract</h1>
2020

2121
<div id="content-main">
2222
<p><b>Important: </b>By executing this contract, the sponsor logo will be placed accordingly to the benefits linked to the sponsorship during the sponsorship period (from {{ contract.sponsorship.start_date|date }} until {{ contract.sponsorship.end_date|date }})</p>
23+
<p>To finalize and execute this contract, you have to upload the final signed document and update it.</p>
2324

24-
<form action="" method="post">
25+
<form action="" method="post" enctype="multipart/form-data">
2526
{% csrf_token %}
2627

28+
{% if error_msg %}
29+
<p style="color: red">{{ error_msg }}</p>
30+
{% endif %}
31+
32+
{% if not contract.signed_document %}
33+
<p>
34+
<p><b><label for="id_signed_document">Signed Contract</label></b></p>
35+
<input type="file" id="id_signed_document" name="signed_document" required>
36+
</p>
37+
{% endif %}
38+
2739
<input name="confirm" value="yes" style="display:none">
2840

2941
<div class="submit-row">

0 commit comments

Comments
 (0)