diff --git a/src/olympia/abuse/actions.py b/src/olympia/abuse/actions.py index fc9bd9d2fd8..31f7cd0e2e6 100644 --- a/src/olympia/abuse/actions.py +++ b/src/olympia/abuse/actions.py @@ -8,6 +8,7 @@ from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ +from olympia.constants.promoted import HIGH_PROFILE, HIGH_PROFILE_RATING import waffle import olympia @@ -250,7 +251,7 @@ def should_hold_action(self): or self.target.groups_list # has any permissions # owns a high profile add-on or any( - addon.promoted_group().high_profile + addon.get(HIGH_PROFILE) for addon in self.target.addons.all() ) ) @@ -281,7 +282,7 @@ def should_hold_action(self): return bool( self.target.status != amo.STATUS_DISABLED # is a high profile add-on - and self.target.promoted_group().high_profile + and self.target.get(HIGH_PROFILE) ) def process_action(self): @@ -369,7 +370,7 @@ def should_hold_action(self): return bool( not self.target.deleted and self.target.reply_to - and self.target.addon.promoted_group().high_profile_rating + and self.target.addon.get(HIGH_PROFILE_RATING) ) def process_action(self): diff --git a/src/olympia/abuse/cinder.py b/src/olympia/abuse/cinder.py index 5660bd90c64..0dae13c3ca2 100644 --- a/src/olympia/abuse/cinder.py +++ b/src/olympia/abuse/cinder.py @@ -331,7 +331,7 @@ def get_attributes(self): # promoted in any way, but we don't care about the promotion being # approved for the current version, it would make more queries and it's # not useful for moderation purposes anyway. - promoted_group = self.addon.promoted_group(currently_approved=False) + group_name = self.addon.group_name(currently_approved=False) data = { 'id': self.id, 'average_daily_users': self.addon.average_daily_users, @@ -341,7 +341,7 @@ def get_attributes(self): 'name': self.get_str(self.addon.name), 'slug': self.addon.slug, 'summary': self.get_str(self.addon.summary), - 'promoted': self.get_str(promoted_group.name if promoted_group else ''), + 'promoted': self.get_str(group_name), } return data diff --git a/src/olympia/addons/indexers.py b/src/olympia/addons/indexers.py index bfbf7d37503..2d9dc8b51c0 100644 --- a/src/olympia/addons/indexers.py +++ b/src/olympia/addons/indexers.py @@ -662,7 +662,7 @@ def extract_document(cls, obj): data['has_privacy_policy'] = bool(obj.privacy_policy) data['is_recommended'] = bool( - obj.promoted and obj.promoted.group == RECOMMENDED + RECOMMENDED in obj.promoted_group() ) data['previews'] = [ @@ -677,10 +677,10 @@ def extract_document(cls, obj): data['promoted'] = ( { - 'group_id': obj.promoted.group_id, + 'group_ids': obj.group_ids, # store the app approvals because .approved_applications needs it. 'approved_for_apps': [ - app.id for app in obj.promoted.approved_applications + app.id for app in obj.approved_applications ], } if obj.promoted diff --git a/src/olympia/addons/models.py b/src/olympia/addons/models.py index 8125e9ce767..acdf277407d 100644 --- a/src/olympia/addons/models.py +++ b/src/olympia/addons/models.py @@ -57,7 +57,7 @@ ) from olympia.constants.browsers import BROWSERS from olympia.constants.categories import CATEGORIES_BY_ID -from olympia.constants.promoted import NOT_PROMOTED, RECOMMENDED +from olympia.constants.promoted import CAN_BE_COMPATIBLE_WITH_ALL_FENIX_VERSIONS, NOT_PROMOTED, RECOMMENDED, PromotedClass from olympia.constants.reviewers import REPUTATION_CHOICES from olympia.files.models import File from olympia.files.utils import extract_translations, resolve_i18n_message @@ -279,7 +279,6 @@ def get_base_queryset_for_queue( select_related_fields = [ 'reviewerflags', 'addonapprovalscounter', - 'promotedaddon', ] if select_related_fields_for_listed: # Most listed queues need these to avoid extra queries because @@ -1556,8 +1555,7 @@ def _is_recommended_theme(self): def promoted_group(self, *, currently_approved=True): """Is the addon currently promoted for the current applications? - Returns the group constant, or NOT_PROMOTED (which is falsey) - otherwise. + Returns the list of group constants. `currently_approved=True` means only returns True if self.current_version is approved for the current promotion & apps. @@ -1567,22 +1565,72 @@ def promoted_group(self, *, currently_approved=True): from olympia.promoted.models import PromotedAddon try: - promoted = self.promotedaddon + promoted_addons = self.promoted_addons.all() except PromotedAddon.DoesNotExist: - return NOT_PROMOTED - is_promoted = not currently_approved or promoted.approved_applications - return promoted.group if is_promoted else NOT_PROMOTED + return [] + + return [promoted.group for promoted in promoted_addons if not currently_approved or promoted.approved_applications] + + def group_name(self, *, currently_approved=True): + """ Returns the string name of the currently groups, comma separated. + + `currently_approved=True` means only returns True if + self.current_version is approved for the current promotion & apps. + If currently_approved=False then promotions where there isn't approval + are returned too. + """ + groups = self.promoted_group(currently_approved=currently_approved) + return ', '.join(group.name for group in groups) + + def get(self, permission, currently_approved=True): + """ Fetch the given permission. + + Based on the type of the permission, returns -- + Bool -> If any group is true + Int -> The maximum value from the groups + Dict -> return the first truthy value, or {} if none. + + `currently_approved=True` means only returns True if + self.current_version is approved for the current promotion & apps. + If currently_approved=False then promotions where there isn't approval + are returned too. + """ + groups = self.promoted_group(currently_approved=currently_approved) + type = PromotedClass.type(permission) + + if type == int: + return max(getattr(group, permission) for group in groups if getattr(group, permission) is not None) + if type == bool: + return any(getattr(group, permission, False) for group in groups) + + for group in groups: + value = getattr(group, permission, None) + if value: + return value + return {} + + @property + def group_ids(self): + groups = self.promoted_group() + return [group.id for group in groups] + + @property + def approved_applications(self): + approved_apps = set() + for promoted in self.promoted_addons.all(): + approved_apps.update(promoted.approved_applications) + return approved_apps @cached_property def promoted(self): promoted_group = self.promoted_group() if promoted_group: - return self.promotedaddon + return self.promoted_addons else: from olympia.promoted.models import PromotedTheme if self._is_recommended_theme(): - return PromotedTheme(addon=self, group_id=RECOMMENDED.id) + return [PromotedTheme(addon=self, group_id=RECOMMENDED.id)] return None @cached_property @@ -1608,7 +1656,7 @@ def can_be_compatible_with_all_fenix_versions(self): versions (i.e. it's a recommended/line extension for Android).""" return ( self.promoted - and self.promoted.group.can_be_compatible_with_all_fenix_versions + and self.get(CAN_BE_COMPATIBLE_WITH_ALL_FENIX_VERSIONS) and amo.ANDROID in self.promoted.approved_applications ) diff --git a/src/olympia/addons/serializers.py b/src/olympia/addons/serializers.py index 312c8523299..89ddf2a6211 100644 --- a/src/olympia/addons/serializers.py +++ b/src/olympia/addons/serializers.py @@ -513,12 +513,12 @@ def validate_is_disabled(self, disable): ): raise exceptions.ValidationError(gettext('File is already disabled.')) if not version.can_be_disabled_and_deleted(): - group = version.addon.promoted_group() + name = version.addon.group_name() msg = gettext( 'The latest approved version of this %s add-on cannot be deleted ' 'because the previous version was not approved for %s promotion. ' 'Please contact AMO Admins if you need help with this.' - ) % (group.name, group.name) + ) % (name, name) raise exceptions.ValidationError(msg) return disable @@ -1562,12 +1562,13 @@ def fake_object(self, data): # set .approved_for_groups cached_property because it's used in # .approved_applications. approved_for_apps = promoted.get('approved_for_apps') - obj.promoted = PromotedAddon( - addon=obj, - approved_application_ids=approved_for_apps, - created=None, - group_id=promoted['group_id'], - ) + for group_id in promoted['group_ids']: + obj.promoted = PromotedAddon( + addon=obj, + approved_application_ids=approved_for_apps, + created=None, + group_id=group_id, + ) # we can safely regenerate these tuples because # .appproved_applications only cares about the current group obj._current_version.approved_for_groups = ( diff --git a/src/olympia/addons/tests/test_indexers.py b/src/olympia/addons/tests/test_indexers.py index 83a7a355c18..6a9ced51cad 100644 --- a/src/olympia/addons/tests/test_indexers.py +++ b/src/olympia/addons/tests/test_indexers.py @@ -513,7 +513,7 @@ def test_extract_promoted(self): self.addon = addon_factory(promoted=RECOMMENDED) extracted = self._extract() assert extracted['promoted'] - assert extracted['promoted']['group_id'] == RECOMMENDED.id + assert RECOMMENDED.id in extracted['promoted']['group_ids'] assert extracted['promoted']['approved_for_apps'] == [ amo.FIREFOX.id, amo.ANDROID.id, @@ -521,7 +521,7 @@ def test_extract_promoted(self): assert extracted['is_recommended'] is True # Specific application. - self.addon.promotedaddon.update(application_id=amo.FIREFOX.id) + self.addon.promoted_addons.first().update(application_id=amo.FIREFOX.id) extracted = self._extract() assert extracted['promoted']['approved_for_apps'] == [amo.FIREFOX.id] assert extracted['is_recommended'] is True @@ -534,7 +534,7 @@ def test_extract_promoted(self): featured_collection.add_addon(self.addon) extracted = self._extract() assert extracted['promoted'] - assert extracted['promoted']['group_id'] == RECOMMENDED.id + assert RECOMMENDED.id in extracted['promoted']['group_ids'] assert extracted['promoted']['approved_for_apps'] == [ amo.FIREFOX.id, amo.ANDROID.id, diff --git a/src/olympia/addons/tests/test_models.py b/src/olympia/addons/tests/test_models.py index a6c7a17e680..acd7597e2ed 100644 --- a/src/olympia/addons/tests/test_models.py +++ b/src/olympia/addons/tests/test_models.py @@ -1727,34 +1727,31 @@ def test_promoted_group(self): addon = addon_factory() # default case - no group so not recommended assert not addon.promoted_group() - # NOT_PROMOTED is falsey - assert addon.promoted_group() == NOT_PROMOTED assert not addon.promoted_group(currently_approved=False) # It's promoted but nothing has been approved promoted = PromotedAddon.objects.create(addon=addon, group_id=LINE.id) assert addon.promoted_group(currently_approved=False) - assert addon.promoted_group() == NOT_PROMOTED assert not addon.promoted_group() # The latest version is approved for the same group. promoted.approve_for_version(version=addon.current_version) assert addon.promoted_group() - assert addon.promoted_group() == LINE + assert LINE in addon.promoted_group() # if the group has changes the approval for the current version isn't # valid promoted.update(group_id=SPOTLIGHT.id) assert not addon.promoted_group() assert addon.promoted_group(currently_approved=False) - assert addon.promoted_group(currently_approved=False) == SPOTLIGHT + assert SPOTLIGHT in addon.promoted_group(currently_approved=False) promoted.approve_for_version(version=addon.current_version) - assert addon.promoted_group() == SPOTLIGHT + assert SPOTLIGHT in addon.promoted_group() # Application specific group membership should work too # if no app is specifed in the PromotedAddon everything should match - assert addon.promoted_group() == SPOTLIGHT + assert SPOTLIGHT in addon.promoted_group() # update to mobile app promoted.update(application_id=amo.ANDROID.id) assert addon.promoted_group() @@ -1765,14 +1762,13 @@ def test_promoted_group(self): del addon.current_version.approved_for_groups assert not addon.promoted_group() promoted.update(application_id=amo.FIREFOX.id) - assert addon.promoted_group() == SPOTLIGHT + assert SPOTLIGHT in addon.promoted_group() # check it doesn't error if there's no current_version addon.current_version.file.update(status=amo.STATUS_DISABLED) addon.update_version() assert not addon.current_version assert not addon.promoted_group() - assert addon.promoted_group() == NOT_PROMOTED assert addon.promoted_group(currently_approved=False) def test_promoted(self): @@ -1821,7 +1817,6 @@ def test_promoted_theme(self): featured_collection.remove_addon(addon) del addon.promoted addon = Addon.objects.get(id=addon.id) - # assert not addon.promotedaddon # but not when it's removed. assert addon.promoted is None diff --git a/src/olympia/addons/tests/test_serializers.py b/src/olympia/addons/tests/test_serializers.py index 3672f4aef69..bfcd8653b3e 100644 --- a/src/olympia/addons/tests/test_serializers.py +++ b/src/olympia/addons/tests/test_serializers.py @@ -552,7 +552,7 @@ def test_promoted(self): assert result['promoted']['apps'] == [amo.FIREFOX.short] # With a recommended theme. - self.addon.promotedaddon.delete() + self.addon.promoted_addons.all().delete() self.addon.update(type=amo.ADDON_STATICTHEME) featured_collection, _ = Collection.objects.get_or_create( id=settings.COLLECTION_FEATURED_THEMES_ID diff --git a/src/olympia/addons/tests/test_views.py b/src/olympia/addons/tests/test_views.py index bfa87bfe237..ea7e5d34431 100644 --- a/src/olympia/addons/tests/test_views.py +++ b/src/olympia/addons/tests/test_views.py @@ -5610,7 +5610,7 @@ def test_filter_by_featured_no_app_no_lang(self): slug='my-addon', name='Featured Addôn', promoted=RECOMMENDED ) addon_factory(slug='other-addon', name='Other Addôn') - assert addon.promoted_group() == RECOMMENDED + assert RECOMMENDED in addon.promoted_group() self.reindex(Addon) data = self.perform_search(self.url, {'featured': 'true'}) @@ -5633,9 +5633,9 @@ def test_filter_by_promoted(self): min=av_min, max=av_max, ) - assert addon.promoted_group() == RECOMMENDED - assert addon.promotedaddon.application_id is None # i.e. all - assert addon.promotedaddon.approved_applications == [amo.FIREFOX, amo.ANDROID] + assert RECOMMENDED in addon.promoted_group() + assert addon.promoted_addons.first().application_id is None # i.e. all + assert addon.approved_applications == [amo.FIREFOX, amo.ANDROID] addon2 = addon_factory(name='Fírefox Addôn', promoted=RECOMMENDED) ApplicationsVersions.objects.get_or_create( @@ -5645,10 +5645,11 @@ def test_filter_by_promoted(self): max=av_max, ) # This case is approved for all apps, but now only set for Firefox - addon2.promotedaddon.update(application_id=amo.FIREFOX.id) - assert addon2.promoted_group() == RECOMMENDED - assert addon2.promotedaddon.application_id is amo.FIREFOX.id - assert addon2.promotedaddon.approved_applications == [amo.FIREFOX] + addon2.promoted_addons.first().update(application_id=amo.FIREFOX.id) + assert RECOMMENDED in addon2.promoted_group() + assert addon2.promoted_addons.first().application_id is amo.FIREFOX.id + assert addon2.promoted_addons.first().approved_applications == [amo.FIREFOX] + assert addon2.approved_applications == [amo.FIREFOX] addon3 = addon_factory(slug='other-addon', name='Other Addôn') ApplicationsVersions.objects.get_or_create( @@ -5668,12 +5669,12 @@ def test_filter_by_promoted(self): max=av_max, ) self.make_addon_promoted(addon4, RECOMMENDED) - addon4.promotedaddon.update(application_id=amo.FIREFOX.id) - addon4.promotedaddon.approve_for_version(addon4.current_version) - addon4.promotedaddon.update(application_id=None) - assert addon4.promoted_group() == RECOMMENDED - assert addon4.promotedaddon.application_id is None # i.e. all - assert addon4.promotedaddon.approved_applications == [amo.FIREFOX] + addon4.promoted_addons.first().update(application_id=amo.FIREFOX.id) + addon4.promoted_addons.first().approve_for_version(addon4.current_version) + addon4.promoted_addons.first().update(application_id=None) + assert RECOMMENDED in addon4.promoted_group() + assert addon4.promoted_addons.first().application_id is None # i.e. all + assert addon4.promoted_addons.first().approved_applications == [amo.FIREFOX] # And repeat with Android rather than Firefox addon5 = addon_factory(name='Andróid Addôn') @@ -5684,12 +5685,12 @@ def test_filter_by_promoted(self): max=av_max, ) self.make_addon_promoted(addon5, RECOMMENDED) - addon5.promotedaddon.update(application_id=amo.ANDROID.id) - addon5.promotedaddon.approve_for_version(addon5.current_version) - addon5.promotedaddon.update(application_id=None) - assert addon5.promoted_group() == RECOMMENDED - assert addon5.promotedaddon.application_id is None # i.e. all - assert addon5.promotedaddon.approved_applications == [amo.ANDROID] + addon5.promoted_addons.first().update(application_id=amo.ANDROID.id) + addon5.promoted_addons.first().approve_for_version(addon5.current_version) + addon5.promoted_addons.first().update(application_id=None) + assert RECOMMENDED in addon5.promoted_group() + assert addon5.promoted_addons.first().application_id is None # i.e. all + assert addon5.promoted_addons.first().approved_applications == [amo.ANDROID] self.reindex(Addon) @@ -6555,8 +6556,8 @@ def tearDown(self): def test_basic(self): addon1 = addon_factory(promoted=RECOMMENDED) addon2 = addon_factory(promoted=RECOMMENDED) - assert addon1.promoted_group() == RECOMMENDED - assert addon2.promoted_group() == RECOMMENDED + assert RECOMMENDED in addon1.promoted_group() + assert RECOMMENDED in addon2.promoted_group() addon_factory() # not recommended so shouldn't show up self.refresh() diff --git a/src/olympia/addons/views.py b/src/olympia/addons/views.py index 1efd7910bb5..6a5b7d43eed 100644 --- a/src/olympia/addons/views.py +++ b/src/olympia/addons/views.py @@ -732,12 +732,12 @@ def update(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs): instance = self.get_object() if not instance.can_be_disabled_and_deleted(): - group = self.get_addon_object().promoted_group() + name = self.get_addon_object().group_name() msg = gettext( 'The latest approved version of this %s add-on cannot be deleted ' 'because the previous version was not approved for %s promotion. ' 'Please contact AMO Admins if you need help with this.' - ) % (group.name, group.name) + ) % (name, name) raise exceptions.ValidationError(msg) return super().destroy(request, *args, **kwargs) diff --git a/src/olympia/amo/tests/__init__.py b/src/olympia/amo/tests/__init__.py index b1bb0d50af3..a411697843c 100644 --- a/src/olympia/amo/tests/__init__.py +++ b/src/olympia/amo/tests/__init__.py @@ -590,7 +590,7 @@ def make_addon_promoted(cls, addon, group, approve_version=False): if approve_version: obj.approve_for_version(addon.current_version) if not created: - addon.promotedaddon.reload() + addon.promoted_addons.first().reload() return obj def _add_fake_throttling_action( @@ -969,7 +969,7 @@ def version_factory(*, file_kw=None, needshumanreview_kw=None, **kw): file_kw = file_kw or {} file_factory(version=ver, **file_kw) if promotion_approved: - kw['addon'].promotedaddon.approve_for_version(version=ver) + kw['addon'].promoted_addons.first().approve_for_version(version=ver) av_min, _ = AppVersion.objects.get_or_create( application=application, version=min_app_version ) diff --git a/src/olympia/bandwagon/views.py b/src/olympia/bandwagon/views.py index 23e98c80f07..629ca8e81ef 100644 --- a/src/olympia/bandwagon/views.py +++ b/src/olympia/bandwagon/views.py @@ -201,7 +201,7 @@ def get_queryset(self): qs = ( CollectionAddon.objects.filter(collection=self.get_collection()) .prefetch_related( - 'addon__promotedaddon', + 'addon__promoted_addons', 'addon___current_version__file___webext_permissions', ) .transform(self._transformer) diff --git a/src/olympia/constants/promoted.py b/src/olympia/constants/promoted.py index f9d90e87030..5a3032541ea 100644 --- a/src/olympia/constants/promoted.py +++ b/src/olympia/constants/promoted.py @@ -4,44 +4,58 @@ from olympia.constants import applications - -_PromotedSuperClass = namedtuple( - '_PromotedSuperClass', - [ - # Be careful when adding to this list to adjust defaults too. - 'id', - 'name', - 'api_name', - 'search_ranking_bump', - 'listed_pre_review', - 'unlisted_pre_review', - 'admin_review', - 'badged', # See BADGE_CATEGORIES in frontend too: both need changing - 'autograph_signing_states', - 'can_primary_hero', # can be added to a primary hero shelf - 'immediate_approval', # will addon be auto-approved once added - 'flag_for_human_review', # will be add-on be flagged for another review - 'can_be_compatible_with_all_fenix_versions', # If addon is promoted for Android - 'high_profile', # the add-on is considered high-profile for review purposes - 'high_profile_rating', # developer replies are considered high-profile - ], - defaults=( - # "Since fields with a default value must come after any fields without - # a default, the defaults are applied to the rightmost parameters" - # No defaults for: id, name, api_name. +ID = 'id' +NAME = 'name' +API_NAME = 'api_name' +SEARCH_RANKING_BUMP = 'search_ranking_bump' +LISTED_PRE_REVIEW = 'listed_pre_review' +UNLISTED_PRE_REVIEW = 'unlisted_pre_review' +ADMIN_REVIEW = 'admin_review' +BADGED = 'badged' +AUTOGRAPH_SIGNING_STATES = 'autograph_signing_states' +CAN_PRIMARY_HERO = 'can_primary_hero' +IMMEDIATE_APPROVAL = 'immediate_approval' +FLAG_FOR_HUMAN_REVIEW = 'flag_for_human_review' +CAN_BE_COMPATIBLE_WITH_ALL_FENIX_VERSIONS = 'can_be_compatible_with_all_fenix_versions' +HIGH_PROFILE = 'high_profile' +HIGH_PROFILE_RATING = 'high_profile_rating' + +defaults=( 0.0, # search_ranking_bump False, # listed_pre_review False, # unlisted_pre_review False, # admin_review False, # badged - {}, # autograph_signing_states - should be a dict of App.short: state + {}, # autograph_signing_states False, # can_primary_hero False, # immediate_approval False, # flag_for_human_review False, # can_be_compatible_with_all_fenix_versions False, # high_profile False, # high_profile_rating - ), + ) + +_PromotedSuperClass = namedtuple( + '_PromotedSuperClass', + [ + # Be careful when adding to this list to adjust defaults too. + ID, + NAME, + API_NAME, + SEARCH_RANKING_BUMP, + LISTED_PRE_REVIEW, + UNLISTED_PRE_REVIEW, + ADMIN_REVIEW, + BADGED, # See BADGE_CATEGORIES in frontend too: both need changing + AUTOGRAPH_SIGNING_STATES, + CAN_PRIMARY_HERO, # can be added to a primary hero shelf + IMMEDIATE_APPROVAL, # will addon be auto-approved once added + FLAG_FOR_HUMAN_REVIEW, # will be add-on be flagged for another review + CAN_BE_COMPATIBLE_WITH_ALL_FENIX_VERSIONS, # If addon is promoted for Android + HIGH_PROFILE, # the add-on is considered high-profile for review purposes + HIGH_PROFILE_RATING # developer replies are considered high-profile + ], + defaults=defaults, ) @@ -50,6 +64,14 @@ class PromotedClass(_PromotedSuperClass): def __bool__(self): return bool(self.id) + + @classmethod + def type(cls, attribute): + try: + index = cls._fields.index(attribute) + return type(defaults[index]) + except ValueError: + raise AttributeError(f"{attribute} is not a valid parameter.") NOT_PROMOTED = PromotedClass( diff --git a/src/olympia/devhub/templates/devhub/includes/addon_details.html b/src/olympia/devhub/templates/devhub/includes/addon_details.html index 2815c98a8e0..2efdfd9e565 100644 --- a/src/olympia/devhub/templates/devhub/includes/addon_details.html +++ b/src/olympia/devhub/templates/devhub/includes/addon_details.html @@ -6,9 +6,8 @@ {% set status_text = _('Invisible') %} {% set tooltip_text = status_tips['invisible'] %} {% else %} - {% set promoted_group = addon.promoted_group() %} - {% if addon.status == amo.STATUS_APPROVED and promoted_group.badged %} - {% set status_text = _('Approved and %s' % promoted_group.name) %} + {% if addon.status == amo.STATUS_APPROVED and addon.get('badged') %} + {% set status_text = _('Approved and %s' % addon.group_name()) %} {% else %} {% set status_text = addon.get_status_display() %} {% endif %} diff --git a/src/olympia/devhub/templates/devhub/new-landing/components/my-addons.html b/src/olympia/devhub/templates/devhub/new-landing/components/my-addons.html index 443d32c234d..40c3592b53c 100644 --- a/src/olympia/devhub/templates/devhub/new-landing/components/my-addons.html +++ b/src/olympia/devhub/templates/devhub/new-landing/components/my-addons.html @@ -1,9 +1,8 @@ {% macro render_status(addon) %} - {% set promoted_group = addon.promoted_group() %} {% if addon.status != amo.STATUS_DISABLED and addon.disabled_by_user %} {% set status_text = _('Invisible') %} - {% elif addon.status == amo.STATUS_APPROVED and promoted_group.badged %} - {% set status_text = _('Approved and %s' % promoted_group.name) %} + {% elif addon.status == amo.STATUS_APPROVED and addon.get('badged') %} + {% set status_text = _('Approved and %s' % addon.group_name()) %} {% else %} {% set status_text = addon.get_status_display() %} {% endif %} diff --git a/src/olympia/devhub/templates/devhub/versions/list.html b/src/olympia/devhub/templates/devhub/versions/list.html index b1017762565..84255bf9108 100644 --- a/src/olympia/devhub/templates/devhub/versions/list.html +++ b/src/olympia/devhub/templates/devhub/versions/list.html @@ -297,7 +297,7 @@

