Merge lp:~elachuni/ubuntu-webcatalog/featured-apps into lp:ubuntu-webcatalog

Proposed by Anthony Lenton
Status: Merged
Approved by: Danny Tamez
Approved revision: 69
Merged at revision: 71
Proposed branch: lp:~elachuni/ubuntu-webcatalog/featured-apps
Merge into: lp:ubuntu-webcatalog
Diff against target: 568 lines (+312/-60)
12 files modified
src/webcatalog/managers.py (+38/-0)
src/webcatalog/models/applications.py (+3/-0)
src/webcatalog/schema.py (+2/-2)
src/webcatalog/static/css/carousel.css (+131/-51)
src/webcatalog/static/js/carousel.js (+13/-4)
src/webcatalog/templates/webcatalog/exhibits_widget.html (+4/-3)
src/webcatalog/templates/webcatalog/featured_apps_widget.html (+52/-0)
src/webcatalog/templates/webcatalog/index.html (+6/-0)
src/webcatalog/tests/__init__.py (+1/-0)
src/webcatalog/tests/test_managers.py (+50/-0)
src/webcatalog/tests/test_views.py (+8/-0)
src/webcatalog/views.py (+4/-0)
To merge this branch: bzr merge lp:~elachuni/ubuntu-webcatalog/featured-apps
Reviewer Review Type Date Requested Status
Danny Tamez (community) Approve
Review via email: mp+96640@code.launchpad.net

Commit message

Implemented a "featured apps" widget for the front page, similar to the one on https://developer.ubuntu.com/

Description of the change

Overview
========
This branch implements a "featured apps" widget for the front page, similar to the one on https://developer.ubuntu.com/

Details
=======
No javascript was taken from developer.u.c as we already have a YUI carousel widget in place for the exhibits widget. Instead, I generalized that a bit so that the controls could be placed outside of the main carousel container.

The list of featured apps was added via a setting. The data for each app is taken straight from the database. If anything in the app seems wrong this would need to be customized on the app itself, and currently would be overridden the next time app-install-data is imported.

The tidy solution for this would be to allow per-app customizations, but this was left for another branch.

A very short video showing off the featured apps widget: http://people.canonical.com/~anthony/featured_apps.ogv

To post a comment you must log in.
Revision history for this message
Danny Tamez (zematynnad) wrote :

