class ScreenshotShotOverride(APIModel): ENCRYPTED_ID_KEY_TOKEN = 'screenshot-override' screenshot_set = models.ForeignKey(ScreenshotSet, related_name='+', null=False, on_delete=models.DO_NOTHING) screenshot_shot = models.ForeignKey(ScreenshotShot, related_name='+', null=False, on_delete=models.DO_NOTHING) override_image = models.ForeignKey(Image, related_name='+', null=False, on_delete=models.DO_NOTHING) device_type = models.CharField(max_length=32) create_time = models.DateTimeField(auto_now_add=True) update_time = models.DateTimeField(auto_now=True) data = hstore_field.HStoreField(null=True) is_landscape = data.bool_property() class Meta: app_label = 'lk' unique_together = ('screenshot_shot', 'device_type') def to_dict(self): return { 'imageUrl': self.override_image.image_url(), 'deviceType': self.device_type, 'orientation': self.is_landscape }
class ScreenshotShot(APIModel): ENCRYPTED_ID_KEY_TOKEN = 'screenshot-shot' user = models.ForeignKey(User, related_name='+', null=True, on_delete=models.DO_NOTHING) screenshot_set = models.ForeignKey(ScreenshotSet, related_name='+', null=False, on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True) update_time = models.DateTimeField(auto_now=True) screenshot_image = models.ForeignKey(Image, related_name='+', null=False, on_delete=models.DO_NOTHING) background_image = models.ForeignKey(Image, related_name='+', null=True, on_delete=models.DO_NOTHING) config = hstore_field.HStoreField(null=True) label = config.string_property() label_position = config.string_property() font = config.string_property() font_size = config.int_property() font_weight = config.int_property() font_color = config.string_property() phone_color = config.string_property() background_color = config.string_property() tablet_is_landscape = config.bool_property() is_landscape = config.bool_property() def to_dict(self): return { 'id': self.encrypted_id, 'createTime': self.date_to_api_date(self.create_time), 'screenshot': self.screenshot_image.to_dict(), 'background': self.background_image and self.background_image.to_dict(), 'backgroundColor': self.background_color, 'overrides': self.get_overrides(), 'label': self.label, 'labelPosition': self.label_position, 'font': self.font, 'fontSize': self.font_size, 'fontWeight': self.font_weight, 'fontColor': self.font_color, 'phoneColor': self.phone_color or 'black', 'isLandscape': self.is_landscape } def get_overrides(self): overrides = ScreenshotShotOverride.objects.filter(screenshot_shot_id=self.id) devices = {} for override in overrides: devices[override.device_type] = override.to_dict() return devices
class AppStoreSalesReportNotification(APIModel): user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True, db_index=True) data = hstore_field.HStoreField() email = data.string_property() my_email = data.bool_property() slack_channel_name = data.string_property() slack_webhook = data.bool_property()
class AppStoreReviewNotification(APIModel): app = models.ForeignKey(AppStoreApp, related_name='+', on_delete=models.DO_NOTHING) user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True, db_index=True) data = hstore_field.HStoreField() email = data.string_property() my_email = data.bool_property() slack_channel_name = data.string_property() slack_webhook = data.bool_property() twitter_handle = data.string_property() reviews_count = data.int_property()
class SDKAppStat(APIModel): class Meta: unique_together = (('app', 'hour')) user = models.ForeignKey(User, related_name='+', db_index=False, on_delete=models.DO_NOTHING) app = models.ForeignKey(SDKApp, related_name='+', db_index=False, on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True) hour = models.DateTimeField() data = hstore_field.HStoreField()
class SDKSessionActivity(APIModel): class Meta: app_label = 'lk' index_together = (('user', 'create_time'), ) user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True) session = models.ForeignKey(SDKSession, related_name='+', on_delete=models.DO_NOTHING) kind = models.CharField(max_length=32) data = hstore_field.HStoreField() remote_addr = data.string_property()
class SDKSession(APIModel): class Meta: app_label = 'lk' index_together = (('user', 'last_accessed_time'), ) ENCRYPTED_ID_KEY_TOKEN = 'sdk-session' user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True) last_upgrade_time = models.DateTimeField(auto_now_add=True, null=True) last_accessed_time = models.DateTimeField(auto_now_add=True) app = models.ForeignKey(SDKApp, related_name='+', on_delete=models.DO_NOTHING) app_version = models.CharField(max_length=32, null=True) app_build = models.CharField(max_length=32, null=True) app_build_debug = models.NullBooleanField(null=True) sdk_platform = models.CharField(max_length=8, null=True) sdk_version = models.CharField(max_length=32, null=True) os = models.CharField(max_length=3, null=True) os_version = models.CharField(max_length=16, null=True) hardware = models.CharField(max_length=32, null=True) screen_height = models.PositiveIntegerField(null=True) screen_width = models.PositiveIntegerField(null=True) screen_scale = models.FloatField(null=True) sdk_user = models.ForeignKey(SDKUser, null=True, on_delete=models.DO_NOTHING) screens = models.PositiveIntegerField(default=0) taps = models.PositiveIntegerField(default=0) visits = models.PositiveIntegerField(default=0) seconds = models.PositiveIntegerField(default=0) data = hstore_field.HStoreField(null=True)
class SlackAccessToken(APIModel): class Meta: app_label = 'lk' # This should really be universally unique, but for testing's sake... unique_together = ('user', 'token') user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True) invalidated_time = models.DateTimeField(null=True) # invalidated tokens become null token = models.CharField(max_length=512, null=True) scope = models.CharField(max_length=128, null=False) # for slack-webhook type connections webhook_data = hstore_field.HStoreField(null=True) webhook_url = webhook_data.string_property() webhook_channel = webhook_data.string_property() webhook_config_url = webhook_data.string_property()
class AppStoreSalesReportSubscription(APIModel): ENCRYPTED_ID_KEY_TOKEN = 'appstore-sales-report-sub' create_time = models.DateTimeField(auto_now_add=True) update_time = models.DateTimeField(auto_now=True) latest_report_date = models.DateField(null=True) user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) enabled = models.BooleanField(default=False) invalidated_time = models.DateTimeField(null=True) data = hstore_field.HStoreField(null=True) email = data.string_property() my_email = data.bool_property() slack_url = data.string_property() slack_channel_id = data.string_property() slack_channel_name = data.string_property() def to_dict(self): d = { 'id': self.encrypted_id, 'createTime': self.date_to_api_date(self.create_time), } if self.email: d['email'] = self.email if self.my_email: d['myEmail'] = True if self.slack_url: d['slackUrl'] = self.slack_url if self.slack_channel_name: d['slackChannel'] = { 'name': self.slack_channel_name, } return d
class RuntimeConfigRule(APIModel): ENCRYPTED_ID_KEY_TOKEN = 'cloudconfiggintool' user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True) sort_time = models.DateTimeField(auto_now_add=True) update_time = models.DateTimeField(auto_now=True, null=True) key = models.CharField(max_length=64) kind = models.CharField(max_length=8, choices=ConfigKind.choices()) namespace = models.CharField(max_length=16, null=True) bundle_id = models.CharField(max_length=256, null=False) value = models.CharField(max_length=2048) description = models.CharField(max_length=2048, null=True) qualifiers = hstore_field.HStoreField(null=True) version = qualifiers.string_property() version_match = qualifiers.string_property() build = qualifiers.string_property() build_match = qualifiers.string_property() ios_version = qualifiers.string_property() ios_version_match = qualifiers.string_property() debug = qualifiers.bool_property() sdk_user_labels = qualifiers.string_property() @property def specificity(self): s = len(self.qualifiers or {}) if self.version_match == MatchOperator.EQUAL: s += 1 if self.build_match == MatchOperator.EQUAL: s += 1 if self.ios_version_match == MatchOperator.EQUAL: s += 1 return s @property def typed_value(self): if self.kind == ConfigKind.INT: return int(self.value) if self.kind == ConfigKind.FLOAT: return float(self.value) if self.kind == ConfigKind.BOOL: return self.value == '1' return self.value def set_typed_value(self, value): if self.kind == ConfigKind.INT: self.value = '%s' % value elif self.kind == ConfigKind.FLOAT: self.value = repr(value) elif self.kind == ConfigKind.BOOL: self.value = '%s' % int(value) else: self.value = value def to_dict(self): result = { 'id': self.encrypted_id, 'key': self.key, 'kind': self.kind, 'description': self.description, 'createTime': self.date_to_api_date(self.create_time), 'sortTime': self.date_to_api_date(self.sort_time), 'updateTime': self.date_to_api_date(self.update_time), 'value': self.typed_value, } if self.bundle_id: result['bundleId'] = self.bundle_id if self.version: result['version'] = self.version if self.version_match: result['versionMatch'] = self.version_match if self.build: result['build'] = self.build if self.build_match: result['buildMatch'] = self.build_match if self.ios_version: result['iosVersion'] = self.ios_version if self.ios_version_match: result['iosVersionMatch'] = self.ios_version_match return result
class User(AbstractBaseUser, APIModel): # Set this to None to get the default key. ENCRYPTED_ID_KEY_TOKEN = None USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['first_name', 'last_name'] first_name = models.CharField(max_length=30, blank=True, null=True) last_name = models.CharField(max_length=30, blank=True, null=True) email = models.EmailField(null=True, unique=True) phone = models.CharField(max_length=30, null=True, unique=True) is_staff = False is_active = True is_superuser = models.BooleanField(default=False) date_joined = models.DateTimeField(auto_now_add=True) delete_time = models.DateTimeField(null=True) # Random user attributes we find out. data = hstore_field.HStoreField(null=True) app_versions = hstore_field.HStoreField(null=True) flags = BitField(default=0, flags=[ 'has_unverified_email', # 0 'unsubscribed_from_email', # 1 'any_reviews_ready', # 2 'any_reviews_pending', # 3 'has_reviews_onboarded', # 4 'beta_optin', # 5 'has_review_monitor', # 6 'has_review_monitor_tweets', # 7 'has_screenshot_builder', # 8 'has_sales_monitor', # 9 'has_sales_report_ready', # 10 'has_sales_onboarded', # 11 'has_websites', # 12 'sent_beta_optin_email', # 13 'has_super_users', # 14 'has_config', # 15 'has_sent_tracking_data', # 16 ]) objects = UserManager() def set_unset_flags(self, set_flags=None, unset_flags=None): if not (set_flags or unset_flags): return flags_to_set_value = 0 for flag in (set_flags or []): flags_to_set_value |= getattr(User.flags, flag) setattr(self.flags, flag, True) flags_to_unset_value = 0 for flag in (unset_flags or []): flags_to_unset_value |= getattr(User.flags, flag) setattr(self.flags, flag, False) User.objects.filter(pk=self.id).update(flags=F('flags').bitor(flags_to_set_value).bitand(~flags_to_unset_value)) self.invalidate_cache() def set_flags(self, set_flags): self.set_unset_flags(set_flags=set_flags) def unset_flags(self, unset_flags): self.set_unset_flags(unset_flags=unset_flags) def mark_has_unverified_email(self, marked=True): if bool(self.flags.has_unverified_email) == marked: return if marked: self.set_flags(['has_unverified_email']) else: self.unset_flags(['has_unverified_email']) def has_perm(self, perm, obj=None): return self.is_superuser def has_module_perms(self, app_label): return self.is_superuser @property def _names(self): return [n for n in (self.first_name, self.last_name) if n] @property def initials(self): names = self._names if names: initials = [n[:1] for n in names] else: if self.email: initials = [self.email[:1]] return (''.join(initials)).upper() @property def full_name(self): name = ' '.join(self._names) if name: return name if self.email: return self.email @property def medium_name(self): names = self._names if len(names) == 2: return '%s %s.' % (names[0], names[1][:1]) if len(names) == 1: return names[0] if self.email: return self.email[:10] + '...' return None @property def short_name(self): names = self._names if len(names): return names[0] if self.email: return self.email[:8] + '...' return None def names_dict(self, first_last=False): names = { 'short': self.short_name, 'full': self.full_name, 'initials': self.initials, } if first_last: names['first'] = self.first_name names['last'] = self.last_name return names # # CACHING # @classmethod def cache_key_for_id(cls, user_id): return 'lk_user:%d' % user_id # # HANDY PROPERTIES # @property def small_avatar_url(self): # FIXME(Taylor) # if self.gae_avatar_url: # return self.gae_avatar_url + '=s200-c' return None @property def large_avatar_url(self): # FIXME(Taylor) # if self.gae_avatar_url: # return self.gae_avatar_url + '=s640-c' return None @property def twitter_handles(self): return [x.handle for x in self.twitter_access_tokens_set.filter(invalidated_time__isnull=True)] @property def products_list(self): products = [] if self.flags.has_review_monitor: products.append('reviews') if self.flags.has_screenshot_builder: products.append('screenshots') if self.flags.has_sales_monitor: products.append('sales') if self.flags.has_websites: products.append('websites') if self.flags.has_super_users: products.append('super_users') if self.flags.has_config: products.append('config') return products # # JSON # def to_dict(self): response = { 'id': self.encrypted_id, 'avatarUrls': { 'small': self.small_avatar_url, 'large': self.large_avatar_url, }, 'names': self.names_dict(first_last=True), 'createTime': self.date_to_api_date(self.date_joined), } if self.email: response['email'] = self.email if self.flags.has_unverified_email: response['hasUnverifiedEmail'] = True if self.flags.any_reviews_pending and not self.flags.any_reviews_ready: response['reviewsPending'] = True if self.flags.has_sales_report_ready: response['salesReportReady'] = True # TODO(Taylor): Generalize this somehow? if not self.flags.has_reviews_onboarded: response['needsReviewsOnboarding'] = True if not self.flags.has_sales_onboarded: response['needsSalesOnboarding'] = True if self.flags.beta_optin: response['beta'] = True response['products'] = self.products_list return response def to_minimal_dict(self): response = { 'id': self.encrypted_id, 'avatarUrls': { 'small': self.small_avatar_url, 'large': self.large_avatar_url, }, 'names': self.names_dict(), } return response def __unicode__(self): return self.full_name
class ScreenshotSet(APIModel): ENCRYPTED_ID_KEY_TOKEN = 'screenshot-set' user = models.ForeignKey(User, related_name='+', null=True, on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True) update_time = models.DateTimeField(auto_now=True) delete_time = models.DateTimeField(null=True, default=None) name = models.CharField(max_length=128) version = models.CharField(max_length=32) shot_count = models.PositiveIntegerField() decorated_images = None data = hstore_field.HStoreField(null=True) platform = data.string_property() @property def public_url(self): return '%sscreenshots/%s' % (settings.SITE_URL, self.encrypted_id) @property def as_image(self): return url2png_url(self.public_url) @property def app_store(self): if self.platform == "Android": return "Google Play" return "App Store" def tweet_text(self): content_format = u"Creating app store images for %s was so simple using Screenshot Builder" trimmed_content_length = 140 - text.TCO_URL_LENGTH - 1 - len(content_format % '') # Now ellipsize the content inside the quotes if necessary. name_version = '%s %s' % (self.name, self.version) if len(name_version) > trimmed_content_length: # - 1 here for ellipsis. name_version = u'%s…' % name_version[:trimmed_content_length - 1] return content_format % name_version @property def twitter_share_url(self): try: return urlutil.appendparams('https://twitter.com/share', text=self.tweet_text(), url=self.public_url) except UnicodeEncodeError: return None def to_dict(self): set_dict = { 'id': self.encrypted_id, 'createTime': self.date_to_api_date(self.create_time), 'updateTime': self.date_to_api_date(self.update_time), 'name': self.name, 'version': self.version, 'imageUrl': self.as_image, 'twitterShareUrl': self.twitter_share_url, 'shotCount': self.shot_count, 'platform': self.platform, 'appStore': self.app_store, } if self.decorated_images: set_dict['previewImages'] = [i.to_dict() for i in self.decorated_images] return set_dict
class SDKUser(APIModel): class Meta: app_label = 'lk' unique_together = ( 'app', 'unique_id', ) ENCRYPTED_ID_KEY_TOKEN = 'sdk-user' user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) app = models.ForeignKey(SDKApp, related_name='+', on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True) last_accessed_time = models.DateTimeField(auto_now_add=True) unique_id = models.CharField(max_length=128, null=True) name = models.CharField(max_length=128, null=True) email = models.CharField(max_length=128, null=True) screens = models.PositiveIntegerField(default=0) taps = models.PositiveIntegerField(default=0) visits = models.PositiveIntegerField(default=0) seconds = models.PositiveIntegerField(default=0) days_active_map = models.BigIntegerField(default=0) monthly_screens = models.PositiveIntegerField(default=0) monthly_taps = models.PositiveIntegerField(default=0) monthly_visits = models.PositiveIntegerField(default=0) monthly_seconds = models.PositiveIntegerField(default=0) monthly_days_active = models.PositiveIntegerField(default=0) labels = TextArrayField(null=True) data = hstore_field.HStoreField(null=True) def is_anonymous(self): return not (self.unique_id or self.name or self.email) @property def monthly_days_active_computed(self): return bitwise.num_bits_64(self.last_month_active_bitmap()) def last_month_active_bitmap(self, now_days_offset=0): offset = self.days_active_bitmap_offset_for_date( now_days_offset=now_days_offset) # This is DAYS_IN_MONTH - 1 here because today is bit 0. shift = offset - (DAYS_IN_MONTH - 1) days_active_map = (self.days_active_map or 0) & bitwise.BITS_64 shifted_map = bitwise.wrapping_right_shift_64(days_active_map, shift) return shifted_map & bitwise.flipped_bits_64(DAYS_IN_MONTH) def weekly_days_active(self, now_days_offset=0): return bitwise.num_bits_64( self.weekly_days_active_bitmap(now_days_offset=now_days_offset)) def weekly_days_active_bitmap(self, now_days_offset=0): return self.last_month_active_bitmap_reversed( now_days_offset=now_days_offset) & 0b1111111 def last_month_active_bitmap_reversed(self, now_days_offset=0): # You will get a DAYS_IN_MONTH-long bitmap, shifted so that: # today is (map & 1), yest. is (map & (1 << 1)), 2d ago is (map & (1 << 2)) return bitwise.reverse_bits( self.last_month_active_bitmap(now_days_offset=now_days_offset), DAYS_IN_MONTH) def days_active_bitmap_offset_for_date(self, now_date=None, now_days_offset=0): if now_date is None: now_date = datetime.now().date() if now_days_offset is not None: now_date += timedelta(days=now_days_offset) return (now_date - self.create_time.date()).days % 64 def days_active_bitmap_valid_bitmask(self, now_days_offset=0): # By default, this is tomorrow's offset. offset = self.days_active_bitmap_offset_for_date( now_days_offset=(1 + now_days_offset)) return bitwise.trailing_window_bitmask_64(offset, DAYS_IN_MONTH) @property def filtered_labels(self): if not self.labels: return [] labels = [] for label in ['super', 'almost', 'fringe']: if label in self.labels: labels.append(label) if 'active-1m' in self.labels: labels.append('active') else: labels.append('inactive') return labels def to_client_dict(self): return { 'name': self.name, 'uniqueId': self.unique_id, 'email': self.email, 'firstVisit': self.date_to_api_date(self.create_time), 'stats': { 'visits': self.monthly_visits, 'days': self.monthly_days_active_computed, }, 'labels': self.filtered_labels, } def to_dict(self, include_raw_labels=False): user = { 'id': self.encrypted_id, 'appId': SDKApp.encrypt_id(self.app_id), 'name': self.name, 'uniqueId': self.unique_id, 'email': self.email, 'latestVisit': self.date_to_api_date(self.last_accessed_time), 'firstVisit': self.date_to_api_date(self.create_time), 'stats': { 'screens': self.monthly_screens, 'taps': self.monthly_taps, 'visits': self.monthly_visits, 'seconds': self.monthly_seconds, 'days': self.monthly_days_active_computed, }, 'labels': self.filtered_labels, } if include_raw_labels: user['rawLabels'] = self.labels return user
class SDKUserIncrementLog(APIModel): sdk_user = models.ForeignKey(SDKUser, on_delete=models.DO_NOTHING) create_time = models.DateTimeField() data = hstore_field.HStoreField(null=True)
class AppStoreAppInfo(APIModel): create_time = models.DateTimeField(auto_now_add=True) app = models.ForeignKey(AppStoreApp, related_name='+', db_index=False, on_delete=models.DO_NOTHING) data = hstore_field.HStoreField() itunes_id = data.long_property() bundle_id = data.string_property() mac_software = data.bool_property() name = data.string_property() description = data.string_property() release_notes = data.string_property() version = data.string_property() icon_60 = data.string_property() icon_100 = data.string_property() icon_512 = data.string_property() category = data.int_property() price = data.float_property() currency = data.string_property() size_bytes = data.long_property() rating = data.float_property() reviews_count = data.int_property() current_version_rating = data.float_property() current_version_reviews_count = data.int_property() content_rating = data.string_property() # 4+, etc. developer_id = data.long_property() developer_url = data.string_property() developer_name = data.string_property() categories = IntegerArrayField() screenshots = TextArrayField() ipad_screenshots = TextArrayField(null=True) release_date = models.DateTimeField() country = models.CharField(null=True, max_length=2) @property def short_name(self): return text.app_short_name(self.name) def to_tiny_dict(self): return { 'iTunesId': self.itunes_id, 'name': self.name, 'icon': { 'small': self.icon_60, }, 'developer': { 'id': self.developer_id, 'name': self.developer_name, }, } def to_dict(self): full_dict = self.to_tiny_dict() full_dict['bundleId'] = self.bundle_id full_dict['version'] = self.version full_dict['category'] = self.category full_dict['description'] = self.description full_dict['icon']['medium'] = self.icon_100 full_dict['icon']['large'] = self.icon_512 full_dict['developer']['url'] = self.developer_url full_dict['rating'] = self.rating full_dict['reviewCount'] = self.reviews_count full_dict['currentRating'] = self.current_version_rating full_dict['currentRatingStars'] = ''.join( [u'★'] * int(self.current_version_rating)) full_dict['currentReviewCount'] = self.current_version_reviews_count full_dict['screenshots'] = self.screenshots return full_dict
class SDKApp(APIModel): class Meta: app_label = 'lk' unique_together = ( 'user', 'bundle_id', ) ENCRYPTED_ID_KEY_TOKEN = 'sdk-app' user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True) update_time = models.DateTimeField(auto_now=True) latest_track_time = models.DateTimeField(null=True) display_name = models.CharField(max_length=128, null=True) bundle_id = models.CharField(max_length=128, null=False) latest_debug_version = models.CharField(max_length=24, null=True) latest_prod_version = models.CharField(max_length=24, null=True) appstore_app = models.ForeignKey(AppStoreApp, null=True, related_name='+', on_delete=models.DO_NOTHING) appstore_app_country = models.CharField(max_length=2, null=True) super_config = TextArrayField(default=_default_super_config, null=False) almost_config = TextArrayField(default=_default_almost_config, null=False) config_parent = models.ForeignKey('self', null=True, related_name='config_children', on_delete=models.DO_NOTHING) products = hstore_field.HStoreField(null=True) config = products.bool_property() super_users = products.bool_property() # Decorated properties. decorated_label_counts = None decorated_config_children = None @property def name(self): if self.display_name: return self.display_name if self.appstore_app: return self.appstore_app.name return self.bundle_id @property def short_name(self): return text.app_short_name(self.name) def to_dict(self): products = [p for p in (self.products or {}) if getattr(self, p)] app = { 'id': self.encrypted_id, 'bundleId': self.bundle_id, 'iTunesId': self.appstore_app and self.appstore_app.itunes_id, 'names': { 'short': self.short_name, 'full': self.name, 'display': self.display_name, }, 'icons': { 'small': self.appstore_app and self.appstore_app.public_small_icon, 'medium': self.appstore_app and self.appstore_app.public_medium_icon, }, 'latestTrackTime': self.date_to_api_date(self.latest_track_time), 'latestVersion': { 'debug': self.latest_debug_version, 'prod': self.latest_prod_version, }, 'products': products, } if self.super_users: super_freq, super_time = self.super_config app['super'] = { 'freq': super_freq, 'time': super_time, } if self.decorated_label_counts is not None: # Return a simplified list here. app['stats'] = { 'active': self.decorated_label_counts.get('active-1m', 0), 'inactive': self.decorated_label_counts.get('inactive-1m', 0), } if self.super_users: app['stats']['super'] = self.decorated_label_counts.get( 'super', 0) app['stats']['almost'] = self.decorated_label_counts.get( 'almost', 0) app['stats']['fringe'] = self.decorated_label_counts.get( 'fringe', 0) if self.decorated_config_children: app['configChildren'] = [ a.to_dict() for a in self.decorated_config_children ] elif self.config_parent_id: app['configChild'] = True return app
class AppWebsite(APIModel): PLATFORMS = ['iPhone'] ENCRYPTED_ID_KEY_TOKEN = 'appwebsites' domain = models.CharField(max_length=100, unique=True, null=True) user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) logo = models.ForeignKey(Image, related_name='+', null=True, on_delete=models.DO_NOTHING) background = models.ForeignKey(Image, related_name='+', null=True, on_delete=models.DO_NOTHING) icon = models.ForeignKey(Image, related_name='+', null=True, on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True, db_index=True) update_time = models.DateTimeField(auto_now=True) delete_time = models.DateTimeField(null=True, default=None) data = hstore_field.HStoreField(null=True) template = data.string_property() app_name = data.string_property() tagline = data.string_property() short_description = data.string_property() long_description = data.string_property() keywords = data.string_property() google_analytics_id = data.string_property() itunes_id = data.string_property() play_store_id = data.string_property() waiting_list_link = data.string_property() waiting_list_label = data.string_property() itunes_campaign_token = data.string_property() itunes_provider_token = data.string_property() support_link = data.string_property() blog_link = data.string_property() login_link = data.string_property() press_link = data.string_property() terms_link = data.string_property() privacy_link = data.string_property() twitter_link = data.string_property() facebook_link = data.string_property() instagram_link = data.string_property() custom_link = data.string_property() custom_link_label = data.string_property() disable_lk_branding = data.bool_property() mixpanel_badge = data.bool_property() terms_text = data.string_property() privacy_text = data.string_property() support_text = data.string_property() primary_color = data.string_property() font = data.string_property() frame_screenshots = data.string_property() custom_css = data.string_property() def to_dict(self, include_pages=False): screenshots = list( self.screenshots .select_related('image') .order_by('order') ) screenshots_by_platform = {p: [] for p in self.PLATFORMS} for screenshot in screenshots: screenshots_by_platform[screenshot.platform].append(screenshot.to_dict()) images = { 'screenshots': screenshots_by_platform, } if self.icon: images['icon'] = {'id': self.icon.encrypted_id, 'url': self.icon.gae_image_url} if self.logo: images['logo'] = {'id': self.logo.encrypted_id, 'url': self.logo.gae_image_url} if self.background: images['background'] = {'id': self.background.encrypted_id, 'url': self.background.gae_image_url} website_data = { 'id': self.encrypted_id, 'domain': self.domain, 'googleAnalyticsId': self.google_analytics_id, 'template': self.template, 'appName': self.app_name, 'tagline': self.tagline, 'shortDescription': self.short_description, 'longDescription': self.long_description, 'keywords': self.keywords, 'author': self.user.full_name, 'itunesId': self.itunes_id, 'playStoreId': self.play_store_id, 'waitingListLink': self.waiting_list_link, 'waitingListLabel': self.waiting_list_label, 'itunesCampaignToken': self.itunes_campaign_token, 'itunesProviderToken': self.itunes_provider_token, 'supportLink': self.support_link, 'blogLink': self.blog_link, 'loginLink': self.login_link, 'pressLink': self.press_link, 'termsLink': self.terms_link, 'privacyLink': self.privacy_link, 'twitterLink': self.twitter_link, 'facebookLink': self.facebook_link, 'instagramLink': self.instagram_link, 'customLink': self.custom_link, 'customLinkLabel': self.custom_link_label, 'primaryColor': self.primary_color, 'font': self.font, 'disableLkBranding': self.disable_lk_branding, 'mixpanelBadge': self.mixpanel_badge, 'frameScreenshots': self.frame_screenshots, 'images': images, 'customCss': self.custom_css } pages = AppWebsitePage.objects.filter(website=self.id) for page in pages: if include_pages: website_data[page.slug] = page.body elif page.body: website_data[page.slug] = True return website_data @property def public_page_link(self): return '%swebsites/dashboard/%s/public/' % (settings.SITE_URL, self.encrypted_id)
class Image(APIModel): ENCRYPTED_ID_KEY_TOKEN = 'image' kind = models.CharField(max_length=32, null=True) user = models.ForeignKey(User, related_name='+', null=True, on_delete=models.DO_NOTHING) create_time = models.DateTimeField(auto_now_add=True) update_time = models.DateTimeField(auto_now=True) ref_count = models.PositiveIntegerField(default=0) deleted = models.BooleanField(default=False) time_taken = models.DateTimeField(null=True) data = hstore_field.HStoreField(null=True) location_latitude = data.float_property() location_longitude = data.float_property() gae_image_url = data.string_property() format = data.string_property() width = data.int_property() height = data.int_property() size_bytes = data.int_property() def increment_ref_count(self): Image.objects.filter(id=self.id).update(ref_count=F('ref_count') + 1) self.ref_count += 1 def decrement_ref_count(self): Image.objects.filter(id=self.id).update(ref_count=F('ref_count') - 1) self.ref_count -= 1 @property def extension(self): # TODO(Taylor): jpeg to jpg? etc. return self.format def image_url(self, width=None, height=None, quality=0): args = [] if width is None and height is None: args.append('s0') else: if width: args.append('w%d' % width) if height: args.append('h%d' % height) if width and height: # crop the image rather than making it tiny args.append('c') if quality: # quality should be 0-100 args.append('q%d' % quality) #return self.gae_image_url + '=%s' % '-'.join(args) self.gae_image_url + '=%s' % '-'.join(args) self.gae_image_url = self.gae_image_url.replace( "localhost", "192.168.0.100") return self.gae_image_url def to_dict(self): return { 'id': self.encrypted_id, 'width': self.width, 'height': self.height, 'imageUrls': { 'small': self.image_url(width=200), 'medium': self.image_url(width=640), 'full': self.image_url(), } }
class AppStoreReviewSubscription(APIModel): ENCRYPTED_ID_KEY_TOKEN = 'appstore-review-sub' create_time = models.DateTimeField(auto_now_add=True) update_time = models.DateTimeField(auto_now=True) user = models.ForeignKey(User, related_name='+', on_delete=models.DO_NOTHING) enabled = models.BooleanField(default=False) invalidated_time = models.DateTimeField(null=True) last_notification_time = models.DateTimeField(null=True) twitter_connection = models.OneToOneField(TwitterAppConnection, related_name='subscription', null=True) data = hstore_field.HStoreField(null=True) email = data.string_property() my_email = data.bool_property() slack_url = data.string_property() slack_channel_id = data.string_property() slack_channel_name = data.string_property() filter_good = data.bool_property() filter_very_good = data.bool_property() filter_app = models.ForeignKey(AppStoreApp, related_name='+', null=True, on_delete=models.DO_NOTHING) def to_dict(self): d = { 'id': self.encrypted_id, 'createTime': self.date_to_api_date(self.create_time), 'lastNotificationTime': self.date_to_api_date(self.last_notification_time), 'filter': {}, } if self.email: d['email'] = self.email if self.my_email: d['myEmail'] = True if self.slack_url: d['slackUrl'] = self.slack_url if self.slack_channel_name: d['slackChannel'] = {'name': self.slack_channel_name} if self.twitter_connection_id: d['twitterHandle'] = self.twitter_connection.handle if self.filter_good: d['filter']['good'] = True if self.filter_very_good: d['filter']['veryGood'] = True if self.filter_app_id: d['filter']['app'] = self.filter_app.to_dict() return d