{% else %} {% if addon.type in (amo.ADDON_EXTENSION, amo.ADDON_LPAPP, amo.ADDON_DICT) %} - {% if channel == amo.CHANNEL_LISTED and addon.promoted_group(currently_approved=False).listed_pre_review %} + {% if channel == amo.CHANNEL_LISTED and addon.get('listed_pre_review', currently_approved=False) %}
  • @@ -382,7 +382,7 @@

    data-toggle-button-selector="#disable_auto_approval_until_next_approval" id="enable_auto_approval_until_next_approval" class="toggle" type="button">Enable Listed Auto-Approval Before Next Manual Approval - {% elif channel == amo.CHANNEL_UNLISTED and addon.promoted_group(currently_approved=False).unlisted_pre_review %} + {% elif channel == amo.CHANNEL_UNLISTED and addon.get('unlisted_pre_review', currently_approved=False) %}
  • diff --git a/src/olympia/reviewers/tests/test_utils.py b/src/olympia/reviewers/tests/test_utils.py index e306f0296dd..a093c020255 100644 --- a/src/olympia/reviewers/tests/test_utils.py +++ b/src/olympia/reviewers/tests/test_utils.py @@ -1940,8 +1940,8 @@ def test_confirm_auto_approved_approves_for_promoted(self): self.helper.handler.confirm_auto_approved() self.addon.reload() - self.addon.promotedaddon.reload() - assert self.addon.promoted_group() == NOTABLE, self.addon.promotedaddon + self.addon.promoted_addons.first().reload() + assert NOTABLE in self.addon.promoted_group(), self.addon.promoted_addons assert self.review_version.reload().approved_for_groups == [ (NOTABLE, amo.FIREFOX), (NOTABLE, amo.ANDROID), @@ -3042,7 +3042,7 @@ def test_nominated_to_approved_recommended(self): assert self.addon.current_version.promoted_approvals.filter( group_id=RECOMMENDED.id ).exists() - assert self.addon.promoted_group() == RECOMMENDED + assert RECOMMENDED in self.addon.promoted_group() def test_nominated_to_approved_other_promoted(self): self.make_addon_promoted(self.addon, LINE) @@ -3051,7 +3051,7 @@ def test_nominated_to_approved_other_promoted(self): assert self.addon.current_version.promoted_approvals.filter( group_id=LINE.id ).exists() - assert self.addon.promoted_group() == LINE + assert LINE in self.addon.promoted_group() def test_approved_update_recommended(self): self.make_addon_promoted(self.addon, RECOMMENDED) @@ -3060,7 +3060,7 @@ def test_approved_update_recommended(self): assert self.addon.current_version.promoted_approvals.filter( group_id=RECOMMENDED.id ).exists() - assert self.addon.promoted_group() == RECOMMENDED + assert RECOMMENDED in self.addon.promoted_group() def test_approved_update_other_promoted(self): self.make_addon_promoted(self.addon, LINE) @@ -3069,7 +3069,7 @@ def test_approved_update_other_promoted(self): assert self.addon.current_version.promoted_approvals.filter( group_id=LINE.id ).exists() - assert self.addon.promoted_group() == LINE + assert LINE in self.addon.promoted_group() def test_autoapprove_fails_for_promoted(self): self.make_addon_promoted(self.addon, RECOMMENDED) @@ -3084,7 +3084,7 @@ def test_autoapprove_fails_for_promoted(self): assert not self.addon.promoted_group() # change to other type of promoted; same should happen - self.addon.promotedaddon.update(group_id=LINE.id) + self.addon.promoted_addons.first().update(group_id=LINE.id) with self.assertRaises(AssertionError): self.test_nomination_to_public() assert not PromotedApproval.objects.filter( @@ -3093,14 +3093,14 @@ def test_autoapprove_fails_for_promoted(self): assert not self.addon.promoted_group() # except for a group that doesn't require prereview - self.addon.promotedaddon.update(group_id=STRATEGIC.id) - assert self.addon.promoted_group() == STRATEGIC + self.addon.promoted_addons.first().update(group_id=STRATEGIC.id) + assert STRATEGIC in self.addon.promoted_group() self.test_nomination_to_public() # But no promotedapproval though assert not PromotedApproval.objects.filter( version=self.addon.current_version ).exists() - assert self.addon.promoted_group() == STRATEGIC + assert STRATEGIC in self.addon.promoted_group() def _test_block_multiple_unlisted_versions(self, redirect_url): old_version = self.review_version @@ -3775,7 +3775,7 @@ def test_nominated_to_public_recommended(self): assert self.addon.current_version.promoted_approvals.filter( group_id=RECOMMENDED.id ).exists() - assert self.addon.promoted_group() == RECOMMENDED + assert RECOMMENDED in self.addon.promoted_group() signature_info, manifest = _get_signature_details(self.file.file.path) diff --git a/src/olympia/reviewers/utils.py b/src/olympia/reviewers/utils.py index ba3cff911fa..cf4ed082097 100644 --- a/src/olympia/reviewers/utils.py +++ b/src/olympia/reviewers/utils.py @@ -20,7 +20,7 @@ from olympia.activity.utils import notify_about_activity_log from olympia.addons.models import Addon, AddonApprovalsCounter, AddonReviewerFlags from olympia.constants.abuse import DECISION_ACTIONS -from olympia.constants.promoted import RECOMMENDED +from olympia.constants.promoted import ADMIN_REVIEW, LISTED_PRE_REVIEW, RECOMMENDED, UNLISTED_PRE_REVIEW from olympia.files.models import File from olympia.lib.crypto.signing import sign_file from olympia.reviewers.models import ( @@ -392,7 +392,6 @@ def get_actions(self): self.version and self.version.channel == amo.CHANNEL_UNLISTED ) version_is_listed = self.version and self.version.channel == amo.CHANNEL_LISTED - promoted_group = self.addon.promoted_group(currently_approved=False) is_static_theme = self.addon.type == amo.ADDON_STATICTHEME # Default permissions / admin needed values if it's just a regular @@ -402,13 +401,13 @@ def get_actions(self): is_admin_needed = is_admin_needed_post_review = False # More complex/specific cases. - if promoted_group == RECOMMENDED: + if RECOMMENDED in self.addon.promoted_group(): permission = amo.permissions.ADDONS_RECOMMENDED_REVIEW permission_post_review = permission elif version_is_unlisted: permission = amo.permissions.ADDONS_REVIEW_UNLISTED permission_post_review = permission - elif promoted_group.admin_review: + elif self.addon.get(ADMIN_REVIEW, currently_approved=False): is_admin_needed = is_admin_needed_post_review = True elif self.content_review: permission = amo.permissions.ADDONS_CONTENT_REVIEW @@ -495,7 +494,7 @@ def get_actions(self): if version_is_unlisted: can_reject_multiple = is_appropriate_reviewer can_approve_multiple = is_appropriate_reviewer - elif self.content_review or promoted_group.listed_pre_review or is_static_theme: + elif self.content_review or self.addon.get(LISTED_PRE_REVIEW, currently_approved=False) or is_static_theme: can_reject_multiple = ( addon_is_valid_and_version_is_listed and is_appropriate_reviewer ) @@ -733,7 +732,7 @@ def get_actions(self): 'available': ( self.version is not None and is_reviewer - and (not promoted_group.admin_review or is_appropriate_reviewer) + and (not self.addon.get('admin_review', currently_approved=False) or is_appropriate_reviewer) ), 'allows_reasons': not is_static_theme, 'requires_reasons': False, @@ -897,21 +896,17 @@ def set_file(self, status, file): file.save() def set_promoted(self, versions=None): - group = self.addon.promoted_group(currently_approved=False) if versions is None: versions = [self.version] elif not versions: return channel = versions[0].channel - if group and ( - (channel == amo.CHANNEL_LISTED and group.listed_pre_review) - or (channel == amo.CHANNEL_UNLISTED and group.unlisted_pre_review) - ): + if (channel == amo.CHANNEL_LISTED and self.addon.get(LISTED_PRE_REVIEW, currently_approved=False)) or (channel == amo.CHANNEL_UNLISTED and self.addon.get(UNLISTED_PRE_REVIEW, currently_approved=False)): # These addons shouldn't be be attempted for auto approval anyway, # but double check that the cron job isn't trying to approve it. assert not self.user.id == settings.TASK_USER_ID for version in versions: - self.addon.promotedaddon.approve_for_version(version) + self.addon.promoted_addons.first().approve_for_version(version) def notify_decision(self): if cinder_jobs := self.data.get('cinder_jobs_to_resolve', ()): diff --git a/src/olympia/versions/management/commands/force_max_android_compatibility.py b/src/olympia/versions/management/commands/force_max_android_compatibility.py index d6b2fb8d019..98e384b3102 100644 --- a/src/olympia/versions/management/commands/force_max_android_compatibility.py +++ b/src/olympia/versions/management/commands/force_max_android_compatibility.py @@ -47,12 +47,12 @@ def handle(self, *args, **kwargs): .filter(version__channel=amo.CHANNEL_LISTED) .filter( # They need to be either: - Q(version__addon__promotedaddon__isnull=True) # Not promoted at all + Q(version__addon__promoted_addons__isnull=True) # Not promoted at all | ~Q( - version__addon__promotedaddon__group_id__in=promoted_groups_ids + version__addon__promoted_addons__group_id__in=promoted_groups_ids ) # Promoted, but not for line / recommended | Q( - version__addon__promotedaddon__application_id=amo.FIREFOX.id + version__addon__promoted_addons__application_id=amo.FIREFOX.id ) # Promoted, but for Firefox only (not Android / not both) ) # If they are already marked as compatible with GA version, we diff --git a/src/olympia/versions/models.py b/src/olympia/versions/models.py index 107abe00c55..dea7b57dde0 100644 --- a/src/olympia/versions/models.py +++ b/src/olympia/versions/models.py @@ -44,6 +44,8 @@ from olympia.constants.applications import APP_IDS from olympia.constants.licenses import CC_LICENSES, FORM_LICENSES, LICENSES_BY_BUILTIN from olympia.constants.promoted import ( + BADGED, + LISTED_PRE_REVIEW, PROMOTED_GROUPS_BY_ID, ) from olympia.constants.scanners import MAD @@ -1072,8 +1074,8 @@ def can_be_disabled_and_deleted(self): from olympia.promoted.models import PromotedApproval if self != self.addon.current_version or ( - not (group := self.addon.promoted_group()) - or not (group.badged and group.listed_pre_review) + not self.addon.promoted_group() + or not (self.addon.get(BADGED) and self.addon.get(LISTED_PRE_REVIEW)) ): return True diff --git a/src/olympia/versions/tests/test_models.py b/src/olympia/versions/tests/test_models.py index 9839389a1b8..8ef5df0f5fb 100644 --- a/src/olympia/versions/tests/test_models.py +++ b/src/olympia/versions/tests/test_models.py @@ -1262,7 +1262,7 @@ def test_promoted_can_be_disabled_and_deleted(self): self.make_addon_promoted(addon, RECOMMENDED, approve_version=True) addon = addon.reload() - assert addon.promoted_group() == RECOMMENDED + assert RECOMMENDED in addon.promoted_group() # But a promoted one, that's in a prereview group, can't be disabled assert not addon.current_version.can_be_disabled_and_deleted() @@ -1305,34 +1305,34 @@ def test_promoted_can_be_disabled_and_deleted(self): assert version_b.can_be_disabled_and_deleted() assert version_c.can_be_disabled_and_deleted() assert version_d.can_be_disabled_and_deleted() - assert addon.promoted_group() == RECOMMENDED + assert RECOMMENDED in addon.promoted_group() # now un-approve version_b version_b.promoted_approvals.update(group_id=NOT_PROMOTED.id) assert version_a.can_be_disabled_and_deleted() assert version_b.can_be_disabled_and_deleted() assert version_c.can_be_disabled_and_deleted() assert not version_d.can_be_disabled_and_deleted() - assert addon.promoted_group() == RECOMMENDED + assert RECOMMENDED in addon.promoted_group() def test_unbadged_non_prereview_promoted_can_be_disabled_and_deleted(self): addon = Addon.objects.get(id=3615) self.make_addon_promoted(addon, LINE, approve_version=True) - assert addon.promoted_group() == LINE + assert LINE in addon.promoted_group() # it's the only version of a group that requires pre-review and is # badged, so can't be deleted. assert not addon.current_version.can_be_disabled_and_deleted() # STRATEGIC isn't pre-reviewd or badged, so it's okay though - addon.promotedaddon.update(group_id=STRATEGIC.id) + addon.promoted_addons.first().update(group_id=STRATEGIC.id) addon.current_version.promoted_approvals.update(group_id=STRATEGIC.id) del addon.current_version.approved_for_groups - assert addon.promoted_group() == STRATEGIC + assert STRATEGIC in addon.promoted_group() assert addon.current_version.can_be_disabled_and_deleted() # SPOTLIGHT is pre-reviewed but not badged, so it's okay too - addon.promotedaddon.update(group_id=SPOTLIGHT.id) + addon.promoted_addons.first().update(group_id=SPOTLIGHT.id) addon.current_version.promoted_approvals.update(group_id=SPOTLIGHT.id) - assert addon.promoted_group() == SPOTLIGHT + assert SPOTLIGHT in addon.promoted_group() assert addon.current_version.can_be_disabled_and_deleted() def test_can_be_disabled_and_deleted_querycount(self):