Nice stuff!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'src/webcatalog/managers.py'
--- src/webcatalog/managers.py 1970-01-01 00:00:00 +0000
+++ src/webcatalog/managers.py 2012-03-08 18:33:37 +0000
@@ -0,0 +1,38 @@
1# -*- coding: utf-8 -*-
2# This file is part of the Apps Directory
3# Copyright (C) 2011 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Affero General Public License as
7# published by the Free Software Foundation, either version 3 of the
8# License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Affero General Public License for more details.
14#
15# You should have received a copy of the GNU Affero General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18"""Django object managers."""
19
20from __future__ import (
21 absolute_import,
22 with_statement,
23 )
24
25
26__metaclass__ = type
27__all__ = [
28 'ApplicationManager',
29 ]
30
31from django.db import models
32
33
34class ApplicationManager(models.Manager):
35 def find_best(self, **kwargs):
36 options = self.filter(**kwargs).order_by('-distroseries__version')
37 if options.exists():
38 return options[0]
039
=== modified file 'src/webcatalog/models/applications.py'
--- src/webcatalog/models/applications.py 2012-03-01 21:38:31 +0000
+++ src/webcatalog/models/applications.py 2012-03-08 18:33:37 +0000
@@ -31,6 +31,7 @@
31from django.db import models31from django.db import models
3232
33from webcatalog.department_filters import department_filters33from webcatalog.department_filters import department_filters
34from webcatalog.managers import ApplicationManager
3435
35__metaclass__ = type36__metaclass__ = type
36__all__ = [37__all__ = [
@@ -103,6 +104,8 @@
103 # series etc.)104 # series etc.)
104 description = models.TextField(blank=True)105 description = models.TextField(blank=True)
105106
107 objects = ApplicationManager()
108
106 def __unicode__(self):109 def __unicode__(self):
107 return u"{0} ({1})".format(self.name, self.package_name)110 return u"{0} ({1})".format(self.name, self.package_name)
108111
109112
=== modified file 'src/webcatalog/schema.py'
--- src/webcatalog/schema.py 2012-03-06 16:47:42 +0000
+++ src/webcatalog/schema.py 2012-03-08 18:33:37 +0000
@@ -17,8 +17,6 @@
1717
18"""configglue schema for the Apps Directory."""18"""configglue schema for the Apps Directory."""
1919
20import django
21
22from configglue.pyschema import ConfigSection20from configglue.pyschema import ConfigSection
23from configglue.pyschema.options import (21from configglue.pyschema.options import (
24 BoolConfigOption,22 BoolConfigOption,
@@ -63,6 +61,8 @@
63 webcatalog.oauth_data_store = StringConfigOption(61 webcatalog.oauth_data_store = StringConfigOption(
64 default='webcatalog.models.oauthtoken.DataStore')62 default='webcatalog.models.oauthtoken.DataStore')
65 webcatalog.convoy_root = StringConfigOption()63 webcatalog.convoy_root = StringConfigOption()
64 webcatalog.featured_apps = LinesConfigOption(item=StringConfigOption(),
65 default=[])
6666
67 google = ConfigSection()67 google = ConfigSection()
68 google.google_analytics_id = StringConfigOption()68 google.google_analytics_id = StringConfigOption()
6969
=== modified file 'src/webcatalog/static/css/carousel.css'
--- src/webcatalog/static/css/carousel.css 2012-03-06 16:55:20 +0000
+++ src/webcatalog/static/css/carousel.css 2012-03-08 18:33:37 +0000
@@ -1,96 +1,176 @@
1/* Base carousel */
1.carousel-wrapper {2.carousel-wrapper {
2 position: relative;3 position: relative;
3 height: 200px;
4 overflow: hidden;4 overflow: hidden;
5}5}
6#carousel {6.carousel {
7 width: 100%;7 width: 100%;
8 height: 100%;8 height: 100%;
9 top: 0;9 top: 0;
10 left: 0;10 left: 0;
11 position: absolute;11 position: absolute;
12 background: #333;
13 display: block;12 display: block;
14}13}
15#carousel .next, #carousel .prev {14.carousel .pagination {
16 background: url(/assets/images/arrow-sprite.png) no-repeat 0 0;
17 position: absolute;
18 z-index: 20;
19 width: 33px;
20 height: 66px;
21 margin-top: -33px;
22}
23#carousel .next:active, #carousel .prev:active {
24 margin-top: -32px;
25 outline: none;
26}
27#carousel .next span, #carousel .prev span {
28 position:absolute;
29 left: -9999em;
30 height: 0;
31 width: 0;
32}
33#carousel .prev {
34 left: 0;
35 top: 50%;
36 background-position:0 0;
37}
38#carousel .prev:hover, #carousel .prev:focus {
39 outline: none;
40 background-position: 0 -116px;
41}
42#carousel .next {
43 right: 0;
44 top: 50%;
45 background-position: 0 -232px;
46}
47#carousel .next:hover, #carousel .next:focus {
48 outline: none;
49 background-position: 0 -348px;
50}
51#carousel .pagination {
52 position: absolute;15 position: absolute;
53 z-index: 20;16 z-index: 20;
54 left: 50%;17 left: 50%;
55 top: 87%;18 top: 87%;
56 margin-left: -35px;19 margin-left: -35px;
57}20}
58#carousel .pagination li {21.carousel .pagination li {
59 float: left;22 float: left;
60 margin-right: 10px;23 margin-right: 10px;
61}24}
62#carousel .pagination li a {25.carousel .pagination li a {
63 display: block;26 display: block;
64 height: 10px;27 height: 10px;
65 width: 10px;28 width: 10px;
66 background: #aea79f;29 background: #aea79f;
67 border-radius: 20px;30 border-radius: 20px;
68}31}
69#carousel .pagination li a:hover, #carousel .pagination li a:focus,32.carousel .pagination li a:hover, .carousel .pagination li a:focus,
70#carousel .pagination li a.active:hover, #carousel .pagination li a.active:focus {33.carousel .pagination li a.active:hover, .carousel .pagination li a.active:focus {
71 background-color: #dd4814;34 background-color: #dd4814;
72}35}
73#carousel .pagination li a.active {36.carousel .pagination li a.active {
74 background-color: #fff;37 background-color: #fff;
75}38}
76#carousel .pagination li a span {39.carousel .pagination li a span {
77 position: absolute;40 position: absolute;
78 left: -999em;41 left: -999em;
79}42}
80#carousel .carousel {43.carousel .carouselol {
81 width: 10000px;44 width: 10000px;
82 overflow: hidden;45 overflow: hidden;
83 position: relative;46 position: relative;
84}47}
85#carousel .carousel li {48.carousel .carouselol li {
86 position: relative;49 position: relative;
87 display: block;50 display: block;
88 background: #333;
89 float: left;51 float: left;
90}52}
91#carousel .carousel a {53.carousel .carouselol a {
92 cursor: hand;54 cursor: hand;
93}55}
94#carousel .carousel .disabled {56.carousel .carouselol .disabled {
95 display: none;57 display: none;
96}58}
59/* End base carousel */
60
61/* Exhibits carousel */
62.exhibits-widget {
63 margin-bottom: 32px;
64}
65.exhibits-widget .carousel-wrapper{
66 height: 200px;
67}
68.exhibits-widget .carousel .next, .exhibits-widget .carousel .prev {
69 background: url(/assets/images/arrow-sprite.png) no-repeat 0 0;
70 position: absolute;
71 z-index: 20;
72 width: 33px;
73 height: 66px;
74 margin-top: -33px;
75}
76.exhibits-widget .carousel .next:active, .exhibits-widget .carousel .prev:active {
77 margin-top: -32px;
78 outline: none;
79}
80.exhibits-widget .carousel .next span, .exhibits-widget .carousel .prev span {
81 position:absolute;
82 left: -9999em;
83 height: 0;
84 width: 0;
85}
86.exhibits-widget .carousel .prev {
87 left: 0;
88 top: 50%;
89 background-position:0 0;
90}
91.exhibits-widget .carousel .prev:hover, .exhibits-widget .carousel .prev:focus {
92 outline: none;
93 background-position: 0 -116px;
94}
95.exhibits-widget .carousel .next {
96 right: 0;
97 top: 50%;
98 background-position: 0 -232px;
99}
100.exhibits-widget .carousel .next:hover, .exhibits-widget .carousel .next:focus {
101 outline: none;
102 background-position: 0 -348px;
103}
104
105/* End exhibits carousel */
106
107/* Featured apps carousel */
108.featured-widget {
109 background: url("/assets/images/pattern-featured.gif") repeat scroll 0 0 #ebe9e7;
110 border: 2px solid #aea79f;
111 border-radius: 4px 4px 4px 4px;
112 margin-bottom: 30px;
113 overflow: hidden;
114 padding: 5px;
115}
116.featured-widget .carousel-wrapper{
117 height: 140px;
118}
119.featured-widget div.carousel-container {
120 background-color: #fff;
121 border: 1px solid #aea79f;
122 border-radius: 4px 4px 4px 4px;
123 overflow: hidden;
124 padding: 10px;
125}
126
127#content .featured-widget table {
128 width: 874px;
129}
130#content .featured-widget table td {
131 border-right: 1px dotted #AEA79F;
132 border-bottom: 0;
133 padding: 0 10px;
134 width: 202px;
135}
136#featured-carousel .pagination li a.active {
137 background-color: #333;
138}
139
140#featured-controls {
141 width: 70px;
142 height: 32px;
143 float: right;
144}
145
146#featured-controls .next, #featured-controls .prev {
147 background: url(/assets/images/arrow-sliders.png) no-repeat 0 0;
148 float: left;
149 z-index: 20;
150 width: 32px;
151 height: 29px;
152}
153#featured-controls .next span, #featured-controls .prev span {
154 position:absolute;
155 left: -9999em;
156 height: 0;
157 width: 0;
158}
159#featured-controls .prev {
160 background-position:0 0;
161}
162#featured-controls .next {
163 background-position: -32px 0;
164}
165#featured-controls .prev:hover, #featured-controls .prev:focus,
166#featured-controls .next:hover, #featured-controls .next:focus {
167 outline: none;
168}
169
170.featured-widget img.icon64 {
171 margin: 0 8px 40px 0;
172 float: left;
173}
174.featured-widget h4 {
175 font-weight: bold;
176}
97177
=== added file 'src/webcatalog/static/images/arrow-sliders.png'
98Binary files src/webcatalog/static/images/arrow-sliders.png 1970-01-01 00:00:00 +0000 and src/webcatalog/static/images/arrow-sliders.png 2012-03-08 18:33:37 +0000 differ178Binary files src/webcatalog/static/images/arrow-sliders.png 1970-01-01 00:00:00 +0000 and src/webcatalog/static/images/arrow-sliders.png 2012-03-08 18:33:37 +0000 differ
=== added file 'src/webcatalog/static/images/pattern-featured.gif'
99Binary files src/webcatalog/static/images/pattern-featured.gif 1970-01-01 00:00:00 +0000 and src/webcatalog/static/images/pattern-featured.gif 2012-03-08 18:33:37 +0000 differ179Binary files src/webcatalog/static/images/pattern-featured.gif 1970-01-01 00:00:00 +0000 and src/webcatalog/static/images/pattern-featured.gif 2012-03-08 18:33:37 +0000 differ
=== modified file 'src/webcatalog/static/js/carousel.js'
--- src/webcatalog/static/js/carousel.js 2012-03-05 23:14:54 +0000
+++ src/webcatalog/static/js/carousel.js 2012-03-08 18:33:37 +0000
@@ -91,12 +91,12 @@
91 generateControls: function() {91 generateControls: function() {
92 var prev = Y.Node.create('<a href="#" class="prev"><span>Previous Slide</span></a>'),92 var prev = Y.Node.create('<a href="#" class="prev"><span>Previous Slide</span></a>'),
93 next = Y.Node.create('<a href="#" class="next"><span>Next Slide</span></a>'),93 next = Y.Node.create('<a href="#" class="next"><span>Next Slide</span></a>'),
94 nodeContainer = this.get("nodeContainer");94 controlsContainer = this.get("controlsContainer");
9595
96 next.on("click", this.next, this);96 next.on("click", this.next, this);
97 prev.on("click", this.prev, this);97 prev.on("click", this.prev, this);
98 nodeContainer.appendChild(prev);98 controlsContainer.appendChild(prev);
99 nodeContainer.appendChild(next);99 controlsContainer.appendChild(next);
100 },100 },
101 advance: function(e, val){101 advance: function(e, val){
102 if (e) {102 if (e) {
@@ -157,7 +157,7 @@
157 }, {157 }, {
158 NAME: "carousel",158 NAME: "carousel",
159 ATTRS: {159 ATTRS: {
160 carouselClassName: { value: "carousel"},160 carouselClassName: { value: "carouselol"},
161 containerHeight: { value: null },161 containerHeight: { value: null },
162 containerWidth: { value: null },162 containerWidth: { value: null },
163 nodeContainer: {163 nodeContainer: {
@@ -169,6 +169,15 @@
169 return n;169 return n;
170 }170 }
171 },171 },
172 controlsContainer: {
173 setter: function(sel) {
174 var n = Y.one(sel);
175 if (!n) {
176 Y.log('UWC:Carousel - invalid selector provided: ' + sel);
177 }
178 return n;
179 }
180 },
172 slideAnimDuration: { value: 1 },181 slideAnimDuration: { value: 1 },
173 slideAnimInterval: { value: 5000 },182 slideAnimInterval: { value: 5000 },
174 slideEasing: { value: Y.Easing.easeBoth },183 slideEasing: { value: Y.Easing.easeBoth },
175184
=== modified file 'src/webcatalog/templates/webcatalog/exhibits_widget.html'
--- src/webcatalog/templates/webcatalog/exhibits_widget.html 2012-03-06 16:55:20 +0000
+++ src/webcatalog/templates/webcatalog/exhibits_widget.html 2012-03-08 18:33:37 +0000
@@ -1,6 +1,6 @@
1<div class="carousel-wrapper">1<div class="carousel-wrapper">
2 <div id="carousel">2 <div id="exhibits-carousel" class="carousel">
3 <ol class="carousel">3 <ol class="carouselol">
4 {% autoescape off %}4 {% autoescape off %}
5 {% comment %}5 {% comment %}
6All slides except the first one start off with a "disabled" class, which will6All slides except the first one start off with a "disabled" class, which will
@@ -22,7 +22,8 @@
22<script type="text/javascript">22<script type="text/javascript">
23YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('uwc-carousel', function(Y) {23YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('uwc-carousel', function(Y) {
24 var caro = new Y.uwc.Carousel({24 var caro = new Y.uwc.Carousel({
25 nodeContainer: "#carousel",25 nodeContainer: "#exhibits-carousel",
26 controlsContainer: "#exhibits-carousel",
26 containerHeight: 200,27 containerHeight: 200,
27 containerWidth: 912,28 containerWidth: 912,
28 autoPlay: true29 autoPlay: true
2930
=== added file 'src/webcatalog/templates/webcatalog/featured_apps_widget.html'
--- src/webcatalog/templates/webcatalog/featured_apps_widget.html 1970-01-01 00:00:00 +0000
+++ src/webcatalog/templates/webcatalog/featured_apps_widget.html 2012-03-08 18:33:37 +0000
@@ -0,0 +1,52 @@
1<div id="featured-controls"></div>
2 <h3>Featured apps on the Ubuntu Software Centre</h3>
3<div class="carousel-container">
4 <div class="carousel-wrapper">
5 <div id="featured-carousel" class="carousel">
6 <ol class="carouselol">
7 {% autoescape off %}
8 {% comment %}
9All slides except the first one start off with a "disabled" class, which will
10make them "display: none". This will avoid loading pointless remote resources
11(banners) if Javascript is disabled. The "disabled" class is then removed via
12Javascript.
13 {% endcomment %}
14 <li class="slide{% if forloop.counter > 1%} disabled{% endif %}">
15 <table><tr>
16 {% for app in featured_apps %}
17 <td>
18 {% if app.icon %}
19 <img class="icon64" src="{{ app.icon.url }}"/>
20 {% else %}
21 <img class="icon64" src="{{ STATIC_URL }}images/applications-other-64.png"/>
22 {% endif %}
23 <h4><a href="{% url wc-package-detail distro=app.distroseries.code_name package_name=app.package_name %}">{{ app.name }}</a></h4>
24 <p>{{ app.departments.all.0.name }} | <b>{% if app.price %}${{ app.price }}{% else %}FREE{% endif %}</b></p>
25 <p>{{ app.comment }}</p>
26 </td>
27 {% if forloop.counter|divisibleby:4 %}
28 </tr></table>
29 </li>
30 <li class="slide{% if forloop.counter > 1%} disabled{% endif %}">
31 <table><tr>
32 {% endif %}
33 {% endfor %}
34 </tr></table>
35 </li>
36 {% endautoescape %}
37 </ol>
38 </div>
39 </div>
40</div>
41<script type="text/javascript">
42YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('uwc-carousel', function(Y) {
43 var caro = new Y.uwc.Carousel({
44 nodeContainer: "#featured-carousel",
45 controlsContainer: "#featured-controls",
46 containerHeight: 200,
47 containerWidth: 912,
48 autoPlay: false
49 });
50 Y.all('.slide').removeClass('disabled');
51});
52</script>
053
=== modified file 'src/webcatalog/templates/webcatalog/index.html'
--- src/webcatalog/templates/webcatalog/index.html 2012-03-05 23:14:54 +0000
+++ src/webcatalog/templates/webcatalog/index.html 2012-03-08 18:33:37 +0000
@@ -19,6 +19,12 @@
19</div>19</div>
20{% endif %}20{% endif %}
2121
22{% if featured_apps %}
23<div class="featured-widget">
24 {% include "webcatalog/featured_apps_widget.html" %}
25</div>
26{% endif %}
27
22<h3>{% trans "Browse application departments" %}:</h3>28<h3>{% trans "Browse application departments" %}:</h3>
2329
24{% for dept in depts %}30{% for dept in depts %}
2531
=== modified file 'src/webcatalog/tests/__init__.py'
--- src/webcatalog/tests/__init__.py 2012-01-06 17:54:47 +0000
+++ src/webcatalog/tests/__init__.py 2012-03-08 18:33:37 +0000
@@ -23,6 +23,7 @@
23from .test_forms import *23from .test_forms import *
24from .test_handlers import *24from .test_handlers import *
25from .test_models import *25from .test_models import *
26from .test_managers import *
26from .test_pep8 import *27from .test_pep8 import *
27from .test_templatetags import *28from .test_templatetags import *
28from .test_utilities import *29from .test_utilities import *
2930
=== added file 'src/webcatalog/tests/test_managers.py'
--- src/webcatalog/tests/test_managers.py 1970-01-01 00:00:00 +0000
+++ src/webcatalog/tests/test_managers.py 2012-03-08 18:33:37 +0000
@@ -0,0 +1,50 @@
1# -*- coding: utf-8 -*-
2# This file is part of the Apps Directory
3# Copyright (C) 2011 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Affero General Public License as
7# published by the Free Software Foundation, either version 3 of the
8# License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Affero General Public License for more details.
14#
15# You should have received a copy of the GNU Affero General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18"""Test cases for object managers."""
19
20from __future__ import (
21 absolute_import,
22 with_statement,
23 )
24
25
26from webcatalog.tests.factory import TestCaseWithFactory
27from webcatalog.models import Application
28
29__metaclass__ = type
30__all__ = [
31 'ApplicationManagerTestCase',
32 ]
33
34
35class ApplicationManagerTestCase(TestCaseWithFactory):
36 def test_find_best_returns_none(self):
37 self.assertIsNone(Application.objects.find_best(package_name='foo'))
38
39 def test_find_best_returns_latest(self):
40 latest = self.factory.make_distroseries(version='14.10')
41 older = self.factory.make_distroseries(version='14.04')
42
43 expected = self.factory.make_application(distroseries=latest)
44 self.factory.make_application(package_name=expected.package_name,
45 distroseries=older)
46
47 retrieved = Application.objects.find_best(
48 package_name=expected.package_name)
49
50 self.assertEqual(expected.id, retrieved.id)
051
=== modified file 'src/webcatalog/tests/test_views.py'
--- src/webcatalog/tests/test_views.py 2012-03-05 23:14:54 +0000
+++ src/webcatalog/tests/test_views.py 2012-03-08 18:33:37 +0000
@@ -421,6 +421,14 @@
421421
422 self.assertContains(response, '<li class="slide', count=1)422 self.assertContains(response, '<li class="slide', count=1)
423423
424 def test_featured_apps(self):
425 app = self.factory.make_application(package_name='foobar')
426
427 with patch_settings(FEATURED_APPS=['foobar', 'baz']):
428 response = self.client.get(reverse('wc-index'))
429
430 self.assertEqual([app], response.context[0]['featured_apps'])
431
424432
425class OverviewTestCase(TestCaseWithFactory):433class OverviewTestCase(TestCaseWithFactory):
426 def test_department_contains_links_to_subdepartments(self):434 def test_department_contains_links_to_subdepartments(self):
427435
=== modified file 'src/webcatalog/views.py'
--- src/webcatalog/views.py 2012-03-06 16:47:42 +0000
+++ src/webcatalog/views.py 2012-03-08 18:33:37 +0000
@@ -117,10 +117,14 @@
117 exhibits = list(Exhibit.objects.filter(Q(display=True) |117 exhibits = list(Exhibit.objects.filter(Q(display=True) |
118 Q(display=None, published=True,)))118 Q(display=None, published=True,)))
119 shuffle(exhibits)119 shuffle(exhibits)
120 featured_apps = [Application.objects.find_best(package_name=app)
121 for app in settings.FEATURED_APPS]
122 featured_apps = [x for x in featured_apps if x]
120123
121 context = RequestContext(request, dict={124 context = RequestContext(request, dict={
122 'depts': depts,125 'depts': depts,
123 'exhibits': exhibits,126 'exhibits': exhibits,
127 'featured_apps': featured_apps,
124 })128 })
125 return render_to_response('webcatalog/index.html',129 return render_to_response('webcatalog/index.html',
126 context_instance=context)130 context_instance=context)

Subscribers

People subscribed via source and target branches