Merge lp:~dholbach/developer-ubuntu-com/store-data into lp:developer-ubuntu-com

Proposed by Daniel Holbach
Status: Merged
Approved by: David Callé
Approved revision: 160
Merged at revision: 140
Proposed branch: lp:~dholbach/developer-ubuntu-com/store-data
Merge into: lp:developer-ubuntu-com
Diff against target: 557 lines (+392/-6)
14 files modified
Makefile (+1/-0)
developer_portal/settings.py (+2/-0)
developer_portal/templatetags/text_extras.py (+26/-0)
locale/developer_portal.pot (+14/-6)
store_data/admin.py (+22/-0)
store_data/cms_plugins.py (+19/-0)
store_data/management/commands/update-gadget-snaps.py (+50/-0)
store_data/management/store/api.py (+52/-0)
store_data/migrations/0001_initial.py (+134/-0)
store_data/models.py (+29/-0)
store_data/tests.py (+3/-0)
store_data/urls.py (+5/-0)
store_data/views.py (+3/-0)
templates/gadget_snap_list.html (+32/-0)
To merge this branch: bzr merge lp:~dholbach/developer-ubuntu-com/store-data
Reviewer Review Type Date Requested Status
David Callé Approve
Michael Hall Pending
Review via email: mp+262684@code.launchpad.net

Commit message

Add small app and management command to store data about gadget snaps (from app store).

Description of the change

Add small app and management command to store data about gadget snaps (from app store).

To post a comment you must log in.
Revision history for this message
Daniel Holbach (dholbach) wrote :

This solves just the data part, we'll still have to figure out how to include this in the site itself via custom plugin or whatever else.

Revision history for this message
David Callé (davidc3) wrote :

Looks good to me, but please have a go at it Mike, I've never dealt with django DBs.

Also, one tiny diff comment.

Revision history for this message
Daniel Holbach (dholbach) wrote :

Marking as WIP as I'll push the display parts to this branch too.

Revision history for this message
Daniel Holbach (dholbach) wrote :

Up for review again, it works for me now.

151. By Daniel Holbach

add icon

Revision history for this message
Michael Hall (mhall119) wrote :

Looks good, just one change before it's done

In your GadgetSnapListPlugin class, please add this line immediately below the render_template line:

text_enabled = True

This will allow the plug to be used inside the rich text editor, making it easier to integrate it with other page content.

152. By Daniel Holbach

add description and screenshot fields

153. By Daniel Holbach

rework api store bits to make them more extensible, add screenshot urls and description

154. By Daniel Holbach

update management command to add description and URLs

155. By Daniel Holbach

add text_enabled = True - thanks Mike

156. By Daniel Holbach

merge David Callé's beautiful work, making the gadget snap list look more beautiful

157. By Daniel Holbach

merged lp:~davidc3/developer-ubuntu-com/store_data-UI_tweaks2

158. By Daniel Holbach

update .pot file

159. By Daniel Holbach

make string translatable

160. By Daniel Holbach

merge from trunk, resolve conflicts

Revision history for this message
David Callé (davidc3) wrote :

