class TranslatedModel(ModelBase): name = TranslatedField() description = TranslatedField() default_locale = models.CharField(max_length=10) no_locale = TranslatedField(require_locale=False) objects = ManagerBase()
class TranslatedModel(ModelBase): name = TranslatedField() description = TranslatedField() default_locale = models.CharField(max_length=10) no_locale = TranslatedField(require_locale=False) translated_through_fk = models.ForeignKey( 'TranslatedModelLinkedAsForeignKey', null=True, default=None) objects = ManagerBase()
class L10nEventlog(caching.base.CachingMixin, models.Model): locale = models.CharField(max_length=30, default='') type = models.CharField(max_length=20, default='') action = models.CharField(max_length=40, default='') field = models.CharField(max_length=20, default='', null=True) user = models.ForeignKey('users.UserProfile') changed_id = models.PositiveIntegerField( default=0, help_text='id of the object being affected by the change') added = models.CharField(max_length=255, default='', null=True) removed = models.CharField(max_length=255, default='', null=True) notes = models.TextField() created = models.DateTimeField(auto_now_add=True) objects = ManagerBase() class Meta: db_table = 'l10n_eventlog' get_latest_by = 'created'
def __init__(self, include_deleted=False): ManagerBase.__init__(self) self.include_deleted = include_deleted
def __init__(self, include_deleted=False): # DO NOT change the default value of include_deleted unless you've read # through the comment just above the Addon managers # declaration/instanciation and understand the consequences. ManagerBase.__init__(self) self.include_deleted = include_deleted
class FileUpload(ModelBase): """Created when a file is uploaded for validation/submission.""" uuid = models.UUIDField(default=uuid.uuid4, editable=False) path = models.CharField(max_length=255, default='') name = models.CharField(max_length=255, default='', help_text="The user's original filename") hash = models.CharField(max_length=255, default='') user = models.ForeignKey('users.UserProfile', null=True, on_delete=models.CASCADE) valid = models.BooleanField(default=False) validation = models.TextField(null=True) automated_signing = models.BooleanField(default=False) compat_with_app = models.PositiveIntegerField( choices=amo.APPS_CHOICES, db_column="compat_with_app_id", null=True) compat_with_appver = models.ForeignKey( AppVersion, null=True, related_name='uploads_compat_for_appver', on_delete=models.CASCADE) # Not all FileUploads will have a version and addon but it will be set # if the file was uploaded using the new API. version = models.CharField(max_length=255, null=True) addon = models.ForeignKey('addons.Addon', null=True, on_delete=models.CASCADE) objects = ManagerBase() class Meta(ModelBase.Meta): db_table = 'file_uploads' def __str__(self): return six.text_type(self.uuid.hex) def save(self, *args, **kw): if self.validation: if self.load_validation()['errors'] == 0: self.valid = True super(FileUpload, self).save(*args, **kw) def add_file(self, chunks, filename, size): if not self.uuid: self.uuid = self._meta.get_field('uuid')._create_uuid() filename = force_text(u'{0}_{1}'.format(self.uuid.hex, filename)) loc = os.path.join(user_media_path('addons'), 'temp', uuid.uuid4().hex) base, ext = os.path.splitext(filename) is_crx = False # Change a ZIP to an XPI, to maintain backward compatibility # with older versions of Firefox and to keep the rest of the XPI code # path as consistent as possible for ZIP uploads. # See: https://github.com/mozilla/addons-server/pull/2785 if ext == '.zip': ext = '.xpi' # If the extension is a CRX, we need to do some actual work to it # before we just convert it to an XPI. We strip the header from the # CRX, then it's good; see more about the CRX file format here: # https://developer.chrome.com/extensions/crx if ext == '.crx': ext = '.xpi' is_crx = True if ext in amo.VALID_ADDON_FILE_EXTENSIONS: loc += ext log.info('UPLOAD: %r (%s bytes) to %r' % (filename, size, loc)) if is_crx: hash_func = write_crx_as_xpi(chunks, loc) else: hash_func = hashlib.sha256() with storage.open(loc, 'wb') as file_destination: for chunk in chunks: hash_func.update(chunk) file_destination.write(chunk) self.path = loc self.name = filename self.hash = 'sha256:%s' % hash_func.hexdigest() self.save() @classmethod def from_post(cls, chunks, filename, size, **params): upload = FileUpload(**params) upload.add_file(chunks, filename, size) return upload @property def processed(self): return bool(self.valid or self.validation) @property def validation_timeout(self): if self.processed: validation = self.load_validation() messages = validation['messages'] timeout_id = [ 'validator', 'unexpected_exception', 'validation_timeout' ] return any(msg['id'] == timeout_id for msg in messages) else: return False @property def processed_validation(self): """Return processed validation results as expected by the frontend.""" if self.validation: # Import loop. from olympia.devhub.utils import process_validation validation = self.load_validation() is_compatibility = self.compat_with_app is not None return process_validation(validation, is_compatibility=is_compatibility, file_hash=self.hash) @property def passed_all_validations(self): return self.processed and self.valid def load_validation(self): return json.loads(self.validation) @property def pretty_name(self): parts = self.name.split('_', 1) if len(parts) > 1: return parts[1] return self.name
class FileUpload(ModelBase): """Created when a file is uploaded for validation/submission.""" uuid = models.UUIDField(default=uuid.uuid4, editable=False) path = models.CharField(max_length=255, default='') name = models.CharField(max_length=255, default='', help_text="The user's original filename") hash = models.CharField(max_length=255, default='') user = models.ForeignKey('users.UserProfile', null=True, on_delete=models.CASCADE) valid = models.BooleanField(default=False) validation = models.TextField(null=True) automated_signing = models.BooleanField(default=False) # Not all FileUploads will have a version and addon but it will be set # if the file was uploaded using the new API. version = models.CharField(max_length=255, null=True) addon = models.ForeignKey('addons.Addon', null=True, on_delete=models.CASCADE) access_token = models.CharField(max_length=40, null=True) ip_address = models.CharField(max_length=45, null=True, default=None) source = models.PositiveSmallIntegerField( choices=amo.UPLOAD_SOURCE_CHOICES, default=None, null=True) objects = ManagerBase() class Meta(ModelBase.Meta): db_table = 'file_uploads' constraints = [ models.UniqueConstraint(fields=('uuid', ), name='uuid'), ] def __str__(self): return str(self.uuid.hex) def save(self, *args, **kw): if self.validation: if self.load_validation()['errors'] == 0: self.valid = True if not self.access_token: self.access_token = self.generate_access_token() super(FileUpload, self).save(*args, **kw) def write_data_to_path(self, chunks): hash_obj = hashlib.sha256() with storage.open(self.path, 'wb') as file_destination: for chunk in chunks: hash_obj.update(chunk) file_destination.write(chunk) return hash_obj def add_file(self, chunks, filename, size): if not self.uuid: self.uuid = self._meta.get_field('uuid')._create_uuid() _base, ext = os.path.splitext(filename) was_crx = ext == '.crx' # Filename we'll expose (but not use for storage). self.name = force_str('{0}_{1}'.format(self.uuid.hex, filename)) # Final path on our filesystem. If it had a valid extension we change # it to .xpi (CRX files are converted before validation, so they will # be treated as normal .xpi for validation). If somehow this is # not a valid archive or the extension is invalid parse_addon() will # eventually complain at validation time or before even reaching the # linter. if ext in amo.VALID_ADDON_FILE_EXTENSIONS: ext = '.xpi' self.path = os.path.join(user_media_path('addons'), 'temp', '{0}{1}'.format(uuid.uuid4().hex, ext)) hash_obj = None if was_crx: try: hash_obj = write_crx_as_xpi(chunks, self.path) except InvalidOrUnsupportedCrx: # We couldn't convert the crx file. Write it to the filesystem # normally, the validation process should reject this with a # proper error later. pass if hash_obj is None: hash_obj = self.write_data_to_path(chunks) self.hash = 'sha256:%s' % hash_obj.hexdigest() # The following log statement is used by foxsec-pipeline. log.info( 'UPLOAD: %r (%s bytes) to %r' % (self.name, size, self.path), extra={ 'email': (self.user.email if self.user and self.user.email else ''), 'upload_hash': self.hash, }, ) self.save() def generate_access_token(self): """ Returns an access token used to secure download URLs. """ return get_random_string(40) def get_authenticated_download_url(self): """ Returns a download URL containing an access token bound to this file. """ absolute_url = urljoin( settings.EXTERNAL_SITE_URL, reverse('files.serve_file_upload', kwargs={'uuid': self.uuid.hex}), ) return '{}?access_token={}'.format(absolute_url, self.access_token) @classmethod def from_post(cls, chunks, filename, size, **params): upload = FileUpload(**params) upload.add_file(chunks, filename, size) return upload @property def processed(self): return bool(self.valid or self.validation) @property def validation_timeout(self): if self.processed: validation = self.load_validation() messages = validation['messages'] timeout_id = [ 'validator', 'unexpected_exception', 'validation_timeout' ] return any(msg['id'] == timeout_id for msg in messages) else: return False @property def processed_validation(self): """Return processed validation results as expected by the frontend.""" if self.validation: # Import loop. from olympia.devhub.utils import process_validation validation = self.load_validation() return process_validation(validation, file_hash=self.hash) @property def passed_all_validations(self): return self.processed and self.valid def load_validation(self): return json.loads(self.validation) @property def pretty_name(self): parts = self.name.split('_', 1) if len(parts) > 1: return parts[1] return self.name
class TranslatedModelWithDefaultNull(ModelBase): name = TranslatedField(default=None) objects = ManagerBase()