Example #1
0
def recommendations(request, version, platform, limit=9):
    """
    Figure out recommended add-ons for an anonymous user based on POSTed guids.

    POST body looks like {"guids": [...]} with an optional "token" key if
    they've been here before.
    """
    try:
        POST = json.loads(request.raw_post_data)
        guids = POST['guids']
    except (ValueError, TypeError, KeyError):
        # Errors: invalid json, didn't get a dict, didn't find "guids".
        return http.HttpResponseBadRequest()

    addon_ids = get_addon_ids(guids)
    index = Collection.make_index(addon_ids)

    ids, recs = Collection.get_recs_from_ids(addon_ids, request.APP, version)
    recs = _recommendations(request, version, platform, limit,
                            index, ids, recs)

    # We're only storing a percentage of the collections we see because the db
    # can't keep up with 100%.
    if not waffle.sample_is_active('disco-pane-store-collections'):
        return recs

    # Users have a token2 if they've been here before. The token matches
    # addon_index in their SyncedCollection.
    if 'token2' in POST:
        token = POST['token2']
        if token == index:
            # We've seen them before and their add-ons have not changed.
            return recs
        elif token != index:
            # We've seen them before and their add-ons changed. Remove the
            # reference to their old synced collection.
            (SyncedCollection.objects.filter(addon_index=index)
             .update(count=F('count') - 1))

    # Try to create the SyncedCollection. There's a unique constraint on
    # addon_index so it will fail if this addon_index already exists. If we
    # checked for existence first and then created a collection there would
    # be a race condition between multiple users with the same addon_index.
    try:
        c = SyncedCollection.objects.create(addon_index=index, count=1)
        c.set_addons(addon_ids)
    except IntegrityError:
        try:
            (SyncedCollection.objects.filter(addon_index=index)
             .update(count=F('count') + 1))
        except Exception, e:
            log.error(u'Could not count++ "%s" (%s).' % (index, e))
Example #2
0
def recommendations(request, version, platform, limit=9):
    """
    Figure out recommended add-ons for an anonymous user based on POSTed guids.

    POST body looks like {"guids": [...]} with an optional "token" key if
    they've been here before.
    """
    try:
        POST = json.loads(request.raw_post_data)
        guids = POST['guids']
    except (ValueError, TypeError, KeyError):
        # Errors: invalid json, didn't get a dict, didn't find "guids".
        return http.HttpResponseBadRequest()

    addon_ids = get_addon_ids(guids)
    index = Collection.make_index(addon_ids)

    ids, recs = Collection.get_recs_from_ids(addon_ids, request.APP, version)
    recs = _recommendations(request, version, platform, limit,
                            index, ids, recs)

    # We're only storing a percentage of the collections we see because the db
    # can't keep up with 100%.
    if not waffle.sample_is_active('disco-pane-store-collections'):
        return recs

    # Users have a token2 if they've been here before. The token matches
    # addon_index in their SyncedCollection.
    if 'token2' in POST:
        token = POST['token2']
        if token == index:
            # We've seen them before and their add-ons have not changed.
            return recs
        elif token != index:
            # We've seen them before and their add-ons changed. Remove the
            # reference to their old synced collection.
            (SyncedCollection.objects.filter(addon_index=index)
             .update(count=F('count') - 1))

    # Try to create the SyncedCollection. There's a unique constraint on
    # addon_index so it will fail if this addon_index already exists. If we
    # checked for existence first and then created a collection there would
    # be a race condition between multiple users with the same addon_index.
    try:
        c = SyncedCollection.objects.create(addon_index=index, count=1)
        c.set_addons(addon_ids)
    except IntegrityError:
        try:
            (SyncedCollection.objects.filter(addon_index=index)
             .update(count=F('count') + 1))
        except Exception, e:
            log.error(u'Could not count++ "%s" (%s).' % (index, e))
