class SourceFile(TextFile): project = models.ForeignKey('Project', related_name='source_files') file_name = models.CharField(max_length=100, validators=regexes.validator( 'source_file_name', _('Invalid file name.'))) folder = 'sources' TARGETS = ( ('app', _('App')), ('worker', _('Worker')), ) target = models.CharField(max_length=10, choices=TARGETS, default='app') @property def project_path(self): if self.target == 'app': return 'src/%s' % self.file_name else: return 'worker_src/%s' % self.file_name class Meta(IdeModel.Meta): unique_together = (('project', 'file_name'))
class Project(IdeModel): owner = models.ForeignKey(User) name = models.CharField(max_length=50) last_modified = models.DateTimeField(auto_now_add=True) PROJECT_TYPES = ( ('native', _('Pebble C SDK')), ('simplyjs', _('Simply.js')), ('pebblejs', _('Pebble.js (beta)')), ('package', _('Pebble Package')), ('rocky', _('Rocky.js')), ) project_type = models.CharField(max_length=10, choices=PROJECT_TYPES, default='native') SDK_VERSIONS = ( ('2', _('SDK 2 (obsolete)')), ('3', _('SDK 4 beta')), ) sdk_version = models.CharField(max_length=6, choices=SDK_VERSIONS, default='2') # New settings for 2.0 app_uuid = models.CharField(max_length=36, blank=True, null=True, default=generate_half_uuid, validators=regexes.validator( 'uuid', _('Invalid UUID.'))) app_company_name = models.CharField(max_length=100, blank=True, null=True) app_short_name = models.CharField(max_length=100, blank=True, null=True) app_long_name = models.CharField(max_length=100, blank=True, null=True) app_version_label = models.CharField(max_length=40, blank=True, null=True, default='1.0') app_is_watchface = models.BooleanField(default=False) app_is_hidden = models.BooleanField(default=False) app_is_shown_on_communication = models.BooleanField(default=False) app_capabilities = models.CharField(max_length=255, blank=True, null=True) app_keys = models.TextField(default="{}") app_jshint = models.BooleanField(default=False) app_platforms = models.TextField(max_length=255, blank=True, null=True) app_modern_multi_js = models.BooleanField(default=True) app_keywords = models.TextField(default='[]') app_capability_list = property( lambda self: self.app_capabilities.split(',')) app_platform_list = property(lambda self: self.app_platforms.split(',') if self.app_platforms else []) OPTIMISATION_CHOICES = ( ('0', 'None'), ('1', 'Limited'), ('s', 'Prefer smaller'), ('2', 'Prefer faster'), ('3', 'Aggressive (faster, bigger)'), ) optimisation = models.CharField(max_length=1, choices=OPTIMISATION_CHOICES, default='s') github_repo = models.CharField(max_length=100, blank=True, null=True) github_branch = models.CharField(max_length=100, blank=True, null=True) github_last_sync = models.DateTimeField(blank=True, null=True) github_last_commit = models.CharField(max_length=40, blank=True, null=True) github_hook_uuid = models.CharField(max_length=36, blank=True, null=True) github_hook_build = models.BooleanField(default=False) project_dependencies = models.ManyToManyField("Project") def __init__(self, *args, **kwargs): super(IdeModel, self).__init__(*args, **kwargs) # For SDK3+, default to array-based message keys. if self.sdk_version != '2' and self.app_keys == '{}': self.app_keys = '[]' if self.sdk_version == '2': self.app_modern_multi_js = False if self.project_type == 'package' and self.app_version_label == '1.0': self.app_version_label = '1.0.0' def set_dependencies(self, dependencies): """ Set the project's dependencies from a dictionary. :param dependencies: A dictionary of dependency->version """ with transaction.atomic(): Dependency.objects.filter(project=self).delete() for name, version in dependencies.iteritems(): dep = Dependency.objects.create(project=self, name=name, version=version) dep.save() def set_interdependencies(self, interdependences): with transaction.atomic(): self.project_dependencies.clear() for project_id in interdependences: project = Project.objects.get(pk=project_id, owner=self.owner, project_type='package') self.project_dependencies.add(project) @property def npm_name(self): """ Get the project's app_short_name as a valid NPM package name. """ name = self.app_short_name.lower() # Remove any invalid characters from the end name = re.sub(r'[^a-z0-9._]+$', '', name) # Any strings of invalid characters in the middle are converted to dashes name = re.sub(r'[^a-z0-9._]+', '-', name) # The name cannot start with [ ._] or end with spaces. name = name.lstrip(' ._').rstrip() return name @property def keywords(self): """ Get the project's keywords as a list of strings """ return json.loads(self.app_keywords) @keywords.setter def keywords(self, value): """ Set the project's keywords from a list of strings """ self.app_keywords = json.dumps(value) def get_dependencies(self, include_interdependencies=True): """ Get the project's dependencies as a dictionary :return: A dictionary of dependency->version """ dependencies = {d.name: d.version for d in self.dependencies.all()} if include_interdependencies: for project in self.project_dependencies.all(): dependencies[project.npm_name] = project.last_build.package_url return dependencies @property def uses_array_message_keys(self): return isinstance(json.loads(self.app_keys), list) def get_parsed_appkeys(self): """ Get the project's app keys, or raise an error of any are invalid. :return: A list of (appkey, value) tuples, where value is either a length or a size, depending on the kind of appkey. """ app_keys = json.loads(self.app_keys) if isinstance(app_keys, dict): return sorted(app_keys.iteritems(), key=lambda x: x[1]) else: parsed_keys = [] for appkey in app_keys: parsed = re.match(regexes.C_IDENTIFIER_WITH_INDEX, appkey) if not parsed: raise ValueError("Bad Appkey %s" % appkey) parsed_keys.append((parsed.group(1), parsed.group(2) or 1)) return parsed_keys def get_last_build(self): try: return self.builds.order_by('-id')[0] except IndexError: return None def get_menu_icon(self): try: return self.resources.filter(is_menu_icon=True)[0] except IndexError: return None def has_platform(self, platform): return self.app_platforms is None or platform in self.app_platform_list @property def semver(self): """ Get the app's version label formatted as a semver. """ if self.project_type == 'package': try: # Packages should have semver app_versions_labels... parse_semver(self.app_version_label) return self.app_version_label except ValueError as e: # but if they don't, we try to convert it from an app-style version label. try: version_to_semver(self.app_version_label) except: raise e return version_to_semver(self.app_version_label) @semver.setter def semver(self, value): """ Set the app's version label from a semver string. """ if self.project_type == 'package': # This throws an error if the semver is invalid. parse_semver(value) self.app_version_label = value else: self.app_version_label = semver_to_version(value) @property def supported_platforms(self): supported_platforms = ["aplite"] if self.sdk_version != '2': supported_platforms.extend(["basalt", "chalk"]) if self.project_type != 'pebblejs': supported_platforms.extend(["diorite", "emery"]) return supported_platforms @property def resources_path(self): return 'src/resources' if self.project_type == 'package' else 'resources' @property def is_standard_project_type(self): return self.project_type in {'native', 'package', 'rocky'} @property def pkjs_entry_point(self): if self.project_type in {'package', 'rocky'}: return 'index.js' elif self.project_type == 'native' and self.app_modern_multi_js: if self.source_files.filter(target='pkjs', file_name='index.js').exists(): return 'index.js' elif self.source_files.filter(target='pkjs', file_name='app.js').exists(): return 'app.js' else: return 'index.js' else: return None def clean(self): is_sdk_2 = self.sdk_version == "2" if is_sdk_2 and self.uses_array_message_keys: raise ValidationError( _("SDK2 appKeys must be an object, not a list.")) if self.project_type != 'package': try: parse_sdk_version(self.app_version_label) except ValueError: raise ValidationError( _("Invalid version string. Versions should be major[.minor]." )) if self.project_type == 'package': try: parse_semver(self.app_version_label) except ValueError: raise ValidationError( _("Invalid version string. Versions should be major.minor.patch" )) if is_sdk_2: raise ValidationError( _("Packages are not available for SDK 2")) if not self.app_modern_multi_js: raise ValidationError( _("Packages must use CommonJS-style JS Handling.")) elif self.project_type == 'rocky': if is_sdk_2: raise ValidationError(_("RockyJS is not available for SDK 2")) if not self.uses_array_message_keys: raise ValidationError( _("RockyJS projects must use array based appmessage keys")) if not self.app_modern_multi_js: raise ValidationError( _("RockyJS projects must use CommonJS-style JS Handling.")) last_build = property(get_last_build) menu_icon = property(get_menu_icon) def __unicode__(self): return u"%s" % self.name
class BuildResult(IdeModel): STATE_WAITING = 1 STATE_FAILED = 2 STATE_SUCCEEDED = 3 STATE_CHOICES = ((STATE_WAITING, _('Pending')), (STATE_FAILED, _('Failed')), (STATE_SUCCEEDED, _('Succeeded'))) DEBUG_INFO_MAP = { 'aplite': ('debug_info.json', 'worker_debug_info.json'), 'basalt': ('basalt_debug_info.json', 'basalt_worker_debug_info.json'), 'chalk': ('chalk_debug_info.json', 'chalk_worker_debug_info.json'), } DEBUG_APP = 0 DEBUG_WORKER = 1 project = models.ForeignKey(Project, related_name='builds') uuid = models.CharField(max_length=36, default=lambda: str(uuid.uuid4()), validators=regexes.validator( 'uuid', _('Invalid UUID.'))) state = models.IntegerField(choices=STATE_CHOICES, default=STATE_WAITING) started = models.DateTimeField(auto_now_add=True, db_index=True) finished = models.DateTimeField(blank=True, null=True) def _get_dir(self): if settings.AWS_ENABLED: return '%s/' % self.uuid else: path = '%s%s/%s/%s/' % (settings.MEDIA_ROOT, self.uuid[0], self.uuid[1], self.uuid) if not os.path.exists(path): os.makedirs(path) return path def get_url(self): if settings.AWS_ENABLED: return "%s%s/" % (settings.MEDIA_URL, self.uuid) else: return '%s%s/%s/%s/' % (settings.MEDIA_URL, self.uuid[0], self.uuid[1], self.uuid) def get_pbw_filename(self): return '%swatchface.pbw' % self._get_dir() def get_build_log(self): return '%sbuild_log.txt' % self._get_dir() def get_pbw_url(self): return '%swatchface.pbw' % self.get_url() def get_build_log_url(self): return '%sbuild_log.txt' % self.get_url() def get_debug_info_filename(self, platform, kind): return self._get_dir() + self.DEBUG_INFO_MAP[platform][kind] def get_debug_info_url(self, platform, kind): return self.get_url() + self.DEBUG_INFO_MAP[platform][kind] def get_simplyjs(self): return '%ssimply.js' % self._get_dir() def get_simplyjs_url(self): return '%ssimply.js' % self.get_url() def save_build_log(self, text): if not settings.AWS_ENABLED: with open(self.build_log, 'w') as f: f.write(text) else: s3.save_file('builds', self.build_log, text, public=True, content_type='text/plain') def read_build_log(self): if not settings.AWS_ENABLED: with open(self.build_log, 'r') as f: return f.read() else: return s3.read_file('builds', self.build_log) def save_debug_info(self, json_info, platform, kind): text = json.dumps(json_info) if not settings.AWS_ENABLED: with open(self.get_debug_info_filename(platform, kind), 'w') as f: f.write(text) else: s3.save_file('builds', self.get_debug_info_filename(platform, kind), text, public=True, content_type='application/json') def save_pbw(self, pbw_path): if not settings.AWS_ENABLED: shutil.move(pbw_path, self.pbw) else: s3.upload_file('builds', self.pbw, pbw_path, public=True, download_filename='%s.pbw' % self.project.app_short_name.replace('/', '-')) def save_simplyjs(self, javascript): if not settings.AWS_ENABLED: with open(self.simplyjs, 'w') as f: f.write(javascript) else: s3.save_file('builds', self.simplyjs, javascript, public=True, content_type='text/javascript') pbw = property(get_pbw_filename) build_log = property(get_build_log) pbw_url = property(get_pbw_url) build_log_url = property(get_build_log_url) simplyjs = property(get_simplyjs) simplyjs_url = property(get_simplyjs_url) def get_sizes(self): sizes = {} for size in self.sizes.all(): sizes[size.platform] = { 'total': size.total_size, 'app': size.binary_size, 'resources': size.resource_size, 'worker': size.worker_size, } return sizes
class Project(IdeModel): owner = models.ForeignKey(User) name = models.CharField(max_length=50) last_modified = models.DateTimeField(auto_now_add=True) PROJECT_TYPES = ( ('native', _('Pebble C SDK')), ('simplyjs', _('Simply.js')), ('pebblejs', _('Pebble.js (beta)')), ) project_type = models.CharField(max_length=10, choices=PROJECT_TYPES, default='native') SDK_VERSIONS = ( ('2', _('SDK 2 (Pebble, Pebble Steel)')), ('3', _('SDK 3 beta (Pebble Time)')), ) sdk_version = models.CharField(max_length=6, choices=SDK_VERSIONS, default='2') # New settings for 2.0 app_uuid = models.CharField(max_length=36, blank=True, null=True, default=generate_half_uuid, validators=regexes.validator( 'uuid', _('Invalid UUID.'))) app_company_name = models.CharField(max_length=100, blank=True, null=True) app_short_name = models.CharField(max_length=100, blank=True, null=True) app_long_name = models.CharField(max_length=100, blank=True, null=True) app_version_label = models.CharField(max_length=40, blank=True, null=True, default='1.0', validators=[version_validator]) app_is_watchface = models.BooleanField(default=False) app_is_hidden = models.BooleanField(default=False) app_is_shown_on_communication = models.BooleanField(default=False) app_capabilities = models.CharField(max_length=255, blank=True, null=True) app_keys = models.TextField(default="{}") app_jshint = models.BooleanField(default=True) app_platforms = models.TextField(max_length=255, blank=True, null=True) app_modern_multi_js = models.BooleanField(default=True) app_keywords = models.TextField(default='[]') app_capability_list = property( lambda self: self.app_capabilities.split(',')) app_platform_list = property(lambda self: self.app_platforms.split(',') if self.app_platforms else []) OPTIMISATION_CHOICES = ( ('0', 'None'), ('1', 'Limited'), ('s', 'Prefer smaller'), ('2', 'Prefer faster'), ('3', 'Aggressive (faster, bigger)'), ) optimisation = models.CharField(max_length=1, choices=OPTIMISATION_CHOICES, default='s') github_repo = models.CharField(max_length=100, blank=True, null=True) github_branch = models.CharField(max_length=100, blank=True, null=True) github_last_sync = models.DateTimeField(blank=True, null=True) github_last_commit = models.CharField(max_length=40, blank=True, null=True) github_hook_uuid = models.CharField(max_length=36, blank=True, null=True) github_hook_build = models.BooleanField(default=False) def set_dependencies(self, dependencies): """ Set the project's dependencies from a dictionary. :param dependencies: A dictionary of dependency->version """ with transaction.atomic(): Dependency.objects.filter(project=self).delete() for name, version in dependencies.iteritems(): dep = Dependency.objects.create(project=self, name=name, version=version) dep.save() @property def keywords(self): """ Get the project's keywords as a list of strings """ return json.loads(self.app_keywords) @keywords.setter def keywords(self, value): """ Set the project's keywords from a list of strings """ self.app_keywords = json.dumps(value) def get_dependencies(self): """ Get the project's dependencies as a dictionary :return: A dictinoary of dependency->version """ return {d.name: d.version for d in self.dependencies.all()} @property def uses_array_message_keys(self): return isinstance(json.loads(self.app_keys), list) def get_parsed_appkeys(self): """ Get the project's app keys, or raise an error of any are invalid. :return: A list of (appkey, value) tuples, where value is either a length or a size, depending on the kind of appkey. """ app_keys = json.loads(self.app_keys) if isinstance(app_keys, dict): return sorted(app_keys.iteritems(), key=lambda x: x[1]) else: parsed_keys = [] for appkey in app_keys: parsed = re.match(regexes.C_IDENTIFIER_WITH_INDEX, appkey) if not parsed: raise ValueError("Bad Appkey %s" % appkey) parsed_keys.append((parsed.group(1), parsed.group(2) or 1)) return parsed_keys def get_last_build(self): try: return self.builds.order_by('-id')[0] except IndexError: return None def get_menu_icon(self): try: return self.resources.filter(is_menu_icon=True)[0] except IndexError: return None def has_platform(self, platform): return self.app_platforms is None or platform in self.app_platform_list @property def semver(self): """ Get the app's version label formatted as a semver """ return version_to_semver(self.app_version_label) @semver.setter def semver(self, value): """ Set the app's version label from a semver string""" self.app_version_label = semver_to_version(value) def clean(self): if isinstance(json.loads(self.app_keys), list) and self.sdk_version == "2": raise ValidationError( _("SDK2 appKeys must be an object, not a list.")) last_build = property(get_last_build) menu_icon = property(get_menu_icon) def __unicode__(self): return u"%s" % self.name
class BuildResult(IdeModel): STATE_WAITING = 1 STATE_FAILED = 2 STATE_SUCCEEDED = 3 STATE_CHOICES = ((STATE_WAITING, _('Pending')), (STATE_FAILED, _('Failed')), (STATE_SUCCEEDED, _('Succeeded'))) DEBUG_INFO_MAP = { 'aplite': ('debug_info.json', 'worker_debug_info.json'), 'basalt': ('basalt_debug_info.json', 'basalt_worker_debug_info.json'), 'chalk': ('chalk_debug_info.json', 'chalk_worker_debug_info.json'), 'diorite': ('diorite_debug_info.json', 'diorite_worker_debug_info.json'), 'emery': ('emery_debug_info.json', 'emery_worker_debug_info.json'), } DEBUG_APP = 0 DEBUG_WORKER = 1 project = models.ForeignKey(Project, related_name='builds') uuid = models.CharField(max_length=36, default=make_uuid, validators=regexes.validator( 'uuid', _('Invalid UUID.'))) state = models.IntegerField(choices=STATE_CHOICES, default=STATE_WAITING) started = models.DateTimeField(auto_now_add=True, db_index=True) finished = models.DateTimeField(blank=True, null=True) def _get_dir(self): return '%s/' % self.uuid def get_url(self): return "%s%s/" % (settings.MEDIA_URL, self.uuid) @property def pbw(self): return '%sproject.pbw' % self._get_dir() @property def pbw_url(self): return '%sproject.pbw' % self.get_url() @property def package(self): return '%ssource.tar.gz' % self._get_dir() @property def package_url(self): return '%ssource.tar.gz' % self.get_url() @property def build_log(self): return '%scompile_log.txt' % self._get_dir() @property def build_log_url(self): return '%scompile_log.txt' % self.get_url() @property def simplyjs(self): return '%ssimply.js' % self._get_dir() def get_debug_info_filename(self, platform, kind): return self._get_dir() + self.DEBUG_INFO_MAP[platform][kind] def save_build_log(self, text): s3.save_file('builds', self.build_log, text, public=True, content_type='text/plain') def read_build_log(self): return s3.read_file('builds', self.build_log) def save_debug_info(self, json_info, platform, kind): text = json.dumps(json_info) s3.save_file('builds', self.get_debug_info_filename(platform, kind), text, public=True, content_type='application/json') def save_package(self, package_path): filename = '%s.tar.gz' % self.project.app_short_name.replace('/', '-') s3.upload_file('builds', self.package, package_path, public=True, download_filename=filename, content_type='application/gzip') def save_pbw(self, pbw_path): s3.upload_file('builds', self.pbw, pbw_path, public=True, download_filename='%s.pbw' % self.project.app_short_name.replace('/', '-')) def save_simplyjs(self, javascript): s3.save_file('builds', self.simplyjs, javascript, public=True, content_type='text/javascript') def get_sizes(self): sizes = {} for size in self.sizes.all(): sizes[size.platform] = { 'total': size.total_size, 'app': size.binary_size, 'resources': size.resource_size, 'worker': size.worker_size, } return sizes
class PublishedMedia(IdeModel): project = models.ForeignKey('Project', related_name='published_media') name = models.CharField(max_length=100, validators=regexes.validator( 'c_identifier', _('Invalid identifier'))) media_id = models.IntegerField() glance = models.CharField(max_length=100, blank=True, validators=regexes.validator( 'c_identifier', _('Invalid identifier'))) has_timeline = models.BooleanField(default=False) timeline_tiny = models.CharField(max_length=100, blank=True, validators=regexes.validator( 'c_identifier', _('Invalid identifier'))) timeline_small = models.CharField(max_length=100, blank=True, validators=regexes.validator( 'c_identifier', _('Invalid identifier'))) timeline_large = models.CharField(max_length=100, blank=True, validators=regexes.validator( 'c_identifier', _('Invalid identifier'))) @classmethod def from_dict(cls, project, data): return cls.objects.create(project=project, name=data['name'], media_id=data.get('id', None), glance=data.get('glance', ''), has_timeline=('timeline' in data), timeline_tiny=data.get('timeline', {}).get('tiny', ''), timeline_small=data.get('timeline', {}).get('small', ''), timeline_large=data.get('timeline', {}).get('large', '')) def to_dict(self): obj = {'name': self.name, 'id': self.media_id} if self.glance: obj['glance'] = self.glance if self.has_timeline: obj['timeline'] = { 'tiny': self.timeline_tiny, 'small': self.timeline_small, 'large': self.timeline_large } return obj def clean(self): if self.has_timeline and self.glance and self.glance != self.timeline_tiny: raise ValidationError( _("If glance and timeline.tiny are both used, they must be identical." )) if self.has_timeline and not (self.timeline_tiny and self.timeline_small and self.timeline_large): raise ValidationError( _("If timeline icons are enabled, they must all be set.")) if not self.glance and not self.has_timeline: raise ValidationError( _("Glance and Timeline cannot both be unset.")) if self.media_id < 0: raise ValidationError(_("Published Media IDs cannot be negative.")) class Meta(IdeModel.Meta): unique_together = (('project', 'name'), ('project', 'media_id'))
class SourceFile(TextFile): project = models.ForeignKey('Project', related_name='source_files') file_name = models.CharField(max_length=100, validators=regexes.validator( 'source_file_name', _('Invalid file name.'))) folder = 'sources' TARGETS = ( ('app', _('App')), ('pkjs', _('PebbleKit JS')), ('worker', _('Worker')), ('public', _('Public Header File')), ('common', _('Shared JS')), ) target = models.CharField(max_length=10, choices=TARGETS, default='app') DIR_MAP = { # Using an OrderedDict here ensures that 'src/' is checked last in get_details_for_path(). 'native': OrderedDict([ ('pkjs', ['src/pkjs', 'src/js']), ('worker', ['worker_src/c', 'worker_src']), ('app', ['src/c', 'src']), ]), 'pebblejs': { 'app': ['src/js'], }, 'simplyjs': { 'app': ['src'], }, 'rocky': { 'app': ['src/rocky'], 'pkjs': ['src/pkjs'], 'common': ['src/common'], }, 'package': { 'app': ['src/c'], 'public': ['include'], 'pkjs': ['src/js'], } } @classmethod def get_details_for_path(cls, project_type, path): """ Given a project type and a path to a source file, determine what the file's target should be and what its name should be. """ targets = cls.DIR_MAP[project_type] for target in targets: for base in targets[target]: base += '/' if path.startswith(base): file_target = target break else: continue break else: raise ValueError( _("Unacceptable file path for this project [%s]") % path) if file_target in ('pkjs', 'common') or project_type in ('pebblejs', 'simplyjs', 'rocky'): expected_exts = ('.js', '.json') else: expected_exts = ('.c', '.h') if not path.endswith(expected_exts): raise ValueError( _("Unacceptable file extension for %s file in [%s]. Expecting %s" ) % (file_target, path, " or ".join(expected_exts))) return path[len(base):], file_target @property def project_path(self): return os.path.join(self.project_dir, self.file_name) @property def project_dir(self): try: return SourceFile.DIR_MAP[self.project.project_type][ self.target][0] except KeyError: Exception("Invalid file type in project") class Meta(IdeModel.Meta): unique_together = (('project', 'file_name', 'target'), )
class ResourceFile(IdeModel): project = models.ForeignKey('Project', related_name='resources') RESOURCE_KINDS = ( ('raw', _('Binary blob')), ('bitmap', _('Bitmap Image')), ('png', _('1-bit PNG')), ('png-trans', _('1-bit PNG with transparency')), ('font', _('True-Type Font')), ('pbi', _('1-bit Pebble image')), ) file_name = models.CharField(max_length=100, validators=regexes.validator( 'resource_file_name', _("Invalid filename."))) kind = models.CharField(max_length=9, choices=RESOURCE_KINDS) is_menu_icon = models.BooleanField(default=False) def get_best_variant(self, tags_string): try: return self.variants.get(tags=tags_string) except ResourceVariant.DoesNotExist: return self.get_default_variant() def rename(self, new_name): if os.path.splitext( self.file_name)[1] != os.path.splitext(new_name)[1]: raise ValidationError( _("Cannot change file type when renaming resource")) self.file_name = new_name def get_default_variant(self): return self.variants.get(tags="") def get_identifiers(self): return ResourceIdentifier.objects.filter(resource_file=self) def copy_all_variants_to_dir(self, path): filename_parts = os.path.splitext(self.file_name) for variant in self.variants.all(): abs_target = "%s/%s%s%s" % (path, filename_parts[0], variant.get_tags_string(), filename_parts[1]) if not abs_target.startswith(path): raise Exception(_("Suspicious filename: %s") % self.file_name) variant.copy_to_path(abs_target) def save(self, *args, **kwargs): self.clean_fields() self.project.last_modified = now() self.project.save() super(ResourceFile, self).save(*args, **kwargs) DIR_MAP = { 'pbi': 'images', 'png': 'images', 'png-trans': 'images', 'bitmap': 'images', 'font': 'fonts', 'raw': 'data' } def get_path(self, variant): return self.get_best_variant(variant).get_path() @property def root_path(self): # Try to get the default variant and return its path try: return self.get_default_variant().root_path except ResourceVariant.DoesNotExist: # Failing that, strip the suffixes off an existing one return self.variants.all()[0].root_path class Meta(IdeModel.Meta): unique_together = (('project', 'file_name'), )
class ResourceIdentifier(IdeModel): resource_file = models.ForeignKey(ResourceFile, related_name='identifiers') resource_id = models.CharField(max_length=100, validators=regexes.validator( 'c_identifier', _("Invalid resource ID."))) character_regex = models.CharField(max_length=100, blank=True, null=True) tracking = models.IntegerField(blank=True, null=True) compatibility = models.CharField(max_length=10, blank=True, null=True) target_platforms = models.CharField(max_length=100, null=True, blank=True, default=None) MEMORY_FORMATS = ( ('Smallest', _('Smallest')), ('SmallestPalette', _('Smallest Palette')), ('1Bit', _('1-bit')), ('8Bit', _('8-bit')), ('1BitPalette', _('1-bit Palette')), ('2BitPalette', _('2-bit Palette')), ('4BitPalette', _('4-bit Palette')), ) memory_format = models.CharField(max_length=15, choices=MEMORY_FORMATS, null=True, blank=True) STORAGE_FORMATS = (('pbi', _('1 bit Pebble Image')), ('png', _('PNG'))) storage_format = models.CharField(max_length=3, choices=STORAGE_FORMATS, null=True, blank=True) SPACE_OPTIMISATIONS = (('storage', _('Storage')), ('memory', _('Memory'))) space_optimisation = models.CharField(max_length=7, choices=SPACE_OPTIMISATIONS, null=True, blank=True) def get_options_dict(self, with_id=False): """ Return the ResourceIdentifier's options as a dictionary. Optionally include its ID in the key 'id' """ d = { # Resource ID 'target_platforms': json.loads(self.target_platforms) if self.target_platforms else None, # Font options 'regex': self.character_regex, 'tracking': self.tracking, 'compatibility': self.compatibility, # Bitmap options 'memory_format': self.memory_format, 'storage_format': self.storage_format, 'space_optimisation': self.space_optimisation } if with_id: d['id'] = self.resource_id return d def save(self, *args, **kwargs): self.resource_file.project.last_modified = now() self.resource_file.project.save() super(ResourceIdentifier, self).save(*args, **kwargs)