Beispiel #1
0
class Collection(ModelBase):
    TYPE_CHOICES = amo.COLLECTION_CHOICES.items()

    # TODO: Use models.UUIDField but it uses max_length=32 hex (no hyphen)
    # uuids so needs some migration.
    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 = NoLinksNoMarkupField(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.PositiveIntegerField(choices=amo.APPS_CHOICES,
                                              db_column='application_id',
                                              null=True,
                                              db_index=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 Themes?')

    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')

    objects = CollectionManager()

    top_tags = TopTags()

    class Meta(ModelBase.Meta):
        db_table = 'collections'
        unique_together = (('author', 'slug'), )

    def __unicode__(self):
        return u'%s (%s)' % (self.name, self.addon_count)

    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()

        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(user_media_path('collection_icons'),
                            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))
            path = "/".join([
                split_id.group(2) or '0',
                "%s.png?m=%s" % (self.id, modified)
            ])
            return user_media_url('collection_icons') + path
        elif self.type == amo.COLLECTION_FAVORITES:
            return settings.STATIC_URL + 'img/icons/heart.png'
        else:
            return settings.STATIC_URL + 'img/icons/collection.png'

    def set_addons(self, addon_ids, comments=None):
        """Replace the current add-ons with a new list of add-on ids."""
        if comments is None:
            comments = {}
        order = {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)
        now = datetime.now()

        with connection.cursor() as cursor:
            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:
                        activity.log_create(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:
                        activity.log_create(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():
            try:
                c = (CollectionAddon.objects.using('default').get(
                    collection=self.id, addon=addon))
            except CollectionAddon.DoesNotExist:
                pass
            else:
                c.comments = comment
                c.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:
            activity.log_create(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:
            activity.log_create(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):
        if request and request.user:
            return (self.publishable_by(request.user) or acl.action_allowed(
                request, amo.permissions.COLLECTION_STATS_VIEW))
        return False

    def publishable_by(self, user):
        return bool(self.owned_by(user) or self.users.filter(pk=user.id))

    def is_public(self):
        return self.listed

    def is_featured(self):
        return FeaturedCollection.objects.filter(collection=self).exists()

    @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)

    @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])
        if instance.is_featured():
            Collection.update_featured_status(sender, instance, **kwargs)

    @staticmethod
    def post_delete(sender, instance, **kwargs):
        from . import tasks
        if kwargs.get('raw'):
            return
        tasks.unindex_collections.delay([instance.id])
        if instance.is_featured():
            Collection.update_featured_status(sender, instance, **kwargs)

    @staticmethod
    def update_featured_status(sender, instance, **kwargs):
        from olympia.addons.tasks import index_addons
        addons = [addon.id for addon in instance.addons.all()]
        if addons:
            clear_get_featured_ids_cache(None, None)
            index_addons.delay(addons)

    def check_ownership(self, request, require_owner, require_author,
                        ignore_disabled, admin):
        """
        Used by acl.check_ownership to see if request.user has permissions for
        the collection.
        """
        from olympia.access import acl
        return acl.check_collection_ownership(request, self, require_owner)
Beispiel #2
0
class Collection(ModelBase):
    id = PositiveAutoField(primary_key=True)
    TYPE_CHOICES = amo.COLLECTION_CHOICES.items()

    uuid = models.UUIDField(blank=True, unique=True, null=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 = NoLinksNoMarkupField(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)

    listed = models.BooleanField(
        default=True, help_text='Collections are either listed or private.')

    application = models.PositiveIntegerField(choices=amo.APPS_CHOICES,
                                              db_column='application_id',
                                              blank=True,
                                              null=True,
                                              db_index=True)
    addon_count = models.PositiveIntegerField(default=0,
                                              db_column='addonCount')

    all_personas = models.BooleanField(
        default=False, help_text='Does this collection only contain Themes?')

    addons = models.ManyToManyField(Addon,
                                    through='CollectionAddon',
                                    related_name='collections')
    author = models.ForeignKey(UserProfile,
                               null=True,
                               related_name='collections')

    objects = CollectionManager()

    class Meta(ModelBase.Meta):
        db_table = 'collections'
        unique_together = (('author', 'slug'), )

    def __str__(self):
        return u'%s (%s)' % (self.name, self.addon_count)

    def save(self, **kw):
        if not self.uuid:
            self.uuid = uuid.uuid4()
        if not self.slug:
            # Work with both, strings (if passed manually on .create()
            # and UUID instances)
            self.slug = str(self.uuid).replace('-', '')[:30]
        self.clean_slug()

        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_id, self.slug])

    def get_abs_url(self):
        return absolutify(self.get_url_path())

    def edit_url(self):
        return reverse('collections.edit', args=[self.author_id, self.slug])

    def delete_url(self):
        return reverse('collections.delete', args=[self.author_id, self.slug])

    def share_url(self):
        return reverse('collections.share', args=[self.author_id, self.slug])

    def stats_url(self):
        return reverse('collections.stats', args=[self.author_id, self.slug])

    @classmethod
    def get_fallback(cls):
        return cls._meta.get_field('default_locale')

    def set_addons(self, addon_ids, comments=None):
        """Replace the current add-ons with a new list of add-on ids."""
        if comments is None:
            comments = {}
        order = {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)
        now = datetime.now()

        with connection.cursor() as cursor:
            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:
                        activity.log_create(amo.LOG.REMOVE_FROM_COLLECTION,
                                            (Addon, addon), self)
            if add:
                insert = '(%s, %s, %s, NOW(), NOW())'
                values = [insert % (a, self.id, idx) for a, idx in add]
                cursor.execute("""
                    INSERT INTO addons_collections
                        (addon_id, collection_id, ordering, created, modified)
                    VALUES %s""" % ','.join(values))
                if self.listed:
                    for addon_id, idx in add:
                        activity.log_create(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 six.iteritems(comments):
            try:
                c = (CollectionAddon.objects.using('default').get(
                    collection=self.id, addon=addon))
            except CollectionAddon.DoesNotExist:
                pass
            else:
                c.comments = comment
                c.save(force_update=True)

        self.save()

    def add_addon(self, addon):
        "Adds an addon to the collection."
        CollectionAddon.objects.get_or_create(addon=addon, collection=self)
        if self.listed:
            activity.log_create(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:
            activity.log_create(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):
        if request and request.user:
            return (self.owned_by(request.user) or acl.action_allowed(
                request, amo.permissions.COLLECTION_STATS_VIEW))
        return False

    def is_public(self):
        return self.listed

    def is_featured(self):
        return FeaturedCollection.objects.filter(collection=self).exists()

    @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)

    @staticmethod
    def post_save(sender, instance, **kwargs):
        from . import tasks
        if kwargs.get('raw'):
            return
        tasks.collection_meta.delay(instance.id)
        if instance.is_featured():
            Collection.update_featured_status(sender, instance, **kwargs)

    @staticmethod
    def post_delete(sender, instance, **kwargs):
        if kwargs.get('raw'):
            return
        if instance.is_featured():
            Collection.update_featured_status(sender, instance, **kwargs)

    @staticmethod
    def update_featured_status(sender, instance, **kwargs):
        from olympia.addons.tasks import index_addons
        addons = kwargs.get('addons',
                            [addon.id for addon in instance.addons.all()])
        if addons:
            clear_get_featured_ids_cache(None, None)
            index_addons.delay(addons)

    def check_ownership(self, request, require_owner, require_author,
                        ignore_disabled, admin):
        """
        Used by acl.check_ownership to see if request.user has permissions for
        the collection.
        """
        from olympia.access import acl
        return acl.check_collection_ownership(request, self, require_owner)
Beispiel #3
0
class Collection(ModelBase):
    id = PositiveAutoField(primary_key=True)
    TYPE_CHOICES = amo.COLLECTION_CHOICES.items()

    uuid = models.UUIDField(blank=True, unique=True, null=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 = NoLinksNoMarkupField(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)

    listed = models.BooleanField(
        default=True, help_text='Collections are either listed or private.')

    application = models.PositiveIntegerField(choices=amo.APPS_CHOICES,
                                              db_column='application_id',
                                              blank=True, null=True)
    addon_count = models.PositiveIntegerField(default=0,
                                              db_column='addonCount')

    addons = models.ManyToManyField(
        Addon, through='CollectionAddon', related_name='collections')
    author = models.ForeignKey(
        UserProfile, null=True, related_name='collections',
        on_delete=models.CASCADE)

    objects = CollectionManager()

    class Meta(ModelBase.Meta):
        db_table = 'collections'
        indexes = [
            models.Index(fields=('application',), name='application_id'),
            models.Index(fields=('created',), name='created_idx'),
            models.Index(fields=('listed',), name='listed'),
            models.Index(fields=('slug',), name='slug_idx'),
            models.Index(fields=('type',), name='type_idx'),
        ]
        constraints = [
            models.UniqueConstraint(fields=('author', 'slug'),
                                    name='author_id'),
        ]

    def __str__(self):
        return u'%s (%s)' % (self.name, self.addon_count)

    def save(self, **kw):
        if not self.uuid:
            self.uuid = uuid.uuid4()
        if not self.slug:
            # Work with both, strings (if passed manually on .create()
            # and UUID instances)
            self.slug = str(self.uuid).replace('-', '')[:30]
        self.clean_slug()

        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_id, self.slug])

    def get_abs_url(self):
        return absolutify(self.get_url_path())

    @classmethod
    def get_fallback(cls):
        return cls._meta.get_field('default_locale')

    def add_addon(self, addon):
        CollectionAddon.objects.get_or_create(addon=addon, collection=self)

    def remove_addon(self, addon):
        CollectionAddon.objects.filter(addon=addon, collection=self).delete()

    def owned_by(self, user):
        return user.id == self.author_id

    def can_view_stats(self, request):
        if request and request.user:
            return (self.owned_by(request.user) or
                    acl.action_allowed(request,
                                       amo.permissions.COLLECTION_STATS_VIEW))
        return False

    def is_public(self):
        return self.listed

    @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)

    @staticmethod
    def post_save(sender, instance, **kwargs):
        from . import tasks
        if kwargs.get('raw'):
            return
        tasks.collection_meta.delay(instance.id)

    def index_addons(self, addons=None):
        """Index add-ons belonging to that collection."""
        from olympia.addons.tasks import index_addons
        addon_ids = [addon.id for addon in (addons or self.addons.all())]
        if addon_ids:
            index_addons.delay(addon_ids)

    def check_ownership(self, request, require_owner, require_author,
                        ignore_disabled, admin):
        """
        Used by acl.check_ownership to see if request.user has permissions for
        the collection.
        """
        from olympia.access import acl
        return acl.check_collection_ownership(request, self, require_owner)