Example #3
0
 def test_addon_index(self):
     c = Collection.objects.get(pk=512)
     c.author = self.user
     eq_(c.addon_index, None)
     ids = c.addons.values_list('id', flat=True)
     c.save()
     eq_(c.addon_index, Collection.make_index(ids))
Example #4
0
 def test_addon_index(self):
     c = Collection.objects.get(pk=512)
     c.author = self.user
     eq_(c.addon_index, None)
     ids = c.addons.values_list("id", flat=True)
     c.save()
     eq_(c.addon_index, Collection.make_index(ids))
Example #5
0
    def test_remove_other_collection(self):
        "403 when you try to add to a collection that isn't yours."
        c = Collection(author=self.other)
        c.save()

        r = self.client.post(reverse('collections.ajax_remove'),
                             {'addon_id': 3615, 'id': c.id}, follow=True)
        eq_(r.status_code, 403)
Example #6
0
 def test_form_uneditable_slug(self):
     """
     Editing a mobile or favorite collection should have an uneditable slug.
     """
     u = UserProfile.objects.get(username='******')
     Collection(author=u, slug='mobile', type=amo.COLLECTION_MOBILE).save()
     url = reverse('collections.edit', args=['admin', 'mobile'])
     r = self.client.get(url, follow=True)
     doc = pq(r.content)
     eq_(len(doc('#id_slug')), 0)
Example #7
0
    def test_user_collection_list(self):
        c1 = Collection(uuid='eb4e3cd8-5cf1-4832-86fb-a90fc6d3765c')
        c2 = Collection(uuid='61780943-e159-4206-8acd-0ae9f63f294c',
                        nickname='my_collection')
        heading = 'My Heading'
        response = unicode(user_collection_list([c1, c2], heading))

        # heading
        eq_(pq(response)('h3').text(), heading)

        # both items
        # TODO reverse URLs
        assert c1.get_url_path() in response, 'Collection UUID link missing.'
        assert c2.get_url_path() in response, (
            'Collection nickname link missing.')

        # empty collection, empty response
        response = unicode(user_collection_list([], heading))
        self.assertFalse(response, 'empty collection should not create a list')
Example #8
0
    def test_listed(self):
        """Make sure the manager's listed() filter works."""
        listed_count = Collection.objects.listed().count()
        # make a private collection
        private = Collection(
            name="Hello", uuid="4e2a1acc-39ae-47ec-956f-46e080ac7f69",
            listed=False, author=self.user)
        private.save()

        listed = Collection.objects.listed()
        eq_(len(listed), listed_count)
Example #9
0
def _collections(request):
    """Handle the request for collections."""

    # Sorting by relevance isn't an option. Instead the default is `weekly`.
    initial = dict(sort="weekly")
    # Update with GET variables.
    initial.update(request.GET.items())
    # Ignore appver/platform and set default number of collections per page.
    initial.update(appver=None, platform=None, pp=DEFAULT_NUM_COLLECTIONS)

    form = SecondarySearchForm(initial)
    form.is_valid()

    qs = Collection.search().filter(listed=True, app=request.APP.id)
    filters = ["sort"]
    mapping = {
        "weekly": "-weekly_subscribers",
        "monthly": "-monthly_subscribers",
        "all": "-subscribers",
        "rating": "-rating",
        "created": "-created",
        "name": "name_sort",
        "updated": "-modified",
    }
    results = _filter_search(
        request,
        qs,
        form.cleaned_data,
        filters,
        sorting=mapping,
        sorting_default="-weekly_subscribers",
        types=amo.COLLECTION_SEARCH_CHOICES,
    )

    query = form.cleaned_data.get("q", "")

    search_opts = {}
    search_opts["limit"] = form.cleaned_data.get("pp", DEFAULT_NUM_COLLECTIONS)
    page = form.cleaned_data.get("page") or 1
    search_opts["offset"] = (page - 1) * search_opts["limit"]
    search_opts["sort"] = form.cleaned_data.get("sort")

    pager = amo.utils.paginate(request, results, per_page=search_opts["limit"])
    c = dict(
        pager=pager,
        form=form,
        query=query,
        opts=search_opts,
        filter=bandwagon.views.get_filter(request),
        search_placeholder="collections",
    )
    return jingo.render(request, "search/collections.html", c)
