Merge lp:~michael.nelson/ubuntu-webcatalog/978000-recommended-apps-2 into lp:ubuntu-webcatalog

Proposed by Michael Nelson
Status: Merged
Approved by: Anthony Lenton
Approved revision: 120
Merged at revision: 111
Proposed branch: lp:~michael.nelson/ubuntu-webcatalog/978000-recommended-apps-2
Merge into: lp:ubuntu-webcatalog
Prerequisite: lp:~michael.nelson/ubuntu-webcatalog/978000-recommended-apps
Diff against target: 331 lines (+91/-63)
12 files modified
django_project/config/main.cfg (+1/-1)
src/webcatalog/context_processors.py (+9/-9)
src/webcatalog/schema.py (+1/-0)
src/webcatalog/static/css/carousel.css (+2/-2)
src/webcatalog/static/css/webcatalog.css (+6/-2)
src/webcatalog/templates/webcatalog/application_detail.html (+26/-9)
src/webcatalog/templates/webcatalog/recommended_apps.html (+12/-0)
src/webcatalog/templates/webcatalog/recommended_apps_widget.html (+18/-20)
src/webcatalog/tests/test_utilities.py (+2/-2)
src/webcatalog/tests/test_views.py (+11/-16)
src/webcatalog/utilities.py (+2/-1)
src/webcatalog/views.py (+1/-1)
To merge this branch: bzr merge lp:~michael.nelson/ubuntu-webcatalog/978000-recommended-apps-2
Reviewer Review Type Date Requested Status
Anthony Lenton (community) Approve
Review via email: mp+102709@code.launchpad.net

Commit message

Add recommended apps to the details page side-bar.

Description of the change

Overview
========

Originally I'd planned to have the recommendations in a carousel like this:

https://bugs.launchpad.net/ubuntu-webcatalog/+bug/978000/+attachment/3094678/+files/978000-app-with-recommendations.png

(hence the work on dry-ing up the carousel templates), but after chatting, we decided to put them in the side bar like this:

https://bugs.launchpad.net/ubuntu-webcatalog/+bug/978000/+attachment/3094789/+files/978000-recommended-sidebar.png

which is what this branch now does.

I decided not to include a default link for non-js, as it would be ugly in the side-bar, and the functionality of adding the recommendations is a progressive enhancement anyway (and not required to use the page).

`fab test`

NOTE: I've modified the name of a context_processor below and updated main.cfg, but I've not yet checked if our settings for deploy inherit from this main.cfg, or whether we need to make the same change there.

NOTE2: I just realised we'll need to provide the recommender api (sreclient.py) in the deploy and afaik it's not yet packaged. The previous branch pulls it in via bzr. Let me know the best way forward and I'll do it in the morning.

To post a comment you must log in.
120. By Michael Nelson

Removed now unnecessary js import.

Revision history for this message
Anthony Lenton (elachuni) wrote :

