Merge lp:~michael.nelson/ubuntu-webcatalog/1045328-bundle-promotion-apps into lp:ubuntu-webcatalog

Proposed by Michael Nelson
Status: Merged
Approved by: Łukasz Czyżykowski
Approved revision: 174
Merged at revision: 170
Proposed branch: lp:~michael.nelson/ubuntu-webcatalog/1045328-bundle-promotion-apps
Merge into: lp:ubuntu-webcatalog
Diff against target: 456 lines (+278/-19)
10 files modified
src/webcatalog/admin.py (+15/-0)
src/webcatalog/migrations/0029_add_application_widget.py (+191/-0)
src/webcatalog/models/__init__.py (+2/-0)
src/webcatalog/models/applications.py (+14/-0)
src/webcatalog/schema.py (+0/-2)
src/webcatalog/templates/webcatalog/application_detail.html (+3/-6)
src/webcatalog/templatetags/webcatalog.py (+7/-0)
src/webcatalog/tests/factory.py (+11/-0)
src/webcatalog/tests/test_views.py (+35/-8)
src/webcatalog/views.py (+0/-3)
To merge this branch: bzr merge lp:~michael.nelson/ubuntu-webcatalog/1045328-bundle-promotion-apps
Reviewer Review Type Date Requested Status
Łukasz Czyżykowski (community) Approve
Review via email: mp+123082@code.launchpad.net

Commit message

Add ApplicationWidget model for dynamic campaign widgets.

Description of the change

Overview
========

This branch adds the ApplicationWidget model which can be used to display custom widgets on any application detail page (see bug 1045328).

`fab test`

Notes:
 * I tried a few options for the model-admin, like tabularinline, but raw_id_field was the only way to *not* present the whole list of apps. The good thing is it presents lots of info about each app to decide, the bad thing is it results in a list of ids, which are opaque once set. We may want to investigate further if it is difficult to use.
 * I considered having a widget per application (implemented) and a widget per package_name (what the existing setting did). I went for the DB relationship on application, which is more general (ie. could potentially select only certain distroseries versions of an app to include the widget), but may be more fiddly to use (need to select multiple versions of each app).

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

REFACTOR: Use inline for displaying widget on application.

