Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
50f36a9
Model statement of work's information
berinhard Dec 4, 2020
790b7f2
Auto increment revision no
berinhard Dec 4, 2020
4a48cda
Automatically populate sponsor info and contact fields
berinhard Dec 4, 2020
40b63e5
Refactor SponsorBenefit creation
berinhard Dec 4, 2020
6c3c6eb
List benefits info
berinhard Dec 4, 2020
b2df24e
Format legal clauses list
berinhard Dec 4, 2020
1129a97
Prevents from rejecting or accepting reviewd applications
berinhard Nov 20, 2020
9d590fb
Display detailed sponsor's address information
berinhard Nov 24, 2020
5210594
Fix f-string and change postal code order
berinhard Nov 24, 2020
2b84431
Implement use case to approve sponsorship
berinhard Dec 4, 2020
47f13a3
Refactor use cases
berinhard Dec 4, 2020
6d002a4
Model admin for statements of work
berinhard Dec 4, 2020
d7bd3a4
Add link from approved sponsorship to draft SOW
berinhard Dec 4, 2020
26eb676
Replace sponsorships preview by SoW preview
berinhard Dec 4, 2020
2c68d71
Black =]
berinhard Dec 4, 2020
a7e3fa8
Add button to preview SoW from change form
berinhard Dec 4, 2020
4104f09
Move sponsor contact to sponsor info and display primary contact's email
berinhard Dec 7, 2020
b589c8f
Approve method requires start/end date
berinhard Dec 9, 2020
1cd894b
Accept use case now updates the sponsorship with more data
berinhard Dec 9, 2020
34bd7a0
Display form when reviewing sponsorship application
berinhard Dec 9, 2020
c8805e5
Prevent sponsorship from being changed after approval/rejection
berinhard Dec 9, 2020
d2ab3f4
Black =S
berinhard Dec 9, 2020
1d3bfc9
Join markdowns so footnotes can work
berinhard Dec 9, 2020
1c01f98
Remove unecessary trailing spaces
berinhard Dec 9, 2020
de28ebe
Fix issue after rebase
berinhard Dec 9, 2020
2252325
Respect db ordering to avoid tests inconsistencies
berinhard Dec 10, 2020
1635f7f
Move admin views to a specific file
berinhard Dec 10, 2020
be2e862
Install django easy pdf
berinhard Dec 10, 2020
7ea7570
Display SoW preview as PDF
berinhard Dec 10, 2020
1ad0efe
Move django-easy-pdf code to a specific module
berinhard Dec 11, 2020
61a3f98
Refactor model's contants
berinhard Dec 11, 2020
8b2e3bb
Create auxiliar function to render PDF document as bytes
berinhard Dec 11, 2020
6df2a64
Add status control to Statement of Work model
berinhard Dec 11, 2020
1d8fbdd
Rename exception
berinhard Dec 11, 2020
767785f
Create function to save the final document version
berinhard Dec 11, 2020
fab8096
Add vscode dir to gitignore
berinhard Dec 11, 2020
4927614
UC to send SoW
berinhard Dec 11, 2020
e73dbf3
Update notifications to use SoW instead of sponsorship obj
berinhard Dec 14, 2020
15e26b8
Impleent view to send statement of work to users
berinhard Dec 14, 2020
4240f8d
Refactor to use EmailMessage instead of send_mail shortcut
berinhard Dec 15, 2020
620cab3
Attach SoW PDF to emails
berinhard Dec 15, 2020
8faad47
Add button to send SoW
berinhard Dec 15, 2020
39508b4
Display an iframe with the PDF file before sending the document
berinhard Dec 15, 2020
e4b6743
Shouldn't edit document fields if not a draft version
berinhard Dec 15, 2020
8d60ed9
Add administrative flag to sponsor's contact
berinhard Dec 21, 2020
83cc781
Enable rollback sponsorship to edit
berinhard Dec 23, 2020
15fac67
Admin view to rollback to edit
berinhard Dec 23, 2020
c70d923
Add button in sponsorship's change form
berinhard Dec 23, 2020
a560c18
Merge branch 'topic/edit-sponsorships' into feature/gen-statement-of-…
berinhard Dec 23, 2020
5f96c01
Move rollback view to views_admin to respect internals structure
berinhard Dec 23, 2020
b426a68
Manage SoW before rolling back an application
berinhard Dec 23, 2020
f0c7d3b
Minor lint warnings
berinhard Dec 23, 2020
9cb22e7
Merge branch 'master' into feature/gen-statement-of-work
berinhard Feb 19, 2021
3c8e7fe
Add document summary content
berinhard Feb 19, 2021
af98295
Style page to closer to the reference
berinhard Feb 19, 2021
c44f404
Add contract bullet items
berinhard Feb 22, 2021
d708d47
List benefits and legal clauses
berinhard Feb 24, 2021
a118aa9
Do not display legal clauses section if nothing to list
berinhard Apr 6, 2021
e5a3da5
Merge branch 'master' into feature/gen-statement-of-work
berinhard Apr 6, 2021
10f7479
Create merge migration
berinhard Apr 6, 2021
df4564e
Remove element that was useful only for development
berinhard Apr 6, 2021
52365f9
Fix pdf tests
berinhard Apr 6, 2021
bde8b01
Return 0 if sponsorship_fee is none None to avoid TypeError from num2…
berinhard Apr 6, 2021
42bf463
Replace description text
berinhard Apr 14, 2021
2544a80
Fix typo and add link
berinhard Apr 14, 2021
627bea3
Rename StatementOfWork model
berinhard Apr 14, 2021
1eebf86
Update admin links
berinhard Apr 14, 2021
1ea83c4
Rename sow variables to contract
berinhard Apr 14, 2021
aa327a8
Rename statement_of_work references
berinhard Apr 14, 2021
e8c1349
Rename last statement references
berinhard Apr 14, 2021
f3fa31e
Move num2words requirement to base-requirements
berinhard Apr 14, 2021
0debe9a
Create commmand to work as initial data migration
berinhard Apr 14, 2021
b47bd18
Remove migration that doesn't work
berinhard Apr 16, 2021
2ce922b
add missing configurations from #1735
ewdurbin Apr 13, 2021
f9b4708
Add logger to approve sponsorship use case
berinhard Apr 16, 2021
b16688b
Remove expired todos
berinhard Apr 16, 2021
e515490
Add missing migration to replace SoW by Contract
berinhard Apr 16, 2021
10f682e
Legal clauses can be empty
berinhard Apr 16, 2021
bbeea08
Also log actitivy when final contract is sent
berinhard Apr 16, 2021
05d7e08
Merge branch 'master' into feature/gen-statement-of-work
ewdurbin Jun 29, 2021
dd25fe3
fix bad merge
ewdurbin Jun 29, 2021
d99c0b5
Store original program name on SponsorBenefit objects
ewdurbin Jun 29, 2021
000c5ac
Benefits must always have a program
berinhard Jul 7, 2021
de432f4
Prevent from sending contract file to sponsor
berinhard Jul 7, 2021
74ebb09
Implement logic to finalize contract and, thus, enable sponsorship be…
berinhard Jul 7, 2021
96b6b5c
Implement operation to nullify a contract
berinhard Jul 7, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ static/stylesheets/no-mq.css
static/stylesheets/style.css
__pycache__
*.db
.vscode
4 changes: 4 additions & 0 deletions base-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ requests[security]>=2.20.0