Example #10
0
    def test_form_uneditable_slug_submit(self):
        """
        Ignore the slug request change, if some jackass thinks he can change
        it.
        """
        u = UserProfile.objects.get(username='******')
        Collection(author=u, slug='mobile', type=amo.COLLECTION_MOBILE).save()
        url = reverse('collections.edit', args=['admin', 'mobile'])
        self.client.post(url, {'name': 'HALP', 'slug': 'halp', 'listed': True},
                         follow=True)

        assert not Collection.objects.filter(slug='halp', author=u)
        assert Collection.objects.filter(slug='mobile', author=u)
Example #11
0
def _collections(request):
    """Handle the request for collections."""

    # Sorting by relevance isn't an option. Instead the default is `weekly`.
    initial = dict(sort='weekly')
    # Update with GET variables.
    initial.update(request.GET.items())
    # Ignore appver/platform and set default number of collections per page.
    initial.update(appver=None, platform=None, pp=DEFAULT_NUM_COLLECTIONS)

    form = SecondarySearchForm(initial)
    form.is_valid()

    qs = Collection.search().filter(listed=True, app=request.APP.id)
    filters = ['sort']
    mapping = {
        'weekly': '-weekly_subscribers',
        'monthly': '-monthly_subscribers',
        'all': '-subscribers',
        'rating': '-rating',
        'created': '-created',
        'name': 'name_sort',
        'updated': '-modified'
    }
    results = _filter_search(
        request,
        qs,
        form.cleaned_data,
        filters,
        sorting=mapping,
        sorting_default='-weekly_subscribers',
        types=amo.COLLECTION_SEARCH_CHOICES)

    form_data = form.cleaned_data.get('q', '')

    search_opts = {}
    search_opts['limit'] = form.cleaned_data.get('pp', DEFAULT_NUM_COLLECTIONS)
    page = form.cleaned_data.get('page') or 1
    search_opts['offset'] = (page - 1) * search_opts['limit']
    search_opts['sort'] = form.cleaned_data.get('sort')

    pager = amo.utils.paginate(request, results, per_page=search_opts['limit'])
    c = dict(
        pager=pager,
        form=form,
        query=form_data,
        opts=search_opts,
        filter=bandwagon.views.get_filter(request),
        search_placeholder='collections')
    return render(request, 'search/collections.html', c)
Example #12
0
def _collections(request):
    """Handle the request for collections."""

    # Sorting by relevance isn't an option. Instead the default is `weekly`.
    initial = dict(sort='weekly')
    # Update with GET variables.
    initial.update(request.GET.items())
    # Ignore appver/platform and set default number of collections per page.
    initial.update(appver=None, platform=None, pp=DEFAULT_NUM_COLLECTIONS)

    form = SecondarySearchForm(initial)
    form.is_valid()

    if waffle.switch_is_active('replace-sphinx'):
        qs = Collection.search().filter(listed=True, app=request.APP.id)
        filters = ['sort']
        mapping = {'weekly': '-weekly_subscribers',
                   'monthly': '-monthly_subscribers',
                   'all': '-subscribers',
                   'rating': '-rating',
                   'created': '-created',
                   'name': 'name_sort',
                   'updated': '-modified'}
        results = _filter_search(request, qs, form.cleaned_data, filters,
                                 sorting=mapping,
                                 sorting_default='-weekly_subscribers',
                                 types=amo.COLLECTION_SEARCH_CHOICES)

    query = form.cleaned_data.get('q', '')

    search_opts = {}
    search_opts['limit'] = form.cleaned_data.get('pp', DEFAULT_NUM_COLLECTIONS)
    page = form.cleaned_data.get('page') or 1
    search_opts['offset'] = (page - 1) * search_opts['limit']
    search_opts['sort'] = form.cleaned_data.get('sort')

    if not waffle.switch_is_active('replace-sphinx'):
        # The new hotness calls this `created`. Sphinx still calls it `newest`.
        if search_opts['sort'] == 'created':
            search_opts['sort'] = 'newest'
        try:
            results = CollectionsClient().query(query, **search_opts)
        except SearchError:
            return jingo.render(request, 'search/down.html', {}, status=503)

    pager = amo.utils.paginate(request, results, per_page=search_opts['limit'])
    c = dict(pager=pager, form=form, query=query, opts=search_opts,
             filter=bandwagon.views.get_filter(request),
             search_placeholder='collections')
    return jingo.render(request, 'search/collections.html', c)
