Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
a28574f
Add missing migration to the existing models (managers and meta info)
berinhard Jul 28, 2022
8a96ed8
New model to keep track of current sponsorship year
berinhard Jul 28, 2022
3a778a0
Make sure the singleton object is populated by default via data migra…
berinhard Jul 28, 2022
c343121
Make sure the singleton logic is implemented at DB-level
berinhard Jul 28, 2022
5677668
Make sure singleton object cannot be deleted
berinhard Jul 28, 2022
dd508bb
Add singleton to admin with disabled permissions for adding or deleting
berinhard Jul 28, 2022
75393ec
Add django-extensions as a requirement to be able to use shell_plus
berinhard Jul 29, 2022
cb6606c
Add application year field to sponsorship model
berinhard Jul 29, 2022
e471a34
Display new field and enable filter on sponsorship admin
berinhard Jul 29, 2022
006ea76
Populate application year when creating it
berinhard Jul 29, 2022
b1044e5
Rename field to be just "year"
berinhard Jul 29, 2022
409ab57
Enable to filter contract by sponsorship year
berinhard Jul 29, 2022
344ba44
Refactoring to centralize year validators
berinhard Jul 29, 2022
789a058
Add year field to configure sponsorship benefits and packages
berinhard Jul 29, 2022
fd055eb
Initialize values for existing sponsorship benefits and packages with…
berinhard Jul 29, 2022
dc61ebc
Year field should be required when creating/editing configured benefits
berinhard Jul 29, 2022
50c6342
Add filter by year to configured benefits and packages
berinhard Jul 29, 2022
1d64711
Refactor configured benefits and packages to build custom manager fro…
berinhard Jul 29, 2022
5d24886
New manager methods to filter configured packages and benefits from c…
berinhard Jul 29, 2022
fc203fb
Sponsorship application form now only lists pkg, benefits, add-ons an…
berinhard Jul 29, 2022
057fbf7
Fix requirements organization
berinhard Jul 29, 2022
1f234ab
Improve form unit tests to make sure we're filtering packages and ben…
berinhard Jul 30, 2022
82cc000
Refactor to encapsulate logic to get the current year within a class …
berinhard Jul 30, 2022
94837d3
Add cache to avoid querying the DB every time the system needs the cu…
berinhard Jul 30, 2022
c862800
Add db index to year fields so querying by them gets faster
berinhard Jul 30, 2022
17f7bed
Add migration command to CI to check if it's running them
berinhard Jul 30, 2022
01941c8
Move fields definition to init so query for current year happens as e…
berinhard Jul 30, 2022
f6a1816
Revert "Add migration command to CI to check if it's running them"
berinhard Jul 30, 2022
26d1b56
add necessary fixtures
ewdurbin Aug 1, 2022
a87d026
Introduce clone method to benefit and related objects
berinhard Aug 2, 2022
3420b5a
Add clone method to be able to copy a benefit configuration to a new …
berinhard Aug 2, 2022
7c11554
Make sure Tiered Quantity config can be copy using the same year's pa…
berinhard Aug 3, 2022
1c6d000
Make sure required assets configurations can be cloned without violat…
berinhard Aug 3, 2022
6ef09ec
Add unit test to make sure the remaining configuration can be cloned
berinhard Aug 3, 2022
e4db39d
Make sure benefit features configurations get cloned as well
berinhard Aug 3, 2022
bf5a50b
Upgrade model-bakery version to the most up to date with Django 2.2 s…
berinhard Aug 3, 2022
fd47cb9
Implement use case to generate clone an sponsorship year configuratio…
berinhard Aug 3, 2022
d5105da
Introduce helper function to build admin base url name
berinhard Aug 3, 2022
679fbc7
Create admin view to clone sponsorship configuration from one year to…
berinhard Aug 4, 2022
daf0f3c
Add form validation to enforce relations between from and target years
berinhard Aug 4, 2022
8ff782b
Add workflow to django admin to enable staff users to clone configura…
berinhard Aug 4, 2022
7985877
Reverse order so most recent years appear first
berinhard Aug 4, 2022
ee2a43b
Refactoring to introduce more generic function to create django log e…
berinhard Aug 4, 2022
8d4d7dd
Update use case to add django admin log entries for new cloned packag…
berinhard Aug 4, 2022
a911c4b
Add parameter to be able to display form for a specific year
berinhard Aug 4, 2022
dced3c2
Enable staff user to preview how the application form from a specific…
berinhard Aug 4, 2022
6cb480c
Display link to preview non active years sponsorship form in admin
berinhard Aug 4, 2022
235669c
Only display links to already configured years if they exist
berinhard Aug 4, 2022
bb101f0
Also display links to list configured year's packages and benefits fr…
berinhard Aug 4, 2022
e8f036f
Add column with links for the active year
berinhard Aug 4, 2022
7c6d2a1
Disable submit button if preview for custom year
berinhard Aug 4, 2022
7de75da
update style for admin warning on application preview to be extra scary
ewdurbin Aug 5, 2022
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 base-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ django-polymorphic==3.0.0
sorl-thumbnail==12.7.0
docxtpl==0.12.0
reportlab==3.6.6
django-extensions==3.1.4
2 changes: 1 addition & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ responses==0.13.3
django-debug-toolbar==3.2.1
coverage
ddt
model-bakery==1.3.2
model-bakery==1.4.0
1,149 changes: 756 additions & 393 deletions fixtures/boxes.json

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions fixtures/flags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[
{
"model": "waffle.flag",
"pk": 1,
"fields": {
"name": "psf_membership",
"everyone": null,
"percent": null,
"testing": true,
"superusers": true,
"staff": false,
"authenticated": false,
"languages": "",
"rollout": false,
"note": "This flag is used to show the PSF Basic and Advanced member registration process.",
"created": "2015-06-05T09:47:03Z",
"modified": "2017-03-22T01:45:42.077Z",
"groups": [],
"users": []
}
},
{
"model": "waffle.flag",
"pk": 2,
"fields": {
"name": "sponsorship-applications-open",
"everyone": true,
"percent": null,
"testing": false,
"superusers": false,
"staff": false,
"authenticated": false,
"languages": "",
"rollout": false,
"note": "Controls if the application form and benefits \"menu\" is visible at https://www.python.org/sponsors/application/\r\n\r\nThe contents of the page when applications are closed is modifiable at https://www.python.org/admin/boxes/box/106/change/",
"created": "2022-07-21T17:16:05Z",
"modified": "2022-07-21T17:24:06.747Z",
"groups": [],
"users": []
}
}
]
2,985 changes: 2,984 additions & 1 deletion fixtures/sponsors.json

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions fixtures/users.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[
{
"model": "users.user",
"pk": 1,
"fields": {
"password": "pbkdf2_sha256$150000$TAqxQ4O0uzV2$3lgFMdRiaBnaUfXtjSRlA/9HzMwYa2ThD38AmTzGYEs=",
"last_login": "2022-08-01T18:52:54.206Z",
"is_superuser": true,
"username": "admin",
"first_name": "",
"last_name": "",
"email": "admin@example.com",
"is_staff": true,
"is_active": true,
"date_joined": "2022-08-01T18:52:39.307Z",
"bio": "",
"bio_markup_type": "markdown",
"search_visibility": 1,
"_bio_rendered": "",
"email_privacy": 2,
"public_profile": true,
"groups": [],
"user_permissions": []
}
},
{
"model": "users.user",
"pk": 2,
"fields": {
"password": "pbkdf2_sha256$150000$TAqxQ4O0uzV2$3lgFMdRiaBnaUfXtjSRlA/9HzMwYa2ThD38AmTzGYEs=",
"last_login": "2022-08-01T18:54:51.727Z",
"is_superuser": false,
"username": "user",
"first_name": "",
"last_name": "",
"email": "user@example.com",
"is_staff": false,
"is_active": true,
"date_joined": "2022-08-01T18:54:10.023Z",
"bio": "",
"bio_markup_type": "markdown",
"search_visibility": 1,
"_bio_rendered": "",
"email_privacy": 2,
"public_profile": true,
"groups": [],
"user_permissions": []
}
},
{
"model": "account.emailaddress",
"pk": 1,
"fields": {
"user": [
"admin"
],
"email": "admin@example.com",
"verified": true,
"primary": true
}
},
{
"model": "account.emailaddress",
"pk": 2,
"fields": {
"user": [
"user"
],
"email": "user@example.com",
"verified": true,
"primary": true
}
}
]
1 change: 1 addition & 0 deletions pydotorg/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
'rest_framework.authtoken',
'django_filters',
'polymorphic',
'django_extensions',
]

