11from django .contrib .contenttypes .admin import GenericTabularInline
2+ from django .contrib .contenttypes .models import ContentType
23from ordered_model .admin import OrderedModelAdmin
3- from polymorphic .admin import PolymorphicInlineSupportMixin , StackedPolymorphicInline
4+ from polymorphic .admin import PolymorphicInlineSupportMixin , StackedPolymorphicInline , PolymorphicParentModelAdmin , \
5+ PolymorphicChildModelAdmin
46
57from django .db .models import Subquery
68from django .template import Context , Template
1315
1416from mailing .admin import BaseEmailTemplateAdmin
1517from sponsors .models import *
18+ from sponsors .models .benefits import RequiredAssetMixin
1619from sponsors import views_admin
1720from sponsors .forms import SponsorshipReviewAdminForm , SponsorBenefitAdminInlineForm , RequiredImgAssetConfigurationForm , \
1821 SponsorshipBenefitAdminForm
@@ -26,14 +29,16 @@ class AssetsInline(GenericTabularInline):
2629 has_delete_permission = lambda self , request , obj : False
2730 readonly_fields = ["internal_name" , "user_submitted_info" , "value" ]
2831
29- def value (self , request , obj = None ):
32+ def value (self , obj = None ):
3033 if not obj or not obj .value :
3134 return ""
3235 return obj .value
36+
3337 value .short_description = "Submitted information"
3438
35- def user_submitted_info (self , request , obj = None ):
36- return bool (self .value (request , obj ))
39+ def user_submitted_info (self , obj = None ):
40+ return bool (self .value (obj ))
41+
3742 user_submitted_info .short_description = "Fullfilled data?"
3843 user_submitted_info .boolean = True
3944
@@ -348,6 +353,7 @@ def get_queryset(self, *args, **kwargs):
348353
349354 def send_notifications (self , request , queryset ):
350355 return views_admin .send_sponsorship_notifications_action (self , request , queryset )
356+
351357 send_notifications .short_description = 'Send notifications to selected'
352358
353359 def get_readonly_fields (self , request , obj ):
@@ -431,6 +437,11 @@ def get_urls(self):
431437 self .admin_site .admin_view (self .rollback_to_editing_view ),
432438 name = "sponsors_sponsorship_rollback_to_edit" ,
433439 ),
440+ path (
441+ "<int:pk>/list-assets" ,
442+ self .admin_site .admin_view (self .list_uploaded_assets_view ),
443+ name = "sponsors_sponsorship_list_uploaded_assets" ,
444+ ),
434445 ]
435446 return my_urls + urls
436447
@@ -551,6 +562,9 @@ def approve_sponsorship_view(self, request, pk):
551562 def approve_signed_sponsorship_view (self , request , pk ):
552563 return views_admin .approve_signed_sponsorship_view (self , request , pk )
553564
565+ def list_uploaded_assets_view (self , request , pk ):
566+ return views_admin .list_uploaded_assets (self , request , pk )
567+
554568
555569@admin .register (LegalClause )
556570class LegalClauseModelAdmin (OrderedModelAdmin ):
@@ -714,9 +728,178 @@ def nullify_contract_view(self, request, pk):
714728
715729@admin .register (SponsorEmailNotificationTemplate )
716730class SponsorEmailNotificationTemplateAdmin (BaseEmailTemplateAdmin ):
731+
717732 def get_form (self , request , obj = None , ** kwargs ):
718733 help_texts = {
719734 "content" : SPONSOR_TEMPLATE_HELP_TEXT ,
720735 }
721736 kwargs .update ({"help_texts" : help_texts })
722737 return super ().get_form (request , obj , ** kwargs )
738+
739+
740+ class AssetTypeListFilter (admin .SimpleListFilter ):
741+ title = "Asset Type"
742+ parameter_name = 'type'
743+
744+ @property
745+ def assets_types_mapping (self ):
746+ return {asset_type .__name__ : asset_type for asset_type in GenericAsset .all_asset_types ()}
747+
748+ def lookups (self , request , model_admin ):
749+ return [(k , v ._meta .verbose_name_plural ) for k , v in self .assets_types_mapping .items ()]
750+
751+ def queryset (self , request , queryset ):
752+ asset_type = self .assets_types_mapping .get (self .value ())
753+ if not asset_type :
754+ return queryset
755+ return queryset .instance_of (asset_type )
756+
757+
758+ class AssociatedBenefitListFilter (admin .SimpleListFilter ):
759+ title = "From Benefit Which Requires Asset"
760+ parameter_name = 'from_benefit'
761+
762+ @property
763+ def benefits_with_assets (self ):
764+ qs = BenefitFeature .objects .required_assets ().values_list ("sponsor_benefit__sponsorship_benefit" ,
765+ flat = True ).distinct ()
766+ benefits = SponsorshipBenefit .objects .filter (id__in = Subquery (qs ))
767+ return {str (b .id ): b for b in benefits }
768+
769+ def lookups (self , request , model_admin ):
770+ return [(k , b .name ) for k , b in self .benefits_with_assets .items ()]
771+
772+ def queryset (self , request , queryset ):
773+ benefit = self .benefits_with_assets .get (self .value ())
774+ if not benefit :
775+ return queryset
776+ internal_names = [
777+ cfg .internal_name
778+ for cfg in benefit .features_config .all ()
779+ if hasattr (cfg , "internal_name" )
780+ ]
781+ return queryset .filter (internal_name__in = internal_names )
782+
783+
784+ class AssetContentTypeFilter (admin .SimpleListFilter ):
785+ title = "Related Object"
786+ parameter_name = 'content_type'
787+
788+ def lookups (self , request , model_admin ):
789+ qs = ContentType .objects .filter (model__in = ["sponsorship" , "sponsor" ])
790+ return [(c_type .pk , c_type .model .title ()) for c_type in qs ]
791+
792+ def queryset (self , request , queryset ):
793+ value = self .value ()
794+ if not value :
795+ return queryset
796+ return queryset .filter (content_type = value )
797+
798+
799+ class AssetWithOrWithoutValueFilter (admin .SimpleListFilter ):
800+ title = "Value"
801+ parameter_name = "value"
802+
803+ def lookups (self , request , model_admin ):
804+ return [
805+ ("with-value" , "With value" ),
806+ ("no-value" , "Without value" ),
807+ ]
808+
809+ def queryset (self , request , queryset ):
810+ value = self .value ()
811+ if not value :
812+ return queryset
813+ with_value_id = [asset .pk for asset in queryset if asset .value ]
814+ if value == "with-value" :
815+ return queryset .filter (pk__in = with_value_id )
816+ else :
817+ return queryset .exclude (pk__in = with_value_id )
818+
819+
820+ @admin .register (GenericAsset )
821+ class GenericAssetModelAdmin (PolymorphicParentModelAdmin ):
822+ list_display = ["id" , "internal_name" , "get_value" , "content_type" , "get_related_object" ]
823+ list_filter = [AssetContentTypeFilter , AssetTypeListFilter , AssetWithOrWithoutValueFilter ,
824+ AssociatedBenefitListFilter ]
825+ actions = ["export_assets_as_zipfile" ]
826+
827+ def get_child_models (self , * args , ** kwargs ):
828+ return GenericAsset .all_asset_types ()
829+
830+ def get_queryset (self , * args , ** kwargs ):
831+ classes = self .get_child_models (* args , ** kwargs )
832+ return self .model .objects .select_related ("content_type" ).instance_of (* classes )
833+
834+ def has_delete_permission (self , * args , ** kwargs ):
835+ return False
836+
837+ def has_add_permission (self , * args , ** kwargs ):
838+ return False
839+
840+ @cached_property
841+ def all_sponsors (self ):
842+ qs = Sponsor .objects .all ()
843+ return {sp .id : sp for sp in qs }
844+
845+ @cached_property
846+ def all_sponsorships (self ):
847+ qs = Sponsorship .objects .all ().select_related ("package" , "sponsor" )
848+ return {sp .id : sp for sp in qs }
849+
850+ def get_value (self , obj ):
851+ html = obj .value
852+ if obj .value and getattr (obj .value , "url" , None ):
853+ html = f"<a href='{ obj .value .url } ' target='_blank'>{ obj .value } </a>"
854+ return mark_safe (html )
855+
856+ get_value .short_description = "Value"
857+
858+ def get_related_object (self , obj ):
859+ """
860+ Returns the content_object as an URL and performs better because
861+ of sponsors and sponsorship cached properties
862+ """
863+ content_object = None
864+ if obj .from_sponsorship :
865+ content_object = self .all_sponsorships [obj .object_id ]
866+ elif obj .from_sponsor :
867+ content_object = self .all_sponsors [obj .object_id ]
868+
869+ if not content_object : # safety belt
870+ return obj .content_object
871+
872+ html = f"<a href='{ content_object .admin_url } ' target='_blank'>{ content_object } </a>"
873+ return mark_safe (html )
874+
875+ get_related_object .short_description = "Associated with"
876+
877+ def export_assets_as_zipfile (self , request , queryset ):
878+ return views_admin .export_assets_as_zipfile (self , request , queryset )
879+ export_assets_as_zipfile .short_description = "Export selected"
880+
881+
882+ class GenericAssetChildModelAdmin (PolymorphicChildModelAdmin ):
883+ """ Base admin class for all GenericAsset child models """
884+ base_model = GenericAsset
885+ readonly_fields = ["uuid" , "content_type" , "object_id" , "content_object" , "internal_name" ]
886+
887+
888+ @admin .register (TextAsset )
889+ class TextAssetModelAdmin (GenericAssetChildModelAdmin ):
890+ base_model = TextAsset
891+
892+
893+ @admin .register (ImgAsset )
894+ class ImgAssetModelAdmin (GenericAssetChildModelAdmin ):
895+ base_model = ImgAsset
896+
897+
898+ @admin .register (FileAsset )
899+ class ImgAssetModelAdmin (GenericAssetChildModelAdmin ):
900+ base_model = FileAsset
901+
902+
903+ @admin .register (ResponseAsset )
904+ class ResponseAssetModelAdmin (GenericAssetChildModelAdmin ):
905+ base_model = ResponseAsset
0 commit comments