+1!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2015-09-01 17:27:25 +0000
3+++ Makefile 2015-09-04 12:56:39 +0000
4@@ -33,6 +33,7 @@
5 @echo DJANGO_SETTINGS_MODULE=\"charm_settings\" >> crontab
6 @echo internal_proxy=\"${internal_proxy}\" >> crontab
7 @echo "0 4 * * * cd ${PWD}; ./update_apidocs.sh > ${PWD}/../../logs/update_apidocs.log 2>${PWD}/../../logs/update_apidocs_errors.log" >> crontab
8+ @echo "15 4 * * * cd ${PWD}; ${PYTHON} manage.py update-gadget-snaps > ${PWD}/../../logs/update_gadgetsnaps.log 2>${PWD}/../../logs/update_gadgetsnaps_errors.log" >> crontab
9 @echo "15 */2 * * * cd ${PWD}; ${PYTHON} manage.py import-external-docs-branches > ${PWD}/../../logs/import-external-docs-branches.log 2>${PWD}/../../logs/import-external-docs-branches_errors.log" >> crontab
10 @crontab ./crontab
11 @rm ./crontab
12
13=== modified file 'developer_portal/settings.py'
14--- developer_portal/settings.py 2015-03-27 21:00:43 +0000
15+++ developer_portal/settings.py 2015-09-04 12:56:39 +0000
16@@ -75,6 +75,8 @@
17
18 'webapp_creator',
19
20+ 'store_data',
21+
22 'api_docs',
23 ]
24
25
26=== added file 'developer_portal/templatetags/text_extras.py'
27--- developer_portal/templatetags/text_extras.py 1970-01-01 00:00:00 +0000
28+++ developer_portal/templatetags/text_extras.py 2015-09-04 12:56:39 +0000
29@@ -0,0 +1,26 @@
30+from django import template
31+import re
32+import random
33+register = template.Library()
34+
35+@register.filter
36+def index(List, item):
37+ return list(List).index(item)+1
38+
39+@register.filter
40+def html_links(text):
41+ # url to link
42+ urls = re.compile(r"((https?):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+-=\\\.&]*)", re.MULTILINE|re.UNICODE)
43+ text = urls.sub(r'<a href="\1" target="_blank">\1</a>', text)
44+ # email to mailto
45+ urls = re.compile(r"([\w\-\.]+@(\w[\w\-]+\.)+[\w\-]+)", re.MULTILINE|re.UNICODE)
46+ text = urls.sub(r'<a href="mailto:\1">\1</a>', text)
47+ return text
48+
49+@register.filter
50+def randint(mini, maxi):
51+ return random.randint(mini, maxi)
52+
53+@register.filter
54+def string(integer):
55+ return str(integer)
56
57=== modified file 'locale/developer_portal.pot'
58--- locale/developer_portal.pot 2015-09-01 15:59:07 +0000
59+++ locale/developer_portal.pot 2015-09-04 12:56:39 +0000
60@@ -8,7 +8,7 @@
61 msgstr ""
62 "Project-Id-Version: PACKAGE VERSION\n"
63 "Report-Msgid-Bugs-To: \n"
64-"POT-Creation-Date: 2015-08-31 13:33+0000\n"
65+"POT-Creation-Date: 2015-09-04 12:56+0000\n"
66 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
67 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
68 "Language-Team: LANGUAGE <LL@li.org>\n"
69@@ -35,26 +35,26 @@
70 msgid "File name of doc to be used as index document, ie \"intro.md\""
71 msgstr ""
72
73-#: developer_portal/settings.py:187 developer_portal/settings.py:195
74+#: developer_portal/settings.py:189 developer_portal/settings.py:197
75 msgid "English"
76 msgstr ""
77
78-#: developer_portal/settings.py:188 developer_portal/settings.py:202
79+#: developer_portal/settings.py:190 developer_portal/settings.py:204
80 msgid "Simplified Chinese"
81 msgstr ""
82
83-#: developer_portal/settings.py:210
84+#: developer_portal/settings.py:212
85 msgid "Spanish"
86 msgstr ""
87
88-#: developer_portal/settings.py:250
89+#: developer_portal/settings.py:252
90 msgid "Page content"
91 msgstr ""
92
93 #. Translators: this is the default text that will be shown
94 #. to editors when editing a page. You can use some HTML,
95 #. but don't go wild :)
96-#: developer_portal/settings.py:258
97+#: developer_portal/settings.py:260
98 msgid "<p>Add content here...</p>"
99 msgstr ""
100
101@@ -64,6 +64,10 @@
102 "profile</a> to use the developer site"
103 msgstr ""
104
105+#: store_data/cms_plugins.py:9
106+msgid "Gadget Snap List Plugin"
107+msgstr ""
108+
109 #: templates/404.html:4
110 msgid "This page does not exist"
111 msgstr ""
112@@ -182,6 +186,10 @@
113 msgid "Search"
114 msgstr ""
115
116+#: templates/gadget_snap_list.html:27
117+msgid "Install Snappy on this device"
118+msgstr ""
119+
120 #: templates/login_failure.html:21
121 msgid ""
122 "If you continue to experience problems with the developer site, please <a "
123
124=== added directory 'store_data'
125=== added file 'store_data/__init__.py'
126=== added file 'store_data/admin.py'
127--- store_data/admin.py 1970-01-01 00:00:00 +0000
128+++ store_data/admin.py 2015-09-04 12:56:39 +0000
129@@ -0,0 +1,22 @@
130+from .models import Release, Architecture, GadgetSnap
131+from django.contrib import admin
132+
133+# Register your models here.
134+
135+
136+class ReleaseAdmin(admin.ModelAdmin):
137+ list_display = ('name',)
138+ search_fields = ('name',)
139+admin.site.register(Release, ReleaseAdmin)
140+
141+
142+class ArchitectureAdmin(admin.ModelAdmin):
143+ list_display = ('name',)
144+ search_fields = ('name',)
145+admin.site.register(Architecture, ArchitectureAdmin)
146+
147+
148+class GadgetSnapAdmin(admin.ModelAdmin):
149+ list_display = ('name',)
150+ search_fields = ('name', 'alias', 'publisher')
151+admin.site.register(GadgetSnap, GadgetSnapAdmin)
152
153=== added file 'store_data/cms_plugins.py'
154--- store_data/cms_plugins.py 1970-01-01 00:00:00 +0000
155+++ store_data/cms_plugins.py 2015-09-04 12:56:39 +0000
156@@ -0,0 +1,19 @@
157+from cms.plugin_base import CMSPluginBase
158+from cms.plugin_pool import plugin_pool
159+from django.utils.translation import ugettext_lazy as _
160+
161+from .models import GadgetSnap
162+
163+
164+class GadgetSnapListPlugin(CMSPluginBase):
165+ name = _("Gadget Snap List Plugin")
166+ render_template = "gadget_snap_list.html"
167+ text_enabled = True
168+
169+ def render(self, context, instance, placeholder):
170+ context.update({
171+ 'gadget_snap_list': GadgetSnap.objects.all(),
172+ })
173+ return context
174+
175+plugin_pool.register_plugin(GadgetSnapListPlugin)
176
177=== added directory 'store_data/management'
178=== added file 'store_data/management/__init__.py'
179=== added directory 'store_data/management/commands'
180=== added file 'store_data/management/commands/__init__.py'
181=== added file 'store_data/management/commands/update-gadget-snaps.py'
182--- store_data/management/commands/update-gadget-snaps.py 1970-01-01 00:00:00 +0000
183+++ store_data/management/commands/update-gadget-snaps.py 2015-09-04 12:56:39 +0000
184@@ -0,0 +1,50 @@
185+#!/usr/bin/python
186+
187+from datetime import datetime
188+import pytz
189+
190+from django.core.management.base import NoArgsCommand
191+
192+from store_data.models import Architecture, Release, GadgetSnap, ScreenshotURL
193+from ..store import api
194+
195+
196+def update_gadget_snaps():
197+ now = datetime.now(pytz.utc)
198+ gadget_snap_data = api.GadgetSnapData()
199+ for entry in gadget_snap_data.data:
200+ gadget_snap, created = GadgetSnap.objects.get_or_create(
201+ store_url=entry['_links']['self']['href'], name=entry['name'],
202+ defaults={
203+ 'icon_url': entry['icon_url'],
204+ 'ratings_average': entry['ratings_average'],
205+ 'alias': entry['alias'], 'price': entry['price'],
206+ 'publisher': entry['publisher'], 'version': entry['version'],
207+ 'last_updated': now, 'description': entry['description']})
208+ if not created:
209+ gadget_snap.last_updated = now
210+ gadget_snap.icon_url = entry['icon_url']
211+ gadget_snap.ratings_average = entry['ratings_average']
212+ gadget_snap.alias = entry['alias']
213+ gadget_snap.price = entry['price']
214+ gadget_snap.publisher = entry['publisher']
215+ gadget_snap.version = entry['version']
216+ gadget_snap.description = entry['description']
217+ gadget_snap.save()
218+ for arch in entry['architecture']:
219+ arch_ob, created = Architecture.objects.get_or_create(name=arch)
220+ gadget_snap.architecture.add(arch_ob)
221+ for release in entry['release']:
222+ rel_ob, created = Release.objects.get_or_create(name=release)
223+ gadget_snap.release.add(rel_ob)
224+ for url in entry['screenshot_urls']:
225+ url_ob, created = ScreenshotURL.objects.get_or_create(url=url)
226+ gadget_snap.screenshot_url.add(url_ob)
227+ GadgetSnap.objects.exclude(last_updated=now).delete()
228+
229+
230+class Command(NoArgsCommand):
231+ help = 'Update list of gadget snaps from store.'
232+
233+ def handle_noargs(self, **options):
234+ update_gadget_snaps()
235
236=== added directory 'store_data/management/store'
237=== added file 'store_data/management/store/__init__.py'
238=== added file 'store_data/management/store/api.py'
239--- store_data/management/store/api.py 1970-01-01 00:00:00 +0000
240+++ store_data/management/store/api.py 2015-09-04 12:56:39 +0000
241@@ -0,0 +1,52 @@
242+import json
243+import sys
244+
245+if sys.version_info.major == 2:
246+ from urllib import urlencode
247+ from urllib2 import Request, urlopen
248+else:
249+ from urllib.parse import urlencode
250+ from urllib.request import Request, urlopen
251+
252+
253+STORE_URL = 'https://search.apps.ubuntu.com'
254+PACKAGE_API = STORE_URL + '/api/v1/search'
255+
256+
257+class GadgetSnapData(object):
258+ data = {}
259+
260+ def get_data(self):
261+ params = urlencode({'q': 'content:oem'})
262+ url = PACKAGE_API + "?%s" % params
263+ req = Request(url)
264+ req.add_header('X-Ubuntu-Frameworks', 'ubuntu-core-15.04-dev1')
265+ # XXX: This is supposed to work, but doesn't.
266+ # req.add_header('X-Ubuntu-Release',
267+ # '[15.04-core|rolling-core|rolling-personal]')
268+ req.add_header('Accept', 'application/hal+json')
269+ f = urlopen(req)
270+ s = f.read().decode('utf-8')
271+ f.close()
272+ packages = json.loads(s)
273+ self.data = packages['_embedded']['clickindex:package']
274+
275+ def get_additional_snap_data(self):
276+ for entry in self.data:
277+ snap_api_url = entry['_links']['self']['href']
278+ req = Request(snap_api_url)
279+ req.add_header('Accept', 'application/hal+json')
280+ f = urlopen(req)
281+ s = f.read().decode('utf-8')
282+ f.close()
283+ data = json.loads(s)
284+ entry['screenshot_urls'] = []
285+ if data['screenshot_url']:
286+ entry['screenshot_urls'] += [data['screenshot_url']]
287+ if data['screenshot_urls']:
288+ entry['screenshot_urls'].extend(data['screenshot_urls'])
289+ entry['description'] = data['description']
290+
291+ def __init__(self):
292+ self.get_data()
293+ self.get_additional_snap_data()
294
295=== added directory 'store_data/migrations'
296=== added file 'store_data/migrations/0001_initial.py'
297--- store_data/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
298+++ store_data/migrations/0001_initial.py 2015-09-04 12:56:39 +0000
299@@ -0,0 +1,134 @@
300+# -*- coding: utf-8 -*-
301+from south.utils import datetime_utils as datetime
302+from south.db import db
303+from south.v2 import SchemaMigration
304+from django.db import models
305+
306+
307+class Migration(SchemaMigration):
308+
309+ def forwards(self, orm):
310+ # Adding model 'Release'
311+ db.create_table(u'store_data_release', (
312+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
313+ ('name', self.gf('django.db.models.fields.CharField')(max_length=25)),
314+ ))
315+ db.send_create_signal(u'store_data', ['Release'])
316+
317+ # Adding model 'Architecture'
318+ db.create_table(u'store_data_architecture', (
319+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
320+ ('name', self.gf('django.db.models.fields.CharField')(max_length=10)),
321+ ))
322+ db.send_create_signal(u'store_data', ['Architecture'])
323+
324+ # Adding model 'ScreenshotURL'
325+ db.create_table(u'store_data_screenshoturl', (
326+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
327+ ('url', self.gf('django.db.models.fields.URLField')(max_length=200, blank=True)),
328+ ))
329+ db.send_create_signal(u'store_data', ['ScreenshotURL'])
330+
331+ # Adding model 'GadgetSnap'
332+ db.create_table(u'store_data_gadgetsnap', (
333+ (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
334+ ('icon_url', self.gf('django.db.models.fields.URLField')(max_length=200, blank=True)),
335+ ('name', self.gf('django.db.models.fields.CharField')(max_length=100)),
336+ ('ratings_average', self.gf('django.db.models.fields.DecimalField')(max_digits=2, decimal_places=1)),
337+ ('alias', self.gf('django.db.models.fields.CharField')(max_length=100, null=True, blank=True)),
338+ ('price', self.gf('django.db.models.fields.DecimalField')(max_digits=5, decimal_places=2)),
339+ ('publisher', self.gf('django.db.models.fields.CharField')(max_length=100)),
340+ ('store_url', self.gf('django.db.models.fields.URLField')(max_length=200, blank=True)),
341+ ('version', self.gf('django.db.models.fields.CharField')(max_length=25)),
342+ ('last_updated', self.gf('django.db.models.fields.DateTimeField')()),
343+ ('description', self.gf('django.db.models.fields.TextField')(max_length=5000)),
344+ ))
345+ db.send_create_signal(u'store_data', ['GadgetSnap'])
346+
347+ # Adding M2M table for field release on 'GadgetSnap'
348+ m2m_table_name = db.shorten_name(u'store_data_gadgetsnap_release')
349+ db.create_table(m2m_table_name, (
350+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
351+ ('gadgetsnap', models.ForeignKey(orm[u'store_data.gadgetsnap'], null=False)),
352+ ('release', models.ForeignKey(orm[u'store_data.release'], null=False))
353+ ))
354+ db.create_unique(m2m_table_name, ['gadgetsnap_id', 'release_id'])
355+
356+ # Adding M2M table for field architecture on 'GadgetSnap'
357+ m2m_table_name = db.shorten_name(u'store_data_gadgetsnap_architecture')
358+ db.create_table(m2m_table_name, (
359+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
360+ ('gadgetsnap', models.ForeignKey(orm[u'store_data.gadgetsnap'], null=False)),
361+ ('architecture', models.ForeignKey(orm[u'store_data.architecture'], null=False))
362+ ))
363+ db.create_unique(m2m_table_name, ['gadgetsnap_id', 'architecture_id'])
364+
365+ # Adding M2M table for field screenshot_url on 'GadgetSnap'
366+ m2m_table_name = db.shorten_name(u'store_data_gadgetsnap_screenshot_url')
367+ db.create_table(m2m_table_name, (
368+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
369+ ('gadgetsnap', models.ForeignKey(orm[u'store_data.gadgetsnap'], null=False)),
370+ ('screenshoturl', models.ForeignKey(orm[u'store_data.screenshoturl'], null=False))
371+ ))
372+ db.create_unique(m2m_table_name, ['gadgetsnap_id', 'screenshoturl_id'])
373+
374+
375+ def backwards(self, orm):
376+ # Deleting model 'Release'
377+ db.delete_table(u'store_data_release')
378+
379+ # Deleting model 'Architecture'
380+ db.delete_table(u'store_data_architecture')
381+
382+ # Deleting model 'ScreenshotURL'
383+ db.delete_table(u'store_data_screenshoturl')
384+
385+ # Deleting model 'GadgetSnap'
386+ db.delete_table(u'store_data_gadgetsnap')
387+
388+ # Removing M2M table for field release on 'GadgetSnap'
389+ db.delete_table(db.shorten_name(u'store_data_gadgetsnap_release'))
390+
391+ # Removing M2M table for field architecture on 'GadgetSnap'
392+ db.delete_table(db.shorten_name(u'store_data_gadgetsnap_architecture'))
393+
394+ # Removing M2M table for field screenshot_url on 'GadgetSnap'
395+ db.delete_table(db.shorten_name(u'store_data_gadgetsnap_screenshot_url'))
396+
397+
398+ models = {
399+ u'store_data.architecture': {
400+ 'Meta': {'object_name': 'Architecture'},
401+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
402+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '10'})
403+ },
404+ u'store_data.gadgetsnap': {
405+ 'Meta': {'object_name': 'GadgetSnap'},
406+ 'alias': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
407+ 'architecture': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['store_data.Architecture']", 'symmetrical': 'False'}),
408+ 'description': ('django.db.models.fields.TextField', [], {'max_length': '5000'}),
409+ 'icon_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
410+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
411+ 'last_updated': ('django.db.models.fields.DateTimeField', [], {}),
412+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
413+ 'price': ('django.db.models.fields.DecimalField', [], {'max_digits': '5', 'decimal_places': '2'}),
414+ 'publisher': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
415+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'max_digits': '2', 'decimal_places': '1'}),
416+ 'release': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['store_data.Release']", 'symmetrical': 'False'}),
417+ 'screenshot_url': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['store_data.ScreenshotURL']", 'symmetrical': 'False'}),
418+ 'store_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
419+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '25'})
420+ },
421+ u'store_data.release': {
422+ 'Meta': {'object_name': 'Release'},
423+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
424+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '25'})
425+ },
426+ u'store_data.screenshoturl': {
427+ 'Meta': {'object_name': 'ScreenshotURL'},
428+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
429+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
430+ }
431+ }
432+
433+ complete_apps = ['store_data']
434\ No newline at end of file
435
436=== added file 'store_data/migrations/__init__.py'
437=== added file 'store_data/models.py'
438--- store_data/models.py 1970-01-01 00:00:00 +0000
439+++ store_data/models.py 2015-09-04 12:56:39 +0000
440@@ -0,0 +1,29 @@
441+from django.db import models
442+
443+
444+class Release(models.Model):
445+ name = models.CharField(max_length=25)
446+
447+
448+class Architecture(models.Model):
449+ name = models.CharField(max_length=10)
450+
451+
452+class ScreenshotURL(models.Model):
453+ url = models.URLField(blank=True)
454+
455+
456+class GadgetSnap(models.Model):
457+ icon_url = models.URLField(blank=True)
458+ release = models.ManyToManyField(Release)
459+ name = models.CharField(max_length=100)
460+ ratings_average = models.DecimalField(max_digits=2, decimal_places=1)
461+ alias = models.CharField(max_length=100, blank=True, null=True)
462+ price = models.DecimalField(max_digits=5, decimal_places=2)
463+ publisher = models.CharField(max_length=100)
464+ store_url = models.URLField(blank=True)
465+ version = models.CharField(max_length=25)
466+ architecture = models.ManyToManyField(Architecture)
467+ last_updated = models.DateTimeField()
468+ description = models.TextField(max_length=5000)
469+ screenshot_url = models.ManyToManyField(ScreenshotURL)
470
471=== added directory 'store_data/static'
472=== added directory 'store_data/static/img'
473=== added directory 'store_data/static/img/boards'
474=== added file 'store_data/static/img/boards/noboard-0.png'
475Binary files store_data/static/img/boards/noboard-0.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-0.png 2015-09-04 12:56:39 +0000 differ
476=== added file 'store_data/static/img/boards/noboard-1.png'
477Binary files store_data/static/img/boards/noboard-1.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-1.png 2015-09-04 12:56:39 +0000 differ
478=== added file 'store_data/static/img/boards/noboard-10.png'
479Binary files store_data/static/img/boards/noboard-10.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-10.png 2015-09-04 12:56:39 +0000 differ
480=== added file 'store_data/static/img/boards/noboard-2.png'
481Binary files store_data/static/img/boards/noboard-2.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-2.png 2015-09-04 12:56:39 +0000 differ
482=== added file 'store_data/static/img/boards/noboard-3.png'
483Binary files store_data/static/img/boards/noboard-3.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-3.png 2015-09-04 12:56:39 +0000 differ
484=== added file 'store_data/static/img/boards/noboard-4.png'
485Binary files store_data/static/img/boards/noboard-4.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-4.png 2015-09-04 12:56:39 +0000 differ
486=== added file 'store_data/static/img/boards/noboard-5.png'
487Binary files store_data/static/img/boards/noboard-5.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-5.png 2015-09-04 12:56:39 +0000 differ
488=== added file 'store_data/static/img/boards/noboard-6.png'
489Binary files store_data/static/img/boards/noboard-6.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-6.png 2015-09-04 12:56:39 +0000 differ
490=== added file 'store_data/static/img/boards/noboard-7.png'
491Binary files store_data/static/img/boards/noboard-7.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-7.png 2015-09-04 12:56:39 +0000 differ
492=== added file 'store_data/static/img/boards/noboard-8.png'
493Binary files store_data/static/img/boards/noboard-8.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-8.png 2015-09-04 12:56:39 +0000 differ
494=== added file 'store_data/static/img/boards/noboard-9.png'
495Binary files store_data/static/img/boards/noboard-9.png 1970-01-01 00:00:00 +0000 and store_data/static/img/boards/noboard-9.png 2015-09-04 12:56:39 +0000 differ
496=== added file 'store_data/tests.py'
497--- store_data/tests.py 1970-01-01 00:00:00 +0000
498+++ store_data/tests.py 2015-09-04 12:56:39 +0000
499@@ -0,0 +1,3 @@
500+from django.test import TestCase
501+
502+# Create your tests here.
503
504=== added file 'store_data/urls.py'
505--- store_data/urls.py 1970-01-01 00:00:00 +0000
506+++ store_data/urls.py 2015-09-04 12:56:39 +0000
507@@ -0,0 +1,5 @@
508+from django.contrib.staticfiles.urls import staticfiles_urlpatterns
509+urlpatterns = staticfiles_urlpatterns()
510+
511+from django.contrib import admin
512+admin.autodiscover()
513
514=== added file 'store_data/views.py'
515--- store_data/views.py 1970-01-01 00:00:00 +0000
516+++ store_data/views.py 2015-09-04 12:56:39 +0000
517@@ -0,0 +1,3 @@
518+from django.shortcuts import render
519+
520+# Create your views here.
521
522=== added file 'templates/gadget_snap_list.html'
523--- templates/gadget_snap_list.html 1970-01-01 00:00:00 +0000
524+++ templates/gadget_snap_list.html 2015-09-04 12:56:39 +0000
525@@ -0,0 +1,32 @@
526+{% load i18n static text_extras %}
527+
528+<div class="row equal-height no-border">
529+ {% for snap in gadget_snap_list %}
530+ {% if gadget_snap_list|index:snap|divisibleby:3 %}
531+ <div class="left four-col last-col box">
532+ {% else %}
533+ <div class="left four-col box">
534+ {% endif %}
535+ {% if snap.screenshot_url.0.url %}
536+ <p><img src="{{ snap.screenshot_url.0.url }}"/></p>
537+ {% else %}
538+ {% with 0|randint:10|string as rand %}
539+ {% with "img/boards/noboard-"|add:rand|add:".png" as noboard %}
540+ <p><img src="{% static noboard %}"/></p>
541+ {% endwith %}
542+ {% endwith %}
543+ {% endif %}
544+ <h3>{{ snap.alias|default_if_none:snap.name }}</h3>
545+ {% if snap.description|length > 180 %}
546+ <p>{{ snap.description|truncatechars:180 }}</p>
547+ {% else %}
548+ <p>{% autoescape off %}{{ snap.description|html_links }}{% endautoescape %}</p>
549+ {% endif %}
550+ <ul class="list-ubuntu">
551+ <li>
552+ <a href="https://developer.ubuntu.com/en/snappy/start/">{% trans "Install Snappy on this device" %}</a>
553+ </li>
554+ </ul>
555+ </div>
556+ {% endfor %}
557+</div>

Subscribers

People subscribed via source and target branches