implement Category browse pages (bug 735578, bug 735582)
This commit is contained in:
Родитель
24f3ea7147
Коммит
8355ecd4bc
|
@ -0,0 +1,51 @@
|
|||
from nose.tools import eq_
|
||||
from pyquery import PyQuery as pq
|
||||
|
||||
import amo
|
||||
import amo.tests
|
||||
from amo.urlresolvers import reverse
|
||||
from amo.utils import urlparams
|
||||
from addons.models import AddonCategory, Category
|
||||
from mkt.webapps.models import Webapp
|
||||
|
||||
|
||||
class TestCategories(amo.tests.ESTestCase):
|
||||
fixtures = ['webapps/337141-steamcube']
|
||||
|
||||
def setUp(self):
|
||||
self.cat = Category.objects.create(name='Lifestyle', slug='lifestyle',
|
||||
type=amo.ADDON_WEBAPP)
|
||||
self.url = reverse('browse.apps', args=[self.cat.slug])
|
||||
self.webapp = Webapp.objects.get(id=337141)
|
||||
AddonCategory.objects.create(addon=self.webapp, category=self.cat)
|
||||
self.webapp.save()
|
||||
self.refresh()
|
||||
|
||||
def test_good_cat(self):
|
||||
r = self.client.get(self.url)
|
||||
eq_(r.status_code, 200)
|
||||
self.assertTemplateUsed(r, 'search/results.html')
|
||||
|
||||
def test_bad_cat(self):
|
||||
r = self.client.get(reverse('browse.apps', args=['xxx']))
|
||||
eq_(r.status_code, 404)
|
||||
|
||||
def test_sidebar(self):
|
||||
r = self.client.get(self.url)
|
||||
a = pq(r.content)('#category-facets .selected a')
|
||||
eq_(a.attr('href'),
|
||||
urlparams(reverse('search.search'), cat=self.cat.id))
|
||||
eq_(a.text(), unicode(self.cat.name))
|
||||
|
||||
def test_sorter(self):
|
||||
r = self.client.get(self.url)
|
||||
li = pq(r.content)('#sorter li:eq(0)')
|
||||
eq_(li.attr('class'), 'selected')
|
||||
eq_(li.find('a').attr('href'),
|
||||
urlparams(reverse('search.search'), cat=self.cat.id,
|
||||
sort='downloads'))
|
||||
|
||||
def test_search_category(self):
|
||||
# Ensure category got preserved in search term.
|
||||
r = self.client.get(self.url)
|
||||
eq_(pq(r.content)('#search input[name=cat]').val(), str(self.cat.id))
|
|
@ -0,0 +1,9 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url('^apps/(?P<category>[^ /]+)?$', views.categories_apps,
|
||||
name='browse.apps'),
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
import jingo
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
import amo
|
||||
from addons.models import Category
|
||||
from mkt.search.views import _app_search
|
||||
|
||||
|
||||
def categories_apps(request, category=None):
|
||||
ctx = {}
|
||||
|
||||
if category is not None:
|
||||
qs = Category.objects.filter(type=amo.ADDON_WEBAPP)
|
||||
ctx['category'] = get_object_or_404(qs, slug=category)
|
||||
|
||||
# Do a search filtered by this category and sort by Weekly Downloads.
|
||||
request.GET = request.GET.copy()
|
||||
request.GET.update({'cat': ctx['category'].id, 'sort': 'downloads'})
|
||||
|
||||
ctx.update(_app_search(request, ctx['category']))
|
||||
|
||||
return jingo.render(request, 'search/results.html', ctx)
|
|
@ -23,9 +23,15 @@
|
|||
{{ _('Privacy Policy') }}</a>
|
||||
{% if product.all_categories %}
|
||||
<h3 class="categories">{{ _('Categories') }}</h3>
|
||||
<span class="vital">
|
||||
{{ product.all_categories|join(' · ')|safe }}
|
||||
</span>
|
||||
<ul>
|
||||
{% for category in categories %}
|
||||
<li>
|
||||
<a href="{{ category.get_url_path() }}">
|
||||
{{ category }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="actions">
|
||||
|
|
|
@ -38,9 +38,13 @@
|
|||
<p class="desc">{{ product.summary|nl2br }}</p>
|
||||
<div class="vitals c">
|
||||
{{ price(product) }}
|
||||
{% if product.all_categories %}
|
||||
{% set cats = product.all_categories %}
|
||||
{% if cats %}
|
||||
<span class="vital">
|
||||
{{ product.all_categories|join(' · ')|safe }}
|
||||
{% for cat in cats %}
|
||||
<a href="{{ cat.get_url_path() }}">{{ cat }}</a>
|
||||
{%- if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{{ impala_reviews_link(product) }}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
{% extends 'mkt/base.html' %}
|
||||
|
||||
{% set query_term = query.q %}
|
||||
{% set search_url = request.get_full_path() %}
|
||||
|
||||
{% if query_term %}
|
||||
{# L10n: {0} is a search term, such as Firebug. #}
|
||||
{% set title = _('{0} | Search')|f(query_term) %}
|
||||
{# L10n: {0} is a search term, such as Firebug. #}
|
||||
{% set heading = _('Search Results for "{0}"')|f(query_term) %}
|
||||
{% elif category %}
|
||||
{% set search_url = url('search.search')|urlparams(cat=category.id) %}
|
||||
{% set title = category.name %}
|
||||
{% set heading = title %}
|
||||
{% else %}
|
||||
{% set title = _('Search') %}
|
||||
{% set heading = title %}
|
||||
|
@ -19,8 +24,8 @@
|
|||
<meta name="WT.oss_r" content="{{ pager.paginator.count }}">
|
||||
{% endblock %}
|
||||
|
||||
{% macro facet(title, id, links) %}
|
||||
<li id="{{ id }}" class="facet">
|
||||
{% macro facet(title, id, links, active=False) %}
|
||||
<li id="{{ id }}" class="facet{{ ' active' if active }}">
|
||||
<h3>{{ title }}</h3>
|
||||
{{ facet_links(links) }}
|
||||
</li>
|
||||
|
@ -30,7 +35,7 @@
|
|||
<ul class="facet-group">
|
||||
{% for link in links recursive %}
|
||||
<li{% if link.selected %} class="selected"{% endif %}>
|
||||
<a href="{{ request.get_full_path()|urlparams(page=None, **link.urlparams) }}"
|
||||
<a href="{{ search_url|urlparams(page=None, **link.urlparams) }}"
|
||||
data-params="{{ dict(page=None, **link.urlparams)|json }}">
|
||||
{{ link.text }}</a>
|
||||
{% if link.children %}
|
||||
|
@ -55,8 +60,8 @@
|
|||
<section id="search-facets" class="col">
|
||||
<h2>{{ _('Filter Results') }}</h2>
|
||||
<ul class="facets island pjax-trigger">
|
||||
{{ facet(_('Category'), 'category-facets', categories, active=category) }}
|
||||
{{ facet(_('Price'), 'price-facets', prices) }}
|
||||
{{ facet(_('Category'), 'category-facets', categories) }}
|
||||
{{ facet(_('Optimized for'), 'device-facets', devices) }}
|
||||
</ul>
|
||||
{{ num_results() }}
|
||||
|
@ -64,7 +69,9 @@
|
|||
<section id="search-results" class="col">
|
||||
<h1>{{ heading }}</h1>
|
||||
<div id="search-listing" class="listing">
|
||||
{% include 'search/results_inner.html' %}
|
||||
{% with search_url=search_url %}
|
||||
{% include 'search/results_inner.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{{ impala_addon_listing_header(request.get_full_path()|urlparams(page=None),
|
||||
{{ impala_addon_listing_header(search_url|urlparams(page=None),
|
||||
sort_opts, query.sort or None, extra_sort_opts) }}
|
||||
{% if applied_filters %}
|
||||
<section class="applied-filters">
|
||||
<h2>{{ _('Applied Filters:') }}</h2>
|
||||
<ol>
|
||||
{% for f in applied_filters %}
|
||||
<li><a href="{{ request.get_full_path()|urlparams(**f.null_urlparams) }}">
|
||||
<li><a href="{{ search_url|urlparams(**f.null_urlparams) }}">
|
||||
{{ f.text }}</a></li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
|
|
|
@ -91,16 +91,11 @@ def sort_sidebar(query, form):
|
|||
for key, text in form.fields['sort'].choices]
|
||||
|
||||
|
||||
def app_search(request):
|
||||
def _app_search(request, category=None):
|
||||
form = forms.AppSearchForm(request.GET)
|
||||
form.is_valid() # Let the form try to clean data.
|
||||
query = form.cleaned_data
|
||||
|
||||
# Remove `sort=price` if `price=free`.
|
||||
if query.get('price') == 'free' and query.get('sort') == 'price':
|
||||
return redirect(amo.utils.urlparams(request.get_full_path(),
|
||||
sort=None))
|
||||
|
||||
qs = (Webapp.search()
|
||||
.filter(type=amo.ADDON_WEBAPP, status=amo.STATUS_PUBLIC,
|
||||
is_disabled=False)
|
||||
|
@ -118,11 +113,14 @@ def app_search(request):
|
|||
pager = amo.utils.paginate(request, qs)
|
||||
facets = pager.object_list.facets
|
||||
|
||||
if query.get('price') == 'free':
|
||||
# Remove 'Sort by Price' option if filtering by free apps.
|
||||
sort_opts = forms.FREE_SORT_CHOICES
|
||||
if category:
|
||||
sort_opts = forms.LISTING_SORT_CHOICES
|
||||
else:
|
||||
sort_opts = form.fields['sort'].choices
|
||||
if query.get('price') == 'free':
|
||||
# Remove 'Sort by Price' option if filtering by free apps.
|
||||
sort_opts = forms.FREE_SORT_CHOICES
|
||||
else:
|
||||
sort_opts = form.fields['sort'].choices
|
||||
|
||||
ctx = {
|
||||
'pager': pager,
|
||||
|
@ -137,13 +135,25 @@ def app_search(request):
|
|||
'devices': device_sidebar(query),
|
||||
}
|
||||
|
||||
applied_filters = []
|
||||
for facet in ['prices', 'categories', 'devices', 'sorting']:
|
||||
for idx, f in enumerate(ctx[facet]):
|
||||
# Show filters where something besides its first/default choice
|
||||
# is selected.
|
||||
if idx and f.selected:
|
||||
applied_filters.append(f)
|
||||
ctx['applied_filters'] = applied_filters
|
||||
if not category:
|
||||
applied_filters = []
|
||||
for facet in ['prices', 'categories', 'devices', 'sorting']:
|
||||
for idx, f in enumerate(ctx[facet]):
|
||||
# Show filters where something besides its first/default choice
|
||||
# is selected.
|
||||
if idx and f.selected:
|
||||
applied_filters.append(f)
|
||||
ctx['applied_filters'] = applied_filters
|
||||
|
||||
return jingo.render(request, 'search/results.html', ctx)
|
||||
return ctx
|
||||
|
||||
|
||||
def app_search(request):
|
||||
# Remove `sort=price` if `price=free`.
|
||||
data = request.GET
|
||||
if data.get('price') == 'free' and data.get('sort') == 'price':
|
||||
return redirect(amo.utils.urlparams(request.get_full_path(),
|
||||
sort=None))
|
||||
|
||||
# Otherwise render results.
|
||||
return jingo.render(request, 'search/results.html', _app_search(request))
|
||||
|
|
|
@ -28,6 +28,7 @@ ROOT_URLCONF = 'mkt.urls'
|
|||
|
||||
INSTALLED_APPS = list(INSTALLED_APPS)
|
||||
INSTALLED_APPS.remove('api')
|
||||
INSTALLED_APPS.remove('browse')
|
||||
INSTALLED_APPS.remove('compat')
|
||||
INSTALLED_APPS.remove('discovery')
|
||||
INSTALLED_APPS.remove('devhub')
|
||||
|
@ -36,18 +37,20 @@ INSTALLED_APPS = tuple(INSTALLED_APPS)
|
|||
|
||||
INSTALLED_APPS += (
|
||||
'mkt.site',
|
||||
'mkt.webapps',
|
||||
'mkt.browse',
|
||||
'mkt.detail',
|
||||
'mkt.developers',
|
||||
'mkt.experiments',
|
||||
'mkt.payments',
|
||||
'mkt.search',
|
||||
'mkt.submit',
|
||||
'mkt.experiments',
|
||||
'mkt.webapps',
|
||||
'devhub', # Put here so helpers.py doesn't get loaded first.
|
||||
)
|
||||
SUPPORTED_NONAPPS += (
|
||||
# this line is here until bug 735120 is fixed.
|
||||
'app',
|
||||
'apps',
|
||||
'dev',
|
||||
'login',
|
||||
'payments',
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
<input id="search-q" type="text" name="q" autocomplete="off" title=""
|
||||
placeholder="{{ search_form.placeholder(search_placeholder) }}"
|
||||
value="{{ search_form.q.value() }}">
|
||||
{% if search_form.cat.value() %}
|
||||
{{ search_form.cat }}
|
||||
{% endif %}
|
||||
{% for key, value in request.GET.items() %}
|
||||
<input type="hidden" name="{{ key }}" value="{{ value }}">
|
||||
{% endfor %}
|
||||
<div id="site-search-suggestions"
|
||||
data-cat="apps" data-src="{{ url('search.suggestions') }}"></div>
|
||||
</form>
|
||||
|
|
|
@ -5,7 +5,6 @@ from django.views.decorators.cache import cache_page
|
|||
from django.views.i18n import javascript_catalog
|
||||
|
||||
from apps.users.views import logout
|
||||
from apps.browse.views import extensions
|
||||
from mkt.developers.views import login
|
||||
|
||||
|
||||
|
@ -23,11 +22,8 @@ urlpatterns = patterns('',
|
|||
# App Detail pages.
|
||||
('^app/%s/' % APP_SLUG, include('mkt.detail.urls')),
|
||||
|
||||
# App Browse pages.
|
||||
('^apps/', include('mkt.webapps.urls')),
|
||||
|
||||
# TODO: Port category pages (bug 735578).
|
||||
url('^categories/(?P<category>[^ /]+)?$', extensions, name='browse.apps'),
|
||||
# Browse pages.
|
||||
('', include('mkt.browse.urls')),
|
||||
|
||||
# Replace the "old" Developer Hub with the "new" Marketplace one.
|
||||
('^developers/', include('mkt.developers.urls')),
|
||||
|
|
Загрузка…
Ссылка в новой задаче