django-honeypot==0.6.0
django-markupfield==1.4.3
django-markupfield-helpers==0.1.1

django-allauth==0.41.0

Expand All @@ -39,3 +40,6 @@ django-filter==1.1.0
django-ordered-model==3.4.1
django-widget-tweaks==1.4.8
django-countries==6.1.3
xhtml2pdf==0.2.5
django-easy-pdf==0.1.1
num2words==0.5.10
1 change: 1 addition & 0 deletions pydotorg/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
'ordered_model',
'widget_tweaks',
'django_countries',
'easy_pdf',

'users',
'boxes',
Expand Down
257 changes: 172 additions & 85 deletions sponsors/admin.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from ordered_model.admin import OrderedModelAdmin

from django.contrib import messages
from django.urls import path, reverse
from django.contrib import admin
from django.contrib.humanize.templatetags.humanize import intcomma
from django.urls import path
from django.utils.html import mark_safe
from django.shortcuts import get_object_or_404, render, redirect

from .models import (
SponsorshipPackage,
Expand All @@ -16,10 +14,10 @@
SponsorContact,
SponsorBenefit,
LegalClause,
Contract,
)
from sponsors import use_cases
from sponsors import views_admin
from sponsors.forms import SponsorshipReviewAdminForm, SponsorBenefitAdminInlineForm
from sponsors.exceptions import SponsorshipInvalidStatusException
from cms.admin import ContentManageableModelAdmin


