def test_bool(self): # bool(VersionString(x)) should behave like bool(x) assert bool(VersionString('')) is False assert bool(VersionString('0')) is True assert bool(VersionString(0)) is True assert bool(VersionString('false')) is True assert bool(VersionString('False')) is True assert bool(VersionString('something')) is True assert bool(VersionString('3.6.*')) is True assert bool(VersionString('3.6')) is True assert bool(VersionString('*')) is True
def test_part_indexing(self): vs = VersionString('32.6pre9') assert vs['major'] == 32 assert vs['minor3'] == 0 assert vs['alpha'] == '' assert vs['pre'] == 'pre' assert vs['pre_ver'] == 9 assert vs[0] == '3' # normal string indexing still works assert vs[3:5] == '6p'
def test_parts(self): assert tuple(VersionString('3.6a5pre9').parts) == ( ('major', 3), ('minor1', 6), ('minor2', 0), ('minor3', 0), ('alpha', 'a'), ('alpha_ver', 5), ('pre', 'pre'), ('pre_ver', 9), ) assert tuple(VersionString('3.6.*').parts) == ( ('major', 3), ('minor1', 6), ('minor2', '*'), ('minor3', '*'), ('alpha', ''), ('alpha_ver', 0), ('pre', ''), ('pre_ver', 0), )
def get_simple_version(version_string): """Extract the version number without the ><= requirements, returning a VersionString instance. This simply extracts the version number without the ><= requirement so it will not be accurate for version requirements that are not >=, <= or = to a version. >>> get_simple_version('>=33.0a1') VersionString('33.0a1') """ if not version_string: version_string = '' return VersionString(re.sub('[<=>]', '', version_string))
def test_comparison(self): assert VersionString('3.6.*') > VersionString('3.6.8') assert VersionString('3.6.*') > VersionString('3.6.65535') assert VersionString('*') > VersionString('65535.0.0.1') assert VersionString('*') > VersionString('65536.65536.65536.65536') assert VersionString('*') > VersionString('98.*') assert VersionString('98.*') < VersionString('*') assert VersionString('65534.*') < VersionString('*') assert VersionString('5.*') > VersionString('5.0.*') assert VersionString('3.6a5pre9') < VersionString('3.6') assert VersionString('3.6a5pre9') < VersionString('3.6b1') assert VersionString('3.6.*') > VersionString('3.6a5pre9') assert VersionString('99.99999999b1') > VersionString('99.99999998b1') assert VersionString('99999999.99b1') > VersionString('99999998.99b1') assert VersionString('*') > VersionString('99999998.99b1')
def test_equality(self): assert VersionString('3.6.0.0') == VersionString('3.6') assert VersionString('3.6.*.0') != VersionString('3.6.*') assert VersionString('*') == VersionString('*.*.*.*') assert VersionString('*.0.0.0') != VersionString('65535') assert VersionString('3.6.*') != VersionString('3.6.65535') assert VersionString('*') != VersionString('65535.65535.65535.65535') assert VersionString('*') != VersionString('65535.0.0.0') assert VersionString('3.6a5pre9') != VersionString('3.6') # edge cases with falsey values assert VersionString('0') != '' assert VersionString('') != '0' assert VersionString('0') is not None assert VersionString('') is not None none = None assert VersionString('0') != none assert VersionString('') != none
class Block(ModelBase): MIN = VersionString('0') MAX = VersionString('*') guid = models.CharField(max_length=255, unique=True, null=False) min_version = VersionStringField(max_length=255, blank=False, default=MIN, validators=(no_asterisk, )) max_version = VersionStringField(max_length=255, blank=False, default=MAX) url = models.CharField(max_length=255, blank=True) reason = models.TextField(blank=True) updated_by = models.ForeignKey(UserProfile, null=True, on_delete=models.SET_NULL) legacy_id = models.CharField(max_length=255, null=False, default='', db_index=True, db_column='kinto_id') submission = models.ManyToManyField('BlocklistSubmission') average_daily_users_snapshot = models.IntegerField(null=True) ACTIVITY_IDS = ( amo.LOG.BLOCKLIST_BLOCK_ADDED.id, amo.LOG.BLOCKLIST_BLOCK_EDITED.id, amo.LOG.BLOCKLIST_BLOCK_DELETED.id, amo.LOG.BLOCKLIST_SIGNOFF.id, ) def __str__(self): return f'Block: {self.guid}' def __init__(self, *args, **kwargs): # Optimized case of creating a Block from Addon so skipping the query. addon = kwargs.pop('addon', None) if addon: kwargs['guid'] = addon.guid self.addon = addon super().__init__(*args, **kwargs) def save(self, **kwargs): assert self.updated_by return super().save(**kwargs) @property def is_imported_from_legacy_regex(self): return self.legacy_id.startswith('*') @property def in_legacy_blocklist(self): return bool(self.legacy_id) @classmethod def get_addons_for_guids_qs(cls, guids): return (Addon.unfiltered.filter( guid__in=guids).order_by('-id').only_translations()) @cached_property def addon(self): return self.get_addons_for_guids_qs((self.guid, )).first() @property def current_adu(self): return self.addon.average_daily_users if self.addon else 0 @cached_property def addon_versions(self): # preload_addon_versions will overwrite this cached_property. self.preload_addon_versions([self]) return self.addon_versions @classmethod def preload_addon_versions(cls, blocks): """Preload block.addon_versions into a list of blocks.""" block_guids = [block.guid for block in blocks] GUID = 'addon__addonguid__guid' qs = (Version.unfiltered.filter(**{ f'{GUID}__in': block_guids }).order_by('id').no_transforms().annotate(**{GUID: models.F(GUID)})) all_addon_versions = defaultdict(list) for version in qs: all_addon_versions[getattr(version, GUID)].append(version) for block in blocks: block.addon_versions = all_addon_versions[block.guid] def clean(self): if self.id: # We're only concerned with edits - self.guid isn't set at this # point for new instances anyway. choices = list(version.version for version in self.addon_versions) if self.min_version not in choices + [self.MIN]: raise ValidationError({'min_version': _('Invalid version')}) if self.max_version not in choices + [self.MAX]: raise ValidationError({'max_version': _('Invalid version')}) if self.min_version > self.max_version: raise ValidationError( _('Min version can not be greater than Max version')) def is_version_blocked(self, version): return self.min_version <= version and self.max_version >= version def review_listed_link(self): has_listed = any(True for version in self.addon_versions if version.channel == amo.RELEASE_CHANNEL_LISTED) if has_listed: url = absolutify( reverse('reviewers.review', kwargs={'addon_id': self.addon.pk})) return format_html('<a href="{}">{}</a>', url, _('Review Listed')) return '' def review_unlisted_link(self): has_unlisted = any(True for version in self.addon_versions if version.channel == amo.RELEASE_CHANNEL_UNLISTED) if has_unlisted: url = absolutify( reverse('reviewers.review', args=('unlisted', self.addon.pk))) return format_html('<a href="{}">{}</a>', url, _('Review Unlisted')) return '' @cached_property def active_submissions(self): return BlocklistSubmission.get_submissions_from_guid(self.guid) @property def is_readonly(self): legacy_submit_off = not waffle.switch_is_active( 'blocklist_legacy_submit') return (legacy_submit_off and self.legacy_id) or self.active_submissions @classmethod def get_blocks_from_guids(cls, guids): """Given a list of guids, return a list of Blocks - either existing instances if the guid exists in a Block, or new instances otherwise. """ # load all the Addon instances together using_db = get_replica() addons = list(cls.get_addons_for_guids_qs(guids).using(using_db)) # And then any existing block instances existing_blocks = { block.guid: block for block in cls.objects.using(using_db).filter(guid__in=guids) } for addon in addons: # get the existing block object or create a new instance block = existing_blocks.get(addon.guid, None) if block: # if it exists hook up the addon instance block.addon = addon else: # otherwise create a new Block block = Block(addon=addon) existing_blocks[block.guid] = block return list(existing_blocks.values())
def apps(self): """Get `AppVersion`s for the application.""" type_ = self.type if type_ == amo.ADDON_LPAPP: # Langpack are only compatible with Firefox desktop at the moment. # https://github.com/mozilla/addons-server/issues/8381 # They are all strictly compatible with a specific version, so # the default min version here doesn't matter much. apps = ((amo.FIREFOX, amo.DEFAULT_WEBEXT_MIN_VERSION), ) elif type_ == amo.ADDON_STATICTHEME: # Static themes are only compatible with Firefox desktop >= 53 # and Firefox for Android >=65. apps = ( (amo.FIREFOX, amo.DEFAULT_STATIC_THEME_MIN_VERSION_FIREFOX), (amo.ANDROID, amo.DEFAULT_STATIC_THEME_MIN_VERSION_ANDROID), ) elif type_ == amo.ADDON_DICT: # WebExt dicts are only compatible with Firefox desktop >= 61. apps = ((amo.FIREFOX, amo.DEFAULT_WEBEXT_DICT_MIN_VERSION_FIREFOX), ) else: webext_min = (amo.DEFAULT_WEBEXT_MIN_VERSION if self.get('browser_specific_settings', None) is None else amo.DEFAULT_WEBEXT_MIN_VERSION_BROWSER_SPECIFIC) # amo.DEFAULT_WEBEXT_MIN_VERSION_BROWSER_SPECIFIC should be 48.0, # which is the same as amo.DEFAULT_WEBEXT_MIN_VERSION_ANDROID, so # no specific treatment for Android. apps = ( (amo.FIREFOX, webext_min), (amo.ANDROID, amo.DEFAULT_WEBEXT_MIN_VERSION_ANDROID), ) if self.get('manifest_version') == 3: # Update minimum supported versions if it's an mv3 addon. mv3_mins = { amo.FIREFOX: amo.DEFAULT_WEBEXT_MIN_VERSION_MV3_FIREFOX, amo.ANDROID: amo.DEFAULT_WEBEXT_MIN_VERSION_MV3_ANDROID, } apps = ((app, max(VersionString(ver), mv3_mins.get(app, mv3_mins[amo.FIREFOX]))) for app, ver in apps) doesnt_support_no_id = (self.strict_min_version and self.strict_min_version < VersionString( amo.DEFAULT_WEBEXT_MIN_VERSION_NO_ID)) if self.guid is None and doesnt_support_no_id: raise forms.ValidationError( gettext('Add-on ID is required for Firefox 47 and below.')) # If a minimum strict version is specified, it needs to be higher # than the version when Firefox started supporting WebExtensions. unsupported_no_matter_what = (self.strict_min_version and self.strict_min_version < VersionString( amo.DEFAULT_WEBEXT_MIN_VERSION)) if unsupported_no_matter_what: msg = gettext('Lowest supported "strict_min_version" is 42.0.') raise forms.ValidationError(msg) for app, default_min_version in apps: if self.guid is None and not self.strict_min_version: strict_min_version = max( VersionString(amo.DEFAULT_WEBEXT_MIN_VERSION_NO_ID), VersionString(default_min_version), ) else: # strict_min_version for this app shouldn't be lower than the # default min version for this app. strict_min_version = max(self.strict_min_version, VersionString(default_min_version)) strict_max_version = self.strict_max_version or VersionString( amo.DEFAULT_WEBEXT_MAX_VERSION) if strict_max_version < strict_min_version: strict_max_version = strict_min_version qs = AppVersion.objects.filter(application=app.id) try: min_appver = qs.get(version=strict_min_version) except AppVersion.DoesNotExist: msg = gettext( 'Unknown "strict_min_version" {appver} for {app}'.format( app=app.pretty, appver=strict_min_version)) raise forms.ValidationError(msg) try: max_appver = qs.get(version=strict_max_version) except AppVersion.DoesNotExist: # If the specified strict_max_version can't be found, raise an # error: we used to use '*' instead but this caused more # problems, especially with langpacks that are really specific # to a given Firefox version. msg = gettext( 'Unknown "strict_max_version" {appver} for {app}'.format( app=app.pretty, appver=strict_max_version)) raise forms.ValidationError(msg) yield Extractor.App(appdata=app, id=app.id, min=min_appver, max=max_appver)