11"""Django admin configuration for the sponsors app."""
22
33import contextlib
4+ from textwrap import dedent
45
56from django .contrib import admin
67from django .contrib .contenttypes .admin import GenericTabularInline
1213from django .template import Context , Template
1314from django .urls import path , reverse
1415from django .utils .functional import cached_property
15- from django .utils .html import mark_safe
16+ from django .utils .html import format_html , format_html_join , mark_safe
1617from import_export import resources
1718from import_export .admin import ImportExportActionModelAdmin
1819from import_export .fields import Field
@@ -296,12 +297,12 @@ def get_benefit_split(self, obj: SponsorshipPackage) -> str:
296297 for i , (name , pct ) in enumerate (split ):
297298 pct_str = f"{ pct :.0f} %"
298299 widths .append (pct_str )
299- spans .append (f"<span title=' { name } ' style='background-color: { colors [i ]} '> { pct_str } </span>" )
300+ spans .append (( name , colors [i ], pct_str ) )
300301 # define a style that will show our span elements like a single horizontal stacked bar chart
301302 style = f"color:#fff;text-align:center;cursor:pointer;display:grid;grid-template-columns:{ ' ' .join (widths )} "
303+ split_bar = format_html_join ("" , "<span title='{}' style='background-color:{}'>{}</span>" , spans )
302304 # wrap it all up and put a bow on it
303- html = f"<div style='{ style } '>{ '' .join (spans )} </div>"
304- return mark_safe (html )
305+ return format_html ("<div style='{}'>{}</div>" , style , split_bar )
305306
306307
307308class SponsorContactInline (admin .TabularInline ):
@@ -325,7 +326,7 @@ class SponsorshipsInline(admin.TabularInline):
325326 def link (self , obj ):
326327 """Return a link to the sponsorship change page."""
327328 url = reverse ("admin:sponsors_sponsorship_change" , args = [obj .id ])
328- return mark_safe ( f "<a href={ url } > { obj . id } </a>" )
329+ return format_html ( "<a href='{}'>{ }</a>" , url , obj . id )
329330
330331
331332@admin .register (Sponsor )
@@ -652,27 +653,25 @@ def get_readonly_fields(self, request, obj):
652653 def sponsor_link (self , obj ):
653654 """Return an HTML link to the sponsor's admin change page."""
654655 url = reverse ("admin:sponsors_sponsor_change" , args = [obj .sponsor .id ])
655- return mark_safe ( f "<a href={ url } > { obj . sponsor . name } </a>" )
656+ return format_html ( "<a href='{}'>{ }</a>" , url , obj . sponsor . name )
656657
657658 @admin .display (description = "Estimated cost" )
658659 def get_estimated_cost (self , obj ):
659660 """Return the estimated cost HTML for customized sponsorships."""
660- cost = None
661661 html = "This sponsorship has not customizations so there's no estimated cost"
662662 if obj .for_modified_package :
663663 msg = "This sponsorship has customizations and this cost is a sum of all benefit's internal values from when this sponsorship was created"
664664 cost = intcomma (obj .estimated_cost )
665- html = f" { cost } USD <br/><b>Important: </b> { msg } "
666- return mark_safe ( html )
665+ html = format_html ( "{ } USD <br/><b>Important: </b> {}" , cost , msg )
666+ return html
667667
668668 @admin .display (description = "Contract" )
669669 def get_contract (self , obj ):
670670 """Return an HTML link to the contract or a placeholder."""
671671 if not obj .contract :
672672 return "---"
673673 url = reverse ("admin:sponsors_contract_change" , args = [obj .contract .pk ])
674- html = f"<a href='{ url } ' target='_blank'>{ obj .contract } </a>"
675- return mark_safe (html )
674+ return format_html ("<a href='{}' target='_blank'>{}</a>" , url , obj .contract )
676675
677676 def get_urls (self ):
678677 """Register custom admin URLs for sponsorship workflow actions."""
@@ -741,19 +740,20 @@ def get_sponsor_web_logo(self, obj):
741740 template = Template (html )
742741 context = Context ({"sponsor" : obj .sponsor })
743742 html = template .render (context )
744- return mark_safe (html )
743+ return mark_safe (html ) # noqa: S308
745744
746745 @admin .display (description = "Print Logo" )
747746 def get_sponsor_print_logo (self , obj ):
748747 """Render and return the sponsor's print logo as a thumbnail image."""
749748 img = obj .sponsor .print_logo
750- html = ""
749+ html = "--- "
751750 if img :
752- html = "{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}"
753- template = Template (html )
751+ template = Template (
752+ "{% load thumbnail %}{% thumbnail img '150x150' format='PNG' quality=100 as im %}<img src='{{ im.url}}'/>{% endthumbnail %}"
753+ )
754754 context = Context ({"img" : img })
755- html = template .render (context )
756- return mark_safe ( html ) if html else "---"
755+ html = mark_safe ( template .render (context )) # noqa: S308
756+ return html
757757
758758 @admin .display (description = "Primary Phone" )
759759 def get_sponsor_primary_phone (self , obj ):
@@ -764,18 +764,24 @@ def get_sponsor_primary_phone(self, obj):
764764 def get_sponsor_mailing_address (self , obj ):
765765 """Return the sponsor's formatted mailing address as HTML."""
766766 sponsor = obj .sponsor
767- city_row = f"{ sponsor .city } - { sponsor .get_country_display ()} ({ sponsor .country } )"
768767 if sponsor .state :
769- city_row = f"{ sponsor .city } - { sponsor .state } - { sponsor .get_country_display ()} ({ sponsor .country } )"
768+ city_row_html = format_html (
769+ "<p>{} - {} - {} ({})</p>" , sponsor .city , sponsor .state , sponsor .get_country_display (), sponsor .country
770+ )
771+ else :
772+ city_row_html = format_html (
773+ "<p>{} - {} ({})</p>" , sponsor .city , sponsor .get_country_display (), sponsor .country
774+ )
770775
771- mail_row = sponsor .mailing_address_line_1
772776 if sponsor .mailing_address_line_2 :
773- mail_row += f" - { sponsor .mailing_address_line_2 } "
777+ mail_row_html = format_html (
778+ "<p>{} - {}</p>" , sponsor .mailing_address_line_1 , sponsor .mailing_address_line_2
779+ )
780+ else :
781+ mail_row_html = format_html ("<p>{}</p>" , sponsor .mailing_address_line_1 )
774782
775- html = f"<p>{ city_row } </p>"
776- html += f"<p>{ mail_row } </p>"
777- html += f"<p>{ sponsor .postal_code } </p>"
778- return mark_safe (html )
783+ postal_code_row = format_html ("<p>{}</p>" , sponsor .postal_code )
784+ return city_row_html + mail_row_html + postal_code_row
779785
780786 @admin .display (description = "Contacts" )
781787 def get_sponsor_contacts (self , obj ):
@@ -785,14 +791,14 @@ def get_sponsor_contacts(self, obj):
785791 primary = [c for c in contacts if c .primary ]
786792 not_primary = [c for c in contacts if not c .primary ]
787793 if primary :
788- html = "<b>Primary contacts</b><ul>"
789- html += "" . join ([ f "<li>{ c . name } : { c . email } / { c . phone } </li>" for c in primary ])
790- html += "</ul>"
794+ html = mark_safe ( "<b>Primary contacts</b><ul>" )
795+ html += format_html_join ( "" , "<li>{}: {} / {}</li>" , [( c . name , c . email , c . phone ) for c in primary ])
796+ html += mark_safe ( "</ul>" )
791797 if not_primary :
792- html += "<b>Other contacts</b><ul>"
793- html += "" . join ([ f "<li>{ c . name } : { c . email } / { c . phone } </li>" for c in not_primary ])
794- html += "</ul>"
795- return mark_safe ( html )
798+ html += mark_safe ( "<b>Other contacts</b><ul>" )
799+ html += format_html_join ( "" , "<li>{}: {} / {}</li>" , [( c . name , c . email , c . phone ) for c in not_primary ])
800+ html += mark_safe ( "</ul>" )
801+ return html
796802
797803 @admin .display (description = "Added by User" )
798804 def get_custom_benefits_added_by_user (self , obj ):
@@ -801,8 +807,7 @@ def get_custom_benefits_added_by_user(self, obj):
801807 if not benefits :
802808 return "---"
803809
804- html = "" .join ([f"<p>{ b } </p>" for b in benefits ])
805- return mark_safe (html )
810+ return format_html_join ("" , "<p>{}</p>" , benefits )
806811
807812 @admin .display (description = "Removed by User" )
808813 def get_custom_benefits_removed_by_user (self , obj ):
@@ -811,8 +816,7 @@ def get_custom_benefits_removed_by_user(self, obj):
811816 if not benefits :
812817 return "---"
813818
814- html = "" .join ([f"<p>{ b } </p>" for b in benefits ])
815- return mark_safe (html )
819+ return format_html_join ("" , "<p>{}</p>" , benefits )
816820
817821 def rollback_to_editing_view (self , request , pk ):
818822 """Delegate to the rollback_to_editing admin view."""
@@ -878,18 +882,25 @@ def links(self, obj):
878882 benefits_url = reverse ("admin:sponsors_sponsorshipbenefit_changelist" )
879883 preview_label = "View sponsorship application"
880884 year = obj .year
881- html = "<ul>"
882885 preview_querystring = f"config_year={ year } "
883886 preview_url = f"{ application_url } ?{ preview_querystring } "
884887 filter_querystring = f"year={ year } "
885888 year_benefits_url = f"{ benefits_url } ?{ filter_querystring } "
886889 year_packages_url = f"{ benefits_url } ?{ filter_querystring } "
887890
888- html += f"<li><a target='_blank' href='{ year_packages_url } '>List packages</a>"
889- html += f"<li><a target='_blank' href='{ year_benefits_url } '>List benefits</a>"
890- html += f"<li><a target='_blank' href='{ preview_url } '>{ preview_label } </a>"
891- html += "</ul>"
892- return mark_safe (html )
891+ return format_html (
892+ dedent ("""
893+ <ul>
894+ <li><a target='_blank' href='{year_packages_url}'>List packages</a>
895+ <li><a target='_blank' href='{year_benefits_url}'>List benefits</a>
896+ <li><a target='_blank' href='{preview_url}'>{preview_label}</a>
897+ </ul>
898+ """ ),
899+ year_packages_url = year_packages_url ,
900+ year_benefits_url = year_benefits_url ,
901+ preview_url = preview_url ,
902+ preview_label = preview_label ,
903+ )
893904
894905 @admin .display (description = "Other configured years" )
895906 def other_years (self , obj ):
@@ -904,22 +915,32 @@ def other_years(self, obj):
904915 application_url = reverse ("select_sponsorship_application_benefits" )
905916 benefits_url = reverse ("admin:sponsors_sponsorshipbenefit_changelist" )
906917 preview_label = "View sponsorship application form for this year"
907- html = "<ul>"
918+ html = mark_safe ( "<ul>" )
908919 for year in configured_years :
909920 preview_querystring = f"config_year={ year } "
910921 preview_url = f"{ application_url } ?{ preview_querystring } "
911922 filter_querystring = f"year={ year } "
912923 year_benefits_url = f"{ benefits_url } ?{ filter_querystring } "
913924 year_packages_url = f"{ benefits_url } ?{ filter_querystring } "
914925
915- html += f"<li><b>{ year } </b>:"
916- html += "<ul>"
917- html += f"<li><a target='_blank' href='{ year_packages_url } '>List packages</a>"
918- html += f"<li><a target='_blank' href='{ year_benefits_url } '>List benefits</a>"
919- html += f"<li><a target='_blank' href='{ preview_url } '>{ preview_label } </a>"
920- html += "</ul></li>"
921- html += "</ul>"
922- return mark_safe (html )
926+ html += format_html (
927+ dedent ("""
928+ <li><b>{year}</b>:"
929+ <ul>
930+ <li><a target='_blank' href='{year_packages_url}'>List packages</a>
931+ <li><a target='_blank' href='{year_benefits_url}'>List benefits</a>
932+ <li><a target='_blank' href='{preview_url}'>{preview_label}</a>
933+ </ul>
934+ </li>
935+ """ ),
936+ year = year ,
937+ year_packages_url = year_packages_url ,
938+ year_benefits_url = year_benefits_url ,
939+ preview_url = preview_url ,
940+ preview_label = preview_label ,
941+ )
942+ html += mark_safe ("</ul>" )
943+ return html
923944
924945 def clone_application_config (self , request ):
925946 """Delegate to the clone_application_config admin view."""
@@ -1042,17 +1063,16 @@ def document_link(self, obj):
10421063 msg = "Download Signed Contract"
10431064
10441065 if url and msg :
1045- html = f '<a href="{ url } " target="_blank">{ msg } </a>'
1046- return mark_safe ( html )
1066+ html = format_html ( '<a href="{}" target="_blank">{}</a>' , url , msg )
1067+ return html
10471068
10481069 @admin .display (description = "Sponsorship" )
10491070 def get_sponsorship_url (self , obj ):
10501071 """Return an HTML link to the related sponsorship's admin page."""
10511072 if not obj .sponsorship :
10521073 return "---"
10531074 url = reverse ("admin:sponsors_sponsorship_change" , args = [obj .sponsorship .pk ])
1054- html = f"<a href='{ url } ' target='_blank'>{ obj .sponsorship } </a>"
1055- return mark_safe (html )
1075+ return format_html ("<a href='{}' target='_blank'>{}</a>" , url , obj .sponsorship )
10561076
10571077 def get_urls (self ):
10581078 """Register custom admin URLs for contract workflow actions."""
@@ -1257,8 +1277,8 @@ def get_value(self, obj):
12571277 """Return the asset value, linking to the file URL if applicable."""
12581278 html = obj .value
12591279 if obj .value and getattr (obj .value , "url" , None ):
1260- html = f "<a href='{ obj . value . url } ' target='_blank'>{ obj . value } </a>"
1261- return mark_safe ( html )
1280+ html = format_html ( "<a href='{}' target='_blank'>{}</a>" , ( obj . value . url , obj . value ))
1281+ return html
12621282
12631283 @admin .display (description = "Associated with" )
12641284 def get_related_object (self , obj ):
@@ -1276,8 +1296,7 @@ def get_related_object(self, obj):
12761296 if not content_object : # safety belt
12771297 return obj .content_object
12781298
1279- html = f"<a href='{ content_object .admin_url } ' target='_blank'>{ content_object } </a>"
1280- return mark_safe (html )
1299+ return format_html ("<a href='{}' target='_blank'>{}</a>" , content_object .admin_url , content_object )
12811300
12821301 @admin .action (description = "Export selected" )
12831302 def export_assets_as_zipfile (self , request , queryset ):
0 commit comments