Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit301bfe2

Browse files
authored
Sponsorship dashboard (python#1892)
* Sort apps by name* Add missing migration after help text change* Add new app to handle custom email dispatching* Add new model to configure sponsor notifications* Minimal admin* Update admin form to validate content as django template* Add button to preview how template will render* Add new benefit configuration to flag email targeatable* Add method to filter sponsorships by included features* Enable user to select which notification template to use* Rename variable* Display warning message if selected sponsorships aren't targetable* Introduce indirection with use case to send the emails* Implement method to create a EmailMessage from a notification template* Display non targetable sponsorship as checkbox instead of text* Add select all/delete all links* Filter emails by benefits, not feature configuration* Better display for notification objects* Add checkbox to select contact type* Update get_message method to accept boolean flags to control recipients* Rename form field name* Send notification to sponsors* Register email dispatch with admin log entry activity* Add input for custom email content* Display input for custom email content* UC expects sponsorship object, not PK* Consider email subject as a template as well* Refactor to move specific email building part to mailing app* Remove warning message* Optimizes sponsorship admin query* Add option to preview notification* Fix parameters names* Organize sections as divs* Remove unused imports and optimize query* Minimal working code to list user sponsorships* Change Sponsorships button to redirect user to sponsorships dashboard* Update HTML to list active/finalized sponsorships* Move sponsorship detail view to users app* Minimal view to update sponsor info* Inline display sponsor contacts* Reuse style from sponsorships application form to edit sponsor information* Improve fields display* Add link to edit sponsor information from sponsorship detail page* Display sponsor information in sponsorship detail page* Do not list the sponsors at sponsorship dashboard page* Add JS to handle with the inline contact forms* TODO note with my tests' checklist* Move button to right* Display fields as readonly* Add unit tests* Remove file inputs* Better organize sponsorships* Revert "Remove file inputs"This reverts commitd7f9047.* Do not allow user to clear file inputs* Do not change file input background* Run code linter* Remove unecessary br* File inputs should be required
1 parent5defd8a commit301bfe2

17 files changed

+870
-178
lines changed

‎pydotorg/context_processors.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ def user_nav_bar_links(request):
3434
nav= {}
3535
ifrequest.user.is_authenticated:
3636
user=request.user
37-
sponsorship_urls= [
38-
{"url":sp.detail_url,"label":f"{sp.sponsor.name}'s sponsorship"}
39-
forspinuser.sponsorships
40-
]
37+
sponsorship_url=None
38+
ifuser.sponsorships.exists():
39+
sponsorship_url=reverse("users:user_sponsorships_dashboard")
40+
41+
# if the section has a urls key, the section buttion will work as a drop-down menu
42+
# if the section has only a url key, the button will be a link instead
4143
nav= {
4244
"account": {
4345
"label":"Your Account",
@@ -54,8 +56,8 @@ def user_nav_bar_links(request):
5456
],
5557
},
5658
"sponsorships": {
57-
"label":"Sponsorships",
58-
"urls":sponsorship_urls,
59+
"label":"Sponsorships Dashboard",
60+
"url":sponsorship_url
5961
}
6062
}
6163

‎pydotorg/tests/test_context_processors.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ def test_user_nav_bar_links_for_non_psf_members(self):
5252
],
5353
},
5454
"sponsorships": {
55-
"label":"Sponsorships",
56-
"urls":[],
55+
"label":"Sponsorships Dashboard",
56+
"url":None,
5757
}
5858
}
5959

@@ -84,8 +84,8 @@ def test_user_nav_bar_links_for_psf_members(self):
8484
],
8585
},
8686
"sponsorships": {
87-
"label":"Sponsorships",
88-
"urls":[],
87+
"label":"Sponsorships Dashboard",
88+
"url":None,
8989
}
9090
}
9191

