diff --git a/apps/search/views.py b/apps/search/views.py index df2603e1f6..d621612bd7 100644 --- a/apps/search/views.py +++ b/apps/search/views.py @@ -295,7 +295,7 @@ class BaseAjaxSearch(object): """ - def __init__(self, request, excluded_ids=[]): + def __init__(self, request, excluded_ids=()): self.request = request self.excluded_ids = excluded_ids self.src = getattr(self, 'src', None) @@ -369,6 +369,19 @@ class PersonaSuggestionsAjax(SearchSuggestionsAjax): class WebappSuggestionsAjax(SearchSuggestionsAjax): types = [amo.ADDON_WEBAPP] + fields = {'id': 'id', + 'name': 'name' + } + + def __init__(self, request, excluded_ids=(), category=None): + self.category = category + SearchSuggestionsAjax.__init__(self, request, excluded_ids) + + def queryset(self): + res = SearchSuggestionsAjax.queryset(self) + if self.category: + res = res.filter(category__in=[self.category]) + return res @json_view diff --git a/media/js/mkt/admin_featuredapp.js b/media/js/mkt/admin_featuredapp.js index 3fddec001f..996e8c78e3 100644 --- a/media/js/mkt/admin_featuredapp.js +++ b/media/js/mkt/admin_featuredapp.js @@ -5,7 +5,8 @@ function registerAddonAutocomplete(node) { width: 300, source: function(request, response) { $.getJSON($(node).attr('data-src'), { - q: request.term + q: request.term, + category: $("#categories").val() }, response); }, focus: function(event, ui) { @@ -13,17 +14,8 @@ function registerAddonAutocomplete(node) { return false; }, select: function(event, ui) { - $(node).val(ui.item.name).attr('data-id', ui.item.id); - var current = template( - ' {name}'); - $td.find('.current-webapp').show().html(current({ - url: ui.item.url, - icon: ui.item.icon, - name: ui.item.name - })); - $td.find('input[type=hidden]').val(ui.item.id); - node.val(''); - node.hide(); + updateAppsList($("#categories"), + ui.item.id); return false; } }).data('autocomplete')._renderItem = function(ul, item) { @@ -33,19 +25,55 @@ function registerAddonAutocomplete(node) { } function newAddonSlot(id) { - var $tbody = $("#" + id + "-webapps"); + var $tbody = $("#featured-webapps"); var $form = $tbody.next().children("tr").clone(); var $input = $form.find('input.placeholder'); registerAddonAutocomplete($input); - $form.find('input[type=hidden]').attr( - "name", $tbody.children().length + "-" + id +"-webapp"); $tbody.append($form); } -$(document).ready(function(){ - $("#home-webapps, #category-webapps").delegate( - '.remove', 'click', _pd(function() {$(this).closest('tr').remove();})); +function showAppsList(cat) { + return appslistXHR('GET', { + category: cat.val() + })}; - $('#home-add').click(_pd(function() { newAddonSlot("home"); })); - $('#category-add').click(_pd(function() { newAddonSlot("category"); })); +function updateAppsList(cat, newItem) { + return appslistXHR('POST', { + category: cat.val(), + add: newItem + })}; + +function deleteFromAppsList(cat, oldItem) { + return appslistXHR('POST', { + category: cat.val(), + delete: oldItem + })}; + +function appslistXHR(verb, data) { + var appslist = $("#featured-webapps"); + var q = $.ajax({type: verb, url: appslist.data("src"), data: data}); + q.then(function (data) { + appslist.html(data); + }); + return q; +}; + +$(document).ready(function(){ + $("#featured-webapps").delegate( + '.remove', 'click', _pd(function() { + deleteFromAppsList($("#categories"), + $(this).data("id")); + })); + var categories = $("#categories"); + var appslist = $("#featured-webapps"); + var p = $.ajax({type: 'GET', + url: categories.data("src")}); + p.then(function(data) { + categories.html(data); + showAppsList(categories); + }); + categories.change(function (e) { + showAppsList(categories); + }); + $('#featured-add').click(_pd(function() { newAddonSlot(); })); }); diff --git a/migrations/421-featured-app-admin.sql b/migrations/421-featured-app-admin.sql new file mode 100644 index 0000000000..1d133f5d9d --- /dev/null +++ b/migrations/421-featured-app-admin.sql @@ -0,0 +1,7 @@ +CREATE TABLE `zadmin_featuredapp` ( + `id` int(11) unsigned AUTO_INCREMENT NOT NULL PRIMARY KEY, + `app_id` integer NOT NULL, + `category_id` int(11) unsigned, + `is_sponsor` bool NOT NULL, + FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci; diff --git a/mkt/search/tests/test_views.py b/mkt/search/tests/test_views.py index bb58470dba..bb777233bd 100644 --- a/mkt/search/tests/test_views.py +++ b/mkt/search/tests/test_views.py @@ -13,6 +13,8 @@ from mkt.search.forms import DEVICE_CHOICES_IDS from mkt.webapps.models import Webapp from mkt.webapps.tests.test_views import PaidAppMixin +from search.tests.test_views import TestAjaxSearch + class SearchBase(amo.tests.ESTestCase): @@ -258,3 +260,39 @@ class TestWebappSearch(PaidAppMixin, SearchBase): # `sort=price` should be removed if `price=free` is in querystring. r = self.client.get(url, {'price': 'free', 'sort': 'price'}) self.assertRedirects(r, urlparams(url, price='free')) + + +class SuggestionsTests(TestAjaxSearch): + + def check_suggestions(self, url, params, addons=()): + r = self.client.get(url + '?' + params) + eq_(r.status_code, 200) + data = json.loads(r.content) + data.sort(key=lambda x: x['id']) + addons.sort(key=lambda x: x.id) + eq_(len(data), len(addons)) + for got, expected in zip(data, addons): + eq_(int(got['id']), expected.id) + eq_(got['name'], unicode(expected.name)) + + def test_webapp_search(self): + url = reverse('search.apps_ajax') + c1 = Category.objects.create(name='groovy', + type=amo.ADDON_WEBAPP) + c2 = Category.objects.create(name='awesome', + type=amo.ADDON_WEBAPP) + g1 = Webapp.objects.create(status=amo.STATUS_PUBLIC, + name='groovy app 1', + type=amo.ADDON_WEBAPP) + a2 = Webapp.objects.create(status=amo.STATUS_PUBLIC, + name='awesome app 2', + type=amo.ADDON_WEBAPP) + AddonCategory.objects.create(category=c1, addon=g1) + AddonCategory.objects.create(category=c2, addon=a2) + self.client.login(username='admin@mozilla.com', password='password') + for a in Webapp.objects.all(): + a.save() + self.refresh() + self.check_suggestions(url, "q=app&category=", addons=[g1, a2]) + self.check_suggestions(url, "q=app&category=%d" % c1.id, addons=[g1]) + self.check_suggestions(url, "q=app&category=%d" % c2.id, addons=[a2]) diff --git a/mkt/search/views.py b/mkt/search/views.py index 69250874cd..1185a4d047 100644 --- a/mkt/search/views.py +++ b/mkt/search/views.py @@ -191,4 +191,7 @@ def app_search(request): @json_view def ajax_search(request): - return WebappSuggestionsAjax(request).items + category = request.GET.get('category', None) or None + if category: + category = int(category) + return WebappSuggestionsAjax(request, category=category).items diff --git a/mkt/site/helpers.py b/mkt/site/helpers.py index 9b2002a86e..dbe7ee8dea 100644 --- a/mkt/site/helpers.py +++ b/mkt/site/helpers.py @@ -182,7 +182,7 @@ def admin_site_links(): return { 'addons': [ ('Search for apps by name or id', reverse('zadmin.addon-search')), - ('Featured add-ons', reverse('admin.featured_apps')), + ('Featured add-ons', reverse('zadmin.featured_apps')), ('Name blocklist', reverse('zadmin.addon-name-blocklist')), ('Fake mail', reverse('zadmin.mail')), ('Flagged reviews', reverse('zadmin.flagged')), diff --git a/mkt/urls.py b/mkt/urls.py index 9ef3d79e96..cc936ee1db 100644 --- a/mkt/urls.py +++ b/mkt/urls.py @@ -12,8 +12,6 @@ from apps.users.urls import (detail_patterns as user_detail_patterns, from mkt.account.urls import (purchases_patterns, settings_patterns, users_patterns as mkt_users_patterns) from mkt.developers.views import login -from mkt.zadmin.views import featured_apps_admin - admin.autodiscover() @@ -92,10 +90,6 @@ urlpatterns = patterns('', # Paypal, needed for IPNs only. ('^services/', include('paypal.urls')), - # Featured apps selector. - url('^admin/apps/featured$', featured_apps_admin, - name='admin.featured_apps'), - # AMO admin (not django admin). ('^admin/', include('zadmin.urls')), diff --git a/mkt/zadmin/models.py b/mkt/zadmin/models.py new file mode 100644 index 0000000000..913681b07b --- /dev/null +++ b/mkt/zadmin/models.py @@ -0,0 +1,13 @@ +from django.db import models + +from addons.models import Category +from mkt.webapps.models import Webapp + + +class FeaturedApp(models.Model): + app = models.ForeignKey(Webapp, null=False) + category = models.ForeignKey(Category, null=True) + is_sponsor = models.BooleanField(default=False) + + class Meta: + db_table = 'zadmin_featuredapp' diff --git a/mkt/zadmin/templates/zadmin/featured_apps_ajax.html b/mkt/zadmin/templates/zadmin/featured_apps_ajax.html new file mode 100644 index 0000000000..839060a3da --- /dev/null +++ b/mkt/zadmin/templates/zadmin/featured_apps_ajax.html @@ -0,0 +1,28 @@ +{% block content %} +{% for row in apps -%} +
+ | {{ row.app.name }} | +{% for dt in row.app.device_types %} + {{ dt }} + {% endfor %} | +
{% if row.app.promo %} + Manage featured graphics + {% else %} + No featured graphics + {% endif %} + | +{% if row.is_sponsor %}Sponsored{% else %}Not sponsored{% endif %} | +Locale etc | +
App | -Delete | - - - {% for idx, app in apps %} -
---|
-
-
- {{ app.name }}
-
-
- ×
- |
- - - × - |