Skip to content

Commit

Permalink
Add PromotedGroup model to replace legacy PromotedClass
Browse files Browse the repository at this point in the history
- Create new PromotedGroup model with comprehensive fields for promotion rules
- Add migration to populate PromotedGroup from existing constants
- Implement test coverage to ensure data integrity and migration correctness
  • Loading branch information
KevinMind committed Feb 4, 2025
1 parent e868087 commit 981884d
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/olympia/constants/promoted.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ def __bool__(self):

# _VERIFIED and _SPONSORED should not be included, they are no longer valid
# promoted groups.
# This data should be kept in sync with the new PromotedGroup model.
# If this list changes, we should update the relevant PromotedGroup instances
# via a data migration to add/remove the "active" field.
PROMOTED_GROUPS = [
NOT_PROMOTED,
RECOMMENDED,
Expand All @@ -160,6 +163,8 @@ def __bool__(self):
NOTABLE,
]

ACTIVE_PROMOTED_GROUP_IDS = [group.id for group in PROMOTED_GROUPS]

BADGED_GROUPS = [group for group in PROMOTED_GROUPS if group.badged]
BADGED_API_NAME = 'badged' # Special alias for all badged groups

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Generated by Django 4.2.18 on 2025-02-04 11:13

from django.db import migrations, models
from olympia.constants.promoted import PROMOTED_GROUPS_BY_ID, ACTIVE_PROMOTED_GROUP_IDS


def create_promoted_groups(apps, schema_editor):
PromotedGroup = apps.get_model('promoted', 'PromotedGroup')
# Import legacy promoted groups from constants

# Loop over all groups (active and inactive) from PROMOTED_GROUPS_BY_ID
for group in PROMOTED_GROUPS_BY_ID.values():
PromotedGroup.objects.create(
id=group.id,
name=group.name,
api_name=group.api_name,
search_ranking_bump=group.search_ranking_bump,
listed_pre_review=group.listed_pre_review,
unlisted_pre_review=group.unlisted_pre_review,
admin_review=group.admin_review,
badged=group.badged,
autograph_signing_states=group.autograph_signing_states,
can_primary_hero=group.can_primary_hero,
immediate_approval=group.immediate_approval,
flag_for_human_review=group.flag_for_human_review,
can_be_compatible_with_all_fenix_versions=group.can_be_compatible_with_all_fenix_versions,
high_profile=group.high_profile,
high_profile_rating=group.high_profile_rating,
active=(group.id in ACTIVE_PROMOTED_GROUP_IDS),
)


def reverse_promoted_groups(apps, schema_editor):
PromotedGroup = apps.get_model('promoted', 'PromotedGroup')
PromotedGroup.objects.all().delete()


class Migration(migrations.Migration):

dependencies = [
('promoted', '0021_auto_20240919_0952'),
]

operations = [
migrations.CreateModel(
name='PromotedGroup',
fields=[
('id', models.SmallIntegerField(help_text='Primary key identifier for the promotion group.', primary_key=True, serialize=False)),
('name', models.CharField(help_text='Human-readable name for the promotion group.', max_length=255)),
('api_name', models.CharField(help_text='Programmatic API name for the promotion group.', max_length=100)),
('search_ranking_bump', models.FloatField(help_text='Boost value used to influence search ranking for add-ons in this group.')),
('listed_pre_review', models.BooleanField(default=False, help_text='Indicates if listed versions require pre-review.')),
('unlisted_pre_review', models.BooleanField(default=False, help_text='Indicates if unlisted versions require pre-review.')),
('admin_review', models.BooleanField(default=False, help_text='Specifies whether the promotion requires administrative review.')),
('badged', models.BooleanField(default=False, help_text='Specifies if the add-on receives a badge upon promotion.')),
('autograph_signing_states', models.JSONField(blank=True, default=dict, help_text='Mapping of application shorthand to autograph signing states.')),
('can_primary_hero', models.BooleanField(default=False, help_text='Determines if the add-on can be featured in a primary hero shelf.')),
('immediate_approval', models.BooleanField(default=False, help_text='If true, add-ons are auto-approved upon saving.')),
('flag_for_human_review', models.BooleanField(default=False, help_text='If true, add-ons are flagged for manual human review.')),
('can_be_compatible_with_all_fenix_versions', models.BooleanField(default=False, help_text='Determines compatibility with all Fenix (Android) versions.')),
('high_profile', models.BooleanField(default=False, help_text='Indicates if the add-on is high-profile for review purposes.')),
('high_profile_rating', models.BooleanField(default=False, help_text='Indicates if developer replies are treated as high-profile.')),
('active', models.BooleanField(default=False, help_text='Marks whether this promotion group is active (inactive groups are considered obsolete).')),
],
),
migrations.RunPython(create_promoted_groups, reverse_promoted_groups),
]
74 changes: 74 additions & 0 deletions src/olympia/promoted/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,80 @@
from olympia.versions.models import Version


class PromotedGroup(models.Model):
"""A promotion group defining the promotion rules for add-ons.
NOTE: This model replaces the legacy PromotedClass and its constants
"""