Expand Down Expand Up @@ -148,7 +146,6 @@ class SponsorshipAdmin(admin.ModelAdmin):
"approved_on",
"start_date",
"end_date",
"display_sponsorship_link",
]
list_filter = ["status", LevelNameFilter]
readonly_fields = [
Expand Down Expand Up @@ -215,16 +212,36 @@ class SponsorshipAdmin(admin.ModelAdmin):
),
]

def get_readonly_fields(self, request, obj):
readonly_fields = [
"for_modified_package",
"sponsor",
"status",
"applied_on",
"rejected_on",
"approved_on",
"finalized_on",
"get_estimated_cost",
"get_sponsor_name",
"get_sponsor_description",
"get_sponsor_landing_page_url",
"get_sponsor_web_logo",
"get_sponsor_print_logo",
"get_sponsor_primary_phone",
"get_sponsor_mailing_address",
"get_sponsor_contacts",
]

if obj and obj.status != Sponsorship.APPLIED:
extra = ["start_date", "end_date", "level_name", "sponsorship_fee"]
readonly_fields.extend(extra)

return readonly_fields

def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
return qs.select_related("sponsor")

def display_sponsorship_link(self, obj):
url = reverse("admin:sponsors_sponsorship_preview", args=[obj.pk])
return mark_safe(f'<a href="{url}" target="_blank">Click to preview</a>')

display_sponsorship_link.short_description = "Preview sponsorship"

def get_estimated_cost(self, obj):
cost = None
html = "This sponsorship has not customizations so there's no estimated cost"
Expand All @@ -236,19 +253,9 @@ def get_estimated_cost(self, obj):

get_estimated_cost.short_description = "Estimated cost"

def preview_sponsorship_view(self, request, pk):
sponsorship = get_object_or_404(self.get_queryset(request), pk=pk)
ctx = {"sponsorship": sponsorship}
return render(request, "sponsors/admin/preview-sponsorship.html", context=ctx)