@@ -97,18 +97,15 @@ def test_user_nav_bar_links_for_psf_members(self):
9797
deftest_user_nav_bar_sponsorship_links(self):
9898
request=self.factory.get('/about/')
9999
request.user=baker.make(settings.AUTH_USER_MODEL,username='foo')
100-
sponsorships=baker.make("sponsors.Sponsorship",submited_by=request.user,_quantity=2,_fill_optional=True)
101-
102-
expected_sponsorships= {
103-
"label":"Sponsorships",
104-
"urls": [
105-
{"url":sp.detail_url,"label":f"{sp.sponsor.name}'s sponsorship"}
106-
forspinrequest.user.sponsorships
107-
]
100+
baker.make("sponsors.Sponsorship",submited_by=request.user,_quantity=2,_fill_optional=True)
101+
102+
expected_section= {
103+
"label":"Sponsorships Dashboard",
104+
"url":reverse("users:user_sponsorships_dashboard")
108105
}
109106

110107
self.assertEqual(
111-
expected_sponsorships,
108+
expected_section,
112109
context_processors.user_nav_bar_links(request)['USER_NAV_BAR']['sponsorships']
113110
)
114111

‎sponsors/forms.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,3 +463,66 @@ def get_notification(self):
463463
subject=self.cleaned_data["subject"],
464464
)
465465
returnself.cleaned_data.get("notification")ordefault_notification
466+
467+
468+
classSponsorUpdateForm(forms.ModelForm):
469+
READONLY_FIELDS= [
470+
"name",
471+
]
472+
473+
web_logo=forms.ImageField(
474+
widget=forms.widgets.FileInput,
475+
help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px",
476+
required=False,
477+
)
478+
print_logo=forms.ImageField(
479+
widget=forms.widgets.FileInput,
480+
help_text="For printed materials, signage, and projection. SVG or EPS",
481+
required=False,
482+
)
483+
484+
def__init__(self,*args,**kwargs):
485+
super().__init__(*args,**kwargs)
486+
formset_kwargs= {"prefix":"contact","instance":self.instance}
487+
factory=forms.inlineformset_factory(
488+
Sponsor,
489+
SponsorContact,
490+
form=SponsorContactForm,
491+
extra=0,
492+
min_num=1,
493+
validate_min=True,
494+
can_delete=True,
495+
can_order=False,
496+
max_num=5,
497+
)
498+
ifself.data:
499+
self.contacts_formset=factory(self.data,**formset_kwargs)
500+
else:
501+
self.contacts_formset=factory(**formset_kwargs)
502+
# display fields as read-only
503+
fordisabledinself.READONLY_FIELDS:
504+
self.fields[disabled].widget.attrs['readonly']=True
505+
506+
classMeta:
507+
exclude= ["created","updated","creator","last_modified_by"]
508+
model=Sponsor
509+
510+
defclean(self):
511+
cleaned_data=super().clean()
512+
513+
ifnotself.contacts_formset.is_valid():
514+
msg="Errors with contact(s) information"
515+
ifnotself.contacts_formset.errors:
516+
msg="You have to enter at least one contact"
517+
raiseforms.ValidationError(msg)
518+
519+
has_primary_contact=any(
520+
f.cleaned_data.get("primary")forfinself.contacts_formset.forms
521+
)
522+
ifnothas_primary_contact:
523+
msg="You have to mark at least one contact as the primary one."
524+
raiseforms.ValidationError(msg)
525+
526+
defsave(self,*args,**kwargs):
527+
super().save(*args,**kwargs)
528+
self.contacts_formset.save()

‎sponsors/models.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
importuuid
22
fromitertoolsimportchain
33
fromnum2wordsimportnum2words
4+
fromdatetimeimportdate
45
fromdjango.confimportsettings
56
fromdjango.core.exceptionsimportObjectDoesNotExist
67
fromdjango.dbimportmodels,transaction
@@ -433,6 +434,13 @@ def agreed_fee(self):
433434
exceptSponsorshipPackage.DoesNotExist:# sponsorship level names can change over time
434435
returnNone
435436

437+
@property
438+
defis_active(self):
439+
conditions= [
440+
self.status==self.FINALIZED,
441+
self.end_dateandself.end_date>date.today()
442+
]
443+
436444
defreject(self):
437445
ifself.REJECTEDnotinself.next_status:
438446
msg=f"Can't reject a{self.get_status_display()} sponsorship."
@@ -492,7 +500,7 @@ def contract_admin_url(self):
492500

493501
@property
494502
defdetail_url(self):
495-
returnreverse("sponsorship_application_detail",args=[self.pk])
503+
returnreverse("users:sponsorship_application_detail",args=[self.pk])
496504

497505
@cached_property
498506
defpackage_benefits(self):
@@ -620,39 +628,39 @@ class Sponsor(ContentManageable):
620628

621629
name=models.CharField(
622630
max_length=100,
623-
verbose_name="Sponsor name",
631+
verbose_name="Name",
624632
help_text="Name of the sponsor, for public display.",
625633
)
626634
description=models.TextField(
627-
verbose_name="Sponsor description",
635+
verbose_name="Description",
628636
help_text="Brief description of the sponsor for public display.",
629637
)
630638
landing_page_url=models.URLField(
631639
blank=True,
632640
null=True,
633-
verbose_name="Sponsor landingpage",
634-
help_text="Sponsor landing page URL. This may be provided by the sponsor, however the linked page may not contain any sales or marketing information.",
641+
verbose_name="Landingpage URL",
642+
help_text="Landing page URL. This may be provided by the sponsor, however the linked page may not contain any sales or marketing information.",
635643
)
636644
twitter_handle=models.CharField(
637645
max_length=32,# Actual limit set by twitter is 15 characters, but that may change?
638646
blank=True,
639647
null=True,
640-
verbose_name="Sponsor twitter hanlde",
648+
verbose_name="Twitter handle",
641649
)
642650
web_logo=models.ImageField(
643651
upload_to="sponsor_web_logos",
644-
verbose_name="Sponsor web logo",
652+
verbose_name="Web logo",
645653
help_text="For display on our sponsor webpage. High resolution PNG or JPG, smallest dimension no less than 256px",
646654
)
647655
print_logo=models.FileField(
648656
upload_to="sponsor_print_logos",
649657
blank=True,
650658
null=True,
651-
verbose_name="Sponsor print logo",
659+
verbose_name="Print logo",
652660
help_text="For printed materials, signage, and projection. SVG or EPS",
653661
)
654662