Revision history for this message
Łukasz Czyżykowski (lukasz-czyzykowski) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/webcatalog/admin.py'
2--- src/webcatalog/admin.py 2012-07-02 21:25:30 +0000
3+++ src/webcatalog/admin.py 2012-09-06 14:09:20 +0000
4@@ -23,6 +23,7 @@
5 from webcatalog.models import (
6 Application,
7 ApplicationMedia,
8+ ApplicationWidget,
9 Department,
10 DistroSeries,
11 Exhibit,
12@@ -36,12 +37,19 @@
13 ]
14
15
16+class ApplicationWidgetInline(admin.TabularInline):
17+ model = ApplicationWidget.applications.through
18+
19+
20 class ApplicationAdmin(admin.ModelAdmin):
21 list_display = ('package_name', 'name', 'comment', 'distroseries',
22 'wilson_score')
23 search_fields = ('package_name', 'name', 'comment')
24 list_filter = ('distroseries', 'imported_from_sca', 'is_latest',
25 'departments')
26+ inlines = [
27+ ApplicationWidgetInline,
28+ ]
29
30
31 class MachineAdmin(admin.ModelAdmin):
32@@ -66,8 +74,15 @@
33 def distroseries(self, obj):
34 return obj.application.distroseries
35
36+
37+class ApplicationWidgetAdmin(admin.ModelAdmin):
38+ list_display = ('name', )
39+ raw_id_fields = ('applications',)
40+
41+
42 admin.site.register(Application, ApplicationAdmin)
43 admin.site.register(ApplicationMedia, ApplicationMediaAdmin)
44+admin.site.register(ApplicationWidget, ApplicationWidgetAdmin)
45 admin.site.register(Department, DepartmentAdmin)
46 admin.site.register(DistroSeries)
47 admin.site.register(Exhibit, ExhibitAdmin)
48
49=== added file 'src/webcatalog/migrations/0029_add_application_widget.py'
50--- src/webcatalog/migrations/0029_add_application_widget.py 1970-01-01 00:00:00 +0000
51+++ src/webcatalog/migrations/0029_add_application_widget.py 2012-09-06 14:09:20 +0000
52@@ -0,0 +1,191 @@
53+# encoding: utf-8
54+import datetime
55+from south.db import db
56+from south.v2 import SchemaMigration
57+from django.db import models
58+
59+class Migration(SchemaMigration):
60+
61+ def forwards(self, orm):
62+
63+ # Adding model 'ApplicationWidget'
64+ db.create_table('webcatalog_applicationwidget', (
65+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
66+ ('name', self.gf('django.db.models.fields.CharField')(max_length=32)),
67+ ('template_snippet', self.gf('django.db.models.fields.TextField')()),
68+ ))
69+ db.send_create_signal('webcatalog', ['ApplicationWidget'])
70+
71+ # Adding M2M table for field applications on 'ApplicationWidget'
72+ db.create_table('webcatalog_applicationwidget_applications', (
73+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
74+ ('applicationwidget', models.ForeignKey(orm['webcatalog.applicationwidget'], null=False)),
75+ ('application', models.ForeignKey(orm['webcatalog.application'], null=False))
76+ ))
77+ db.create_unique('webcatalog_applicationwidget_applications', ['applicationwidget_id', 'application_id'])
78+
79+
80+ def backwards(self, orm):
81+
82+ # Deleting model 'ApplicationWidget'
83+ db.delete_table('webcatalog_applicationwidget')
84+
85+ # Removing M2M table for field applications on 'ApplicationWidget'
86+ db.delete_table('webcatalog_applicationwidget_applications')
87+
88+
89+ models = {
90+ 'auth.group': {
91+ 'Meta': {'object_name': 'Group'},
92+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
93+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
94+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
95+ },
96+ 'auth.permission': {
97+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
98+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
99+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
100+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
101+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
102+ },
103+ 'auth.user': {
104+ 'Meta': {'object_name': 'User'},
105+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
106+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
107+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
108+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
109+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
110+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
111+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
112+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
113+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
114+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
115+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
116+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
117+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
118+ },
119+ 'contenttypes.contenttype': {
120+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
121+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
122+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
123+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
124+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
125+ },
126+ 'webcatalog.application': {
127+ 'Meta': {'ordering': "('-wilson_score', 'name')", 'unique_together': "(('distroseries', 'package_name'),)", 'object_name': 'Application'},
128+ 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
129+ 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
130+ 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
131+ 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
132+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
133+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
134+ 'debtags': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
135+ 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
136+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
137+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
138+ 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
139+ 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
140+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
141+ 'imported_from_sca': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
142+ 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
143+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
144+ 'license': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
145+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
146+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
147+ 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
148+ 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
149+ 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
150+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
151+ 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
152+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
153+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
154+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
155+ 'wilson_score': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
156+ },
157+ 'webcatalog.applicationmedia': {
158+ 'Meta': {'ordering': "('url',)", 'unique_together': "(('application', 'url'),)", 'object_name': 'ApplicationMedia'},
159+ 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
160+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
161+ 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
162+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
163+ },
164+ 'webcatalog.applicationwidget': {
165+ 'Meta': {'object_name': 'ApplicationWidget'},
166+ 'applications': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Application']", 'symmetrical': 'False'}),
167+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
168+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
169+ 'template_snippet': ('django.db.models.fields.TextField', [], {})
170+ },
171+ 'webcatalog.consumer': {
172+ 'Meta': {'object_name': 'Consumer'},
173+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
174+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
175+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
176+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'WYDKgmMYvbPrqsjqWjFpMMVGEfHfHS'", 'max_length': '255', 'blank': 'True'}),
177+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
178+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
179+ },
180+ 'webcatalog.department': {
181+ 'Meta': {'object_name': 'Department'},
182+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
183+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
184+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
185+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
186+ },
187+ 'webcatalog.distroseries': {
188+ 'Meta': {'object_name': 'DistroSeries'},
189+ 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
190+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
191+ 'prerelease': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
192+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
193+ },
194+ 'webcatalog.exhibit': {
195+ 'Meta': {'object_name': 'Exhibit'},
196+ 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
197+ 'click_url': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '200'}),
198+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
199+ 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
200+ 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
201+ 'html': ('django.db.models.fields.TextField', [], {}),
202+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
203+ 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
204+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
205+ 'sca_id': ('django.db.models.fields.IntegerField', [], {}),
206+ 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'})
207+ },
208+ 'webcatalog.machine': {
209+ 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
210+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
211+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
212+ 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
213+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
214+ 'package_list': ('django.db.models.fields.TextField', [], {}),
215+ 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
216+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
217+ },
218+ 'webcatalog.nonce': {
219+ 'Meta': {'unique_together': "(('nonce', 'token', 'consumer'),)", 'object_name': 'Nonce'},
220+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
221+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
222+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
223+ 'nonce': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
224+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
225+ },
226+ 'webcatalog.reviewstatsimport': {
227+ 'Meta': {'object_name': 'ReviewStatsImport'},
228+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
229+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
230+ 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
231+ },
232+ 'webcatalog.token': {
233+ 'Meta': {'object_name': 'Token'},
234+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
235+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
236+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
237+ 'token': ('django.db.models.fields.CharField', [], {'default': "'pjfQGToPAEUjSRBWGoACORELAevZrhxtbALJSHxqfaXZAEtIym'", 'max_length': '50', 'primary_key': 'True'}),
238+ 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'RbVwxdrIRcQnwdlszhxgeoFHoaLpOMUjZebJkAggzeKrAHGcHZ'", 'max_length': '50'}),
239+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
240+ }
241+ }
242+
243+ complete_apps = ['webcatalog']
244
245=== modified file 'src/webcatalog/models/__init__.py'
246--- src/webcatalog/models/__init__.py 2012-03-19 08:16:51 +0000
247+++ src/webcatalog/models/__init__.py 2012-09-06 14:09:20 +0000
248@@ -26,6 +26,7 @@
249 'DistroSeries',
250 'Application',
251 'ApplicationMedia',
252+ 'ApplicationWidget',
253 'Department',
254 'Exhibit',
255 'ReviewStatsImport',
256@@ -36,6 +37,7 @@
257 from .applications import (
258 Application,
259 ApplicationMedia,
260+ ApplicationWidget,
261 Department,
262 DistroSeries,
263 Exhibit,
264
265=== modified file 'src/webcatalog/models/applications.py'
266--- src/webcatalog/models/applications.py 2012-08-21 13:57:11 +0000
267+++ src/webcatalog/models/applications.py 2012-09-06 14:09:20 +0000
268@@ -41,6 +41,7 @@
269 __all__ = [
270 'Application',
271 'ApplicationMedia',
272+ 'ApplicationWidget',
273 'Department',
274 'DistroSeries',
275 'Exhibit',
276@@ -314,6 +315,19 @@
277 return reverse('wc-package-list') + '?' + pkgs
278
279
280+class ApplicationWidget(models.Model):
281+ name = models.CharField(max_length=32,
282+ help_text="Just a reference for the admin view.")
283+ applications = models.ManyToManyField(Application)
284+ template_snippet = models.TextField()
285+
286+ def __unicode__(self):
287+ return self.name
288+
289+ class Meta:
290+ app_label = 'webcatalog'
291+
292+
293 # GroupPermissions can't go in fixtures because Permissions are stored as
294 # model metadata, so our Permission's primary key can change without warning
295 def post_syncdb_handler(sender, **kwargs):
296
297=== modified file 'src/webcatalog/schema.py'
298--- src/webcatalog/schema.py 2012-08-23 15:12:54 +0000
299+++ src/webcatalog/schema.py 2012-09-06 14:09:20 +0000
300@@ -66,8 +66,6 @@
301 number_top_rated_apps = schema.IntOption(default=8)
302 screenshots_base_url = schema.StringOption(
303 default='http://screenshots.ubuntu.com/')
304- hib_apps = schema.ListOption(
305- item=schema.StringOption(), default=[])
306 ubuntu_series_for_versions = schema.DictOption(
307 item=schema.StringOption())
308
309
310=== modified file 'src/webcatalog/templates/webcatalog/application_detail.html'
311--- src/webcatalog/templates/webcatalog/application_detail.html 2012-06-28 13:48:45 +0000
312+++ src/webcatalog/templates/webcatalog/application_detail.html 2012-09-06 14:09:20 +0000
313@@ -109,12 +109,9 @@
314 <p>{{ application.description|htmlize_package_description }}</p>
315 <div class="install-button">{% install_options application %}</div>
316 </div>
317- {% if display_hib_widget %}
318- <div id="hib-widget" style="width: 652px; padding:24px 16px 8px; clear:both">
319- <p>{{ application.name }} is currently also available as part of the <a href="http://www.humblebundle.com/?utm_source=ubuntu">Humble Bundle</a></p>
320- <iframe style="display: block; margin:0 auto; border:none;" src="http://www.humblebundle.com/_widget/html" width="410" height="150"></iframe>
321- </div>
322- {% endif %}
323+ {% for widget in application.applicationwidget_set.all %}
324+ {% render_app_widget widget.template_snippet %}
325+ {% endfor %}
326 <div class="license">
327 <table>
328 <tr>
329
330=== modified file 'src/webcatalog/templatetags/webcatalog.py'
331--- src/webcatalog/templatetags/webcatalog.py 2012-07-02 21:25:30 +0000
332+++ src/webcatalog/templatetags/webcatalog.py 2012-09-06 14:09:20 +0000
333@@ -32,6 +32,7 @@
334 from django.utils.html import escape
335 from django.utils.safestring import mark_safe
336 from django.utils.translation import ugettext as _
337+from django.template import Template
338
339 from webcatalog.models import Application
340 from webcatalog.utilities import UserAgentString
341@@ -196,3 +197,9 @@
342
343 return dict(stars=stars, total=num_ratings,
344 STATIC_URL=settings.STATIC_URL, size=size)
345+
346+
347+@register.simple_tag(takes_context=True)
348+def render_app_widget(context, template_snippet):
349+ template = Template(template_snippet)
350+ return template.render(context)
351
352=== modified file 'src/webcatalog/tests/factory.py'
353--- src/webcatalog/tests/factory.py 2012-08-23 10:07:28 +0000
354+++ src/webcatalog/tests/factory.py 2012-09-06 14:09:20 +0000
355@@ -40,6 +40,7 @@
356 from webcatalog.models import (
357 Application,
358 ApplicationMedia,
359+ ApplicationWidget,
360 Consumer,
361 Department,
362 DistroSeries,
363@@ -250,6 +251,16 @@
364 expire_date=expire_date
365 )
366
367+ def make_app_widget(self, applications=None, template_snippet=None):
368+ if applications is None:
369+ applications = [self.make_application()]
370+ if template_snippet is None:
371+ template_snippet = u"Promotion for {{ application.name }}"
372+
373+ widget = ApplicationWidget.objects.create(
374+ template_snippet=template_snippet)
375+ widget.applications = applications
376+
377
378 class TestCaseWithFactory(TestCase):
379
380
381=== modified file 'src/webcatalog/tests/test_views.py'
382--- src/webcatalog/tests/test_views.py 2012-07-02 21:25:30 +0000
383+++ src/webcatalog/tests/test_views.py 2012-09-06 14:09:20 +0000
384@@ -205,16 +205,43 @@
385 self.assertContains(response, "https://twitter.com/share")
386
387 def test_hib_widget_is_present_for_hib_apps(self):
388- with patch_settings(HIB_APPS=['pkgfoo']):
389- response, app = self.get_app_and_response()
390-
391- self.assertContains(response, '<div id="hib-widget"')
392+ app = self.factory.make_application(package_name='pkgfoo',
393+ name=u'A great foo')
394+ self.factory.make_app_widget(
395+ applications=[app],
396+ template_snippet=u"<p>Promotion for {{ application.name }}</p>")
397+
398+ response = self.get_package_details_response('pkgfoo')
399+
400+ self.assertContains(response, '<p>Promotion for A great foo</p>')
401
402 def test_hib_widget_not_present_for_non_hib_apps(self):
403- with patch_settings(HIB_APPS=['something', 'different']):
404- response, app = self.get_app_and_response()
405-
406- self.assertNotContains(response, '<div id="hib-widget"')
407+ self.factory.make_application(package_name='pkgfoo',
408+ name=u'A great foo')
409+ other_app = self.factory.make_application(package_name='pkgbar',
410+ name=u'Another app')
411+ self.factory.make_app_widget(
412+ applications=[other_app],
413+ template_snippet=u"Promotion for {{ application.name }}")
414+
415+ response = self.get_package_details_response('pkgfoo')
416+
417+ self.assertNotContains(response, 'Promotion for')
418+
419+ def test_multiple_promotions_for_app(self):
420+ app = self.factory.make_application(package_name='pkgfoo',
421+ name=u'A great foo')
422+ self.factory.make_app_widget(
423+ applications=[app],
424+ template_snippet=u"Promotion X for {{ application.name }}")
425+ self.factory.make_app_widget(
426+ applications=[app],
427+ template_snippet=u"Promotion Y for {{ application.name }}")
428+
429+ response = self.get_package_details_response('pkgfoo')
430+
431+ self.assertContains(response, 'Promotion X for A great foo')
432+ self.assertContains(response, 'Promotion Y for A great foo')
433
434 def test_twitter_link_does_not_contain_none(self):
435 app = self.factory.make_application()
436
437=== modified file 'src/webcatalog/views.py'
438--- src/webcatalog/views.py 2012-07-02 21:25:30 +0000
439+++ src/webcatalog/views.py 2012-09-06 14:09:20 +0000
440@@ -219,8 +219,6 @@
441
442 debtags = None if not app.debtags else json.loads(app.debtags)
443
444- hib_apps = getattr(settings, 'HIB_APPS', [])
445-
446 atts = {
447 'application': app,
448 'available_distroseries': app.available_distroseries(),
449@@ -230,7 +228,6 @@
450 reverse('wc-package-detail', args=[package_name])),
451 'email_form': form,
452 'debtags': debtags,
453- 'display_hib_widget': app.package_name in hib_apps
454 }
455
456 return render_to_response(

Subscribers

People subscribed via source and target branches