class CollectionAddon(amo.models.ModelBase): addon = models.ForeignKey(Addon) collection = models.ForeignKey(Collection) # category (deprecated: for "Fashion Your Firefox") comments = LinkifiedField(null=True) downloads = models.PositiveIntegerField(default=0) user = models.ForeignKey(UserProfile, null=True) ordering = models.PositiveIntegerField(default=0, help_text='Add-ons are displayed in ascending order ' 'based on this field.') class Meta(amo.models.ModelBase.Meta): db_table = 'addons_collections' unique_together = (('addon', 'collection'),)
class License(amo.models.ModelBase): OTHER = 0 name = TranslatedField(db_column='name') url = models.URLField(null=True) builtin = models.PositiveIntegerField(default=OTHER) text = LinkifiedField() on_form = models.BooleanField(default=False, help_text='Is this a license choice in the devhub?') some_rights = models.BooleanField(default=False, help_text='Show "Some Rights Reserved" instead of the license name?') icons = models.CharField(max_length=255, null=True, help_text='Space-separated list of icon identifiers.') objects = LicenseManager() class Meta: db_table = 'licenses' def __unicode__(self): return unicode(self.name)
class CollectionAddon(amo.models.ModelBase): addon = models.ForeignKey(Addon) collection = models.ForeignKey(Collection) # category (deprecated: for "Fashion Your Firefox") comments = LinkifiedField(null=True) downloads = models.PositiveIntegerField(default=0) user = models.ForeignKey(UserProfile, null=True) ordering = models.PositiveIntegerField( default=0, help_text='Add-ons are displayed in ascending order ' 'based on this field.') class Meta(amo.models.ModelBase.Meta): db_table = 'addons_collections' unique_together = (('addon', 'collection'), ) @staticmethod def post_save_or_delete(sender, instance, **kwargs): """Update Collection.addon_count.""" from . import tasks tasks.collection_meta.delay(instance.collection_id, using='default')
class FancyModel(amo.models.ModelBase): """Mix it up with purified and linkified fields.""" purified = PurifiedField() linkified = LinkifiedField()
class Collection(CollectionBase, amo.models.ModelBase): TYPE_CHOICES = amo.COLLECTION_CHOICES.items() uuid = models.CharField(max_length=36, blank=True, unique=True) name = TranslatedField(require_locale=False) # nickname is deprecated. Use slug. nickname = models.CharField(max_length=30, blank=True, unique=True, null=True) slug = models.CharField(max_length=30, blank=True, null=True) description = LinkifiedField(require_locale=False) default_locale = models.CharField(max_length=10, default='en-US', db_column='defaultlocale') type = models.PositiveIntegerField(db_column='collection_type', choices=TYPE_CHOICES, default=0) icontype = models.CharField(max_length=25, blank=True) listed = models.BooleanField( default=True, help_text='Collections are either listed or private.') subscribers = models.PositiveIntegerField(default=0) downloads = models.PositiveIntegerField(default=0) weekly_subscribers = models.PositiveIntegerField(default=0) monthly_subscribers = models.PositiveIntegerField(default=0) application = models.ForeignKey(Application, null=True) addon_count = models.PositiveIntegerField(default=0, db_column='addonCount') upvotes = models.PositiveIntegerField(default=0) downvotes = models.PositiveIntegerField(default=0) rating = models.FloatField(default=0) all_personas = models.BooleanField( default=False, help_text='Does this collection only contain personas?') addons = models.ManyToManyField(Addon, through='CollectionAddon', related_name='collections') author = models.ForeignKey(UserProfile, null=True, related_name='collections') users = models.ManyToManyField(UserProfile, through='CollectionUser', related_name='collections_publishable') addon_index = models.CharField( max_length=40, null=True, db_index=True, help_text='Custom index for the add-ons in this collection') # This gets overwritten in the transformer. share_counts = collections.defaultdict(int) objects = CollectionManager() top_tags = TopTags() class Meta(amo.models.ModelBase.Meta): db_table = 'collections' unique_together = (('author', 'slug'), ) def __unicode__(self): return u'%s (%s)' % (self.name, self.addon_count) def flush_urls(self): urls = ['*%s' % self.get_url_path(), self.icon_url] return urls def save(self, **kw): if not self.uuid: self.uuid = unicode(uuid.uuid4()) if not self.slug: self.slug = self.uuid[:30] self.clean_slug() # Maintain our index of add-on ids. if self.id: ids = self.addons.values_list('id', flat=True) self.addon_index = self.make_index(ids) super(Collection, self).save(**kw) def clean_slug(self): if self.type in SPECIAL_SLUGS: self.slug = SPECIAL_SLUGS[self.type] return if self.slug in SPECIAL_SLUGS.values(): self.slug += '~' if not self.author: return qs = self.author.collections.using('default') slugs = dict((slug, id) for slug, id in qs.values_list('slug', 'id')) if self.slug in slugs and slugs[self.slug] != self.id: for idx in range(len(slugs)): new = '%s-%s' % (self.slug, idx + 1) if new not in slugs: self.slug = new return def get_url_path(self): return reverse('collections.detail', args=[self.author_username, self.slug]) def get_abs_url(self): return absolutify(self.get_url_path()) def get_img_dir(self): return os.path.join(settings.COLLECTIONS_ICON_PATH, str(self.id / 1000)) def upvote_url(self): return reverse('collections.vote', args=[self.author_username, self.slug, 'up']) def downvote_url(self): return reverse('collections.vote', args=[self.author_username, self.slug, 'down']) def edit_url(self): return reverse('collections.edit', args=[self.author_username, self.slug]) def watch_url(self): return reverse('collections.watch', args=[self.author_username, self.slug]) def delete_url(self): return reverse('collections.delete', args=[self.author_username, self.slug]) def delete_icon_url(self): return reverse('collections.delete_icon', args=[self.author_username, self.slug]) def share_url(self): return reverse('collections.share', args=[self.author_username, self.slug]) def feed_url(self): return reverse('collections.detail.rss', args=[self.author_username, self.slug]) def stats_url(self): return reverse('collections.stats', args=[self.author_username, self.slug]) @property def author_username(self): return self.author.username if self.author else 'anonymous' @classmethod def get_fallback(cls): return cls._meta.get_field('default_locale') @property def url_slug(self): """uuid or nickname if chosen""" return self.nickname or self.uuid @property def icon_url(self): modified = int(time.mktime(self.modified.timetuple())) if self.icontype: # [1] is the whole ID, [2] is the directory split_id = re.match(r'((\d*?)\d{1,3})$', str(self.id)) return settings.COLLECTION_ICON_URL % (split_id.group(2) or 0, self.id, modified) elif self.type == amo.COLLECTION_FAVORITES: return settings.MEDIA_URL + 'img/icons/heart.png' else: return settings.MEDIA_URL + 'img/icons/collection.png' def set_addons(self, addon_ids, comments={}): """Replace the current add-ons with a new list of add-on ids.""" order = dict((a, idx) for idx, a in enumerate(addon_ids)) # Partition addon_ids into add/update/remove buckets. existing = set( self.addons.using('default').values_list('id', flat=True)) add, update = [], [] for addon in addon_ids: bucket = update if addon in existing else add bucket.append((addon, order[addon])) remove = existing.difference(addon_ids) cursor = connection.cursor() now = datetime.now() if remove: cursor.execute("DELETE FROM addons_collections " "WHERE collection_id=%s AND addon_id IN (%s)" % (self.id, ','.join(map(str, remove)))) if self.listed: for addon in remove: amo.log(amo.LOG.REMOVE_FROM_COLLECTION, (Addon, addon), self) if add: insert = '(%s, %s, %s, NOW(), NOW(), 0)' values = [insert % (a, self.id, idx) for a, idx in add] cursor.execute(""" INSERT INTO addons_collections (addon_id, collection_id, ordering, created, modified, downloads) VALUES %s""" % ','.join(values)) if self.listed: for addon_id, idx in add: amo.log(amo.LOG.ADD_TO_COLLECTION, (Addon, addon_id), self) for addon, ordering in update: (CollectionAddon.objects.filter( collection=self.id, addon=addon).update(ordering=ordering, modified=now)) for addon, comment in comments.iteritems(): c = (CollectionAddon.objects.using('default').filter( collection=self.id, addon=addon)) if c: c[0].comments = comment c[0].save(force_update=True) self.save() def is_subscribed(self, user): """Determines if the user is subscribed to this collection.""" return self.following.filter(user=user).exists() def add_addon(self, addon): "Adds an addon to the collection." CollectionAddon.objects.get_or_create(addon=addon, collection=self) if self.listed: amo.log(amo.LOG.ADD_TO_COLLECTION, addon, self) self.save() # To invalidate Collection. def remove_addon(self, addon): CollectionAddon.objects.filter(addon=addon, collection=self).delete() if self.listed: amo.log(amo.LOG.REMOVE_FROM_COLLECTION, addon, self) self.save() # To invalidate Collection. def owned_by(self, user): return (user.id == self.author_id) def can_view_stats(self, request): from access import acl if request and request.amo_user: return (self.publishable_by(request.amo_user) or acl.action_allowed(request, 'CollectionStats', 'View')) return False @caching.cached_method def publishable_by(self, user): return bool(self.owned_by(user) or self.users.filter(pk=user.id)) @staticmethod def transformer(collections): if not collections: return author_ids = set(c.author_id for c in collections) authors = dict( (u.id, u) for u in UserProfile.objects.filter(id__in=author_ids)) for c in collections: c.author = authors.get(c.author_id) c_dict = dict((c.pk, c) for c in collections) sharing.attach_share_counts(CollectionShareCountTotal, 'collection', c_dict) @staticmethod def post_save(sender, instance, **kwargs): from . import tasks if kwargs.get('raw'): return tasks.collection_meta.delay(instance.id, using='default') tasks.index_collections.delay([instance.id]) @staticmethod def post_delete(sender, instance, **kwargs): from . import tasks if kwargs.get('raw'): return tasks.unindex_collections.delay([instance.id])