id = models.SmallIntegerField(
primary_key=True,
help_text='Primary key identifier for the promotion group.',
choices=PROMOTED_GROUP_CHOICES,
)
name = models.CharField(
max_length=255, help_text='Human-readable name for the promotion group.'
)
api_name = models.CharField(
max_length=100, help_text='Programmatic API name for the promotion group.'
)
search_ranking_bump = models.FloatField(
help_text=(
'Boost value used to influence search ranking for add-ons in this group.'
)
)
listed_pre_review = models.BooleanField(
default=False, help_text='Indicates if listed versions require pre-review.'
)
unlisted_pre_review = models.BooleanField(
default=False, help_text='Indicates if unlisted versions require pre-review.'
)
admin_review = models.BooleanField(
default=False,
help_text='Specifies whether the promotion requires administrative review.',
)
badged = models.BooleanField(
default=False,
help_text='Specifies if the add-on receives a badge upon promotion.',
)
autograph_signing_states = models.JSONField(
default=dict,
blank=True,
help_text='Mapping of application shorthand to autograph signing states.',
)
can_primary_hero = models.BooleanField(
default=False,
help_text='Determines if the add-on can be featured in a primary hero shelf.',
)
immediate_approval = models.BooleanField(
default=False, help_text='If true, add-ons are auto-approved upon saving.'
)
flag_for_human_review = models.BooleanField(
default=False, help_text='If true, add-ons are flagged for manual human review.'
)
can_be_compatible_with_all_fenix_versions = models.BooleanField(
default=False,
help_text='Determines compatibility with all Fenix (Android) versions.',
)
high_profile = models.BooleanField(
default=False,
help_text='Indicates if the add-on is high-profile for review purposes.',
)
high_profile_rating = models.BooleanField(
default=False,
help_text='Indicates if developer replies are treated as high-profile.',
)
active = models.BooleanField(
default=False,
help_text=(
'Marks whether this promotion group is active '
'(inactive groups are considered obsolete).'
),
)

def __str__(self):
return self.name


class PromotedAddon(ModelBase):
APPLICATION_CHOICES = ((None, 'All Applications'),) + APPS_CHOICES
group_id = models.SmallIntegerField(
Expand Down
49 changes: 48 additions & 1 deletion src/olympia/promoted/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
from olympia import amo, core
from olympia.amo.tests import TestCase, addon_factory, user_factory, version_factory
from olympia.constants import applications
from olympia.constants.promoted import PROMOTED_GROUP_CHOICES
from olympia.constants.promoted import (
ACTIVE_PROMOTED_GROUP_IDS,
PROMOTED_GROUP_CHOICES,
PROMOTED_GROUPS_BY_ID,
)
from olympia.promoted.models import (
PromotedAddon,
PromotedApproval,
PromotedGroup,
)
from olympia.versions.utils import get_review_due_date

Expand Down Expand Up @@ -244,3 +249,45 @@ def test_approve_for_addon(self):
promo.addon.reload()
assert promo.addon.promoted_group().id == PROMOTED_GROUP_CHOICES.SPOTLIGHT
assert promo.addon.current_version.version == '0.123a'


class TestPromotedGroup(TestCase):
def test_promoted_group_data_is_derived_from_promoted_groups(self):
# Loop over all groups from PROMOTED_GROUPS_BY_ID to ensure complete coverage
for const_group in PROMOTED_GROUPS_BY_ID.values():
try:
pg = PromotedGroup.objects.get(pk=const_group.id)
except PromotedGroup.DoesNotExist:
self.fail(f'PromotedGroup with id={const_group.id} not found')

self.assertEqual(pg.name, const_group.name)
self.assertEqual(pg.api_name, const_group.api_name)
self.assertAlmostEqual(
pg.search_ranking_bump, const_group.search_ranking_bump
)
self.assertEqual(pg.listed_pre_review, const_group.listed_pre_review)
self.assertEqual(pg.unlisted_pre_review, const_group.unlisted_pre_review)
self.assertEqual(pg.admin_review, const_group.admin_review)
self.assertEqual(pg.badged, const_group.badged)
self.assertEqual(
pg.autograph_signing_states, const_group.autograph_signing_states
)
self.assertEqual(pg.can_primary_hero, const_group.can_primary_hero)
self.assertEqual(pg.immediate_approval, const_group.immediate_approval)
self.assertEqual(
pg.flag_for_human_review, const_group.flag_for_human_review
)
self.assertEqual(
pg.can_be_compatible_with_all_fenix_versions,
const_group.can_be_compatible_with_all_fenix_versions,
)
self.assertEqual(pg.high_profile, const_group.high_profile)
self.assertEqual(pg.high_profile_rating, const_group.high_profile_rating)
expected_active = const_group.id in ACTIVE_PROMOTED_GROUP_IDS
self.assertEqual(pg.active, expected_active)

def test_str_method(self):
# Ensure the __str__ method returns the name
for const_group in PROMOTED_GROUPS_BY_ID.values():
pg = PromotedGroup.objects.get(pk=const_group.id)
self.assertEqual(str(pg), const_group.name)

0 comments on commit 981884d

Please sign in to comment.