Example #13
0
def get_synced_collection(addon_ids, token):
    """
    Get a synced collection for these addons. May reuse an existing collection.

    The token is associated with the collection.
    """
    index = Collection.make_index(addon_ids)
    try:
        c = (SyncedCollection.objects.no_cache().filter(addon_index=index))[0]
    except IndexError:
        c = SyncedCollection.objects.create(listed=False)
        c.set_addons(addon_ids)

    c.token_set.create(token=token)
    return c
Example #14
0
def _collections(request):
    """Handle the request for collections."""

    # Sorting by relevance isn't an option. Instead the default is `weekly`.
    initial = dict(sort='weekly')
    # Update with GET variables.
    initial.update(request.GET.items())
    # Ignore appver/platform and set default number of collections per page.
    initial.update(appver=None, platform=None, pp=DEFAULT_NUM_COLLECTIONS)

    form = SecondarySearchForm(initial)
    form.is_valid()

    qs = Collection.search().filter(listed=True, app=request.APP.id)
    filters = ['sort']
    mapping = {
        'weekly': '-weekly_subscribers',
        'monthly': '-monthly_subscribers',
        'all': '-subscribers',
        'rating': '-rating',
        'created': '-created',
        'name': 'name_sort',
        'updated': '-modified'
    }
    results = _filter_search(request,
                             qs,
                             form.cleaned_data,
                             filters,
                             sorting=mapping,
                             sorting_default='-weekly_subscribers',
                             types=amo.COLLECTION_SEARCH_CHOICES)

    query = form.cleaned_data.get('q', '')

    search_opts = {}
    search_opts['limit'] = form.cleaned_data.get('pp', DEFAULT_NUM_COLLECTIONS)
    page = form.cleaned_data.get('page') or 1
    search_opts['offset'] = (page - 1) * search_opts['limit']
    search_opts['sort'] = form.cleaned_data.get('sort')

    pager = amo.utils.paginate(request, results, per_page=search_opts['limit'])
    c = dict(pager=pager,
             form=form,
             query=query,
             opts=search_opts,
             filter=bandwagon.views.get_filter(request),
             search_placeholder='collections')
    return jingo.render(request, 'search/collections.html', c)
Example #15
0
def es_collections_json(request):
    app = request.GET.get("app", "")
    q = request.GET.get("q", "")
    qs = Collection.search()
    try:
        qs = qs.query(id__startswith=int(q))
    except ValueError:
        qs = qs.query(name__text=q)
    try:
        qs = qs.filter(app=int(app))
    except ValueError:
        pass
    data = []
    for c in qs[:7]:
        data.append({"id": c.id, "name": unicode(c.name), "all_personas": c.all_personas, "url": c.get_url_path()})
    return data
Example #16
0
    def test_icon_url(self):
        # Has no icon.
        c = Collection(pk=512, modified=datetime.datetime.now())
        assert c.icon_url.endswith('img/icons/collection.png')

        c.icontype = 'image/png'
        url = c.icon_url.split('?')[0]
        assert url.endswith('0/512.png')

        c.id = 12341234
        url = c.icon_url.split('?')[0]
        assert url.endswith('12341/12341234.png')

        c.icontype = None
        c.type = amo.COLLECTION_FAVORITES
        assert c.icon_url.endswith('img/icons/heart.png')
Example #17
0
def es_collections_json(request):
    app = request.GET.get('app', '')
    q = request.GET.get('q', '')
    qs = Collection.search()
    try:
        qs = qs.query(id__startswith=int(q))
    except ValueError:
        qs = qs.query(name__text=q)
    try:
        qs = qs.filter(app=int(app))
    except ValueError:
        pass
    data = []
    for c in qs[:7]:
        data.append({'id': c.id,
                     'name': unicode(c.name),
                     'all_personas': c.all_personas,
                     'url': c.get_url_path()})
    return data
Example #18
0
def es_collections_json(request):
    app = request.GET.get('app', '')
    q = request.GET.get('q', '')
    qs = Collection.search()
    try:
        qs = qs.query(id__startswith=int(q))
    except ValueError:
        qs = qs.query(name__text=q)
    try:
        qs = qs.filter(app=int(app))
    except ValueError:
        pass
    data = []
    for c in qs[:7]:
        data.append({'id': c.id,
                     'name': unicode(c.name),
                     'all_personas': c.all_personas,
                     'url': c.get_url_path()})
    return data
Example #19
0
def collection_factory(**kw):
    data = {
        'type': amo.COLLECTION_NORMAL,
        'application': amo.FIREFOX.id,
        'name': 'Collection %s' % abs(hash(datetime.now())),
        'addon_count': random.randint(200, 2000),
        'subscribers': random.randint(1000, 5000),
        'monthly_subscribers': random.randint(100, 500),
        'weekly_subscribers': random.randint(10, 50),
        'upvotes': random.randint(100, 500),
        'downvotes': random.randint(100, 500),
        'listed': True,
    }
    data.update(kw)
    c = Collection(**data)
    c.slug = data['name'].replace(' ', '-').lower()
    c.rating = (c.upvotes - c.downvotes) * math.log(c.upvotes + c.downvotes)
    c.created = c.modified = datetime(2011, 11, 11, random.randint(0, 23),
                                      random.randint(0, 59))
    c.save()
    return c
Example #20
0
def create_collection(application):
    """Create a Collection for the given `application`."""
    data = {
        'type': amo.COLLECTION_NORMAL,
        'application': application,
        'name': 'Collection %s' % abs(hash(datetime.now())),
        'addon_count': random.randint(200, 2000),
        'subscribers': random.randint(1000, 5000),
        'monthly_subscribers': random.randint(100, 500),
        'weekly_subscribers': random.randint(10, 50),
        'upvotes': random.randint(100, 500),
        'downvotes': random.randint(100, 500),
        'listed': True,
    }
    c = Collection(**data)
    c.slug = slugify(data['name'])
    c.rating = (c.upvotes - c.downvotes) * math.log(c.upvotes + c.downvotes)
    c.created = c.modified = datetime(2014, 10, 27, random.randint(0, 23),
                                      random.randint(0, 59))
    c.save()
    return c
Example #21
0
File: views.py Project: rlr/zamboni
def recommendations(request, limit=5):
    """
    Figure out recommended add-ons for an anonymous user based on POSTed guids.

    POST body looks like {"guids": [...]} with an optional "token" key if
    they've been here before.
    """
    if request.method != 'POST':
        return http.HttpResponseNotAllowed(['POST'])

    try:
        POST = json.loads(request.raw_post_data)
        guids = POST['guids']
    except (ValueError, TypeError, KeyError):
        # Errors: invalid json, didn't get a dict, didn't find "guids".
        return http.HttpResponseBadRequest()

    addon_ids = get_addon_ids(guids)
    token = POST['token'] if 'token' in POST else get_random_token()

    if 'token' in POST:
        q = SyncedCollection.objects.filter(token_set__token=token)
        if q:
            # We've seen this user before.
            synced = q[0]
            if synced.addon_index == Collection.make_index(addon_ids):
                # Their add-ons didn't change, get out quick.
                recs = synced.get_recommendations()
                return _recommendations(request, limit, token, recs)
            else:
                # Remove the link to the current sync, make a new one below.
                synced.token_set.get(token=token).delete()

    synced = get_synced_collection(addon_ids, token)
    recs = synced.get_recommendations()
    return _recommendations(request, limit, token, recs)
Example #22
0
    def test_barometer(self):
        self.client.get('/')
        jingo.load_helpers()
        collection = Collection(upvotes=1,
                                slug='mccrackin',
                                author=UserProfile(username='******'))
        # Mock logged out.
        c = {
            'request': Mock(path='yermom', GET=Mock(urlencode=lambda: '')),
            'user': Mock(),
            'settings': Mock()
        }
        c['request'].user.is_authenticated.return_value = False
        doc = pq(barometer(c, collection))
        eq_(doc('form')[0].action, '/en-US/firefox/users/login?to=yermom')

        # Mock logged in.
        c['request'].amo_user.votes.filter.return_value = [Mock(vote=1)]
        c['request'].user.is_authenticated.return_value = True
        barometer(c, collection)
        doc = pq(barometer(c, collection))
        eq_(
            doc('form')[0].action,
            reverse('collections.vote', args=['clouserw', 'mccrackin', 'up']))
Example #23
0
 def test_addon_index(self):
     c = Collection.objects.get(pk=5)
     eq_(c.addon_index, None)
     ids = c.addons.values_list('id', flat=True)
     c.save()
     eq_(c.addon_index, Collection.make_index(ids))
Example #24
0
 def test_publishable_by(self):
     c = Collection(pk=512, author=self.other)
     CollectionUser(collection=c, user=self.user).save()
     eq_(c.publishable_by(self.user), True)
Example #25
0
    POST body looks like {"guids": [...]} with an optional "token" key if
    they've been here before.
    """
    if not compat_mode:
        compat_mode = get_compat_mode(version)

    try:
        POST = json.loads(request.raw_post_data)
        guids = POST['guids']
    except (ValueError, TypeError, KeyError), e:
        # Errors: invalid json, didn't get a dict, didn't find "guids".
        log.debug('Recommendations return 405 because: %s' % e)
        return http.HttpResponseBadRequest()

    addon_ids = get_addon_ids(guids)
    index = Collection.make_index(addon_ids)

    ids, recs = Collection.get_recs_from_ids(addon_ids, request.APP, version,
                                             compat_mode)
    recs = _recommendations(request, version, platform, limit, index, ids,
                            recs, compat_mode)

    # We're only storing a percentage of the collections we see because the db
    # can't keep up with 100%.
    if not waffle.sample_is_active('disco-pane-store-collections'):
        return recs

    # Users have a token2 if they've been here before. The token matches
    # addon_index in their SyncedCollection.
    if 'token2' in POST:
        token = POST['token2']
Example #26
0
 def test_is_subscribed(self):
     c = Collection(pk=512)
     c.following.create(user=self.user)
     assert c.is_subscribed(self.user)
Example #27
0
    POST body looks like {"guids": [...]} with an optional "token" key if
    they've been here before.
    """
    if not compat_mode:
        compat_mode = get_compat_mode(version)

    try:
        POST = json.loads(request.raw_post_data)
        guids = POST['guids']
    except (ValueError, TypeError, KeyError), e:
        # Errors: invalid json, didn't get a dict, didn't find "guids".
        log.debug('Recommendations return 405 because: %s' % e)
        return http.HttpResponseBadRequest()

    addon_ids = get_addon_ids(guids)
    index = Collection.make_index(addon_ids)

    ids, recs = Collection.get_recs_from_ids(addon_ids, request.APP, version,
                                             compat_mode)
    recs = _recommendations(request, version, platform, limit, index, ids,
                            recs, compat_mode)

    # We're only storing a percentage of the collections we see because the db
    # can't keep up with 100%.
    if not waffle.sample_is_active('disco-pane-store-collections'):
        return recs

    # Users have a token2 if they've been here before. The token matches
    # addon_index in their SyncedCollection.
    if 'token2' in POST:
        token = POST['token2']