2010-02-06 01:06:16 +03:00
|
|
|
import collections
|
|
|
|
|
2010-06-16 03:03:48 +04:00
|
|
|
from django import http
|
2010-08-17 06:55:08 +04:00
|
|
|
from django.conf import settings
|
2010-06-19 03:11:12 +04:00
|
|
|
from django.http import HttpResponsePermanentRedirect
|
2010-03-24 04:05:50 +03:00
|
|
|
from django.shortcuts import get_object_or_404
|
2010-05-19 05:24:20 +04:00
|
|
|
from django.views.decorators.cache import cache_page
|
2010-02-10 22:35:25 +03:00
|
|
|
|
2010-06-21 20:38:09 +04:00
|
|
|
from tower import ugettext_lazy as _lazy
|
2010-02-06 01:06:16 +03:00
|
|
|
import jingo
|
|
|
|
import product_details
|
|
|
|
|
2010-02-10 22:35:25 +03:00
|
|
|
import amo.utils
|
|
|
|
from addons.models import Addon, Category
|
2010-05-19 05:24:20 +04:00
|
|
|
from amo.urlresolvers import reverse
|
2010-06-09 22:11:59 +04:00
|
|
|
from addons.views import BaseFilter
|
2010-02-10 22:35:25 +03:00
|
|
|
from translations.query import order_by_translation
|
2010-02-06 01:06:16 +03:00
|
|
|
|
|
|
|
|
|
|
|
languages = dict((lang.lower(), val)
|
|
|
|
for lang, val in product_details.languages.items())
|
|
|
|
|
|
|
|
|
|
|
|
def locale_display_name(locale):
|
|
|
|
"""
|
|
|
|
Return (english name, native name) for the locale.
|
|
|
|
|
|
|
|
Raises KeyError if the locale can't be found.
|
|
|
|
"""
|
|
|
|
if not locale:
|
|
|
|
raise KeyError
|
|
|
|
|
|
|
|
if locale.lower() in languages:
|
|
|
|
v = languages[locale.lower()]
|
|
|
|
return v['English'], v['native']
|
|
|
|
else:
|
|
|
|
# Take out the regional portion and try again.
|
|
|
|
hyphen = locale.rfind('-')
|
|
|
|
if hyphen == -1:
|
|
|
|
raise KeyError
|
|
|
|
else:
|
|
|
|
return locale_display_name(locale[:hyphen])
|
|
|
|
|
|
|
|
|
|
|
|
Locale = collections.namedtuple('Locale', 'locale display native dicts packs')
|
|
|
|
|
|
|
|
|
2010-08-03 22:21:37 +04:00
|
|
|
class AddonFilter(BaseFilter):
|
|
|
|
opts = (('name', _lazy(u'Name')),
|
|
|
|
('updated', _lazy(u'Updated')),
|
|
|
|
('created', _lazy(u'Created')),
|
|
|
|
('popular', _lazy(u'Downloads')),
|
|
|
|
('rating', _lazy(u'Rating')))
|
|
|
|
|
|
|
|
|
|
|
|
def addon_listing(request, addon_type, default='popular'):
|
|
|
|
# Set up the queryset and filtering for themes & extension listing pages.
|
|
|
|
status = [amo.STATUS_PUBLIC]
|
|
|
|
|
|
|
|
if request.GET.get('unreviewed', False) and not settings.SANDBOX_PANIC:
|
|
|
|
unreviewed = 'on'
|
|
|
|
else:
|
|
|
|
unreviewed = None
|
|
|
|
|
|
|
|
if unreviewed:
|
|
|
|
status.append(amo.STATUS_UNREVIEWED)
|
|
|
|
|
|
|
|
qs = Addon.objects.listed(request.APP, *status).filter(type=addon_type)
|
|
|
|
filter = AddonFilter(request, qs, 'sort', default)
|
|
|
|
return filter.qs, filter, unreviewed
|
|
|
|
|
|
|
|
|
2010-06-28 23:42:17 +04:00
|
|
|
def _get_locales(addons):
|
|
|
|
"""Does the heavy lifting for language_tools."""
|
2010-06-30 21:13:53 +04:00
|
|
|
# This is a generator so we can {% cache addons %} in the template without
|
|
|
|
# running any of this code.
|
2010-02-06 01:06:16 +03:00
|
|
|
for addon in addons:
|
|
|
|
locale = addon.target_locale.lower()
|
|
|
|
try:
|
|
|
|
english, native = locale_display_name(locale)
|
|
|
|
# Add the locale as a differentiator if we had to strip the
|
|
|
|
# regional portion.
|
|
|
|
if locale not in languages:
|
|
|
|
native = '%s (%s)' % (native, locale)
|
|
|
|
addon.locale_display, addon.locale_native = english, native
|
|
|
|
except KeyError:
|
|
|
|
english = u'%s (%s)' % (addon.name, locale)
|
|
|
|
addon.locale_display, addon.locale_native = english, ''
|
|
|
|
|
2010-06-30 21:13:53 +04:00
|
|
|
# We don't need the whole add-on so only store the parts in use.
|
|
|
|
def slim(addon):
|
|
|
|
return {'id': addon.id,
|
|
|
|
'file_size': addon.current_version.all_files[0].size,
|
|
|
|
'locale_disambiguation': addon.locale_disambiguation}
|
|
|
|
|
2010-02-06 01:06:16 +03:00
|
|
|
locales = {}
|
2010-08-03 08:10:44 +04:00
|
|
|
for locale, addons in amo.utils.sorted_groupby(addons, 'target_locale'):
|
2010-02-06 01:06:16 +03:00
|
|
|
addons = list(addons)
|
2010-06-30 21:13:53 +04:00
|
|
|
dicts = [slim(a) for a in addons if a.type == amo.ADDON_DICT]
|
|
|
|
packs = [slim(a) for a in addons if a.type == amo.ADDON_LPAPP]
|
2010-02-06 01:06:16 +03:00
|
|
|
addon = addons[0]
|
|
|
|
locales[locale] = Locale(addon.target_locale, addon.locale_display,
|
|
|
|
addon.locale_native, dicts, packs)
|
|
|
|
|
2010-06-30 21:13:53 +04:00
|
|
|
for locale in sorted(locales.items(), key=lambda x: x[1].display):
|
|
|
|
yield locale
|
|
|
|
|
2010-06-28 23:42:17 +04:00
|
|
|
|
|
|
|
# We never use the category, but this makes it
|
|
|
|
# uniform with the other type listings.
|
|
|
|
def language_tools(request, category=None):
|
|
|
|
types = (amo.ADDON_DICT, amo.ADDON_LPAPP)
|
2010-06-30 21:13:53 +04:00
|
|
|
addons = (Addon.objects.public()
|
|
|
|
.filter(appsupport__app=request.APP.id, type__in=types,
|
|
|
|
target_locale__isnull=False).exclude(target_locale=''))
|
2010-06-28 23:42:17 +04:00
|
|
|
|
2010-06-30 21:13:53 +04:00
|
|
|
locales = _get_locales(addons)
|
2010-05-17 18:34:35 +04:00
|
|
|
|
|
|
|
search_cat = '%s,0' % amo.ADDON_DICT
|
|
|
|
|
2010-02-06 01:06:16 +03:00
|
|
|
return jingo.render(request, 'browse/language_tools.html',
|
2010-06-30 21:13:53 +04:00
|
|
|
{'locales': locales, 'addons': addons,
|
|
|
|
'search_cat': search_cat})
|
2010-02-10 22:35:25 +03:00
|
|
|
|
|
|
|
|
|
|
|
def themes(request, category=None):
|
2010-03-24 04:04:57 +03:00
|
|
|
q = Category.objects.filter(application=request.APP.id,
|
|
|
|
type=amo.ADDON_THEME)
|
2010-02-10 22:35:25 +03:00
|
|
|
categories = order_by_translation(q, 'name')
|
|
|
|
|
2010-08-03 22:21:37 +04:00
|
|
|
addons, filter, unreviewed = addon_listing(request, amo.ADDON_THEME)
|
2010-02-10 22:35:25 +03:00
|
|
|
|
2010-06-03 22:36:22 +04:00
|
|
|
if category is not None:
|
2010-06-16 03:03:48 +04:00
|
|
|
try:
|
|
|
|
category = dict((c.slug, c) for c in categories)[category]
|
|
|
|
except KeyError:
|
|
|
|
raise http.Http404()
|
2010-06-03 22:36:22 +04:00
|
|
|
addons = addons.filter(categories__id=category.id)
|
2010-02-10 22:35:25 +03:00
|
|
|
|
2010-07-08 00:20:40 +04:00
|
|
|
count = addons.with_index(addons='type_status_inactive_idx').count()
|
|
|
|
themes = amo.utils.paginate(request, addons, count=count)
|
2010-02-10 22:35:25 +03:00
|
|
|
|
2010-05-17 18:34:35 +04:00
|
|
|
# Pre-selected category for search form
|
|
|
|
search_cat = '%s,0' % amo.ADDON_THEME
|
|
|
|
|
2010-02-10 22:35:25 +03:00
|
|
|
return jingo.render(request, 'browse/themes.html',
|
2010-07-08 00:20:40 +04:00
|
|
|
{'categories': categories,
|
2010-06-03 22:36:22 +04:00
|
|
|
'themes': themes, 'category': category,
|
2010-06-09 22:11:59 +04:00
|
|
|
'sorting': filter.field,
|
2010-03-24 04:04:57 +03:00
|
|
|
'sort_opts': filter.opts,
|
2010-05-17 18:34:35 +04:00
|
|
|
'unreviewed': unreviewed,
|
|
|
|
'search_cat': search_cat})
|
2010-03-24 04:04:57 +03:00
|
|
|
|
|
|
|
|
2010-03-24 04:05:50 +03:00
|
|
|
def extensions(request, category=None):
|
|
|
|
TYPE = amo.ADDON_EXTENSION
|
|
|
|
|
|
|
|
if category is not None:
|
|
|
|
q = Category.objects.filter(application=request.APP.id, type=TYPE)
|
|
|
|
category = get_object_or_404(q, slug=category)
|
2010-04-01 00:55:24 +04:00
|
|
|
|
|
|
|
if 'sort' not in request.GET and category:
|
|
|
|
return category_landing(request, category)
|
|
|
|
|
2010-08-03 22:21:37 +04:00
|
|
|
addons, filter, unreviewed = addon_listing(request, TYPE)
|
2010-04-01 00:55:24 +04:00
|
|
|
|
|
|
|
if category:
|
2010-03-24 04:05:50 +03:00
|
|
|
addons = addons.filter(categories__id=category.id)
|
|
|
|
|
2010-06-09 04:16:28 +04:00
|
|
|
count = addons.with_index(addons='type_status_inactive_idx').count()
|
|
|
|
addons = amo.utils.paginate(request, addons, count=count)
|
2010-03-24 04:05:50 +03:00
|
|
|
|
2010-05-17 18:34:35 +04:00
|
|
|
search_cat = '%s,%s' % (TYPE, category.id if category else 0)
|
|
|
|
|
2010-03-24 04:05:50 +03:00
|
|
|
return jingo.render(request, 'browse/extensions.html',
|
|
|
|
{'category': category, 'addons': addons,
|
2010-04-14 08:22:29 +04:00
|
|
|
'unreviewed': unreviewed,
|
2010-06-09 22:11:59 +04:00
|
|
|
'sorting': filter.field,
|
2010-05-17 18:34:35 +04:00
|
|
|
'sort_opts': filter.opts,
|
|
|
|
'search_cat': search_cat})
|
2010-04-01 00:55:24 +04:00
|
|
|
|
|
|
|
|
2010-06-09 22:11:59 +04:00
|
|
|
class CategoryLandingFilter(BaseFilter):
|
2010-04-01 00:55:24 +04:00
|
|
|
|
2010-04-23 07:34:54 +04:00
|
|
|
opts = (('featured', _lazy('Featured')),
|
|
|
|
('created', _lazy('Recently Added')),
|
2010-06-08 04:24:44 +04:00
|
|
|
('popular', _lazy('Top Downloads')),
|
2010-04-23 07:34:54 +04:00
|
|
|
('rating', _lazy('Top Rated')))
|
2010-04-01 00:55:24 +04:00
|
|
|
|
2010-04-09 05:42:02 +04:00
|
|
|
def __init__(self, request, base, category, key, default):
|
|
|
|
self.category = category
|
|
|
|
super(CategoryLandingFilter, self).__init__(request, base, key,
|
|
|
|
default)
|
|
|
|
|
2010-06-09 22:11:59 +04:00
|
|
|
def filter_featured(self):
|
|
|
|
return Addon.objects.filter(addoncategory__feature=True,
|
|
|
|
addoncategory__category=self.category)
|
2010-04-01 00:55:24 +04:00
|
|
|
|
|
|
|
|
|
|
|
def category_landing(request, category):
|
|
|
|
base = (Addon.objects.listed(request.APP).exclude(type=amo.ADDON_PERSONA)
|
|
|
|
.filter(categories__id=category.id))
|
2010-04-09 05:42:02 +04:00
|
|
|
filter = CategoryLandingFilter(request, base, category,
|
2010-04-01 00:55:24 +04:00
|
|
|
key='browse', default='featured')
|
|
|
|
|
2010-06-07 21:20:39 +04:00
|
|
|
search_cat = '%s,%s' % (category.type, category.id)
|
2010-05-17 18:34:35 +04:00
|
|
|
|
2010-04-01 00:55:24 +04:00
|
|
|
return jingo.render(request, 'browse/category_landing.html',
|
2010-05-17 18:34:35 +04:00
|
|
|
{'category': category, 'filter': filter,
|
|
|
|
'search_cat': search_cat})
|
2010-04-01 03:46:30 +04:00
|
|
|
|
|
|
|
|
|
|
|
def creatured(request, category):
|
|
|
|
TYPE = amo.ADDON_EXTENSION
|
|
|
|
q = Category.objects.filter(application=request.APP.id, type=TYPE)
|
|
|
|
category = get_object_or_404(q, slug=category)
|
2010-05-29 01:31:42 +04:00
|
|
|
addons = Addon.objects.public().filter(addoncategory__feature=True,
|
|
|
|
addoncategory__category=category)
|
2010-04-01 03:46:30 +04:00
|
|
|
return jingo.render(request, 'browse/creatured.html',
|
|
|
|
{'addons': addons, 'category': category})
|
2010-04-02 19:43:45 +04:00
|
|
|
|
|
|
|
|
2010-06-09 22:11:59 +04:00
|
|
|
class PersonasFilter(BaseFilter):
|
2010-04-02 19:43:45 +04:00
|
|
|
|
2010-04-23 07:34:54 +04:00
|
|
|
opts = (('up-and-coming', _lazy('Up & Coming')),
|
|
|
|
('created', _lazy('Recently Added')),
|
2010-08-19 05:31:17 +04:00
|
|
|
('popular', _lazy('Most Popular')),
|
|
|
|
('rating', _lazy('Top Rated')))
|
2010-04-02 19:43:45 +04:00
|
|
|
|
|
|
|
def _filter(self, field):
|
|
|
|
qs = Addon.objects
|
|
|
|
if field == 'created':
|
2010-06-09 04:53:20 +04:00
|
|
|
return (qs.order_by('-created')
|
|
|
|
.with_index(addons='created_type_idx'))
|
2010-04-02 19:43:45 +04:00
|
|
|
elif field == 'popular':
|
2010-06-09 04:53:20 +04:00
|
|
|
return (qs.order_by('-persona__popularity')
|
|
|
|
.with_index(personas='personas_popularity_idx'))
|
2010-04-02 19:43:45 +04:00
|
|
|
elif field == 'rating':
|
2010-06-09 04:53:20 +04:00
|
|
|
return (qs.order_by('-bayesian_rating')
|
|
|
|
.with_index(addons='rating_type_idx'))
|
2010-04-02 19:43:45 +04:00
|
|
|
else:
|
2010-06-09 04:53:20 +04:00
|
|
|
return (qs.order_by('-persona__movers')
|
|
|
|
.with_index(personas='personas_movers_idx'))
|
2010-04-02 19:43:45 +04:00
|
|
|
|
|
|
|
|
|
|
|
def personas(request, category=None):
|
|
|
|
TYPE = amo.ADDON_PERSONA
|
|
|
|
q = Category.objects.filter(application=request.APP.id,
|
|
|
|
type=TYPE)
|
|
|
|
categories = order_by_translation(q, 'name')
|
|
|
|
|
2010-06-16 07:14:49 +04:00
|
|
|
base = (Addon.objects.public().filter(type=TYPE)
|
|
|
|
.extra(select={'_app': request.APP.id}))
|
2010-05-21 07:32:20 +04:00
|
|
|
featured = base & Addon.objects.featured(request.APP)
|
|
|
|
is_homepage = category is None and 'sort' not in request.GET
|
2010-04-07 21:40:56 +04:00
|
|
|
|
2010-04-02 19:43:45 +04:00
|
|
|
if category is not None:
|
|
|
|
category = get_object_or_404(q, slug=category)
|
2010-04-07 21:40:56 +04:00
|
|
|
base = base.filter(categories__id=category.id)
|
2010-04-02 19:43:45 +04:00
|
|
|
|
|
|
|
filter = PersonasFilter(request, base, key='sort', default='up-and-coming')
|
|
|
|
|
2010-04-07 21:40:56 +04:00
|
|
|
if 'sort' in request.GET:
|
|
|
|
template = 'grid.html'
|
|
|
|
else:
|
|
|
|
template = 'category_landing.html'
|
|
|
|
|
2010-06-09 07:59:42 +04:00
|
|
|
if category:
|
|
|
|
count = category.count
|
|
|
|
else:
|
|
|
|
# Pass the count from base instead of letting it come from
|
|
|
|
# filter.qs.count() since that would join against personas.
|
|
|
|
count = base.with_index(addons='type_status_inactive_idx').count()
|
2010-06-09 04:53:20 +04:00
|
|
|
addons = amo.utils.paginate(request, filter.qs, 30, count=count)
|
2010-05-17 18:34:35 +04:00
|
|
|
|
2010-06-21 20:38:09 +04:00
|
|
|
search_cat = 'personas'
|
2010-05-17 18:34:35 +04:00
|
|
|
|
2010-04-07 21:40:56 +04:00
|
|
|
return jingo.render(request, 'browse/personas/' + template,
|
2010-04-02 19:43:45 +04:00
|
|
|
{'categories': categories, 'category': category,
|
2010-05-17 18:34:35 +04:00
|
|
|
'filter': filter, 'addons': addons,
|
2010-05-21 07:32:20 +04:00
|
|
|
'featured': featured, 'is_homepage': is_homepage,
|
2010-05-17 18:34:35 +04:00
|
|
|
'search_cat': search_cat})
|
2010-04-19 21:06:59 +04:00
|
|
|
|
|
|
|
|
2010-05-19 05:24:20 +04:00
|
|
|
@cache_page(60 * 60 * 24 * 365)
|
2010-08-03 22:21:37 +04:00
|
|
|
def legacy_redirects(request, type_, category=None, format=None):
|
2010-05-19 05:24:20 +04:00
|
|
|
type_slug = amo.ADDON_SLUGS.get(int(type_), 'extensions')
|
|
|
|
if not category or category == 'all':
|
|
|
|
url = reverse('browse.%s' % type_slug)
|
|
|
|
else:
|
|
|
|
cat = get_object_or_404(Category.objects, id=category)
|
2010-08-03 22:21:37 +04:00
|
|
|
if format == 'rss':
|
|
|
|
url = reverse('browse.%s.rss' % type_slug, args=[cat.slug])
|
|
|
|
else:
|
|
|
|
url = reverse('browse.%s' % type_slug, args=[cat.slug])
|
2010-05-27 04:29:21 +04:00
|
|
|
mapping = {'updated': 'updated', 'newest': 'created', 'name': 'name',
|
|
|
|
'weeklydownloads': 'popular', 'averagerating': 'rating'}
|
|
|
|
if 'sort' in request.GET and request.GET['sort'] in mapping:
|
|
|
|
url += '?sort=%s' % mapping[request.GET['sort']]
|
2010-05-19 05:24:20 +04:00
|
|
|
return HttpResponsePermanentRedirect(url)
|
2010-06-19 03:11:12 +04:00
|
|
|
|
|
|
|
|
|
|
|
def search_tools(request, category=None):
|
|
|
|
APP, TYPE = request.APP, amo.ADDON_SEARCH
|
|
|
|
qs = Category.objects.filter(application=APP.id, type=TYPE)
|
|
|
|
categories = order_by_translation(qs, 'name')
|
|
|
|
|
2010-08-03 22:21:37 +04:00
|
|
|
addons, filter, unreviewed = addon_listing(request, TYPE)
|
2010-06-19 03:11:12 +04:00
|
|
|
|
|
|
|
if category is not None:
|
|
|
|
category = get_object_or_404(qs, slug=category)
|
|
|
|
addons = addons.filter(categories__id=category.id)
|
|
|
|
|
|
|
|
addons = amo.utils.paginate(request, addons)
|
|
|
|
|
|
|
|
return jingo.render(request, 'browse/search_tools.html',
|
|
|
|
{'categories': categories, 'category': category,
|
|
|
|
'addons': addons, 'filter': filter,
|
|
|
|
'unreviewed': unreviewed})
|
2010-08-02 05:17:36 +04:00
|
|
|
|
2010-08-03 22:21:37 +04:00
|
|
|
|
2010-08-02 05:17:36 +04:00
|
|
|
def featured(request, category=None):
|
|
|
|
addons = Addon.objects.featured(request.APP)
|
|
|
|
return jingo.render(request, 'browse/featured.html', {'addons': addons})
|