# Fixtures
Expand Down
3 changes: 2 additions & 1 deletion pydotorg/settings/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'pythondotorg-local-cache',
}
}

Expand Down
124 changes: 109 additions & 15 deletions sponsors/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@
from sponsors.models.benefits import RequiredAssetMixin
from sponsors import views_admin
from sponsors.forms import SponsorshipReviewAdminForm, SponsorBenefitAdminInlineForm, RequiredImgAssetConfigurationForm, \
SponsorshipBenefitAdminForm
SponsorshipBenefitAdminForm, CloneApplicationConfigForm
from cms.admin import ContentManageableModelAdmin


def get_url_base_name(Model):
return f"{Model._meta.app_label}_{Model._meta.model_name}"


class AssetsInline(GenericTabularInline):
model = GenericAsset
extra = 0
Expand Down Expand Up @@ -113,7 +117,7 @@ class SponsorshipBenefitAdmin(PolymorphicInlineSupportMixin, OrderedModelAdmin):
"internal_value",
"move_up_down_links",
]
list_filter = ["program", "package_only", "packages", "new", "a_la_carte", "unavailable"]
list_filter = ["program", "year", "package_only", "packages", "new", "a_la_carte", "unavailable"]
search_fields = ["name"]
form = SponsorshipBenefitAdminForm