Looks fine

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'django_project/config/main.cfg'
--- django_project/config/main.cfg 2012-04-18 22:29:29 +0000
+++ django_project/config/main.cfg 2012-04-19 23:28:18 +0000
@@ -49,8 +49,8 @@
49 django.core.context_processors.media49 django.core.context_processors.media
50 django.core.context_processors.static50 django.core.context_processors.static
51 django.contrib.messages.context_processors.messages51 django.contrib.messages.context_processors.messages
52 webcatalog.context_processors.google_analytics_id
53 webcatalog.context_processors.user_agent52 webcatalog.context_processors.user_agent
53 webcatalog.context_processors.settings_for_templates
5454
55static_root = ./django_project/static/55static_root = ./django_project/static/
56static_url = /assets/56static_url = /assets/
5757
=== modified file 'src/webcatalog/context_processors.py'
--- src/webcatalog/context_processors.py 2012-03-07 22:57:41 +0000
+++ src/webcatalog/context_processors.py 2012-04-19 23:28:18 +0000
@@ -20,15 +20,15 @@
20from django.conf import settings20from django.conf import settings
2121
2222
23def google_analytics_id(request):
24 """Adds the google analytics id to the context if it's present."""
25 return {
26 'google_analytics_id': getattr(settings, 'GOOGLE_ANALYTICS_ID', None),
27 'secondary_google_analytics_id':
28 getattr(settings, 'SECONDARY_GOOGLE_ANALYTICS_ID', None),
29 }
30
31
32def user_agent(request):23def user_agent(request):
33 """Adds the browser user-agent string to the context if present."""24 """Adds the browser user-agent string to the context if present."""
34 return dict(user_agent=request.META.get('HTTP_USER_AGENT', ''))25 return dict(user_agent=request.META.get('HTTP_USER_AGENT', ''))
26
27
28def settings_for_templates(request):
29 """Adds some settings used in templates so views can know less :)"""
30 return dict(
31 num_recommended_apps=settings.NUM_RECOMMENDED_APPS,
32 google_analytics_id=getattr(settings, 'GOOGLE_ANALYTICS_ID', None),
33 secondary_google_analytics_id=getattr(settings,
34 'SECONDARY_GOOGLE_ANALYTICS_ID', None))
3535
=== modified file 'src/webcatalog/schema.py'
--- src/webcatalog/schema.py 2012-04-19 23:28:18 +0000
+++ src/webcatalog/schema.py 2012-04-19 23:28:18 +0000
@@ -92,3 +92,4 @@
92 class recommender(schema.Section):92 class recommender(schema.Section):
93 rec_service_root = schema.StringOption(93 rec_service_root = schema.StringOption(
94 default="http://rec.ubuntu.com/api/1.0")94 default="http://rec.ubuntu.com/api/1.0")
95 num_recommended_apps = schema.IntOption(default=4)
9596
=== modified file 'src/webcatalog/static/css/carousel.css'
--- src/webcatalog/static/css/carousel.css 2012-04-19 23:28:18 +0000
+++ src/webcatalog/static/css/carousel.css 2012-04-19 23:28:18 +0000
@@ -146,7 +146,7 @@
146 border-right: 0;146 border-right: 0;
147}147}
148148
149#featured-carousel .pagination li a.active,149.featured-widget .pagination li a.active,
150#top-rated-carousel .pagination li a.active {150#top-rated-carousel .pagination li a.active {
151 background-color: #333;151 background-color: #333;
152}152}
@@ -213,7 +213,7 @@
213 bottom: 0;213 bottom: 0;
214}214}
215215
216.top-rated-stars {216.carousel-container .top-rated-stars {
217 position: relative;217 position: relative;
218 left: -50px;218 left: -50px;
219 top: -15px;219 top: -15px;
220220
=== modified file 'src/webcatalog/static/css/webcatalog.css'
--- src/webcatalog/static/css/webcatalog.css 2012-04-05 01:09:37 +0000
+++ src/webcatalog/static/css/webcatalog.css 2012-04-19 23:28:18 +0000
@@ -365,9 +365,13 @@
365 border-bottom: 1px dotted gray;365 border-bottom: 1px dotted gray;
366}366}
367367
368.emaillinkportlet .portletheader, .debtags .portletheader, .versions .portletheader {368.emaillinkportlet .portletheader, .debtags .portletheader,
369.versions .portletheader, .recommendedportlet .portletheader {
369 background: #bbb;370 background: #bbb;
370}371}
372.portletactions .star-rating {
373 padding-top: 10px;
374}
371375
372#emailForm {376#emailForm {
373 margin-top: 8px;377 margin-top: 8px;
@@ -422,4 +426,4 @@
422 font-size: 32px;426 font-size: 32px;
423 margin: 32px;427 margin: 32px;
424 color: #888;428 color: #888;
425}
426\ No newline at end of file429\ No newline at end of file
430}
427431
=== modified file 'src/webcatalog/templates/webcatalog/application_detail.html'
--- src/webcatalog/templates/webcatalog/application_detail.html 2012-04-18 21:27:46 +0000
+++ src/webcatalog/templates/webcatalog/application_detail.html 2012-04-19 23:28:18 +0000
@@ -5,21 +5,34 @@
5{% block title %}Application {{ application.name }}{% endblock %}5{% block title %}Application {{ application.name }}{% endblock %}
6{% block header %}Details for {{ application.name }}{% endblock %}6{% block header %}Details for {{ application.name }}{% endblock %}
7{% block head_extra %}7{% block head_extra %}
8{{ block.super }}
9<link rel="stylesheet" type="text/css" href="{% url wc-combo %}?light/css/reset.css&light/css/styles.css&css/webcatalog.css&light/css/forms.css&css/carousel.css"/>8<link rel="stylesheet" type="text/css" href="{% url wc-combo %}?light/css/reset.css&light/css/styles.css&css/webcatalog.css&light/css/forms.css&css/carousel.css"/>
10<script src="{% url wc-combo %}?yui/3.4.0/build/yui/yui-min.js&js/carousel.js"></script>9<script src="{% url wc-combo %}?yui/3.4.0/build/yui/yui-min.js&js/carousel.js"></script>
11<script>10<script>
12YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('io-base', 'node-base', function (Y) {11YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('io-base', 'node-base', function (Y) {
13 function complete(id, obj){
14 var reviews_html = obj.responseText;
15 var reviews_div = Y.one('#reviews_placeholder');
16 reviews_div.set("innerHTML", reviews_html);
17 };
18 Y.on('io:complete', complete, Y);
1912
20 // XXX michaeln 2011-09-16 bug=851660 Autoload further batches of results.13 // XXX michaeln 2011-09-16 bug=851660 Autoload further batches of results.
21 var uri = "{% url wc-package-reviews application.distroseries.code_name application.package_name %}";14 var reviews_uri = "{% url wc-package-reviews application.distroseries.code_name application.package_name %}";
22 Y.io(uri);15 Y.io(reviews_uri, {
16 on: {
17 complete: function(id, obj){
18 var reviews_html = obj.responseText;
19 var reviews_div = Y.one('#reviews_placeholder');
20 reviews_div.set("innerHTML", reviews_html);
21 }
22 }
23 });
24 var recommends_div = Y.one('.portlet.recommendedportlet');
25 if (recommends_div){
26 var recommendations_uri = "{% url wc-package-recommends application.package_name %}";
27 Y.io(recommendations_uri, {
28 on: {
29 complete: function(id, obj){
30 var recommends_html = obj.responseText;
31 recommends_div.set("innerHTML", recommends_html);
32 }
33 }
34 });
35 }
23});36});
24</script>37</script>
25<meta property="og:site_name" content="Ubuntu Apps Directory" />38<meta property="og:site_name" content="Ubuntu Apps Directory" />
@@ -67,6 +80,10 @@
67 </ul>80 </ul>
68 </div>81 </div>
69 {% endif %}82 {% endif %}
83 {% if num_recommended_apps %}
84 <div class="portlet recommendedportlet">
85 </div>
86 {% endif %}
70 </div>87 </div>
71<div class="main-column">88<div class="main-column">
72 {% include "webcatalog/breadcrumbs_snippet.html" %}89 {% include "webcatalog/breadcrumbs_snippet.html" %}
7390
=== modified file 'src/webcatalog/templates/webcatalog/recommended_apps.html'
--- src/webcatalog/templates/webcatalog/recommended_apps.html 2012-04-19 23:28:18 +0000
+++ src/webcatalog/templates/webcatalog/recommended_apps.html 2012-04-19 23:28:18 +0000
@@ -23,5 +23,17 @@
23<div class="featured-widget">23<div class="featured-widget">
24 {% include "webcatalog/recommended_apps_widget.html" %}24 {% include "webcatalog/recommended_apps_widget.html" %}
25</div>25</div>
26<script type="text/javascript">
27YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('uwc-carousel', function(Y) {
28 var caro = new Y.uwc.Carousel({
29 nodeContainer: ".featured-widget .carousel",
30 controlsContainer: ".featured-widget .carousel-controls",
31 containerHeight: 200,
32 containerWidth: 912,
33 autoPlay: false
34 });
35 Y.all('.slide').removeClass('disabled');
36});
37</script>
26{% endblock %}38{% endblock %}
2739
2840
=== modified file 'src/webcatalog/templates/webcatalog/recommended_apps_widget.html'
--- src/webcatalog/templates/webcatalog/recommended_apps_widget.html 2012-04-19 23:28:18 +0000
+++ src/webcatalog/templates/webcatalog/recommended_apps_widget.html 2012-04-19 23:28:18 +0000
@@ -1,20 +1,18 @@
1<div id="recommended-carousel">1{% load webcatalog %}
2<div class="carousel-controls"></div>2<div class="portletheader">Other people also downloaded...</div>
3 <h3>Other people also downloaded...</h3>3<ul class="portletactions">
4{% with ratings=1 apps=recommended_apps %}4{% for app in recommended_apps %}
5{% include "webcatalog/app_carousel_widget.html" %}5 <li>
6{% endwith %}6 <a href="{% url wc-package-detail package_name=app.package_name %}">
7</div>7 <div class="top-rated-stars">
8<script type="text/javascript">8 {% rating_summary app.ratings_average 'small' app.ratings_total %}
9YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('uwc-carousel', function(Y) {9 </div>
10 var caro = new Y.uwc.Carousel({10 <img class="icon64" src="{{ app.icon_url_or_default }}"/>
11 nodeContainer: "#recommended-carousel .carousel",11 <h4>{{ app.name }}</h4>
12 controlsContainer: "#recommended-carousel .carousel-controls",12 {% with dept=app.departments.all.0.name %}
13 containerHeight: 200,13 <p>{% if dept %}{{ dept }} | {% endif %}<b>{% if app.price %}${{ app.price }}{% else %}FREE{% endif %}</b></p>
14 containerWidth: 912,14 {% endwith %}
15 autoPlay: false15</a>
16 });16 </li>
17 Y.all('.slide').removeClass('disabled');17{% endfor %}
18});18</ul>
19</script>
20
2119
=== modified file 'src/webcatalog/tests/test_utilities.py'
--- src/webcatalog/tests/test_utilities.py 2012-04-19 23:28:18 +0000
+++ src/webcatalog/tests/test_utilities.py 2012-04-19 23:28:18 +0000
@@ -140,7 +140,7 @@
140 self.mock_recommend_app = self.recommend_app_patcher.start()140 self.mock_recommend_app = self.recommend_app_patcher.start()
141 self.addCleanup(self.recommend_app_patcher.stop)141 self.addCleanup(self.recommend_app_patcher.stop)
142 self.eg_recommends = {142 self.eg_recommends = {
143 u'rid': u'defe066c7ad7c43f71bac58c3f23cc62', 143 u'rid': u'defe066c7ad7c43f71bac58c3f23cc62',
144 u'data': [144 u'data': [
145 {u'rating': 4.0, u'package_name': u'nautilus-gksu'},145 {u'rating': 4.0, u'package_name': u'nautilus-gksu'},
146 {u'rating': 4.0, u'package_name': u'tribaltrouble2'},146 {u'rating': 4.0, u'package_name': u'tribaltrouble2'},
@@ -161,7 +161,7 @@
161161
162 def test_get_recommends_caches_result(self):162 def test_get_recommends_caches_result(self):
163 self.assertIs(None, cache.get('get_recommends_for_package-firefox'))163 self.assertIs(None, cache.get('get_recommends_for_package-firefox'))
164 164
165 result = WebServices().get_recommends_for_package('firefox')165 result = WebServices().get_recommends_for_package('firefox')
166166
167 self.assertEqual(167 self.assertEqual(
168168
=== modified file 'src/webcatalog/tests/test_views.py'
--- src/webcatalog/tests/test_views.py 2012-04-19 23:28:18 +0000
+++ src/webcatalog/tests/test_views.py 2012-04-19 23:28:18 +0000
@@ -143,6 +143,12 @@
143 app.package_name,143 app.package_name,
144 ])))144 ])))
145145
146 def test_link_to_recommended_apps_not_present(self):
147 with patch_settings(NUM_RECOMMENDED_APPS=0):
148 response, app = self.get_app_and_response()
149
150 self.assertNotContains(response, '<div class="recommendations">')
151
146 def test_button_for_non_puchase_app(self):152 def test_button_for_non_puchase_app(self):
147 response, app = self.get_app_and_response()153 response, app = self.get_app_and_response()
148154
@@ -334,7 +340,7 @@
334340
335 response = self.client.get(self.get_app_details_url(app))341 response = self.client.get(self.get_app_details_url(app))
336342
337 self.assertContains(response, 'id="screenshots-carousel"')343 self.assertContains(response, 'Y.uwc.Carousel', 1)
338344
339 def test_debtags_displayed(self):345 def test_debtags_displayed(self):
340 app = self.factory.make_application(debtags='["joystick"]')346 app = self.factory.make_application(debtags='["joystick"]')
@@ -1085,8 +1091,7 @@
10851091
1086 def test_renders_recommendations(self):1092 def test_renders_recommendations(self):
1087 app = self.factory.make_application(package_name='firefox')1093 app = self.factory.make_application(package_name='firefox')
1088 pkgnames = ['nautilus-gksu', 'tribaltrouble2', 'acm', 'zgv',1094 pkgnames = ['nautilus-gksu', 'tribaltrouble2', 'acm']
1089 'nautilus-wallpaper']
1090 for pkgname in pkgnames:1095 for pkgname in pkgnames:
1091 self.factory.make_application(package_name=pkgname)1096 self.factory.make_application(package_name=pkgname)
10921097
@@ -1096,19 +1101,9 @@
1096 self.assertEqual(200, response.status_code)1101 self.assertEqual(200, response.status_code)
1097 self.assertTemplateUsed(1102 self.assertTemplateUsed(
1098 response, 'webcatalog/recommended_apps.html')1103 response, 'webcatalog/recommended_apps.html')
1099 self.assertContains(response, '<div class="top-rated-stars">', 5)1104 # Only recommendations for apps that exist in the database are
11001105 # included.
1101 def test_excludes_non_existing_recommendations(self):1106 self.assertContains(response, '<div class="top-rated-stars">', 3)
1102 app = self.factory.make_application(package_name='firefox')
1103 pkgnames = ['nautilus-gksu', 'tribaltrouble2', 'acm', 'zgv']
1104 for pkgname in pkgnames:
1105 self.factory.make_application(package_name=pkgname)
1106
1107 response = self.client.get(
1108 reverse('wc-package-recommends', args=['firefox']))
1109
1110 self.assertEqual(200, response.status_code)
1111 self.assertContains(response, '<div class="top-rated-stars">', 4)
11121107
1113 def test_renders_widget_only_for_json_requests(self):1108 def test_renders_widget_only_for_json_requests(self):
1114 app = self.factory.make_application(package_name='firefox')1109 app = self.factory.make_application(package_name='firefox')
11151110
=== modified file 'src/webcatalog/utilities.py'
--- src/webcatalog/utilities.py 2012-04-19 23:28:18 +0000
+++ src/webcatalog/utilities.py 2012-04-19 23:28:18 +0000
@@ -166,7 +166,8 @@
166166
167 @property167 @property
168 def recommender_api(self):168 def recommender_api(self):
169 return SoftwareCenterRecommenderAPI(service_root=settings.REC_SERVICE_ROOT)169 return SoftwareCenterRecommenderAPI(
170 service_root=settings.REC_SERVICE_ROOT)
170171
171172
172def full_claimed_id(consumer_key):173def full_claimed_id(consumer_key):
173174
=== modified file 'src/webcatalog/views.py'
--- src/webcatalog/views.py 2012-04-19 23:28:18 +0000
+++ src/webcatalog/views.py 2012-04-19 23:28:18 +0000
@@ -227,7 +227,7 @@
227 if recommended_app:227 if recommended_app:
228 recommended_apps.append(recommended_app)228 recommended_apps.append(recommended_app)
229 num_recommends += 1229 num_recommends += 1
230 if num_recommends > 5:230 if num_recommends == settings.NUM_RECOMMENDED_APPS:
231 break231 break
232232
233 context = dict(application=app, recommended_apps=recommended_apps)233 context = dict(application=app, recommended_apps=recommended_apps)

Subscribers

People subscribed via source and target branches