implement Category browse pages (bug 735578, bug 735582)

This commit is contained in:
Chris Van 2012-03-22 19:53:28 -07:00
Родитель 24f3ea7147
Коммит 8355ecd4bc
13 изменённых файлов: 151 добавлений и 42 удалений

0
mkt/browse/__init__.py Normal file
Просмотреть файл

Просмотреть файл

Просмотреть файл

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

9
mkt/browse/urls.py Normal file
Просмотреть файл

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

23
mkt/browse/views.py Normal file
Просмотреть файл

@ -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(' &middot; ')|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(' &middot; ')|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')),