Expand Down Expand Up @@ -150,11 +154,12 @@ class SponsorshipBenefitAdmin(PolymorphicInlineSupportMixin, OrderedModelAdmin):

def get_urls(self):
urls = super().get_urls()
base_name = get_url_base_name(self.model)
my_urls = [
path(
"<int:pk>/update-related-sponsorships",
self.admin_site.admin_view(self.update_related_sponsorships),
name="sponsors_sponsorshipbenefit_update_related",
name=f"{base_name}_update_related",
),
]
return my_urls + urls
Expand All @@ -166,8 +171,8 @@ def update_related_sponsorships(self, *args, **kwargs):
@admin.register(SponsorshipPackage)
class SponsorshipPackageAdmin(OrderedModelAdmin):
ordering = ("order",)
list_display = ["name", "advertise", "move_up_down_links"]
list_filter = ["advertise"]
list_display = ["name", "year", "advertise", "move_up_down_links"]
list_filter = ["advertise", "year"]
search_fields = ["name"]

def get_readonly_fields(self, request, obj=None):
Expand Down Expand Up @@ -294,12 +299,13 @@ class SponsorshipAdmin(admin.ModelAdmin):
"sponsor",
"status",
"package",
"year",
"applied_on",
"approved_on",
"start_date",
"end_date",
]
list_filter = [SponsorshipStatusListFilter, "package", TargetableEmailBenefitsFilter]
list_filter = [SponsorshipStatusListFilter, "package", "year", TargetableEmailBenefitsFilter]
actions = ["send_notifications"]
fieldsets = [
(
Expand All @@ -311,6 +317,7 @@ class SponsorshipAdmin(admin.ModelAdmin):
"status",
"package",
"sponsorship_fee",
"year",
"get_estimated_cost",
"start_date",
"end_date",
Expand Down Expand Up @@ -407,6 +414,9 @@ def get_readonly_fields(self, request, obj):
extra = ["start_date", "end_date", "package", "level_name", "sponsorship_fee"]
readonly_fields.extend(extra)

if obj.year:
readonly_fields.append("year")

return readonly_fields

def sponsor_link(self, obj):
Expand Down Expand Up @@ -436,33 +446,34 @@ def get_contract(self, obj):

def get_urls(self):
urls = super().get_urls()
base_name = get_url_base_name(self.model)
my_urls = [
path(
"<int:pk>/reject",
# TODO: maybe it would be better to create a specific
# group or permission to review sponsorship applications
self.admin_site.admin_view(self.reject_sponsorship_view),
name="sponsors_sponsorship_reject",
name=f"{base_name}_reject",
),
path(
"<int:pk>/approve-existing",
self.admin_site.admin_view(self.approve_signed_sponsorship_view),
name="sponsors_sponsorship_approve_existing_contract",
name=f"{base_name}_approve_existing_contract",
),
path(
"<int:pk>/approve",
self.admin_site.admin_view(self.approve_sponsorship_view),
name="sponsors_sponsorship_approve",
name=f"{base_name}_approve",
),
path(
"<int:pk>/enable-edit",
self.admin_site.admin_view(self.rollback_to_editing_view),
name="sponsors_sponsorship_rollback_to_edit",
name=f"{base_name}_rollback_to_edit",
),
path(
"<int:pk>/list-assets",
self.admin_site.admin_view(self.list_uploaded_assets_view),
name="sponsors_sponsorship_list_uploaded_assets",
name=f"{base_name}_list_uploaded_assets",
),
]
return my_urls + urls
Expand Down Expand Up @@ -588,6 +599,87 @@ def list_uploaded_assets_view(self, request, pk):
return views_admin.list_uploaded_assets(self, request, pk)


@admin.register(SponsorshipCurrentYear)
class SponsorshipCurrentYearAdmin(admin.ModelAdmin):
list_display = ["year", "links", "other_years"]
change_list_template = "sponsors/admin/sponsors_sponsorshipcurrentyear_changelist.html"

def has_add_permission(self, *args, **kwargs):
return False

def has_delete_permission(self, *args, **kwargs):
return False

def get_urls(self):
urls = super().get_urls()
base_name = get_url_base_name(self.model)
my_urls = [
path(
"clone-year-config",
self.admin_site.admin_view(self.clone_application_config),
name=f"{base_name}_clone",
),
]
return my_urls + urls

def links(self, obj):
clone_form = CloneApplicationConfigForm()
configured_years = clone_form.configured_years

application_url = reverse("select_sponsorship_application_benefits")
benefits_url = reverse("admin:sponsors_sponsorshipbenefit_changelist")
packages_url = reverse("admin:sponsors_sponsorshippackage_changelist")
preview_label = 'View sponsorship application'
year = obj.year
html = "<ul>"
preview_querystring = f"config_year={year}"
preview_url = f"{application_url}?{preview_querystring}"
filter_querystring = f"year={year}"
year_benefits_url = f"{benefits_url}?{filter_querystring}"
year_packages_url = f"{benefits_url}?{filter_querystring}"

html += f"<li><a target='_blank' href='{year_packages_url}'>List packages</a>"
html += f"<li><a target='_blank' href='{year_benefits_url}'>List benefits</a>"
html += f"<li><a target='_blank' href='{preview_url}'>{preview_label}</a>"
html += "</ul>"
return mark_safe(html)
links.short_description = "Links"

def other_years(self, obj):
clone_form = CloneApplicationConfigForm()
configured_years = clone_form.configured_years
try:
configured_years.remove(obj.year)
except ValueError:
pass
if not configured_years:
return "---"

application_url = reverse("select_sponsorship_application_benefits")
benefits_url = reverse("admin:sponsors_sponsorshipbenefit_changelist")
packages_url = reverse("admin:sponsors_sponsorshippackage_changelist")
preview_label = 'View sponsorship application form for this year'
html = "<ul>"
for year in configured_years:
preview_querystring = f"config_year={year}"
preview_url = f"{application_url}?{preview_querystring}"
filter_querystring = f"year={year}"
year_benefits_url = f"{benefits_url}?{filter_querystring}"
year_packages_url = f"{benefits_url}?{filter_querystring}"

html += f"<li><b>{year}</b>:"
html += "<ul>"
html += f"<li><a target='_blank' href='{year_packages_url}'>List packages</a>"
html += f"<li><a target='_blank' href='{year_benefits_url}'>List benefits</a>"
html += f"<li><a target='_blank' href='{preview_url}'>{preview_label}</a>"
html += "</ul></li>"
html += "</ul>"
return mark_safe(html)
other_years.short_description = "Other configured years"

def clone_application_config(self, request):
return views_admin.clone_application_config(self, request)

@admin.register(LegalClause)
class LegalClauseModelAdmin(OrderedModelAdmin):
list_display = ["internal_name"]
Expand All @@ -596,6 +688,7 @@ class LegalClauseModelAdmin(OrderedModelAdmin):
@admin.register(Contract)
class ContractModelAdmin(admin.ModelAdmin):
change_form_template = "sponsors/admin/contract_change_form.html"
list_filter = ["sponsorship__year"]
list_display = [
"id",
"sponsorship",
Expand Down Expand Up @@ -711,26 +804,27 @@ def get_sponsorship_url(self, obj):

def get_urls(self):
urls = super().get_urls()
base_name = get_url_base_name(self.model)
my_urls = [
path(
"<int:pk>/preview",
self.admin_site.admin_view(self.preview_contract_view),
name="sponsors_contract_preview",
name=f"{base_name}_preview",
),
path(
"<int:pk>/send",
self.admin_site.admin_view(self.send_contract_view),
name="sponsors_contract_send",
name=f"{base_name}_send",
),
path(
"<int:pk>/execute",
self.admin_site.admin_view(self.execute_contract_view),
name="sponsors_contract_execute",
name=f"{base_name}_execute",
),
path(
"<int:pk>/nullify",
self.admin_site.admin_view(self.nullify_contract_view),
name="sponsors_contract_nullify",
name=f"{base_name}_nullify",
),
]
return my_urls + urls
Expand Down
Loading