655-
primary_phone=models.CharField("SponsorPrimary Phone",max_length=32)
663+
primary_phone=models.CharField("Primary Phone",max_length=32)
656664
mailing_address_line_1=models.CharField(
657665
verbose_name="Mailing Address line 1",max_length=128,default=""
658666
)

‎sponsors/tests/test_views.py

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -317,42 +317,3 @@ def test_redirect_user_back_to_benefits_selection_if_post_without_valid_set_of_b
317317
self.client.cookies["sponsorship_selected_benefits"]="invalid"
318318
r=self.client.post(self.url,data=self.data)
319319
self.assertRedirects(r,reverse("select_sponsorship_application_benefits"))
320-
321-
322-
classSponsorshipDetailViewTests(TestCase):
323-
324-
defsetUp(self):
325-
self.user=baker.make(settings.AUTH_USER_MODEL)
326-
self.client.force_login(self.user)
327-
self.sponsorship=baker.make(
328-
Sponsorship,submited_by=self.user,status=Sponsorship.APPLIED,_fill_optional=True
329-
)
330-
self.url=reverse(
331-
"sponsorship_application_detail",args=[self.sponsorship.pk]
332-
)
333-
334-
deftest_display_template_with_sponsorship_info(self):
335-
response=self.client.get(self.url)
336-
context=response.context
337-
338-
self.assertTemplateUsed(response,"sponsors/sponsorship_detail.html")
339-
self.assertEqual(context["sponsorship"],self.sponsorship)
340-
341-
deftest_404_if_sponsorship_does_not_exist(self):
342-
self.sponsorship.delete()
343-
response=self.client.get(self.url)
344-
self.assertEqual(response.status_code,404)
345-
346-
deftest_login_required(self):
347-
login_url=settings.LOGIN_URL
348-
redirect_url=f"{login_url}?next={self.url}"
349-
self.client.logout()
350-
351-
r=self.client.get(self.url)
352-
353-
self.assertRedirects(r,redirect_url)
354-
355-
deftest_404_if_sponsorship_does_not_belong_to_user(self):
356-
self.client.force_login(baker.make(settings.AUTH_USER_MODEL))# log in with a new user
357-
response=self.client.get(self.url)
358-
self.assertEqual(response.status_code,404)

‎sponsors/urls.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,4 @@
1010
path('application/',views.SelectSponsorshipApplicationBenefitsView.as_view(),
1111
name="select_sponsorship_application_benefits",
1212
),
13-
path(
14-
"application/<int:pk>/detail/",
15-
views.SponsorshipDetailView.as_view(),
16-
name="sponsorship_application_detail",
17-
),
1813
]

‎sponsors/views.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
1-
importjson
21
fromitertoolsimportchain
32
fromdjango.confimportsettings
43
fromdjango.contribimportmessages
54
fromdjango.contrib.auth.decoratorsimportlogin_required
6-
fromdjango.contrib.auth.mixinsimportLoginRequiredMixin
75
fromdjango.dbimporttransaction
86
fromdjango.forms.utilsimportErrorList
9-
fromdjango.httpimportJsonResponse
107
fromdjango.shortcutsimportredirect,render
118
fromdjango.urlsimportreverse_lazy,reverse
129
fromdjango.utils.decoratorsimportmethod_decorator
13-
fromdjango.views.genericimportListView,FormView,DetailView
10+
fromdjango.views.genericimportFormView,DetailView,RedirectView
1411

1512
from .modelsimport (
16-
Sponsor,
1713
SponsorshipBenefit,
1814
SponsorshipPackage,
1915
SponsorshipProgram,
20-
Sponsorship,
2116
)
2217

2318
fromsponsorsimportcookies
@@ -174,12 +169,3 @@ def form_valid(self, form):
174169
)
175170
cookies.delete_sponsorship_selected_benefits(response)
176171
returnresponse
177-
178-
179-
@method_decorator(login_required(login_url=settings.LOGIN_URL),name="dispatch")
180-
classSponsorshipDetailView(DetailView):
181-
context_object_name='sponsorship'
182-
template_name='sponsors/sponsorship_detail.html'
183-
184-
defget_queryset(self):
185-
returnself.request.user.sponsorships

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp