示例#1
0
文件: test_utils.py 项目: rlr/fjord
    def test_not_str(self):
        eq_(u'', smart_str(1))
        eq_(u'', smart_str(1.1))
        eq_(u'', smart_str(True))
        eq_(u'', smart_str(['a']))

        eq_(u'', smart_str(None))
示例#2
0
    def test_not_str(self):
        assert u'' == smart_str(1)
        assert u'' == smart_str(1.1)
        assert u'' == smart_str(True)
        assert u'' == smart_str(['a'])

        assert u'' == smart_str(None)
示例#3
0
文件: views.py 项目: deshraj/fjord
    def get(self, request):
        events = get_product_details_history()

        products = smart_str(request.GET.get('products'))
        date_start = smart_str(request.GET.get('date_start'))
        date_end = smart_str(request.GET.get('date_end'))

        if products:
            products = [prod.strip() for prod in products.split(',')]
            events = [
                event for event in events if event['product'] in products
            ]

        if date_start:
            events = [event for event in events if event['date'] >= date_start]

        if date_end:
            events = [event for event in events if event['date'] <= date_end]

        return rest_framework.response.Response({
            'date_start':
            date_start if date_start else None,
            'date_end':
            date_end if date_end else None,
            'products':
            ','.join(products) if products else None,
            'count':
            len(events),
            'events':
            list(events)
        })
示例#4
0
    def test_not_str(self):
        assert u'' == smart_str(1)
        assert u'' == smart_str(1.1)
        assert u'' == smart_str(True)
        assert u'' == smart_str(['a'])

        assert u'' == smart_str(None)
示例#5
0
    def test_not_str(self):
        eq_(u"", smart_str(1))
        eq_(u"", smart_str(1.1))
        eq_(u"", smart_str(True))
        eq_(u"", smart_str(["a"]))

        eq_(u"", smart_str(None))
示例#6
0
文件: views.py 项目: Givemore/fjord
    def get(self, request):
        events = get_product_details_history()

        products = smart_str(request.GET.get('products'))
        date_start = smart_str(request.GET.get('date_start'))
        date_end = smart_str(request.GET.get('date_end'))

        if products:
            products = [prod.strip() for prod in products.split(',')]
            events = [event for event in events
                      if event['product'] in products]

        if date_start:
            events = [event for event in events
                      if event['date'] >= date_start]

        if date_end:
            events = [event for event in events
                      if event['date'] <= date_end]

        return rest_framework.response.Response({
            'date_start': date_start if date_start else None,
            'date_end': date_end if date_end else None,
            'products': ','.join(products) if products else None,
            'count': len(events),
            'events': list(events)
        })
示例#7
0
    def test_not_str(self):
        eq_(u'', smart_str(1))
        eq_(u'', smart_str(1.1))
        eq_(u'', smart_str(True))
        eq_(u'', smart_str(['a']))

        eq_(u'', smart_str(None))
示例#8
0
文件: views.py 项目: KrystalYu/fjord
def feedback_router_dev(request, product=None, version=None, channel=None,
                        *args, **kwargs):
    """DEV ONLY FEEDBACK ROUTER"""
    view = None

    if '_type' in request.POST:
        # Checks to see if `_type` is in the POST data and if so this
        # is coming from Firefox for Android which doesn't know
        # anything about csrf tokens. If that's the case, we send it
        # to a view specifically for FfA Otherwise we pass it to one
        # of the normal views, which enforces CSRF. Also, nix the
        # product just in case we're crossing the streams and
        # confusing new-style product urls with old-style backwards
        # compatability for the Android form.
        #
        # FIXME: Remove this hairbrained monstrosity when we don't need to
        # support the method that Firefox for Android currently uses to
        # post feedback which worked with the old input.mozilla.org.

        # This lets us measure how often this section of code kicks
        # off and thus how often old android stuff is happening. When
        # we're not seeing this anymore, we can nix all the old
        # android stuff.
        statsd.incr('feedback.oldandroid')

        return android_about_feedback(request, request.locale)

    product = smart_str(product, fallback=None)
    # FIXME - validate these better
    version = smart_str(version)
    channel = smart_str(channel).lower()

    if product == 'fxos' or request.BROWSER.browser == 'Firefox OS':
        # Firefox OS gets shunted to a different form which has
        # different Firefox OS specific questions.
        view = firefox_os_stable_feedback
        product = 'fxos'

    elif product in PRODUCT_OVERRIDE:
        # The "product" is really a specific form to use. So we None
        # out the product and let that form view deal with everything.
        view = PRODUCT_OVERRIDE[product]
        product = None

    elif product is None or product not in models.Product.get_product_map():
        # The product wasn't specified or doesn't exist, so we spit
        # out the product picker.
        template = 'feedback/picker.html'

        products = models.Product.objects.all()
        return render(request, template, {
            'products': products
        })

    view = view or generic_feedback_dev

    return view(request, request.locale, product, version, channel,
                *args, **kwargs)
示例#9
0
文件: views.py 项目: KrystalYu/fjord
    def post(self, request):
        """Handles posting a new heartbeat item"""
        post_data = dict(request.DATA)

        hb = {}

        # Figure out if there's any missing data.
        missing = []
        for key in ('locale', 'platform', 'product', 'version', 'channel',
                    'answer'):
            if key in post_data:
                hb[key] = smart_str(post_data.pop(key))
            else:
                missing.append(key)

        if 'poll' in post_data:
            poll_slug = smart_str(post_data.pop('poll'))
        else:
            missing.append('poll')

        if missing:
            return rest_framework.response.Response(
                status=400,
                data=({'msg': 'missing fields: ' + ', '.join(sorted(missing))})
            )

        # Figure out if the poll exists and is enabled.
        try:
            poll = Poll.objects.get(slug=poll_slug)
        except Poll.DoesNotExist:
            return rest_framework.response.Response(
                status=400,
                data=({'msg': 'poll "%s" does not exist' % poll_slug})
            )

        if not poll.enabled:
            return rest_framework.response.Response(
                status=400,
                data=({'msg': 'poll "%s" is not currently running' %
                       poll_slug})
            )

        hb['poll'] = poll

        # Add the extra POST data fields by tossing them in the
        # "extra" field.
        hb['extra'] = post_data

        hb = Answer(**hb)
        hb.save()

        return rest_framework.response.Response(
            status=201,
            data={'msg': 'success!'})
示例#10
0
def translations_management_backfill_view(request):
    """Takes start and end dates and a model and backfills translations"""
    date_start = smart_date(request.POST.get('date_start'))
    date_end = smart_date(request.POST.get('date_end'))
    model_path = smart_str(request.POST.get('model'))

    if request.method == 'POST' and date_start and date_end and model_path:
        # NB: We just let the errors propagate because this is an
        # admin page. That way we get a traceback and all that detail.

        # We add one day to date_end so that it picks up the entire day of
        # date_end.
        #
        # FIXME: We should do this in a less goofy way.
        date_end = date_end + timedelta(days=1)

        model_cls = import_by_path(model_path)

        # Get list of ids of all objects that need translating.
        id_list = list(
            model_cls.objects.need_translations(
                date_start=date_start, date_end=date_end
            )
            .values_list('id', flat=True)
        )

        num = len(id_list)

        CHUNK_SIZE = 100

        # Need to generate a bunch of separate tasks because they take
        # a long time to run, so we do CHUNK_SIZE per task.
        while id_list:
            chunk = id_list[:CHUNK_SIZE]
            id_list = id_list[CHUNK_SIZE:]
            translate_tasks_by_id_list.delay(model_path, chunk)

        messages.success(
            request,
            u'Task created to backfill translations for %s instances' % num
        )

        return HttpResponseRedirect(request.path)

    from fjord.translations.tasks import REGISTERED_MODELS
    model_classes = [
        cls.__module__ + '.' + cls.__name__
        for cls in REGISTERED_MODELS
    ]

    return render(request, 'admin/translations_backfill.html', {
        'title': 'Translations - General Maintenance - Backfill',
        'settings': settings,
        'model_classes': model_classes,
        'date_start': request.POST.get('date_start', ''),
        'date_end': request.POST.get('date_end', ''),
        'model': request.POST.get('model', '')
    })
示例#11
0
文件: admin.py 项目: Ritsyy/fjord
def translations_management_backfill_view(request):
    """Takes start and end dates and a model and backfills translations"""
    date_start = smart_date(request.POST.get('date_start'))
    date_end = smart_date(request.POST.get('date_end'))
    model_path = smart_str(request.POST.get('model'))

    if request.method == 'POST' and date_start and date_end and model_path:
        # NB: We just let the errors propagate because this is an
        # admin page. That way we get a traceback and all that detail.

        # We add one day to date_end so that it picks up the entire day of
        # date_end.
        #
        # FIXME: We should do this in a less goofy way.
        date_end = date_end + timedelta(days=1)

        model_cls = import_by_path(model_path)

        # Get list of ids of all objects that need translating.
        id_list = list(
            model_cls.objects.need_translations(
                date_start=date_start, date_end=date_end
            )
            .values_list('id', flat=True)
        )

        num = len(id_list)

        CHUNK_SIZE = 100

        # Need to generate a bunch of separate tasks because they take
        # a long time to run, so we do CHUNK_SIZE per task.
        while id_list:
            chunk = id_list[:CHUNK_SIZE]
            id_list = id_list[CHUNK_SIZE:]
            translate_tasks_by_id_list.delay(model_path, chunk)

        messages.success(
            request,
            u'Task created to backfill translations for %s instances' % num
        )

        return HttpResponseRedirect(request.path)

    from fjord.translations.tasks import REGISTERED_MODELS
    model_classes = [
        cls.__module__ + '.' + cls.__name__
        for cls in REGISTERED_MODELS
    ]

    return render(request, 'admin/translations_backfill.html', {
        'title': 'Translations - General Maintenance - Backfill',
        'settings': settings,
        'model_classes': model_classes,
        'date_start': request.POST.get('date_start', ''),
        'date_end': request.POST.get('date_end', ''),
        'model': request.POST.get('model', '')
    })
示例#12
0
文件: views.py 项目: adifcsa/fjord
def feedback_router(request, product_slug=None, version=None, channel=None,
                    *args, **kwargs):
    """Figure out which flow to use for a product and route accordingly

    .. Note::

       1. We never want to cache this view

       2. Pages returned from this view will get an::

              X-Frame-Options: DENY

          HTTP header. That's important because these pages have magic
          powers and should never be used in frames. Please do not
          change this!

    """
    # The old Firefox for Android would POST form data to /feedback/. If we see
    # that, we fix the request up and then handle it as a feedback post.
    if '_type' in request.POST:
        request = fix_oldandroid(request)
        return _handle_feedback_post(request, request.locale)

    # FIXME - validate these better
    product_slug = smart_str(product_slug, fallback=None)
    version = smart_str(version)
    channel = smart_str(channel).lower()

    view_fun = get_config(product_slug)['view']

    if product_slug not in models.Product.objects.get_product_map():
        # If the product doesn't exist, redirect them to the picker.
        return HttpResponseRedirect(reverse('picker'))

    # Convert the product_slug to a product and send them on their way.
    product = models.Product.objects.from_slug(product_slug)
    return view_fun(request, request.locale, product, version, channel,
                    *args, **kwargs)
示例#13
0
文件: views.py 项目: willkg/fjord
def feedback_router(request, product_slug=None, version=None, channel=None,
                    *args, **kwargs):
    """Figure out which flow to use for a product and route accordingly

    .. Note::

       1. We never want to cache this view

       2. Pages returned from this view will get an::

              X-Frame-Options: DENY

          HTTP header. That's important because these pages have magic
          powers and should never be used in frames. Please do not
          change this!

    """
    # The old Firefox for Android would POST form data to /feedback/. If we see
    # that, we fix the request up and then handle it as a feedback post.
    if '_type' in request.POST:
        request = fix_oldandroid(request)
        return _handle_feedback_post(request, request.locale)

    # FIXME - validate these better
    product_slug = smart_str(product_slug, fallback=None)
    version = smart_str(version)
    channel = smart_str(channel).lower()

    view_fun = get_config(product_slug)['view']

    if product_slug not in models.Product.objects.get_product_map():
        # If the product doesn't exist, redirect them to the picker.
        return HttpResponseRedirect(reverse('picker'))

    # Convert the product_slug to a product and send them on their way.
    product = models.Product.objects.from_slug(product_slug)
    return view_fun(request, request.locale, product, version, channel,
                    *args, **kwargs)
示例#14
0
文件: admin.py 项目: DESHRAJ/fjord
def translations_management_backfill_view(request):
    """Takes start and end dates and a model and backfills translations"""
    date_start = smart_date(request.POST.get('date_start'))
    date_end = smart_date(request.POST.get('date_end'))
    model = smart_str(request.POST.get('model'))

    if request.method == 'POST' and date_start and date_end and model:
        # NB: We just let the errors propagate because this is an
        # admin page. That way we get a traceback and all that detail.

        # We add one day to date_end so that it picks up the entire day of
        # date_end.
        #
        # FIXME: We should do this in a less goofy way.
        date_end = date_end + timedelta(days=1)

        model_cls = import_by_path(model)

        # FIXME: This assumes the model has a "created" field. If it
        # doesn't, then this breaks. When we have another model that we
        # want to translate, we can figure out how to generalize this
        # then.
        objects = model_cls.objects.filter(
            created__gte=date_start,
            created__lte=date_end
        )

        total_jobs = 0

        for instance in objects:
            total_jobs += len(create_translation_tasks(instance))

        messages.success(request, '%s jobs added' % total_jobs)
        return HttpResponseRedirect(request.path)

    from fjord.translations.tasks import REGISTERED_MODELS
    model_classes = [
        cls.__module__ + '.' + cls.__name__
        for cls in REGISTERED_MODELS
    ]

    return render(request, 'admin/translations_backfill.html', {
        'title': 'Translations - General Maintenance - Backfill',
        'settings': settings,
        'model_classes': model_classes,
        'date_start': request.POST.get('date_start', ''),
        'date_end': request.POST.get('date_end', ''),
        'model': request.POST.get('model', '')
    })
示例#15
0
def translations_management_backfill_view(request):
    """Takes start and end dates and a model and backfills translations"""
    date_start = smart_date(request.POST.get('date_start'))
    date_end = smart_date(request.POST.get('date_end'))
    model = smart_str(request.POST.get('model'))

    if request.method == 'POST' and date_start and date_end and model:
        # NB: We just let the errors propagate because this is an
        # admin page. That way we get a traceback and all that detail.

        # We add one day to date_end so that it picks up the entire day of
        # date_end.
        #
        # FIXME: We should do this in a less goofy way.
        date_end = date_end + timedelta(days=1)

        model_cls = import_by_path(model)

        # FIXME: This assumes the model has a "created" field. If it
        # doesn't, then this breaks. When we have another model that we
        # want to translate, we can figure out how to generalize this
        # then.
        objects = model_cls.objects.filter(created__gte=date_start,
                                           created__lte=date_end)

        total_jobs = 0

        for instance in objects:
            total_jobs += len(create_translation_tasks(instance))

        messages.success(request, '%s jobs added' % total_jobs)
        return HttpResponseRedirect(request.path)

    from fjord.translations.tasks import REGISTERED_MODELS
    model_classes = [
        cls.__module__ + '.' + cls.__name__ for cls in REGISTERED_MODELS
    ]

    return render(
        request, 'admin/translations_backfill.html', {
            'title': 'Translations - General Maintenance - Backfill',
            'settings': settings,
            'model_classes': model_classes,
            'date_start': request.POST.get('date_start', ''),
            'date_end': request.POST.get('date_end', ''),
            'model': request.POST.get('model', '')
        })
示例#16
0
    def get(self, request):
        flavorslugs = smart_str(request.GET.get('flavors', '')).split(',')
        max_count = smart_int(request.GET.get('max', None))
        max_count = max_count or 100
        max_count = min(max(1, max_count), 10000)

        if not flavorslugs:
            return self.rest_error(
                status=400,
                errors='You must specify flavors to retrieve alerts for.')

        flavors = []
        for flavorslug in flavorslugs:
            try:
                flavor = AlertFlavor.objects.get(slug=flavorslug)

            except AlertFlavor.DoesNotExist:
                return self.rest_error(
                    status=404,
                    errors='Flavor "{}" does not exist.'.format(flavorslug))

            self.check_object_permissions(request, flavor)

            if not flavor.enabled:
                return self.rest_error(
                    status=400,
                    errors='Flavor "{}" is disabled.'.format(flavorslug))

            flavors.append(flavor)

        alerts = Alert.objects.filter(flavor__in=flavors).order_by('-created')

        alerts_ser = AlertSerializer(alerts[:max_count], many=True)
        return rest_framework.response.Response({
            'total': alerts.count(),
            'count': len(alerts_ser.data),
            'alerts': alerts_ser.data
        })
示例#17
0
 def test_str(self):
     eq_('a', smart_str('a'))
     eq_(u'a', smart_str(u'a'))
示例#18
0
 def test_str(self):
     eq_("a", smart_str("a"))
     eq_(u"a", smart_str(u"a"))
示例#19
0
 def test_str(self):
     assert 'a' == smart_str('a')
     assert u'a' == smart_str(u'a')
示例#20
0
 def test_str(self):
     assert 'a' == smart_str('a')
     assert u'a' == smart_str(u'a')
示例#21
0
文件: views.py 项目: TroJan/fjord
def feedback_router(request, product=None, version=None, channel=None,
                    *args, **kwargs):
    """Determine a view to use, and call it.

    If product is given, reference `product_routes` to look up a view.
    If `product` is not passed, or isn't found in `product_routes`,
    asssume the user is either a stable desktop Firefox or a stable
    mobile Firefox based on the parsed UA, and serve them the
    appropriate page. This is to handle the old formname way of doing
    things. At some point P, we should measure usage of the old
    formnames and deprecate them.

    This also handles backwards-compatability with the old Firefox for
    Android form which can't have a CSRF token.

    .. Note::

       1. We never want to cache this view

       2. Pages returned from this view will get an::

              X-Frame-Options: DENY

          HTTP header. That's important because these pages have magic
          powers and should never be used in frames. Please do not
          change this!

    """
    view = None

    if '_type' in request.POST:
        # Checks to see if `_type` is in the POST data and if so this
        # is coming from Firefox for Android which doesn't know
        # anything about csrf tokens. If that's the case, we send it
        # to a view specifically for FfA Otherwise we pass it to one
        # of the normal views, which enforces CSRF. Also, nix the
        # product just in case we're crossing the streams and
        # confusing new-style product urls with old-style backwards
        # compatability for the Android form.
        #
        # FIXME: Remove this hairbrained monstrosity when we don't need to
        # support the method that Firefox for Android currently uses to
        # post feedback which worked with the old input.mozilla.org.
        view = android_about_feedback
        product = None

        # This lets us measure how often this section of code kicks
        # off and thus how often old android stuff is happening. When
        # we're not seeing this anymore, we can nix all the old
        # android stuff.
        statsd.incr('feedback.oldandroid')

        return android_about_feedback(request, request.locale)

    # FIXME - validate these better
    product = smart_str(product, fallback=None)
    version = smart_str(version)
    channel = smart_str(channel).lower()

    if product == 'fxos' or request.BROWSER.browser == 'Firefox OS':
        # Firefox OS gets shunted to a different form which has
        # different Firefox OS specific questions.
        view = firefox_os_stable_feedback
        product = 'fxos'

    elif product in PRODUCT_OVERRIDE:
        # If the product is really a form name, we use that
        # form specifically.
        view = PRODUCT_OVERRIDE[product]
        product = None

    elif (product is None
          or product not in models.Product.objects.get_product_map()):

        picker_products = models.Product.objects.filter(
            enabled=True, on_picker=True)
        return render(request, 'feedback/picker.html', {
            'products': picker_products
        })

    product = models.Product.objects.from_slug(product)

    if view is None:
        view = generic_feedback

    return view(request, request.locale, product, version, channel,
                *args, **kwargs)
示例#22
0
def analytics_flagged(request):
    """View showing responses with flags

    NOTE: This is not permanent and might go away depending on how the
    spicedham prototype works.

    """
    template = 'analytics/analyzer/flags.html'

    # FIXME: Importing this here so all the changes are localized to
    # this function.  If we decide to go forward with this, we should
    # unlocalize it.

    from django.contrib import messages
    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
    from django.http import HttpResponseRedirect

    from fjord.flags.models import Flag

    if request.method == 'POST':
        flag_action = request.POST.get('flag')
        if flag_action:
            # We do some shenanigans here to make sure we're fetching
            # and operating on a flag_set that's not
            # cached. django-cache-machine doesn't invalidate m2m
            # queries correctly. :(
            resp = get_object_or_404(
                Response, pk=smart_int(request.POST['id']))
            flag = get_object_or_404(Flag, name=flag_action)

            resp_flags = dict([(f.name, f)
                               for f in resp.flag_set.no_cache().all()])
            if flag.name in resp_flags:
                del resp_flags[flag.name]
                messages.success(request, 'removed %s flag from %d' % (
                    flag_action, resp.id))
            else:
                resp_flags[flag.name] = flag
                messages.success(request, 'added %s flag from %d' % (
                    flag_action, resp.id))

            resp.flag_set.clear()
            resp.flag_set.add(*resp_flags.values())
            return HttpResponseRedirect(request.get_full_path())

    resp_filter = smart_str(request.GET.get('filter'))

    # Only look at en-US locale responses since Monday September 8th,
    # 2014 we pushed the integration code out.
    response_list = (Response.uncached
                     .filter(locale=u'en-US')
                     .filter(created__gte='2014-09-08'))

    counts = {
        'total': response_list.count(),
        'abuse': response_list.filter(flag__name='abuse').count(),
        'abuse-wrong': response_list.filter(flag__name='abuse-wrong').count(),
        'false-positive': (response_list
                           .filter(flag__name='abuse')
                           .filter(flag__name='abuse-wrong').count()),
    }
    counts['false-negative'] = (
        counts['abuse-wrong'] - counts['false-positive']
    )

    if resp_filter:
        response_list = response_list.filter(flag__name=resp_filter)

    paginator = Paginator(response_list, 50)

    page = request.GET.get('page')
    try:
        responses = paginator.page(page)
    except PageNotAnInteger:
        responses = paginator.page(1)
    except EmptyPage:
        responses = paginator.page(paginator.num_pages)

    return render(request, template, {
        'counts': counts,
        'responses': responses
    })
示例#23
0
文件: test_utils.py 项目: rlr/fjord
 def test_str(self):
     eq_('a', smart_str('a'))
     eq_(u'a', smart_str(u'a'))
示例#24
0
文件: views.py 项目: deshraj/fjord
def feedback_router(request,
                    product=None,
                    version=None,
                    channel=None,
                    *args,
                    **kwargs):
    """Determine a view to use, and call it.

    If product is given, reference `product_routes` to look up a view.
    If `product` is not passed, or isn't found in `product_routes`,
    asssume the user is either a stable desktop Firefox or a stable
    mobile Firefox based on the parsed UA, and serve them the
    appropriate page. This is to handle the old formname way of doing
    things. At some point P, we should measure usage of the old
    formnames and deprecate them.

    This also handles backwards-compatability with the old Firefox for
    Android form which can't have a CSRF token.

    Note: We never want to cache this view.

    """
    view = None

    if '_type' in request.POST:
        # Checks to see if `_type` is in the POST data and if so this
        # is coming from Firefox for Android which doesn't know
        # anything about csrf tokens. If that's the case, we send it
        # to a view specifically for FfA Otherwise we pass it to one
        # of the normal views, which enforces CSRF. Also, nix the
        # product just in case we're crossing the streams and
        # confusing new-style product urls with old-style backwards
        # compatability for the Android form.
        #
        # FIXME: Remove this hairbrained monstrosity when we don't need to
        # support the method that Firefox for Android currently uses to
        # post feedback which worked with the old input.mozilla.org.
        view = android_about_feedback
        product = None

        # This lets us measure how often this section of code kicks
        # off and thus how often old android stuff is happening. When
        # we're not seeing this anymore, we can nix all the old
        # android stuff.
        statsd.incr('feedback.oldandroid')

        return android_about_feedback(request, request.locale)

    # FIXME - validate these better
    product = smart_str(product, fallback=None)
    version = smart_str(version)
    channel = smart_str(channel).lower()

    if product == 'fxos' or request.BROWSER.browser == 'Firefox OS':
        # Firefox OS gets shunted to a different form which has
        # different Firefox OS specific questions.
        view = firefox_os_stable_feedback
        product = 'fxos'

    elif product in PRODUCT_OVERRIDE:
        # If the product is really a form name, we use that
        # form specifically.
        view = PRODUCT_OVERRIDE[product]
        product = None

    elif (product is None or product not in models.Product.get_product_map()):

        picker_products = models.Product.objects.filter(enabled=True,
                                                        on_picker=True)
        return render(request, 'feedback/picker.html',
                      {'products': picker_products})

    product = models.Product.from_slug(product)

    if view is None:
        view = generic_feedback

    return view(request, request.locale, product, version, channel, *args,
                **kwargs)