Merge lp:~dholbach/developer-ubuntu-com/store-data into lp:developer-ubuntu-com
- store-data
- Merge into stable
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 |
Related bugs: |
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).
Daniel Holbach (dholbach) wrote : | # |
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.
Daniel Holbach (dholbach) wrote : | # |
Marking as WIP as I'll push the display parts to this branch too.
Daniel Holbach (dholbach) wrote : | # |
Up for review again, it works for me now.
- 151. By Daniel Holbach
-
add icon
Michael Hall (mhall119) wrote : | # |
Looks good, just one change before it's done
In your GadgetSnapListP
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
Preview Diff
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' |
475 | Binary 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' |
477 | Binary 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' |
479 | Binary 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' |
481 | Binary 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' |
483 | Binary 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' |
485 | Binary 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' |
487 | Binary 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' |
489 | Binary 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' |
491 | Binary 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' |
493 | Binary 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' |
495 | Binary 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> |
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.