Merge lp:~beuno/python-oops-tools/show-newest-oopses into lp:python-oops-tools
- show-newest-oopses
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~beuno/python-oops-tools/show-newest-oopses |
Merge into: | lp:python-oops-tools |
Diff against target: |
309 lines (+85/-18) 9 files modified
src/oopstools/oops/dboopsloader.py (+4/-4) src/oopstools/oops/dbsummaries.py (+5/-2) src/oopstools/oops/helpers.py (+6/-4) src/oopstools/oops/oopsstore.py (+15/-4) src/oopstools/oops/templates/newest.html (+12/-0) src/oopstools/oops/test/oopsstore.txt (+32/-0) src/oopstools/oops/test/test_runner.py (+2/-1) src/oopstools/oops/views.py (+7/-2) src/oopstools/urls.py (+2/-1) |
To merge this branch: | bzr merge lp:~beuno/python-oops-tools/show-newest-oopses |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benji York (community) | code | Approve | |
Review via email: mp+129752@code.launchpad.net |
Commit message
Add a view to see the newest oopses.
Description of the change
Add an option to see the newest oopses by project.
- 52. By Martin Albisetti
-
Review comments
Martin Albisetti (beuno) wrote : | # |
> The whitespace fixes are appreciated. One class of whitespace change
> looks like a search/replace error though: lines 8 and 103 of the diff
> have one-element lists that included a trailing comma. That was
> somewhat odd, but the added space after the comma is even oddder. I
> suggest removing both the comma and the space.
Done.
> I don't know much about the on-disk layout for OOPS storage, but I would
> be a little concerned that non-oops files and directories would confuse
> the "newest" method. If this trait is common to the rest of the
> OOPS-tools code, then my concern can be ignored.
It is. In this case, the worst that can happen is linking to something that isn't an oops.
> The test for the new "newest" function (line 239 of the diff) contains a
> dependency on the order of elements returned from a call the "values"
> method of a dict. That order can vary, so the returned list needs to be
> sorted in the doctest in order to avoid spurious test failures.
>
> The above applies to the test on line 246 of the diff as well.
Done!
- 53. By Martin Albisetti
-
Start talking to the DB instead
Unmerged revisions
- 53. By Martin Albisetti
-
Start talking to the DB instead
- 52. By Martin Albisetti
-
Review comments
- 51. By Martin Albisetti
-
Add test
- 50. By Martin Albisetti
-
Clean up things a bit
- 49. By Martin Albisetti
-
Working, quick and dirty
Preview Diff
1 | === modified file 'src/oopstools/oops/dboopsloader.py' | |||
2 | --- src/oopstools/oops/dboopsloader.py 2011-10-21 13:54:29 +0000 | |||
3 | +++ src/oopstools/oops/dboopsloader.py 2012-10-17 22:06:22 +0000 | |||
4 | @@ -87,7 +87,7 @@ | |||
5 | 87 | # XXX matsubara: remove the if clause when refactoring this code | 87 | # XXX matsubara: remove the if clause when refactoring this code |
6 | 88 | # to use a single oops directory. | 88 | # to use a single oops directory. |
7 | 89 | if type(oopsdir) == str: | 89 | if type(oopsdir) == str: |
9 | 90 | oopsdir = [oopsdir,] | 90 | oopsdir = [oopsdir, ] |
10 | 91 | 91 | ||
11 | 92 | # Walk through all subdirectories containing OOPSes and ensure there | 92 | # Walk through all subdirectories containing OOPSes and ensure there |
12 | 93 | # is a matching database structure for them. | 93 | # is a matching database structure for them. |
13 | @@ -141,7 +141,7 @@ | |||
14 | 141 | before_date = date - datetime.timedelta(days=1) | 141 | before_date = date - datetime.timedelta(days=1) |
15 | 142 | after_date = date + datetime.timedelta(days=2) | 142 | after_date = date + datetime.timedelta(days=2) |
16 | 143 | query = Oops.objects.filter( | 143 | query = Oops.objects.filter( |
18 | 144 | date__range=(before_date,after_date), | 144 | date__range=(before_date, after_date), |
19 | 145 | pathname__startswith=prefix).values_list( | 145 | pathname__startswith=prefix).values_list( |
20 | 146 | 'pathname', flat=True) | 146 | 'pathname', flat=True) |
21 | 147 | oops_reports = set( | 147 | oops_reports = set( |
22 | @@ -151,7 +151,7 @@ | |||
23 | 151 | oops = self._load_oops(datedir, filename) | 151 | oops = self._load_oops(datedir, filename) |
24 | 152 | if oops is not None: | 152 | if oops is not None: |
25 | 153 | yield oops | 153 | yield oops |
27 | 154 | # We update the last_date only when oops | 154 | # We update the last_date only when oops |
28 | 155 | # has the date different to what we already have | 155 | # has the date different to what we already have |
29 | 156 | # This speeds up the loading process | 156 | # This speeds up the loading process |
30 | 157 | if entry.last_date != date: | 157 | if entry.last_date != date: |
31 | @@ -177,7 +177,7 @@ | |||
32 | 177 | # condition. | 177 | # condition. |
33 | 178 | logger.error( | 178 | logger.error( |
34 | 179 | "OOPS %s/%s already in the DB.", datedir, filename) | 179 | "OOPS %s/%s already in the DB.", datedir, filename) |
36 | 180 | transaction.rollback() # Continue to next one. | 180 | transaction.rollback() # Continue to next one. |
37 | 181 | else: | 181 | else: |
38 | 182 | logger.error( | 182 | logger.error( |
39 | 183 | "%s raised with datedir: %s filename: %s ", | 183 | "%s raised with datedir: %s filename: %s ", |
40 | 184 | 184 | ||
41 | === modified file 'src/oopstools/oops/dbsummaries.py' | |||
42 | --- src/oopstools/oops/dbsummaries.py 2012-09-11 02:08:56 +0000 | |||
43 | +++ src/oopstools/oops/dbsummaries.py 2012-10-17 22:06:22 +0000 | |||
44 | @@ -32,6 +32,7 @@ | |||
45 | 32 | ############################################################################# | 32 | ############################################################################# |
46 | 33 | # Groups | 33 | # Groups |
47 | 34 | 34 | ||
48 | 35 | |||
49 | 35 | def _format_http_method_count(data): | 36 | def _format_http_method_count(data): |
50 | 36 | tmp = [] | 37 | tmp = [] |
51 | 37 | for method in TRACKED_HTTP_METHODS + ('Other',): | 38 | for method in TRACKED_HTTP_METHODS + ('Other',): |
52 | @@ -40,6 +41,7 @@ | |||
53 | 40 | tmp.append("%s: %s" % (method, count)) | 41 | tmp.append("%s: %s" % (method, count)) |
54 | 41 | return ' '.join(tmp) | 42 | return ' '.join(tmp) |
55 | 42 | 43 | ||
56 | 44 | |||
57 | 43 | def _escape(value): | 45 | def _escape(value): |
58 | 44 | if value is not None: | 46 | if value is not None: |
59 | 45 | if not isinstance(value, unicode): | 47 | if not isinstance(value, unicode): |
60 | @@ -333,7 +335,7 @@ | |||
61 | 333 | 'oopsinfestation__exception_value') | 335 | 'oopsinfestation__exception_value') |
62 | 334 | ) | 336 | ) |
63 | 335 | super(ErrorSection, self).__init__( | 337 | super(ErrorSection, self).__init__( |
65 | 336 | title, errors, max_count, _all_groups = all_groups) | 338 | title, errors, max_count, _all_groups=all_groups) |
66 | 337 | 339 | ||
67 | 338 | 340 | ||
68 | 339 | class NotFoundSection(ErrorSection): | 341 | class NotFoundSection(ErrorSection): |
69 | @@ -362,7 +364,7 @@ | |||
70 | 362 | '-count', 'most_expensive_statement') | 364 | '-count', 'most_expensive_statement') |
71 | 363 | ) | 365 | ) |
72 | 364 | super(TimeOutSection, self).__init__( | 366 | super(TimeOutSection, self).__init__( |
74 | 365 | title, errors, max_count, _all_groups = all_groups) | 367 | title, errors, max_count, _all_groups=all_groups) |
75 | 366 | 368 | ||
76 | 367 | # We perform the top value queries outside of the ORM for performance. | 369 | # We perform the top value queries outside of the ORM for performance. |
77 | 368 | # | 370 | # |
78 | @@ -772,6 +774,7 @@ | |||
79 | 772 | ErrorSection, dict(title='Exceptions', max_count=50), section_set) | 774 | ErrorSection, dict(title='Exceptions', max_count=50), section_set) |
80 | 773 | self.addSections(*section_set) | 775 | self.addSections(*section_set) |
81 | 774 | 776 | ||
82 | 777 | |||
83 | 775 | class ISDErrorSummary(GenericErrorSummary): | 778 | class ISDErrorSummary(GenericErrorSummary): |
84 | 776 | """Summarize ISD error reports (placeholder)""" | 779 | """Summarize ISD error reports (placeholder)""" |
85 | 777 | 780 | ||
86 | 778 | 781 | ||
87 | === modified file 'src/oopstools/oops/helpers.py' | |||
88 | --- src/oopstools/oops/helpers.py 2011-10-13 20:18:51 +0000 | |||
89 | +++ src/oopstools/oops/helpers.py 2012-10-17 22:06:22 +0000 | |||
90 | @@ -101,7 +101,7 @@ | |||
91 | 101 | def parsedate(date_string): | 101 | def parsedate(date_string): |
92 | 102 | """Return a naive date time object for the given string. | 102 | """Return a naive date time object for the given string. |
93 | 103 | 103 | ||
95 | 104 | This function ignores subsecond accuracy and the timezone. | 104 | This function ignores subsecond accuracy and the timezone. |
96 | 105 | May be used to parse a ISO 8601 date string. | 105 | May be used to parse a ISO 8601 date string. |
97 | 106 | 106 | ||
98 | 107 | >>> ds = '2009-01-02' | 107 | >>> ds = '2009-01-02' |
99 | @@ -132,7 +132,7 @@ | |||
100 | 132 | """Generate a summary with the given prefix and dates.""" | 132 | """Generate a summary with the given prefix and dates.""" |
101 | 133 | script_cmd = '%s/analyse_error_reports' % settings.BIN_DIR | 133 | script_cmd = '%s/analyse_error_reports' % settings.BIN_DIR |
102 | 134 | 134 | ||
104 | 135 | args = [script_cmd,] | 135 | args = [script_cmd] |
105 | 136 | for prefix in prefixes: | 136 | for prefix in prefixes: |
106 | 137 | args.append("-p%s" % prefix) | 137 | args.append("-p%s" % prefix) |
107 | 138 | for d in (from_date, to_date): | 138 | for d in (from_date, to_date): |
108 | @@ -161,7 +161,7 @@ | |||
109 | 161 | pos = text.find(pattern) | 161 | pos = text.find(pattern) |
110 | 162 | if pos < 0: | 162 | if pos < 0: |
111 | 163 | return "Pattern: '%s' not found in %s" % (pattern, text) | 163 | return "Pattern: '%s' not found in %s" % (pattern, text) |
113 | 164 | return text[pos+len(pattern):].split("===")[0].strip() | 164 | return text[pos + len(pattern):].split("===")[0].strip() |
114 | 165 | 165 | ||
115 | 166 | 166 | ||
116 | 167 | def reset_database(): | 167 | def reset_database(): |
117 | @@ -209,6 +209,7 @@ | |||
118 | 209 | 209 | ||
119 | 210 | _cached_launchpadlib = None | 210 | _cached_launchpadlib = None |
120 | 211 | 211 | ||
121 | 212 | |||
122 | 212 | def get_launchpadlib(): | 213 | def get_launchpadlib(): |
123 | 213 | """Return a launchpadlib instance from the cache. | 214 | """Return a launchpadlib instance from the cache. |
124 | 214 | 215 | ||
125 | @@ -225,6 +226,7 @@ | |||
126 | 225 | 226 | ||
127 | 226 | _today = None | 227 | _today = None |
128 | 227 | 228 | ||
129 | 229 | |||
130 | 228 | def today(): | 230 | def today(): |
131 | 229 | """Return a date time object from the cache. | 231 | """Return a date time object from the cache. |
132 | 230 | 232 | ||
133 | @@ -238,6 +240,6 @@ | |||
134 | 238 | 240 | ||
135 | 239 | def load_prefixes(report_name): | 241 | def load_prefixes(report_name): |
136 | 240 | """Return a list of Prefix objects grouped by Report.""" | 242 | """Return a list of Prefix objects grouped by Report.""" |
138 | 241 | from oopstools.oops.models import Report | 243 | from oopstools.oops.models import Report |
139 | 242 | report = Report.objects.get(name=report_name) | 244 | report = Report.objects.get(name=report_name) |
140 | 243 | return [prefix.value for prefix in report.prefixes.all()] | 245 | return [prefix.value for prefix in report.prefixes.all()] |
141 | 244 | 246 | ||
142 | === modified file 'src/oopstools/oops/oopsstore.py' | |||
143 | --- src/oopstools/oops/oopsstore.py 2011-10-13 20:18:51 +0000 | |||
144 | +++ src/oopstools/oops/oopsstore.py 2012-10-17 22:06:22 +0000 | |||
145 | @@ -21,7 +21,7 @@ | |||
146 | 21 | import datetime | 21 | import datetime |
147 | 22 | import itertools | 22 | import itertools |
148 | 23 | 23 | ||
150 | 24 | from oopstools.oops.models import Oops, OopsReadError, oops_re | 24 | from oopstools.oops.models import Oops, OopsReadError, oops_re, Prefix |
151 | 25 | 25 | ||
152 | 26 | 26 | ||
153 | 27 | # the section of the OOPS ID before the instance identifier is the | 27 | # the section of the OOPS ID before the instance identifier is the |
154 | @@ -77,7 +77,7 @@ | |||
155 | 77 | # XXX matsubara: remove the if clause when refactoring this code | 77 | # XXX matsubara: remove the if clause when refactoring this code |
156 | 78 | # to use a single oops directory. | 78 | # to use a single oops directory. |
157 | 79 | if type(oopsdir) == str: | 79 | if type(oopsdir) == str: |
159 | 80 | oopsdir = [oopsdir,] | 80 | oopsdir = [oopsdir] |
160 | 81 | # XXX matsubara: find_dirs changes directories to do its job and | 81 | # XXX matsubara: find_dirs changes directories to do its job and |
161 | 82 | # before calling it, record the original directory the script is | 82 | # before calling it, record the original directory the script is |
162 | 83 | # in and after it finishes building | 83 | # in and after it finishes building |
163 | @@ -123,7 +123,7 @@ | |||
164 | 123 | dse = match.group('dse') | 123 | dse = match.group('dse') |
165 | 124 | oops = match.group('oopsprefix') + match.group('id') | 124 | oops = match.group('oopsprefix') + match.group('id') |
166 | 125 | if date: | 125 | if date: |
168 | 126 | pass # got enough data | 126 | pass # got enough data |
169 | 127 | elif dse: | 127 | elif dse: |
170 | 128 | # dse is the number of days since the epoch | 128 | # dse is the number of days since the epoch |
171 | 129 | day = epoch + datetime.timedelta(days=int(dse) - 1) | 129 | day = epoch + datetime.timedelta(days=int(dse) - 1) |
172 | @@ -145,7 +145,7 @@ | |||
173 | 145 | # between the OOPS filename and the OOPS id, that's why | 145 | # between the OOPS filename and the OOPS id, that's why |
174 | 146 | # there're two different regex to identify those. | 146 | # there're two different regex to identify those. |
175 | 147 | oops_prefix = filename.split(".")[1] | 147 | oops_prefix = filename.split(".")[1] |
177 | 148 | oops_prefix = re.sub("\d+$", "", oops_prefix) | 148 | oops_prefix = re.sub("\d+$", "", oops_prefix) |
178 | 149 | # Normalize oops prefix | 149 | # Normalize oops prefix |
179 | 150 | return oops_prefix.upper() | 150 | return oops_prefix.upper() |
180 | 151 | 151 | ||
181 | @@ -183,3 +183,14 @@ | |||
182 | 183 | return itertools.ifilter( | 183 | return itertools.ifilter( |
183 | 184 | lambda oops: startdatetime <= oops.date <= enddatetime, | 184 | lambda oops: startdatetime <= oops.date <= enddatetime, |
184 | 185 | oops_results) | 185 | oops_results) |
185 | 186 | |||
186 | 187 | def newest(self, prefix, limit=50): | ||
187 | 188 | """Get the newest oopses.""" | ||
188 | 189 | oops_list = list() | ||
189 | 190 | |||
190 | 191 | prefix = Prefix.objects.get(value__exact=prefix) | ||
191 | 192 | query = Oops.objects.filter( | ||
192 | 193 | prefix__exact=prefix).order_by('date')[:limit] | ||
193 | 194 | for oops in query: | ||
194 | 195 | oops_list.append(oops) | ||
195 | 196 | return oops_list | ||
196 | 186 | 197 | ||
197 | === added file 'src/oopstools/oops/templates/newest.html' | |||
198 | --- src/oopstools/oops/templates/newest.html 1970-01-01 00:00:00 +0000 | |||
199 | +++ src/oopstools/oops/templates/newest.html 2012-10-17 22:06:22 +0000 | |||
200 | @@ -0,0 +1,12 @@ | |||
201 | 1 | {% extends "base.html" %} | ||
202 | 2 | |||
203 | 3 | {% block title %}Newest OOPS Reports{% endblock %} | ||
204 | 4 | |||
205 | 5 | {% block summaries %} | ||
206 | 6 | |||
207 | 7 | <ul> | ||
208 | 8 | {% for oops in latest_oopses %} | ||
209 | 9 | <li><a href="/oops/?oopsid={{ oops }}">{{ oops }}</a></li> | ||
210 | 10 | {% endfor %} | ||
211 | 11 | </ul> | ||
212 | 12 | {% endblock %} | ||
213 | 0 | 13 | ||
214 | === modified file 'src/oopstools/oops/test/oopsstore.txt' | |||
215 | --- src/oopstools/oops/test/oopsstore.txt 2011-10-13 20:18:51 +0000 | |||
216 | +++ src/oopstools/oops/test/oopsstore.txt 2012-10-17 22:06:22 +0000 | |||
217 | @@ -119,3 +119,35 @@ | |||
218 | 119 | >>> [oops.oopsid for oops in | 119 | >>> [oops.oopsid for oops in |
219 | 120 | ... store.search(start_date, end_date, prefixes=['CCW'])] | 120 | ... store.search(start_date, end_date, prefixes=['CCW'])] |
220 | 121 | [] | 121 | [] |
221 | 122 | |||
222 | 123 | Show the newest oopses for each directory. | ||
223 | 124 | |||
224 | 125 | >>> newest_oopses = store.newest() | ||
225 | 126 | >>> len(newest_oopses) | ||
226 | 127 | 2 | ||
227 | 128 | |||
228 | 129 | >>> sorted(newest_oopses.values()) | ||
229 | 130 | [['01149-1925canistelubuntu0.oops'], ['61295.SMPM25']] | ||
230 | 131 | |||
231 | 132 | newest() can optionally limit the amount of oopses it returns. | ||
232 | 133 | |||
233 | 134 | >>> from datetime import date | ||
234 | 135 | >>> from django.conf import settings | ||
235 | 136 | >>> from oopstools.oops.helpers import create_fake_oops | ||
236 | 137 | >>> oops_dir_one = os.path.join( | ||
237 | 138 | ... settings.OOPSDIR[0], 'dir1', '2008-03-18') | ||
238 | 139 | >>> fake_oops_one = open(oops_dir_one + "/" + "68350.X1", "w") | ||
239 | 140 | >>> contents = create_fake_oops(date(2008, 3, 18)) | ||
240 | 141 | >>> fake_oops_one.write(contents) | ||
241 | 142 | >>> fake_oops_one.close() | ||
242 | 143 | >>> newest_oopses = store.newest() | ||
243 | 144 | >>> sorted(newest_oopses.values()) | ||
244 | 145 | [['01149-1925canistelubuntu0.oops'], ['68350.X1', '61295.SMPM25']] | ||
245 | 146 | |||
246 | 147 | When limiting to 1, we only get 1 oops from dir1 | ||
247 | 148 | |||
248 | 149 | >>> newest_oopses = store.newest(limit=1) | ||
249 | 150 | >>> sorted(newest_oopses.values()) | ||
250 | 151 | [['01149-1925canistelubuntu0.oops'], ['68350.X1']] | ||
251 | 152 | |||
252 | 153 | >>> os.remove(fake_oops_one.name) | ||
253 | 122 | 154 | ||
254 | === modified file 'src/oopstools/oops/test/test_runner.py' | |||
255 | --- src/oopstools/oops/test/test_runner.py 2011-10-13 20:18:51 +0000 | |||
256 | +++ src/oopstools/oops/test/test_runner.py 2012-10-17 22:06:22 +0000 | |||
257 | @@ -24,7 +24,8 @@ | |||
258 | 24 | from django.test.simple import DjangoTestSuiteRunner | 24 | from django.test.simple import DjangoTestSuiteRunner |
259 | 25 | 25 | ||
260 | 26 | DEFAULT_OPTIONS = ( | 26 | DEFAULT_OPTIONS = ( |
262 | 27 | doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS |doctest.REPORT_NDIFF) | 27 | doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | doctest.REPORT_NDIFF) |
263 | 28 | |||
264 | 28 | 29 | ||
265 | 29 | class CustomTestRunner(DjangoTestSuiteRunner): | 30 | class CustomTestRunner(DjangoTestSuiteRunner): |
266 | 30 | 31 | ||
267 | 31 | 32 | ||
268 | === modified file 'src/oopstools/oops/views.py' | |||
269 | --- src/oopstools/oops/views.py 2011-11-03 00:08:18 +0000 | |||
270 | +++ src/oopstools/oops/views.py 2012-10-17 22:06:22 +0000 | |||
271 | @@ -44,7 +44,7 @@ | |||
272 | 44 | oopsids.add(query_str.upper()) | 44 | oopsids.add(query_str.upper()) |
273 | 45 | oopsids.add(query_str.lower()) | 45 | oopsids.add(query_str.lower()) |
274 | 46 | if not query_str.upper().startswith('OOPS-'): | 46 | if not query_str.upper().startswith('OOPS-'): |
276 | 47 | oopsids.update(map(lambda x:'OOPS-'+x, oopsids)) | 47 | oopsids.update(map(lambda x: 'OOPS-' + x, oopsids)) |
277 | 48 | 48 | ||
278 | 49 | # Check each ID in both the database and filesystem | 49 | # Check each ID in both the database and filesystem |
279 | 50 | # (should maybe have an API option for this, instead of doing it manually?) | 50 | # (should maybe have an API option for this, instead of doing it manually?) |
280 | @@ -151,4 +151,9 @@ | |||
281 | 151 | return render_to_response("summary.html", dictionary=data) | 151 | return render_to_response("summary.html", dictionary=data) |
282 | 152 | 152 | ||
283 | 153 | 153 | ||
285 | 154 | 154 | def newest(request): | |
286 | 155 | """Show the newest oopses for all projects.""" | ||
287 | 156 | store = OopsStore(settings.OOPSDIR) | ||
288 | 157 | latest_oopses = store.newest(prefix=request.GET.get('prefix')) | ||
289 | 158 | data = {'latest_oopses': latest_oopses} | ||
290 | 159 | return render_to_response("newest.html", dictionary=data) | ||
291 | 155 | 160 | ||
292 | === modified file 'src/oopstools/urls.py' | |||
293 | --- src/oopstools/urls.py 2011-10-13 20:18:51 +0000 | |||
294 | +++ src/oopstools/urls.py 2012-10-17 22:06:22 +0000 | |||
295 | @@ -24,7 +24,7 @@ | |||
296 | 24 | # Example: | 24 | # Example: |
297 | 25 | # (r'^oopstools/', include('oopstools.foo.urls')), | 25 | # (r'^oopstools/', include('oopstools.foo.urls')), |
298 | 26 | 26 | ||
300 | 27 | # Uncomment the admin/doc line below and add 'django.contrib.admindocs' | 27 | # Uncomment the admin/doc line below and add 'django.contrib.admindocs' |
301 | 28 | # to INSTALLED_APPS to enable admin documentation: | 28 | # to INSTALLED_APPS to enable admin documentation: |
302 | 29 | # (r'^admin/doc/', include('django.contrib.admindocs.urls')), | 29 | # (r'^admin/doc/', include('django.contrib.admindocs.urls')), |
303 | 30 | 30 | ||
304 | @@ -39,4 +39,5 @@ | |||
305 | 39 | (r'^oops[.py]*/meta$', 'oopstools.oops.views.meta'), | 39 | (r'^oops[.py]*/meta$', 'oopstools.oops.views.meta'), |
306 | 40 | (r'^oops/static/(?P<path>.*)$', 'django.views.static.serve', | 40 | (r'^oops/static/(?P<path>.*)$', 'django.views.static.serve', |
307 | 41 | {'document_root': settings.STATIC_DOC_ROOT}), | 41 | {'document_root': settings.STATIC_DOC_ROOT}), |
308 | 42 | (r'^newest/$', 'oopstools.oops.views.newest') | ||
309 | 42 | ) | 43 | ) |
The whitespace fixes are appreciated. One class of whitespace change
looks like a search/replace error though: lines 8 and 103 of the diff
have one-element lists that included a trailing comma. That was
somewhat odd, but the added space after the comma is even oddder. I
suggest removing both the comma and the space.
I don't know much about the on-disk layout for OOPS storage, but I would
be a little concerned that non-oops files and directories would confuse
the "newest" method. If this trait is common to the rest of the
OOPS-tools code, then my concern can be ignored.
The test for the new "newest" function (line 239 of the diff) contains a
dependency on the order of elements returned from a call the "values"
method of a dict. That order can vary, so the returned list needs to be
sorted in the doctest in order to avoid spurious test failures.
The above applies to the test on line 246 of the diff as well.
Once these small issues are fixed, the code is good to land.