diff --git a/media/js/devreg/lookup-tool.js b/media/js/devreg/lookup-tool.js
index 057d9e31c87..679f478bd24 100644
--- a/media/js/devreg/lookup-tool.js
+++ b/media/js/devreg/lookup-tool.js
@@ -45,10 +45,9 @@ require(['prefetchManifest']);
}));
// Search suggestions.
- $('#account-search').searchSuggestions($('#account-search-suggestions'),
- processResults);
- $('#app-search').searchSuggestions($('#app-search-suggestions'),
- processResults);
+ $('#account-search').searchSuggestions($('#account-search-suggestions'), processResults);
+ $('#app-search').searchSuggestions($('#app-search-suggestions'), processResults);
+ $('#website-search').searchSuggestions($('#website-search-suggestions'), processResults);
// Show All Results.
var searchTerm = '';
@@ -114,7 +113,7 @@ require(['prefetchManifest']);
id: item.id,
email: item.email || '',
name: item.name || item.display_name,
- status: item.status
+ status: item.status || 'none'
};
if (d.url && d.id) {
d.name = escape_(d.name);
diff --git a/migrations/913-websites-lookup-perms.sql b/migrations/913-websites-lookup-perms.sql
new file mode 100644
index 00000000000..ba3cc3a63cb
--- /dev/null
+++ b/migrations/913-websites-lookup-perms.sql
@@ -0,0 +1,3 @@
+-- Changing Websites:Review to Websites:* and removing duplicate Stats:View.
+UPDATE groups SET rules='Apps:*,Websites:*,Users:Edit,Stats:View,AdminTools:View,AccountLookup:View,AppLookup:View,Lookup:View' WHERE name='Staff';
+UPDATE groups SET rules=CONCAT(rules, ',WebsiteLookup:View') WHERE name IN ('Staff', 'Support Staff', 'Carriers and Operators');
diff --git a/mkt/lookup/helpers.py b/mkt/lookup/helpers.py
index d1964955573..edf79e42455 100644
--- a/mkt/lookup/helpers.py
+++ b/mkt/lookup/helpers.py
@@ -15,7 +15,6 @@ def format_currencies(context, currencies):
return jinja2.Markup(cs)
-# page_type is used for setting the link 'sel' class (activity/purchases)
@register.function
def user_header(account, title, is_admin=False, page_type=''):
t = env.get_template('lookup/helpers/user_header.html')
@@ -23,7 +22,6 @@ def user_header(account, title, is_admin=False, page_type=''):
'title': title, 'page_type': page_type}))
-# page_type is used for setting the link 'sel' class
@register.function
@jinja2.contextfunction
def app_header(context, app, page_type=''):
@@ -41,6 +39,15 @@ def app_header(context, app, page_type=''):
'is_operator': is_operator}))
+@register.function
+@jinja2.contextfunction
+def website_header(context, website, page_type=''):
+ t = env.get_template('lookup/helpers/website_header.html')
+
+ return jinja2.Markup(t.render({'website': website,
+ 'page_type': page_type}))
+
+
@register.function
@jinja2.contextfunction
def is_operator(context):
diff --git a/mkt/lookup/serializers.py b/mkt/lookup/serializers.py
index c4b60fdef96..fc85de8bf51 100644
--- a/mkt/lookup/serializers.py
+++ b/mkt/lookup/serializers.py
@@ -4,6 +4,7 @@
import mkt
from mkt.webapps.serializers import ESAppSerializer
+from mkt.websites.serializers import ESWebsiteSerializer
class AppLookupSerializer(ESAppSerializer):
@@ -30,3 +31,13 @@ def get_app_status(self, obj):
else:
status = mkt.STATUS_CHOICES_API_v2[obj.status]
return status
+
+
+class WebsiteLookupSerializer(ESWebsiteSerializer):
+ url = serializers.SerializerMethodField('get_website_summary_url')
+
+ class Meta(ESWebsiteSerializer.Meta):
+ fields = ['id', 'name', 'url']
+
+ def get_website_summary_url(self, obj):
+ return reverse('lookup.website_summary', args=[obj.id])
diff --git a/mkt/lookup/templates/lookup/helpers/website_header.html b/mkt/lookup/templates/lookup/helpers/website_header.html
new file mode 100644
index 00000000000..ad61ce85d70
--- /dev/null
+++ b/mkt/lookup/templates/lookup/helpers/website_header.html
@@ -0,0 +1,15 @@
+
+ {{ _('Website Lookup results for') }}
+ {{ website.name }}
+ ({{ website.pk }})
+
+
+
+
+
+
diff --git a/mkt/lookup/templates/lookup/home.html b/mkt/lookup/templates/lookup/home.html
index 7a0f2cf3b71..b92aeb973f5 100644
--- a/mkt/lookup/templates/lookup/home.html
+++ b/mkt/lookup/templates/lookup/home.html
@@ -13,4 +13,7 @@
{% if action_allowed('AppLookup', 'View') %}
{% include 'lookup/includes/app_search.html' %}
{% endif %}
+ {% if action_allowed('WebsiteLookup', 'View') %}
+ {% include 'lookup/includes/website_search.html' %}
+ {% endif %}
{% endblock %}
diff --git a/mkt/lookup/templates/lookup/includes/website_search.html b/mkt/lookup/templates/lookup/includes/website_search.html
new file mode 100644
index 00000000000..382721cdc16
--- /dev/null
+++ b/mkt/lookup/templates/lookup/includes/website_search.html
@@ -0,0 +1,13 @@
+
diff --git a/mkt/lookup/templates/lookup/website_summary.html b/mkt/lookup/templates/lookup/website_summary.html
new file mode 100644
index 00000000000..088b3de8c8f
--- /dev/null
+++ b/mkt/lookup/templates/lookup/website_summary.html
@@ -0,0 +1,56 @@
+{% extends 'lookup/base.html' %}
+
+{% block breadcrumbs %}
+{% endblock %}
+
+{% block content %}
+ {% include 'lookup/includes/website_search.html' %}
+
+
+ {{ website_header(website, 'summary') }}
+
+
+
+ - {{ _('Title') }}
+ - {{ website.title }}
+
+
+ - {{ _('Name - Short Name') }}
+ - {{ website.name }} - {{ website.short_name }}
+
+
+ - {{ _('Description') }}
+ - {{ website.description }}
+
+
+ - {{ _('URL') }}
+ - {{ website.url }}
+
+
+ - {{ _('Mobile URL') }}
+ - {{ website.mobile_url }}
+
+
+ - {{ _('Keywords') }}
+ - {{ website.keywords_list|join(', ') }}
+
+
+ - {{ _('Preferred Regions') }}
+ - {{ website.get_preferred_regions(sort_by='name')|map(attribute='name')|join(', ') }}
+
+
+ - {{ _('Categories') }}
+ - {{ website.categories|categories_names|join(', ') }}
+
+
+ - {{ _('Status') }}
+ -
+ {{ mkt.STATUS_CHOICES[website.status] }}
+ {% if website.is_disabled %}
+ ({{ _('disabled') }})
+ {% endif %}
+
+
+
+
+{% endblock %}
diff --git a/mkt/lookup/tests/test_serializers.py b/mkt/lookup/tests/test_serializers.py
new file mode 100644
index 00000000000..a61485c48ff
--- /dev/null
+++ b/mkt/lookup/tests/test_serializers.py
@@ -0,0 +1,27 @@
+from django.core.urlresolvers import reverse
+
+from nose.tools import eq_
+
+from mkt.lookup.serializers import WebsiteLookupSerializer
+from mkt.site.tests import ESTestCase
+from mkt.websites.indexers import WebsiteIndexer
+from mkt.websites.utils import website_factory
+
+
+class TestWebsiteLookupSerializer(ESTestCase):
+
+ def setUp(self):
+ self.website = website_factory()
+ self.refresh('website')
+
+ def serialize(self):
+ obj = WebsiteIndexer.search().filter(
+ 'term', id=self.website.pk).execute().hits[0]
+ return WebsiteLookupSerializer(obj).data
+
+ def test_basic(self):
+ data = self.serialize()
+ eq_(data['id'], self.website.id)
+ eq_(data['name'], {'en-US': self.website.name})
+ eq_(data['url'],
+ reverse('lookup.website_summary', args=[self.website.id]))
diff --git a/mkt/lookup/tests/test_views.py b/mkt/lookup/tests/test_views.py
index f9e2b6600e5..c36e9be0e5a 100644
--- a/mkt/lookup/tests/test_views.py
+++ b/mkt/lookup/tests/test_views.py
@@ -18,9 +18,9 @@
import mkt.site.tests
from mkt.abuse.models import AbuseReport
from mkt.access.models import Group, GroupUser
-from mkt.constants.payments import (
- FAILED, PENDING, PROVIDER_BANGO, PROVIDER_REFERENCE,
- SOLITUDE_REFUND_STATUSES)
+from mkt.constants.payments import (FAILED, PENDING, PROVIDER_BANGO,
+ PROVIDER_REFERENCE,
+ SOLITUDE_REFUND_STATUSES)
from mkt.developers.models import (ActivityLog, AddonPaymentAccount,
PaymentAccount, SolitudeSeller)
from mkt.developers.providers import get_provider
@@ -38,6 +38,7 @@
from mkt.tags.models import Tag
from mkt.users.models import UserProfile
from mkt.webapps.models import AddonUser, Webapp
+from mkt.websites.utils import website_factory
class SummaryTest(TestCase):
@@ -293,6 +294,7 @@ class SearchTestMixin(object):
def search(self, expect_objects=True, **data):
res = self.client.get(self.url, data)
+ eq_(res.status_code, 200)
data = json.loads(res.content)
if expect_objects:
assert len(data['objects']), 'should be more than 0 objects'
@@ -1174,3 +1176,38 @@ def test_logs(self):
doc = pq(res.content)
assert 'manifest updated' in doc('li.item').eq(0).text()
assert 'Comment on' in doc('li.item').eq(1).text()
+
+
+class TestWebsiteSearch(ESTestCase, SearchTestMixin):
+ fixtures = fixture('user_support_staff', 'user_999')
+
+ def setUp(self):
+ super(TestWebsiteSearch, self).setUp()
+ self.url = reverse('lookup.website_search')
+ self.website = website_factory()
+ self.refresh('website')
+ self.login('support-staff@mozilla.com')
+
+ def search(self, *args, **kwargs):
+ if 'lang' not in kwargs:
+ kwargs.update({'lang': 'en-US'})
+ return super(TestWebsiteSearch, self).search(*args, **kwargs)
+
+ def verify_result(self, data):
+ eq_(data['objects'][0]['id'], self.website.pk)
+ eq_(data['objects'][0]['name'], self.website.name.localized_string)
+ eq_(data['objects'][0]['url'], reverse('lookup.website_summary',
+ args=[self.website.pk]))
+
+ def test_auth_required(self):
+ self.client.logout()
+ res = self.client.get(self.url)
+ eq_(res.status_code, 403)
+
+ def test_by_name(self):
+ data = self.search(q=self.website.name.localized_string)
+ self.verify_result(data)
+
+ def test_by_id(self):
+ data = self.search(q=self.website.pk)
+ self.verify_result(data)
diff --git a/mkt/lookup/urls.py b/mkt/lookup/urls.py
index 8c6ff0a583a..4407adc4014 100644
--- a/mkt/lookup/urls.py
+++ b/mkt/lookup/urls.py
@@ -27,6 +27,14 @@
)
+# These views all start with website/.
+website_patterns = patterns(
+ '',
+ url(r'^summary$', views.website_summary,
+ name='lookup.website_summary'),
+)
+
+
# These views all start with transaction ID.
transaction_patterns = patterns(
'',
@@ -49,7 +57,10 @@
name='lookup.transaction_search'),
url(r'^user_search$', views.user_search,
name='lookup.user_search'),
+ url(r'^website_search$', views.WebsiteLookupSearchView.as_view(),
+ name='lookup.website_search'),
(r'^app/(?P[^/]+)/', include(app_patterns)),
+ (r'^website/(?P[^/]+)/', include(website_patterns)),
(r'^transaction/(?P[^/]+)/',
include(transaction_patterns)),
(r'^user/(?P[^/]+)/', include(user_patterns)),
diff --git a/mkt/lookup/views.py b/mkt/lookup/views.py
index bbfd2931f2f..b7b076e3c59 100644
--- a/mkt/lookup/views.py
+++ b/mkt/lookup/views.py
@@ -33,7 +33,7 @@
from mkt.developers.views_payments import _redirect_to_bango_portal
from mkt.lookup.forms import (APIFileStatusForm, APIStatusForm, DeleteUserForm,
TransactionRefundForm, TransactionSearchForm)
-from mkt.lookup.serializers import AppLookupSerializer
+from mkt.lookup.serializers import AppLookupSerializer, WebsiteLookupSerializer
from mkt.prices.models import AddonPaymentData, Refund
from mkt.purchase.models import Contribution
from mkt.reviewers.models import QUEUE_TARAKO
@@ -41,8 +41,11 @@
from mkt.search.views import SearchView
from mkt.site.decorators import json_view, login_required, permission_required
from mkt.site.utils import paginate
+from mkt.tags.models import attach_tags
from mkt.users.models import UserProfile
from mkt.webapps.models import Webapp
+from mkt.websites.models import Website
+from mkt.websites.views import WebsiteSearchView
log = commonware.log.getLogger('z.lookup')
@@ -314,6 +317,18 @@ def app_summary(request, addon_id):
})
+@login_required
+@permission_required([('WebsiteLookup', 'View')])
+def website_summary(request, addon_id):
+ website = get_object_or_404(Website, pk=addon_id)
+ if not hasattr(website, 'keywords_list'):
+ attach_tags([website], m2m_name='keywords')
+
+ return render(request, 'lookup/website_summary.html', {
+ 'website': website,
+ })
+
+
@login_required
@permission_required([('AccountLookup', 'View')])
def app_activity(request, addon_id):
@@ -443,6 +458,21 @@ def get_paginate_by(self, *args, **kwargs):
**kwargs)
+class WebsiteLookupSearchView(WebsiteSearchView):
+ permission_classes = [GroupPermission('WebsiteLookup', 'View')]
+ filter_backends = [SearchQueryFilter]
+ serializer_class = WebsiteLookupSerializer
+ paginate_by = lkp.SEARCH_LIMIT
+ max_paginate_by = lkp.MAX_RESULTS
+
+ def get_paginate_by(self, *args, **kwargs):
+ if self.request.GET.get(self.paginate_by_param) == 'max':
+ return self.max_paginate_by
+ else:
+ return super(WebsiteLookupSearchView,
+ self).get_paginate_by(*args, **kwargs)
+
+
def _app_summary(user_id):
sql = """
select currency,
diff --git a/mkt/site/fixtures/data/user_operator.json b/mkt/site/fixtures/data/user_operator.json
index cca553cc596..9d3d7411605 100644
--- a/mkt/site/fixtures/data/user_operator.json
+++ b/mkt/site/fixtures/data/user_operator.json
@@ -19,7 +19,7 @@
"pk": 322,
"model": "access.group",
"fields": {
- "rules": "AppLookup:View,Lookup:View,Operators:*",
+ "rules": "AppLookup:View,WebsiteLookup:View,Lookup:View,Operators:*",
"notes": "",
"modified": "2012-05-22 17:53:57",
"name": "Operators",
diff --git a/mkt/site/fixtures/data/user_support_staff.json b/mkt/site/fixtures/data/user_support_staff.json
index 11306241a55..e7d8be9d563 100644
--- a/mkt/site/fixtures/data/user_support_staff.json
+++ b/mkt/site/fixtures/data/user_support_staff.json
@@ -19,7 +19,7 @@
"pk": 50059,
"model": "access.group",
"fields": {
- "rules": "AccountLookup:View,Apps:Configure,Transaction:Refund,Transaction:View,Lookup:View,AppLookup:View,BangoPortal:Redirect",
+ "rules": "AccountLookup:View,Apps:Configure,Transaction:Refund,Transaction:View,Lookup:View,AppLookup:View,WebsiteLookup:View,BangoPortal:Redirect",
"notes": "",
"modified": "2012-05-22 17:53:57",
"name": "Support Staff",
diff --git a/mkt/site/fixtures/data/users.json b/mkt/site/fixtures/data/users.json
index 675d3e9cca9..3fb0d5f7676 100644
--- a/mkt/site/fixtures/data/users.json
+++ b/mkt/site/fixtures/data/users.json
@@ -167,7 +167,7 @@
"pk": 50057,
"model": "access.group",
"fields": {
- "rules": "AccountLookup:View,Apps:Configure,Transaction:View,Transaction:Refund,Lookup:View,AppLookup:View",
+ "rules": "AccountLookup:View,Apps:Configure,Transaction:View,Transaction:Refund,Lookup:View,AppLookup:View,WebsiteLookup:View",
"notes": "",
"modified": "2012-05-22 17:53:57",
"name": "Support Staff",
diff --git a/mkt/site/fixtures/init.json b/mkt/site/fixtures/init.json
index d1a1f08a5df..f92567389cd 100644
--- a/mkt/site/fixtures/init.json
+++ b/mkt/site/fixtures/init.json
@@ -1344,7 +1344,7 @@
"modified": "2012-04-19T18:43:22",
"name": "Staff",
"notes": "",
- "rules": "Apps:*,Users:Edit,Stats:View,AdminTools:View,AccountLookup:View,AppLookup:View,Lookup:View,Stats:View,Websites:*"
+ "rules": "Apps:*,Users:Edit,Stats:View,AdminTools:View,AccountLookup:View,AppLookup:View,WebsiteLookup:View,Lookup:View,Stats:View,Websites:*"
},
"model": "access.group",
"pk": 50000
@@ -1476,7 +1476,7 @@
"modified": "2012-05-22T17:12:04",
"name": "Support Staff",
"notes": "",
- "rules": "AccountLookup:View,Apps:Configure,Transaction:View,Transaction:View,Transaction:Refund,Lookup:View,AppLookup:View"
+ "rules": "AccountLookup:View,Apps:Configure,Transaction:View,Transaction:View,Transaction:Refund,Lookup:View,AppLookup:View,WebsiteLookup:View"
},
"model": "access.group",
"pk": 50057
@@ -1564,7 +1564,7 @@
"modified": "2013-09-13T12:54:58",
"name": "Operators",
"notes": "For operators to perform app lookups",
- "rules": "Lookup:View,AppLookup:View,Operators:*"
+ "rules": "Lookup:View,AppLookup:View,WebsiteLookup:View,Operators:*"
},
"model": "access.group",
"pk": 50070
diff --git a/mkt/websites/models.py b/mkt/websites/models.py
index 7ddceaa00a6..1131bb9053d 100644
--- a/mkt/websites/models.py
+++ b/mkt/websites/models.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+import operator
import os.path
from django.conf import settings
@@ -8,6 +9,7 @@
from django_extensions.db.fields.json import JSONField
+import mkt
from lib.utils import static_url
from mkt.constants.applications import DEVICE_TYPE_LIST
from mkt.constants.base import LISTED_STATUSES, STATUS_CHOICES, STATUS_NULL
@@ -116,6 +118,17 @@ def get_icon_url(self, size):
def get_url_path(self):
return reverse('website.detail', kwargs={'pk': self.pk})
+ def get_preferred_regions(self, sort_by='slug'):
+ """
+ Return a list of region objects the website is preferred in, e.g.::
+
+ [, ...]
+
+ """
+ _regions = map(mkt.regions.REGIONS_CHOICES_ID_DICT.get,
+ self.preferred_regions)
+ return sorted(_regions, key=operator.attrgetter(sort_by))
+
class WebsitePopularity(ModelBase):
website = models.ForeignKey(Website, related_name='popularity')
diff --git a/mkt/websites/tests/test_models.py b/mkt/websites/tests/test_models.py
index a556d6d6c6d..e1671366c05 100644
--- a/mkt/websites/tests/test_models.py
+++ b/mkt/websites/tests/test_models.py
@@ -1,8 +1,11 @@
+import json
+
import mock
from nose.tools import eq_
from lib.utils import static_url
from mkt.constants.applications import DEVICE_TYPE_LIST
+from mkt.constants.regions import URY, USA
from mkt.site.tests import TestCase
from mkt.websites.models import Website
from mkt.websites.utils import website_factory
@@ -43,6 +46,12 @@ def test_get_icon_no_icon(self):
website = Website(pk=1)
assert website.get_icon_url(32).endswith('/default-32.png')
+ def test_get_preferred_regions(self):
+ website = Website()
+ website.preferred_regions = json.dumps([URY.id, USA.id])
+ eq_([r.slug for r in website.get_preferred_regions()],
+ [USA.slug, URY.slug])
+
class TestWebsiteESIndexation(TestCase):
@mock.patch('mkt.search.indexers.BaseIndexer.index_ids')