Merge lp:~michael.nelson/ubuntu-webcatalog/1045328-bundle-promotion-apps into lp:ubuntu-webcatalog
- 1045328-bundle-promotion-apps
- Merge into trunk
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 |
Related bugs: |
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).
- 174. By Michael Nelson
-
REFACTOR: Use inline for displaying widget on application.
Łukasz Czyżykowski (lukasz-czyzykowski) : | # |
Preview Diff
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( |