def get_urls(self):
urls = super().get_urls()
my_urls = [
path(
"<int:pk>/preview",
self.admin_site.admin_view(self.preview_sponsorship_view),
name="sponsors_sponsorship_preview",
),
path(
"<int:pk>/reject",
# TODO: maybe it would be better to create a specific
Expand Down Expand Up @@ -345,77 +352,157 @@ def get_sponsor_contacts(self, obj):
get_sponsor_contacts.short_description = "Contacts"

def rollback_to_editing_view(self, request, pk):
sponsorship = get_object_or_404(self.get_queryset(request), pk=pk)

if request.method.upper() == "POST" and request.POST.get("confirm") == "yes":
try:
sponsorship.rollback_to_editing()
sponsorship.save()
self.message_user(
request, "Sponsorship is now editable!", messages.SUCCESS
)
except SponsorshipInvalidStatusException as e:
self.message_user(request, str(e), messages.ERROR)
return views_admin.rollback_to_editing_view(self, request, pk)

redirect_url = reverse(
"admin:sponsors_sponsorship_change", args=[sponsorship.pk]
)
return redirect(redirect_url)
def reject_sponsorship_view(self, request, pk):
return views_admin.reject_sponsorship_view(self, request, pk)

context = {"sponsorship": sponsorship}
return render(
request,
"sponsors/admin/rollback_sponsorship_to_editing.html",
context=context,
)
def approve_sponsorship_view(self, request, pk):
return views_admin.approve_sponsorship_view(self, request, pk)

def reject_sponsorship_view(self, request, pk):
sponsorship = get_object_or_404(self.get_queryset(request), pk=pk)

if request.method.upper() == "POST" and request.POST.get("confirm") == "yes":
try:
use_case = use_cases.RejectSponsorshipApplicationUseCase.build()
use_case.execute(sponsorship)
self.message_user(
request, "Sponsorship was rejected!", messages.SUCCESS
)
except SponsorshipInvalidStatusException as e:
self.message_user(request, str(e), messages.ERROR)

redirect_url = reverse(
"admin:sponsors_sponsorship_change", args=[sponsorship.pk]
)
return redirect(redirect_url)
@admin.register(LegalClause)
class LegalClauseModelAdmin(OrderedModelAdmin):
list_display = ["internal_name"]

context = {"sponsorship": sponsorship}
return render(
request, "sponsors/admin/reject_application.html", context=context
)

def approve_sponsorship_view(self, request, pk):
sponsorship = get_object_or_404(self.get_queryset(request), pk=pk)

if request.method.upper() == "POST" and request.POST.get("confirm") == "yes":
try:
sponsorship.approve()
sponsorship.save()
self.message_user(
request, "Sponsorship was approved!", messages.SUCCESS
@admin.register(Contract)
class ContractModelAdmin(admin.ModelAdmin):
change_form_template = "sponsors/admin/contract_change_form.html"
list_display = [
"id",
"sponsorship",
"created_on",
"last_update",
"status",
"get_revision",
"document_link",
]

def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
return qs.select_related("sponsorship__sponsor")

def get_revision(self, obj):
return obj.revision if obj.is_draft else "Final"

get_revision.short_description = "Revision"

fieldsets = [
(
"Info",
{
"fields": ("sponsorship", "status", "revision"),
},
),
(
"Editable",
{
"fields": (
"sponsor_info",
"sponsor_contact",
"benefits_list",
"legal_clauses",
),
},
),
(
"Files",
{
"fields": (
"document",
"signed_document",
)
except SponsorshipInvalidStatusException as e:
self.message_user(request, str(e), messages.ERROR)
},
),
(
"Activities log",
{
"fields": (
"created_on",
"last_update",
"sent_on",
),
"classes": ["collapse"],
},
),
]

redirect_url = reverse(
"admin:sponsors_sponsorship_change", args=[sponsorship.pk]
)
return redirect(redirect_url)
def get_readonly_fields(self, request, obj):
readonly_fields = [
"status",
"created_on",
"last_update",
"sent_on",
"sponsorship",
"revision",
"document",
]

context = {"sponsorship": sponsorship}
return render(
request, "sponsors/admin/approve_application.html", context=context
)
if obj and not obj.is_draft:
extra = [
"sponsor_info",
"sponsor_contact",
"benefits_list",
"legal_clauses",
]
readonly_fields.extend(extra)

return readonly_fields

def document_link(self, obj):
html, url, msg = "---", "", ""

if obj.is_draft:
url = obj.preview_url
msg = "Preview document"
elif obj.document:
url = obj.document.url
msg = "Download Contract"
elif obj.signed_document:
url = obj.signed_document.url
msg = "Download Signed Contract"

if url and msg:
html = f'<a href="{url}" target="_blank">{msg}</a>'
return mark_safe(html)

document_link.short_description = "Contract document"

@admin.register(LegalClause)
class LegalClauseModelAdmin(OrderedModelAdmin):
list_display = ["internal_name"]
def get_urls(self):
urls = super().get_urls()
my_urls = [
path(
"<int:pk>/preview",
self.admin_site.admin_view(self.preview_contract_view),
name="sponsors_contract_preview",
),
path(
"<int:pk>/send",
self.admin_site.admin_view(self.send_contract_view),
name="sponsors_contract_send",
),
path(
"<int:pk>/execute",
self.admin_site.admin_view(self.execute_contract_view),
name="sponsors_contract_execute",
),
path(
"<int:pk>/nullify",
self.admin_site.admin_view(self.nullify_contract_view),
name="sponsors_contract_nullify",
),
]
return my_urls + urls

def preview_contract_view(self, request, pk):
return views_admin.preview_contract_view(self, request, pk)

def send_contract_view(self, request, pk):
return views_admin.send_contract_view(self, request, pk)

def execute_contract_view(self, request, pk):
return views_admin.execute_contract_view(self, request, pk)

def nullify_contract_view(self, request, pk):
return views_admin.nullify_contract_view(self, request, pk)
9 changes: 8 additions & 1 deletion sponsors/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@ class SponsorWithExistingApplicationException(Exception):
"""


class SponsorshipInvalidStatusException(Exception):
class InvalidStatusException(Exception):
"""
Raised when user tries to change the Sponsorship's status
to a new one but from an invalid current status
"""


class SponsorshipInvalidDateRangeException(Exception):
"""
Raised when user tries to approve a sponsorship with a start date
greater than the end date.
"""
Loading