Merge lp:~elachuni/ubuntu-webcatalog/display-custom-screenshot into lp:ubuntu-webcatalog

Proposed by Anthony Lenton
Status: Merged
Approved by: Anthony Lenton
Approved revision: 49
Merged at revision: 47
Proposed branch: lp:~elachuni/ubuntu-webcatalog/display-custom-screenshot
Merge into: lp:ubuntu-webcatalog
Diff against target: 170 lines (+53/-14)
8 files modified
src/webcatalog/management/commands/import_ratings_stats.py (+4/-2)
src/webcatalog/models/applications.py (+1/-1)
src/webcatalog/static/css/webcatalog.css (+6/-0)
src/webcatalog/templates/webcatalog/application_detail.html (+4/-4)
src/webcatalog/tests/factory.py (+2/-2)
src/webcatalog/tests/test_commands.py (+14/-0)
src/webcatalog/tests/test_forms.py (+10/-0)
src/webcatalog/tests/test_views.py (+12/-5)
To merge this branch: bzr merge lp:~elachuni/ubuntu-webcatalog/display-custom-screenshot
Reviewer Review Type Date Requested Status
Michael Nelson (community) Approve
Review via email: mp+70956@code.launchpad.net

Commit message

Display the custom screenshot_url in the application details page, if included.

Description of the change

Overview
========
A branch to display the custom screenshot_url in the application details page, if included.

Details
=======
The screenshot_url field was modified slightly so that it doesn't verify if the page actually exists. This is a bad idea for url fields in general (it expects us to allow arbitrary outbound connections from our DC), but in this case specifically it made even less sense as most of the time the field is populated by a script that doesn't commit typos.

While I was there, I found an issue with the import_ratings_stats command (at least when working against sqlite) when there are many ratings returned, so I fixed and added a test for that too.

Finally, I added a bit of css to the thumbnail screenshot to make it match the current USC widget: 225px wide, and with a slight white and gray padded border.

To post a comment you must log in.
Revision history for this message
Michael Nelson (michael.nelson) wrote :

Ouch... what is the actual error returned for that query? Is it because the sql ends up way too large? Anyway, great fix!

Nice test for the verify_exists too. I've always wondered about that kind of thing... how far do we go when testing the configuration of our models? We need to check the configuration of our models (required fields, for example), or even valid data when we've customized what's valid and what's not (with custom clean, or a RegExField). Other times we add a test to ensure the configuration isn't reverted.

Great stuff :)

review: Approve
Revision history for this message
Anthony Lenton (elachuni) wrote :

Hi Michael,

> Ouch... what is the actual error returned for that query? Is it because the
> sql ends up way too large? Anyway, great fix!

It's not just the SQL ends up being too large, but it seems there's a limit to the amount of variables you can use in a query. Removing the fix makes the test fail like this: https://pastebin.canonical.com/51010/
Googling for the error ("too many SQL variables") this limit is 999: http://www.trash-factor.com/content/too-many-sql-variables-sqlite-error
And apparently each constant in the "AND package_name IN (.....)" clause counts towards that.

> Nice test for the verify_exists too. I've always wondered about that kind of
> thing... how far do we go when testing the configuration of our models? We
> need to check the configuration of our models (required fields, for example),
> or even valid data when we've customized what's valid and what's not (with
> custom clean, or a RegExField). Other times we add a test to ensure the
> configuration isn't reverted.

I'd usually avoid checking model field arguments like this (like checking max_length works on CharFields, which I agree is pointless), but I've seen verify_exists=True come back a couple of times on one or another field, so the test is to ensure that we don't happen to put it back to True at some point on this field at least, and to document the reasons which here aren't trivial. Hopefully if somebody happens to change this down the road, the test will fail, the developer will read the comment and put it back, or at least be aware of the issues at play :)

Thanks for the review!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/webcatalog/management/commands/import_ratings_stats.py'
2--- src/webcatalog/management/commands/import_ratings_stats.py 2011-07-20 15:31:13 +0000
3+++ src/webcatalog/management/commands/import_ratings_stats.py 2011-08-09 20:20:16 +0000
4@@ -59,10 +59,12 @@
5
6 def update_apps_with_stats(self, distroseries, stats):
7 stats_dict = dict((stat.package_name, stat) for stat in stats)
8- apps = Application.objects.filter(distroseries=distroseries,
9- package_name__in=stats_dict.keys()).order_by('package_name')
10+ apps = Application.objects.filter(distroseries=distroseries)
11+ apps = apps.order_by('package_name')
12
13 for app in apps:
14+ if not app.package_name in stats_dict:
15+ continue
16 stat = stats_dict[app.package_name]
17 app.ratings_average = stat.ratings_average
18 app.ratings_total = stat.ratings_total
19
20=== modified file 'src/webcatalog/models/applications.py'
21--- src/webcatalog/models/applications.py 2011-07-27 16:52:06 +0000
22+++ src/webcatalog/models/applications.py 2011-08-09 20:20:16 +0000
23@@ -69,7 +69,7 @@
24 comment = models.CharField(max_length=255, blank=True)
25 popcon = models.IntegerField(null=True, blank=True)
26 channel = models.CharField(max_length=255, blank=True)
27- screenshot_url = models.URLField(blank=True,
28+ screenshot_url = models.URLField(blank=True, verify_exists=False,
29 help_text="Only use this if it is other than the normal screenshot url.")
30 mimetype = models.CharField(max_length=2048, blank=True)
31 architectures = models.CharField(max_length=255, blank=True)
32
33=== modified file 'src/webcatalog/static/css/webcatalog.css'
34--- src/webcatalog/static/css/webcatalog.css 2011-07-27 09:13:49 +0000
35+++ src/webcatalog/static/css/webcatalog.css 2011-08-09 20:20:16 +0000
36@@ -56,6 +56,12 @@
37 margin: 0 16px;
38 }
39
40+.screenshot img {
41+ width: 225px;
42+ border: 1px solid gray;
43+ padding: 3px;
44+}
45+
46 .noscreenshot {
47 padding: 41px 32px;
48 border: 1px solid white;
49
50=== modified file 'src/webcatalog/templates/webcatalog/application_detail.html'
51--- src/webcatalog/templates/webcatalog/application_detail.html 2011-07-27 09:13:49 +0000
52+++ src/webcatalog/templates/webcatalog/application_detail.html 2011-08-09 20:20:16 +0000
53@@ -40,11 +40,11 @@
54 </div>
55 <div class="description">
56 <div class="screenshot">
57- {% comment %}
58- Add a |thumbnail filter which will use either the custom screenshot_url
59- (if it's provided) or default to the thumbnail url below.
60- {% endcomment %}
61+ {% if application.screenshot_url %}
62+ <img src="{{ application.screenshot_url }}" />
63+ {% else %}
64 <img src="http://screenshots.ubuntu.com/thumbnail-with-version/{{ application.package_name }}/ignored" />
65+ {% endif %}
66 </div>
67 <p>{{ application.description|htmlize_package_description }}</p>
68 </div>
69
70=== modified file 'src/webcatalog/tests/factory.py'
71--- src/webcatalog/tests/factory.py 2011-07-29 15:57:17 +0000
72+++ src/webcatalog/tests/factory.py 2011-08-09 20:20:16 +0000
73@@ -92,7 +92,7 @@
74 def make_application(self, package_name=None, name=None,
75 comment=None, description=None, icon_name='', icon=None,
76 distroseries=None, arch='i686', ratings_average=None,
77- ratings_total=None, ratings_histogram=''):
78+ ratings_total=None, ratings_histogram='', screenshot_url=None):
79 if name is None:
80 name = self.get_unique_string(prefix='Readable Name')
81 if package_name is None:
82@@ -109,7 +109,7 @@
83 description=description, popcon=999, icon=icon,
84 icon_name=icon_name, distroseries=distroseries, architectures=arch,
85 ratings_average=ratings_average, ratings_total=ratings_total,
86- ratings_histogram=ratings_histogram)
87+ ratings_histogram=ratings_histogram, screenshot_url=screenshot_url)
88
89 def make_department(self, name, parent=None):
90 return Department.objects.create(name=name, parent=parent)
91
92=== modified file 'src/webcatalog/tests/test_commands.py'
93--- src/webcatalog/tests/test_commands.py 2011-07-20 11:07:59 +0000
94+++ src/webcatalog/tests/test_commands.py 2011-08-09 20:20:16 +0000
95@@ -528,3 +528,17 @@
96 self.assertEqual('[0, 1, 0, 1, 2]', scribus.ratings_histogram)
97 otherapp = Application.objects.get(id=otherapp.id)
98 self.assertEqual(None, otherapp.ratings_total)
99+
100+ def test_update_works_with_many_stats(self):
101+ """Ensure update_apps_with_stats doesn't fail with many ratings."""
102+ natty = self.factory.make_distroseries(code_name='natty')
103+ stats = [ReviewsStats.from_dict(dict(
104+ package_name=self.factory.get_unique_string(prefix='package-'),
105+ ratings_average='5.00', ratings_total=1,
106+ histogram='[0, 0, 0, 0, 1]')) for x in range(3000)]
107+ command = ImportRatingsStatsCommand()
108+
109+ # update_apps_with_stats returns None on success:
110+ self.assertIsNone(command.update_apps_with_stats(natty, stats))
111+
112+
113\ No newline at end of file
114
115=== modified file 'src/webcatalog/tests/test_forms.py'
116--- src/webcatalog/tests/test_forms.py 2011-06-23 12:38:12 +0000
117+++ src/webcatalog/tests/test_forms.py 2011-08-09 20:20:16 +0000
118@@ -131,3 +131,13 @@
119 form_key = desktop_field_mappings.get(key.lower(), key.lower())
120 self.assertEqual(
121 extra_desktop_info[key], form.cleaned_data[form_key])
122+
123+ def test_screenshot_url_isnt_verified(self):
124+ # Verifying if this URLField actually exists is a bad idea:
125+ # - It expects us to allow arbitrary outgoing connections from the DC
126+ # - It adds a significant delay to tests unless carefully mocked
127+ # - It doesn't provide a real benefit as 99% of the time the field
128+ # is populated by a script that doesn't have typo issues.
129+ form = ApplicationForm(dict(screenshot_url='http://foo.com:42/broken',
130+ section='required', name='required', package_name='required'))
131+ self.assertTrue(form.is_valid())
132
133=== modified file 'src/webcatalog/tests/test_views.py'
134--- src/webcatalog/tests/test_views.py 2011-07-27 09:13:49 +0000
135+++ src/webcatalog/tests/test_views.py 2011-08-09 20:20:16 +0000
136@@ -52,16 +52,17 @@
137 class ApplicationDetailTestCase(TestCaseWithFactory):
138
139 def get_app_and_response(self, code_name='natty', version='11.04',
140- arch='x86_64', name=None, comment=None,
141- description=None, detail_distro=None,
142- detail_package=None, for_purchase=False,
143- useragent=UBUNTU_USERAGENT):
144+ arch='x86_64', name=None, comment=None,
145+ description=None, detail_distro=None,
146+ detail_package=None, for_purchase=False,
147+ screenshot_url=None, useragent=UBUNTU_USERAGENT):
148 series, created = DistroSeries.objects.get_or_create(
149 code_name=code_name, version=version)
150 app = self.factory.make_application(package_name='pkgfoo',
151 distroseries=series, arch=arch,
152 name=name, comment=comment,
153- description=description)
154+ description=description,
155+ screenshot_url=screenshot_url)
156 if for_purchase:
157 app.for_purchase = True
158 app.save()
159@@ -102,6 +103,12 @@
160 response, '<img src="http://screenshots.ubuntu.com/'
161 'thumbnail-with-version/pkgfoo/ignored"')
162
163+ def test_includes_custom_screenshot_url_if_provided(self):
164+ expected = 'http://foo.com/%s.png' + self.factory.get_unique_string()
165+ response, app = self.get_app_and_response(screenshot_url=expected)
166+
167+ self.assertContains(response, expected)
168+
169 def test_link_to_apt_package(self):
170 response, app = self.get_app_and_response()
171

Subscribers

People subscribed via source and target branches