Merge lp:~dylanmccall/harvest/opportunity-edit into lp:harvest
- opportunity-edit
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 235 |
Proposed branch: | lp:~dylanmccall/harvest/opportunity-edit |
Merge into: | lp:harvest |
Diff against target: |
1060 lines (+437/-183) 26 files modified
EXTERNALS (+4/-0) harvest/common/templatetags/humanize_timediff.py (+51/-0) harvest/common/templatetags/list_to_columns.py (+0/-30) harvest/media/css/style.css (+113/-30) harvest/media/js/harvest.js (+10/-8) harvest/opportunities/admin.py (+8/-2) harvest/opportunities/forms.py (+6/-2) harvest/opportunities/models.py (+19/-6) harvest/opportunities/urls.py (+24/-8) harvest/opportunities/views.py (+91/-60) harvest/templates/base.html (+2/-2) harvest/templates/one_column.html (+9/-0) harvest/templates/opportunities/filter.html (+1/-1) harvest/templates/opportunities/include/filter_results.html (+1/-1) harvest/templates/opportunities/include/opportunity.html (+2/-4) harvest/templates/opportunities/include/opportunity_details.html (+7/-0) harvest/templates/opportunities/include/opportunity_details_edit.html (+48/-0) harvest/templates/opportunities/include/opportunity_li.html (+3/-0) harvest/templates/opportunities/include/opportunity_notes_list.html (+9/-0) harvest/templates/opportunities/include/opportunity_outer_li.html (+3/-0) harvest/templates/opportunities/include/package_details.html (+1/-3) harvest/templates/opportunities/opportunity_edit.html (+11/-14) harvest/templates/opportunities/single_package.html (+5/-12) harvest/templates/opportunities/xhr/opportunity_edit.html (+3/-0) harvest/templates/opportunities/xhr/opportunity_li.html (+3/-0) harvest/templates/opportunities/xhr/opportunity_outer_li.html (+3/-0) |
To merge this branch: | bzr merge lp:~dylanmccall/harvest/opportunity-edit |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Holbach | Approve | ||
Review via email: mp+32544@code.launchpad.net |
Commit message
Description of the change
Improved the opportunity edit form and the style for some surrounding elements. Implemented the new Notes system (which is like comments, but shorter and simpler with a 250 character limit).
Screenshot: http://
This branch does not address how the form is opened or where it goes after submitting, which is still a problem when running with Javascript.
- 243. By Dylan McCall
-
Improved formatting for form errors and #messages.
Fixed some inconsistency with padding and margins around elements.
- 244. By Dylan McCall
-
Fixed wrapping for .opportunity-
details. edit > form > ul.opportunity- switches
Dylan McCall (dylanmccall) wrote : | # |
Daniel Holbach (dholbach) wrote : | # |
Good work. I like your code changes, but there's a few small glitches which we can address through follow-up bugs.
Daniel Holbach (dholbach) wrote : | # |
Nevermind, can't reproduce the issue I had with duplicated opportunity entries.
Preview Diff
1 | === modified file 'EXTERNALS' | |||
2 | --- EXTERNALS 2010-07-14 15:34:17 +0000 | |||
3 | +++ EXTERNALS 2010-08-13 02:04:41 +0000 | |||
4 | @@ -10,6 +10,10 @@ | |||
5 | 10 | By "metajack" | 10 | By "metajack" |
6 | 11 | <http://djangosnippets.org/snippets/797/> | 11 | <http://djangosnippets.org/snippets/797/> |
7 | 12 | 12 | ||
8 | 13 | ./harvest/common/templatetags/humanize_timediff.py | ||
9 | 14 | By "supsupmo" | ||
10 | 15 | <http://djangosnippets.org/snippets/412/> | ||
11 | 16 | |||
12 | 13 | ./harvest/media/css/reset.css | 17 | ./harvest/media/css/reset.css |
13 | 14 | By Eric Meyer | 18 | By Eric Meyer |
14 | 15 | <http://meyerweb.com/eric/tools/css/reset/> | 19 | <http://meyerweb.com/eric/tools/css/reset/> |
15 | 16 | 20 | ||
16 | === added file 'harvest/common/templatetags/humanize_timediff.py' | |||
17 | --- harvest/common/templatetags/humanize_timediff.py 1970-01-01 00:00:00 +0000 | |||
18 | +++ harvest/common/templatetags/humanize_timediff.py 2010-08-13 02:04:41 +0000 | |||
19 | @@ -0,0 +1,51 @@ | |||
20 | 1 | #Template filter to provide a string similar to the built in timesince | ||
21 | 2 | #filter, but slightly fuzzier. | ||
22 | 3 | #From <http://djangosnippets.org/snippets/412/> with localization added | ||
23 | 4 | |||
24 | 5 | from django.utils.translation import ungettext | ||
25 | 6 | from django import template | ||
26 | 7 | |||
27 | 8 | register = template.Library() | ||
28 | 9 | |||
29 | 10 | @register.filter | ||
30 | 11 | def humanize_timediff(timestamp = None): | ||
31 | 12 | """ | ||
32 | 13 | Returns a humanized string representing time difference | ||
33 | 14 | between now() and the input timestamp. | ||
34 | 15 | |||
35 | 16 | The output rounds up to days, hours, minutes, or seconds. | ||
36 | 17 | 4 days 5 hours returns '4 days' | ||
37 | 18 | 0 days 4 hours 3 minutes returns '4 hours', etc... | ||
38 | 19 | """ | ||
39 | 20 | import datetime | ||
40 | 21 | |||
41 | 22 | timeDiff = datetime.datetime.now() - timestamp | ||
42 | 23 | days = timeDiff.days | ||
43 | 24 | hours = timeDiff.seconds/3600 | ||
44 | 25 | minutes = timeDiff.seconds%3600/60 | ||
45 | 26 | seconds = timeDiff.seconds%3600%60 | ||
46 | 27 | |||
47 | 28 | str = "" | ||
48 | 29 | tStr = "" | ||
49 | 30 | if days > 0: | ||
50 | 31 | str = ungettext('%(days)d day', '%(days)d days', days) % { | ||
51 | 32 | 'days' : days | ||
52 | 33 | } | ||
53 | 34 | return str | ||
54 | 35 | elif hours > 0: | ||
55 | 36 | str = ungettext('%(hours)d hour', '%(hours)d hours', hours) % { | ||
56 | 37 | 'hours' : hours | ||
57 | 38 | } | ||
58 | 39 | return str | ||
59 | 40 | elif minutes > 0: | ||
60 | 41 | str = ungettext('%(minutes)d minute', '%(minutes)d minutes', minutes) % { | ||
61 | 42 | 'minutes' : minutes | ||
62 | 43 | } | ||
63 | 44 | return str | ||
64 | 45 | elif seconds > 0: | ||
65 | 46 | str = ungettext('%(seconds)d second', '%(seconds)d seconds', seconds) % { | ||
66 | 47 | 'seconds' : seconds | ||
67 | 48 | } | ||
68 | 49 | return str | ||
69 | 50 | else: | ||
70 | 51 | return None | ||
71 | 0 | 52 | ||
72 | === removed file 'harvest/common/templatetags/list_to_columns.py' | |||
73 | --- harvest/common/templatetags/list_to_columns.py 2009-07-08 11:31:32 +0000 | |||
74 | +++ harvest/common/templatetags/list_to_columns.py 1970-01-01 00:00:00 +0000 | |||
75 | @@ -1,30 +0,0 @@ | |||
76 | 1 | from django import template | ||
77 | 2 | |||
78 | 3 | register = template.Library() | ||
79 | 4 | |||
80 | 5 | class SplitListNode(template.Node): | ||
81 | 6 | def __init__(self, list, cols, new_list): | ||
82 | 7 | self.list = list | ||
83 | 8 | self.cols = cols | ||
84 | 9 | self.new_list = new_list | ||
85 | 10 | |||
86 | 11 | def split_seq(self, list, cols=2): | ||
87 | 12 | start = 0 | ||
88 | 13 | for i in xrange(cols): | ||
89 | 14 | stop = start + len(list[i::cols]) | ||
90 | 15 | yield list[start:stop] | ||
91 | 16 | start = stop | ||
92 | 17 | |||
93 | 18 | def render(self, context): | ||
94 | 19 | context[self.new_list] = self.split_seq(context[self.list], | ||
95 | 20 | int(self.cols)) | ||
96 | 21 | return '' | ||
97 | 22 | |||
98 | 23 | def list_to_columns(parser, token): | ||
99 | 24 | bits = token.contents.split() | ||
100 | 25 | if len(bits) != 5: | ||
101 | 26 | raise template.TemplateSyntaxError, "list_to_columns list as new_list 2" | ||
102 | 27 | if bits[2] != 'as': | ||
103 | 28 | raise template.TemplateSyntaxError, "second argument to the list_to_columns tag must be 'as'" | ||
104 | 29 | return SplitListNode(bits[1], bits[4], bits[3]) | ||
105 | 30 | list_to_columns = register.tag(list_to_columns) | ||
106 | 31 | 0 | ||
107 | === modified file 'harvest/media/css/style.css' | |||
108 | --- harvest/media/css/style.css 2010-08-05 17:20:01 +0000 | |||
109 | +++ harvest/media/css/style.css 2010-08-13 02:04:41 +0000 | |||
110 | @@ -43,6 +43,17 @@ | |||
111 | 43 | color:rgb(180,180,180); | 43 | color:rgb(180,180,180); |
112 | 44 | } | 44 | } |
113 | 45 | 45 | ||
114 | 46 | form label { | ||
115 | 47 | font-weight:bold; | ||
116 | 48 | } | ||
117 | 49 | form .error input, form .error textarea, form .error input#new_note { | ||
118 | 50 | border:solid 2px rgb(140,0,0); | ||
119 | 51 | } | ||
120 | 52 | form ul.errorlist { | ||
121 | 53 | font-size:smaller; | ||
122 | 54 | color:rgb(140,0,0); | ||
123 | 55 | } | ||
124 | 56 | |||
125 | 46 | 57 | ||
126 | 47 | 58 | ||
127 | 48 | #container { | 59 | #container { |
128 | @@ -59,34 +70,33 @@ | |||
129 | 59 | display:block; | 70 | display:block; |
130 | 60 | width:100%; | 71 | width:100%; |
131 | 61 | min-height:80px; | 72 | min-height:80px; |
132 | 62 | |||
133 | 63 | padding-top:12px; | 73 | padding-top:12px; |
134 | 64 | padding-bottom:12px; | ||
135 | 65 | } | 74 | } |
136 | 66 | #header a { | 75 | #header a { |
137 | 67 | color:rgb(0,0,0); | 76 | color:rgb(0,0,0); |
138 | 68 | } | 77 | } |
140 | 69 | #header #pagetitle { | 78 | #header #sitetitle { |
141 | 70 | display:inline; | 79 | display:inline; |
142 | 71 | position:static; | 80 | position:static; |
144 | 72 | margin-left:80px; | 81 | margin-left:40px; |
145 | 73 | margin-right:40px; | 82 | margin-right:40px; |
146 | 83 | margin-bottom:10px; | ||
147 | 74 | 84 | ||
148 | 75 | text-transform:lowercase; | 85 | text-transform:lowercase; |
149 | 76 | color:rgb(0,0,0); | 86 | color:rgb(0,0,0); |
150 | 77 | font-family:'Molengo', 'Bitstream Vera Sans', 'DejaVu Sans', sans-serif; | 87 | font-family:'Molengo', 'Bitstream Vera Sans', 'DejaVu Sans', sans-serif; |
151 | 78 | } | 88 | } |
153 | 79 | #header #pagetitle #sitelogo { | 89 | #header #sitetitle #sitelogo { |
154 | 80 | width:auto; /* line up with #sitename font-size */ | 90 | width:auto; /* line up with #sitename font-size */ |
155 | 81 | height:36px; | 91 | height:36px; |
156 | 82 | } | 92 | } |
158 | 83 | #header #pagetitle #sitename { | 93 | #header #sitetitle #sitename { |
159 | 84 | display:inline; | 94 | display:inline; |
160 | 85 | position:static; | 95 | position:static; |
161 | 86 | 96 | ||
162 | 87 | font-size:36px; | 97 | font-size:36px; |
163 | 88 | } | 98 | } |
165 | 89 | #header #pagetitle #releasename { | 99 | #header #sitetitle #releasename { |
166 | 90 | display:inline; | 100 | display:inline; |
167 | 91 | position:static; | 101 | position:static; |
168 | 92 | 102 | ||
169 | @@ -112,6 +122,30 @@ | |||
170 | 112 | padding-bottom:140px; | 122 | padding-bottom:140px; |
171 | 113 | } | 123 | } |
172 | 114 | 124 | ||
173 | 125 | #content > #messages { | ||
174 | 126 | display:block; | ||
175 | 127 | margin:10px 0px; | ||
176 | 128 | padding:5px 10px; | ||
177 | 129 | font-size:larger; | ||
178 | 130 | background-color:rgb(242,151,93); | ||
179 | 131 | color:rgb(255,255,255); | ||
180 | 132 | } | ||
181 | 133 | |||
182 | 134 | #content > .pagetitle { | ||
183 | 135 | margin:10px 0px; | ||
184 | 136 | max-width:30em; | ||
185 | 137 | padding:0px 10px; | ||
186 | 138 | letter-spacing:-1px; | ||
187 | 139 | font-size:18px; | ||
188 | 140 | line-height:1em; | ||
189 | 141 | } | ||
190 | 142 | |||
191 | 143 | #content > .main { | ||
192 | 144 | margin:10px 0px; | ||
193 | 145 | padding:0px 20px; | ||
194 | 146 | max-width:60em; | ||
195 | 147 | } | ||
196 | 148 | |||
197 | 115 | 149 | ||
198 | 116 | 150 | ||
199 | 117 | #filters { | 151 | #filters { |
200 | @@ -120,7 +154,7 @@ | |||
201 | 120 | float:left; | 154 | float:left; |
202 | 121 | min-width:160px; | 155 | min-width:160px; |
203 | 122 | 156 | ||
205 | 123 | padding:0px 20px 20px 20px; | 157 | padding:0px 20px; |
206 | 124 | font-size:12px; | 158 | font-size:12px; |
207 | 125 | line-height:1.4em; | 159 | line-height:1.4em; |
208 | 126 | color:rgb(51,51,51); | 160 | color:rgb(51,51,51); |
209 | @@ -207,9 +241,9 @@ | |||
210 | 207 | #filters .editfilter input { | 241 | #filters .editfilter input { |
211 | 208 | width:8em; | 242 | width:8em; |
212 | 209 | border:none; | 243 | border:none; |
213 | 244 | border-bottom:1px solid rgb(180,180,180); | ||
214 | 245 | |||
215 | 210 | padding:0px 2px 0px 2px; | 246 | padding:0px 2px 0px 2px; |
216 | 211 | border-bottom:1px solid rgb(180,180,180); | ||
217 | 212 | |||
218 | 213 | background-color:inherit; | 247 | background-color:inherit; |
219 | 214 | color:inherit; | 248 | color:inherit; |
220 | 215 | font:inherit; | 249 | font:inherit; |
221 | @@ -265,48 +299,43 @@ | |||
222 | 265 | -moz-border-radius:3px 3px 0px 0px; | 299 | -moz-border-radius:3px 3px 0px 0px; |
223 | 266 | border-radius:3px 3px 0px 0px; /* lines up with li.sourcepackage's border */ | 300 | border-radius:3px 3px 0px 0px; /* lines up with li.sourcepackage's border */ |
224 | 267 | } | 301 | } |
226 | 268 | .sourcepackage > a.sourcepackage-header { | 302 | a.sourcepackage-header { |
227 | 269 | color:inherit; | 303 | color:inherit; |
228 | 270 | text-decoration:none; | 304 | text-decoration:none; |
229 | 271 | } | 305 | } |
231 | 272 | .sourcepackage > .sourcepackage-header > .sourcepackage-name { | 306 | .sourcepackage-header > .sourcepackage-name { |
232 | 273 | display:inline; | 307 | display:inline; |
233 | 274 | margin-right:10px; | 308 | margin-right:10px; |
234 | 275 | color:rgb(0,0,0); | 309 | color:rgb(0,0,0); |
235 | 276 | font-size:16px; | 310 | font-size:16px; |
236 | 277 | } | 311 | } |
238 | 278 | .sourcepackage > .sourcepackage-header > .status { | 312 | .sourcepackage-header > .status { |
239 | 279 | display:none; /* harvest.js will override this where necessary */ | 313 | display:none; /* harvest.js will override this where necessary */ |
240 | 280 | } | 314 | } |
242 | 281 | .sourcepackage > .sourcepackage-header > .status img { | 315 | .sourcepackage-header > .status img { |
243 | 282 | width:16px; | 316 | width:16px; |
244 | 283 | height:16px; | 317 | height:16px; |
245 | 284 | } | 318 | } |
247 | 285 | .sourcepackage > .sourcepackage-header > .sourcepackage-summary { | 319 | .sourcepackage-header > .sourcepackage-summary { |
248 | 286 | display:inline; | 320 | display:inline; |
249 | 287 | float:right; | 321 | float:right; |
250 | 288 | text-align:right; | 322 | text-align:right; |
251 | 289 | font-size:10px; | 323 | font-size:10px; |
252 | 290 | } | 324 | } |
253 | 291 | /* prelights */ | 325 | /* prelights */ |
257 | 292 | .sourcepackage > a.sourcepackage-header:hover, | 326 | a.sourcepackage-header:hover, |
258 | 293 | .sourcepackage > a.sourcepackage-header:focus { | 327 | a.sourcepackage-header:focus { |
256 | 294 | /* use the opportunity count or experience measure here */ | ||
259 | 295 | background-color:rgb(240,255,243); | 328 | background-color:rgb(240,255,243); |
260 | 296 | } | 329 | } |
263 | 297 | .sourcepackage > a.sourcepackage-header:hover .sourcepackage-name, | 330 | a.sourcepackage-header:hover .sourcepackage-name, |
264 | 298 | .sourcepackage > a.sourcepackage-header:focus .sourcepackage-name { | 331 | a.sourcepackage-header:focus .sourcepackage-name { |
265 | 299 | text-decoration:underline; | 332 | text-decoration:underline; |
266 | 300 | } | 333 | } |
267 | 301 | 334 | ||
269 | 302 | .sourcepackage > .sourcepackage-details { | 335 | .sourcepackage-details { |
270 | 303 | display:block; | 336 | display:block; |
271 | 304 | padding-top:20px; | ||
272 | 305 | /* inner padding should be left:20px, right:20px and bottom:5px */ | 337 | /* inner padding should be left:20px, right:20px and bottom:5px */ |
273 | 306 | } | 338 | } |
274 | 307 | li.sourcepackage > .sourcepackage-details { | ||
275 | 308 | padding-top:0px; | ||
276 | 309 | } | ||
277 | 310 | 339 | ||
278 | 311 | /* collapsed state. (expanded is the default) */ | 340 | /* collapsed state. (expanded is the default) */ |
279 | 312 | li.sourcepackage.collapsed { | 341 | li.sourcepackage.collapsed { |
280 | @@ -334,7 +363,7 @@ | |||
281 | 334 | } | 363 | } |
282 | 335 | 364 | ||
283 | 336 | 365 | ||
285 | 337 | .sourcepackage-details > .opportunity-list { | 366 | li.sourcepackage > .sourcepackage-details > .opportunity-list { |
286 | 338 | padding-left:20px; | 367 | padding-left:20px; |
287 | 339 | margin-bottom:5px; | 368 | margin-bottom:5px; |
288 | 340 | } | 369 | } |
289 | @@ -394,17 +423,71 @@ | |||
290 | 394 | font-size:smaller; | 423 | font-size:smaller; |
291 | 395 | } | 424 | } |
292 | 396 | 425 | ||
294 | 397 | .opportunity > .opportunity-details { | 426 | li.opportunity > .opportunity-details { |
295 | 398 | display:block; | 427 | display:block; |
296 | 399 | margin-left:10px; | 428 | margin-left:10px; |
297 | 400 | } | 429 | } |
299 | 401 | .opportunity-details > .opportunity-status { | 430 | li.opportunity > .opportunity-details.edit { |
300 | 431 | background-color:rgb(255,255,240); | ||
301 | 432 | } | ||
302 | 433 | |||
303 | 434 | .opportunity-notes { | ||
304 | 402 | display:block; | 435 | display:block; |
306 | 403 | max-width:30em; | 436 | width:100%; |
307 | 437 | max-width:35em; | ||
308 | 404 | border-left:dashed 1px rgb(180,180,180); | 438 | border-left:dashed 1px rgb(180,180,180); |
310 | 405 | padding-left:5px; | 439 | padding:0px 5px 0px 5px; |
311 | 406 | font-size:12px; | 440 | font-size:12px; |
312 | 407 | } | 441 | } |
313 | 442 | .opportunity-notes input#new_note { | ||
314 | 443 | width:100%; | ||
315 | 444 | border:none; | ||
316 | 445 | border-bottom:1px solid rgb(180,180,180); | ||
317 | 446 | |||
318 | 447 | padding:2px 5px 2px 5px; | ||
319 | 448 | background-color:inherit; | ||
320 | 449 | color:inherit; | ||
321 | 450 | font:inherit; | ||
322 | 451 | } | ||
323 | 452 | .opportunity-notes > ul { | ||
324 | 453 | margin:0.5em 0; | ||
325 | 454 | max-height:10em; | ||
326 | 455 | } | ||
327 | 456 | .opportunity-notes > ul > li { | ||
328 | 457 | line-height:1.2em; | ||
329 | 458 | margin-bottom:0.5em; | ||
330 | 459 | } | ||
331 | 460 | .opportunity-notes > ul > li > .signature { | ||
332 | 461 | margin-left:1em; | ||
333 | 462 | vertical-align:sub; | ||
334 | 463 | color:rgb(180,180,180); | ||
335 | 464 | font-style:italic; | ||
336 | 465 | font-size:smaller; | ||
337 | 466 | } | ||
338 | 467 | |||
339 | 468 | |||
340 | 469 | |||
341 | 470 | .opportunity-details.edit > form > div.opportunity-notes { | ||
342 | 471 | float:left; | ||
343 | 472 | margin-right:4em; | ||
344 | 473 | margin-bottom:2em; | ||
345 | 474 | } | ||
346 | 475 | |||
347 | 476 | .opportunity-details.edit > form > ul.opportunity-switches { | ||
348 | 477 | float:left; | ||
349 | 478 | } | ||
350 | 479 | .opportunity-details.edit > form > ul.opportunity-switches > li { | ||
351 | 480 | white-space:nowrap; | ||
352 | 481 | margin-bottom:0.5em; | ||
353 | 482 | } | ||
354 | 483 | .opportunity-details.edit > form > ul.opportunity-switches > li.separate-top { | ||
355 | 484 | margin-bottom:2em; | ||
356 | 485 | } | ||
357 | 486 | |||
358 | 487 | .opportunity-details.edit > form > .actions { | ||
359 | 488 | clear:both; | ||
360 | 489 | padding-top:1em; | ||
361 | 490 | } | ||
362 | 408 | 491 | ||
363 | 409 | 492 | ||
364 | 410 | 493 | ||
365 | 411 | 494 | ||
366 | === modified file 'harvest/media/js/harvest.js' | |||
367 | --- harvest/media/js/harvest.js 2010-07-21 20:59:34 +0000 | |||
368 | +++ harvest/media/js/harvest.js 2010-08-13 02:04:41 +0000 | |||
369 | @@ -52,6 +52,10 @@ | |||
370 | 52 | var harvest = new function () { | 52 | var harvest = new function () { |
371 | 53 | 53 | ||
372 | 54 | /* Globals and constants */ | 54 | /* Globals and constants */ |
373 | 55 | var XHR_ROOT = '/opportunities/xhr/'; /* TODO: would be nice to get this from Django */ | ||
374 | 56 | var RESULTS_URL = XHR_ROOT + 'results/' | ||
375 | 57 | var OPPORTUNITIES_URL = XHR_ROOT + 'opportunity/'; | ||
376 | 58 | |||
377 | 55 | var MEDIA_PATH = '/media/' /* we should get this from Django somehow */ | 59 | var MEDIA_PATH = '/media/' /* we should get this from Django somehow */ |
378 | 56 | var MEDIA = { 'pkg' : { 'loading' : MEDIA_PATH+'img/pkg-status-loading.gif' }, | 60 | var MEDIA = { 'pkg' : { 'loading' : MEDIA_PATH+'img/pkg-status-loading.gif' }, |
379 | 57 | 'results' : { 'waiting' : MEDIA_PATH+'img/results-status-waiting.gif', | 61 | 'results' : { 'waiting' : MEDIA_PATH+'img/results-status-waiting.gif', |
380 | @@ -312,13 +316,13 @@ | |||
381 | 312 | } | 316 | } |
382 | 313 | 317 | ||
383 | 314 | 318 | ||
385 | 315 | function Package (dom_node, details_url, opps_query, expanded_cb, collapsed_cb) { | 319 | function Package (dom_node, opps_query, expanded_cb, collapsed_cb) { |
386 | 316 | /* Created for each package inside the #results element */ | 320 | /* Created for each package inside the #results element */ |
387 | 317 | 321 | ||
389 | 318 | /* gtksourceview gives an error box around "package", so we'll have to forego the convention */ | 322 | /* gtksourceview highlights "package" as an error, so we'll have to forego the convention */ |
390 | 319 | var pkg = this; | 323 | var pkg = this; |
391 | 320 | 324 | ||
393 | 321 | this.id = $(dom_node).attr('data-results-packageid'); | 325 | this.id = $(dom_node).attr('data-package-id'); |
394 | 322 | this.details = $(dom_node).children('.sourcepackage-details'); | 326 | this.details = $(dom_node).children('.sourcepackage-details'); |
395 | 323 | 327 | ||
396 | 324 | this.loading_xhr = null; | 328 | this.loading_xhr = null; |
397 | @@ -370,7 +374,7 @@ | |||
398 | 370 | 374 | ||
399 | 371 | this.loading_xhr = $.ajax({ | 375 | this.loading_xhr = $.ajax({ |
400 | 372 | type: "GET", | 376 | type: "GET", |
402 | 373 | url: details_url + this.id, dataType: 'html', | 377 | url: RESULTS_URL + this.id, dataType: 'html', |
403 | 374 | data: opps_query, | 378 | data: opps_query, |
404 | 375 | complete: function (xhr, status) { | 379 | complete: function (xhr, status) { |
405 | 376 | pkg.hide_status('loading'); | 380 | pkg.hide_status('loading'); |
406 | @@ -452,7 +456,6 @@ | |||
407 | 452 | this.future_query = {}; | 456 | this.future_query = {}; |
408 | 453 | 457 | ||
409 | 454 | this.container = $(dom_node); | 458 | this.container = $(dom_node); |
410 | 455 | this.query_url = $(dom_node).attr('data-results-url'); | ||
411 | 456 | this.output = $(dom_node).children('#results'); | 459 | this.output = $(dom_node).children('#results'); |
412 | 457 | this.status_bubble = $(dom_node).children('#results-status'); | 460 | this.status_bubble = $(dom_node).children('#results-status'); |
413 | 458 | 461 | ||
414 | @@ -488,10 +491,9 @@ | |||
415 | 488 | 491 | ||
416 | 489 | results_packages.each(function () { | 492 | results_packages.each(function () { |
417 | 490 | var dom_node = $(this); | 493 | var dom_node = $(this); |
418 | 491 | var details_url = results.query_url + '/'; | ||
419 | 492 | var opps_query = results.current_query; /* would be nice to only send properties starting with opp: */ | 494 | var opps_query = results.current_query; /* would be nice to only send properties starting with opp: */ |
420 | 493 | 495 | ||
422 | 494 | var pkg = new Package(dom_node, details_url, opps_query, | 496 | var pkg = new Package(dom_node, opps_query, |
423 | 495 | pkg_expanded_cb, pkg_collapsed_cb); | 497 | pkg_expanded_cb, pkg_collapsed_cb); |
424 | 496 | results.packages[pkg.id] = pkg; | 498 | results.packages[pkg.id] = pkg; |
425 | 497 | 499 | ||
426 | @@ -545,7 +547,7 @@ | |||
427 | 545 | 547 | ||
428 | 546 | this.loading_xhr = $.ajax({ | 548 | this.loading_xhr = $.ajax({ |
429 | 547 | type: "GET", | 549 | type: "GET", |
431 | 548 | url: this.query_url, dataType: 'html', | 550 | url: RESULTS_URL, dataType: 'html', |
432 | 549 | data: load_query, | 551 | data: load_query, |
433 | 550 | complete: function (xhr, status) { | 552 | complete: function (xhr, status) { |
434 | 551 | results.hide_status('loading'); | 553 | results.hide_status('loading'); |
435 | 552 | 554 | ||
436 | === modified file 'harvest/opportunities/admin.py' | |||
437 | --- harvest/opportunities/admin.py 2009-08-30 14:53:53 +0000 | |||
438 | +++ harvest/opportunities/admin.py 2010-08-13 02:04:41 +0000 | |||
439 | @@ -1,9 +1,15 @@ | |||
440 | 1 | from django.contrib import admin | 1 | from django.contrib import admin |
442 | 2 | from opportunities.models import Opportunity, OpportunityList, SourcePackage | 2 | from opportunities.models import Opportunity, Note, OpportunityList, SourcePackage |
443 | 3 | |||
444 | 4 | class NoteInline(admin.TabularInline): | ||
445 | 5 | model = Note | ||
446 | 6 | extra = 1 | ||
447 | 7 | raw_id_fields = ('author', ) | ||
448 | 3 | 8 | ||
449 | 4 | class OpportunityAdmin(admin.ModelAdmin): | 9 | class OpportunityAdmin(admin.ModelAdmin): |
451 | 5 | list_display = ('description', 'sourcepackage', 'opportunitylist', 'last_updated') | 10 | list_display = ('description', 'sourcepackage', 'opportunitylist', 'last_updated', 'note_set') |
452 | 6 | list_filter = ('opportunitylist', 'last_updated') | 11 | list_filter = ('opportunitylist', 'last_updated') |
453 | 12 | inlines = [NoteInline] | ||
454 | 7 | 13 | ||
455 | 8 | class OpportunityListAdmin(admin.ModelAdmin): | 14 | class OpportunityListAdmin(admin.ModelAdmin): |
456 | 9 | list_display = ('name', 'description', 'last_updated', 'active') | 15 | list_display = ('name', 'description', 'last_updated', 'active') |
457 | 10 | 16 | ||
458 | === modified file 'harvest/opportunities/forms.py' | |||
459 | --- harvest/opportunities/forms.py 2010-03-08 16:53:44 +0000 | |||
460 | +++ harvest/opportunities/forms.py 2010-08-13 02:04:41 +0000 | |||
461 | @@ -1,9 +1,13 @@ | |||
462 | 1 | from django import forms | 1 | from django import forms |
463 | 2 | |||
464 | 3 | from models import Opportunity | 2 | from models import Opportunity |
465 | 3 | from django.utils.translation import ugettext as _ | ||
466 | 4 | 4 | ||
467 | 5 | class OpportunityForm(forms.ModelForm): | 5 | class OpportunityForm(forms.ModelForm): |
468 | 6 | new_note = forms.CharField(label=_("Enter a new note here"), | ||
469 | 7 | required=False, | ||
470 | 8 | max_length=250) #from models.Note.text | ||
471 | 9 | |||
472 | 6 | class Meta: | 10 | class Meta: |
473 | 7 | model = Opportunity | 11 | model = Opportunity |
475 | 8 | exclude = ('description', 'url', 'last_updated', 'since', 'sourcepackage', 'opportunitylist', 'valid') | 12 | fields = ('experience', 'applied', 'reviewed') |
476 | 9 | 13 | ||
477 | 10 | 14 | ||
478 | === modified file 'harvest/opportunities/models.py' | |||
479 | --- harvest/opportunities/models.py 2010-08-05 17:20:01 +0000 | |||
480 | +++ harvest/opportunities/models.py 2010-08-13 02:04:41 +0000 | |||
481 | @@ -8,10 +8,10 @@ | |||
482 | 8 | PACKAGE_RED_THRESHOLD = 20 | 8 | PACKAGE_RED_THRESHOLD = 20 |
483 | 9 | 9 | ||
484 | 10 | EXPERIENCE_CHOICES = ( | 10 | EXPERIENCE_CHOICES = ( |
489 | 11 | (0, '---'), | 11 | (0, ""), |
490 | 12 | (1, 'Easy'), | 12 | (1, _("Easy")), |
491 | 13 | (2, 'Medium'), | 13 | (2, _("Medium")), |
492 | 14 | (3, 'Hard'), | 14 | (3, _("Hard")), |
493 | 15 | ) | 15 | ) |
494 | 16 | 16 | ||
495 | 17 | class PackageSet(models.Model): | 17 | class PackageSet(models.Model): |
496 | @@ -83,9 +83,8 @@ | |||
497 | 83 | applied = models.BooleanField(_("Applied"), default=False, blank=True) | 83 | applied = models.BooleanField(_("Applied"), default=False, blank=True) |
498 | 84 | sourcepackage = models.ForeignKey(SourcePackage) | 84 | sourcepackage = models.ForeignKey(SourcePackage) |
499 | 85 | opportunitylist = models.ForeignKey(OpportunityList) | 85 | opportunitylist = models.ForeignKey(OpportunityList) |
500 | 86 | comment = models.TextField(_("Comment"), blank=True) | ||
501 | 87 | valid = models.BooleanField(_("Valid"), default=True) | 86 | valid = models.BooleanField(_("Valid"), default=True) |
503 | 88 | experience = models.IntegerField(_("Required Experience"), choices=EXPERIENCE_CHOICES, default=0, | 87 | experience = models.IntegerField(_("Difficulty"), choices=EXPERIENCE_CHOICES, default=0, |
504 | 89 | help_text=_("Level of experience required for this specific opportunity.")) | 88 | help_text=_("Level of experience required for this specific opportunity.")) |
505 | 90 | 89 | ||
506 | 91 | class Meta: | 90 | class Meta: |
507 | @@ -108,6 +107,20 @@ | |||
508 | 108 | summary_list.append(_("Invalid")) | 107 | summary_list.append(_("Invalid")) |
509 | 109 | return summary_list | 108 | return summary_list |
510 | 110 | 109 | ||
511 | 110 | |||
512 | 111 | class Note(models.Model): | ||
513 | 112 | opportunity = models.ForeignKey(Opportunity) | ||
514 | 113 | date = models.DateTimeField(auto_now_add=True) | ||
515 | 114 | author = models.ForeignKey(User) | ||
516 | 115 | text = models.CharField(max_length=250) | ||
517 | 116 | |||
518 | 117 | def __unicode__(self): | ||
519 | 118 | text = self.text | ||
520 | 119 | if len(text) > 40: | ||
521 | 120 | text = text[:40]+u"\u2026" | ||
522 | 121 | return '%s: %s' % (self.author, text) | ||
523 | 122 | |||
524 | 123 | |||
525 | 111 | class ActionLogEntry(models.Model): | 124 | class ActionLogEntry(models.Model): |
526 | 112 | timestamp = models.DateTimeField(_("Timestamp")) | 125 | timestamp = models.DateTimeField(_("Timestamp")) |
527 | 113 | who = models.ForeignKey(User) | 126 | who = models.ForeignKey(User) |
528 | 114 | 127 | ||
529 | === modified file 'harvest/opportunities/urls.py' | |||
530 | --- harvest/opportunities/urls.py 2010-07-30 21:45:23 +0000 | |||
531 | +++ harvest/opportunities/urls.py 2010-08-13 02:04:41 +0000 | |||
532 | @@ -1,12 +1,28 @@ | |||
533 | 1 | from django.conf.urls.defaults import * | 1 | from django.conf.urls.defaults import * |
534 | 2 | 2 | ||
535 | 3 | urlpatterns = patterns('', | 3 | urlpatterns = patterns('', |
544 | 4 | url(r'^opportunity/(?P<opportunity_id>[\d]+)/edit$', 'opportunities.views.opportunity_edit', name='opportunity_edit'), | 4 | url(r'^$', |
545 | 5 | 5 | 'opportunities.views.filter', | |
546 | 6 | url(r'^filter$', 'opportunities.views.opportunities_filter', name='opportunities_filter'), | 6 | name='filter'), |
547 | 7 | 7 | ||
548 | 8 | url(r'^package/(?P<package_name>.+)', 'opportunities.views.single_package', name='single_package'), | 8 | url(r'^package/(?P<package_name>.+)/$', |
549 | 9 | 9 | 'opportunities.views.single_package', | |
550 | 10 | url(r'^xhr/results$', 'opportunities.views.opportunities_xhr_filter_results', name='opportunities_xhr_filter_results'), | 10 | name='single_package'), |
551 | 11 | url(r'^xhr/results/(?P<package_id>[\d]+)$', 'opportunities.views.opportunities_xhr_package_details', name='opportunities_xhr_package_details'), | 11 | |
552 | 12 | url(r'^opportunity/(?P<opportunity_id>[\d]+)/edit/$', | ||
553 | 13 | 'opportunities.views.opportunity_edit', | ||
554 | 14 | name='opportunity_edit'), | ||
555 | 15 | |||
556 | 16 | |||
557 | 17 | url(r'^xhr/results/$', | ||
558 | 18 | 'opportunities.views.xhr_filter_results'), | ||
559 | 19 | |||
560 | 20 | url(r'^xhr/results/(?P<package_id>[\d]+)/$', | ||
561 | 21 | 'opportunities.views.xhr_package_details'), | ||
562 | 22 | |||
563 | 23 | url(r'xhr/opportunity/(?P<opportunity_id>[\d]+)/$', | ||
564 | 24 | 'opportunities.views.xhr_opportunity_li'), | ||
565 | 25 | |||
566 | 26 | url(r'^xhr/opportunity/(?P<opportunity_id>[\d]+)/edit/$', | ||
567 | 27 | 'opportunities.views.xhr_opportunity_edit'), | ||
568 | 12 | ) | 28 | ) |
569 | 13 | 29 | ||
570 | === modified file 'harvest/opportunities/views.py' | |||
571 | --- harvest/opportunities/views.py 2010-07-30 21:45:23 +0000 | |||
572 | +++ harvest/opportunities/views.py 2010-08-13 02:04:41 +0000 | |||
573 | @@ -13,11 +13,67 @@ | |||
574 | 13 | import models | 13 | import models |
575 | 14 | import forms | 14 | import forms |
576 | 15 | 15 | ||
577 | 16 | from models import Opportunity, Note # for form processing | ||
578 | 17 | |||
579 | 16 | from filters import HarvestFilters | 18 | from filters import HarvestFilters |
580 | 17 | from wrappers import PackageWrapper, PackageListWrapper | 19 | from wrappers import PackageWrapper, PackageListWrapper |
581 | 18 | 20 | ||
582 | 21 | def _create_packages_list(request, filters_pkg, filters_opp): | ||
583 | 22 | # XXX: rockstar: Eep! We shouldn't be storing the None as a string. We | ||
584 | 23 | # should re-think this model relationship. | ||
585 | 24 | #sourcepackages_list = models.SourcePackage.objects.exclude(name='None') | ||
586 | 25 | |||
587 | 26 | sourcepackages_list = models.SourcePackage.objects.distinct() | ||
588 | 27 | sourcepackages_list = filters_pkg.process_queryset(sourcepackages_list) | ||
589 | 28 | |||
590 | 29 | #opportunities_list is filtered right away to only check opportunities belonging to selected packages | ||
591 | 30 | opportunities_list = models.Opportunity.objects.distinct().filter(sourcepackage__in=sourcepackages_list) | ||
592 | 31 | opportunities_list = filters_opp.process_queryset(opportunities_list) | ||
593 | 32 | |||
594 | 33 | #TODO: need to filter out opportunities with valid=False again | ||
595 | 34 | #TODO: would it be more efficient to group opportunities by their sourcepackages first, then run filters_opp.process_queryset() for each of those groups? | ||
596 | 35 | |||
597 | 36 | pkg_list_wrapper = PackageListWrapper(request, sourcepackages_list, opportunities_list) | ||
598 | 37 | |||
599 | 38 | return pkg_list_wrapper | ||
600 | 39 | |||
601 | 40 | |||
602 | 41 | |||
603 | 42 | def filter(request): | ||
604 | 43 | filters = HarvestFilters() | ||
605 | 44 | filters.update_from_http(request) | ||
606 | 45 | filters_pkg = filters.find('pkg') | ||
607 | 46 | filters_opp = filters.find('opp') | ||
608 | 47 | |||
609 | 48 | pkg_list_wrapper = _create_packages_list(request, filters_pkg, filters_opp) | ||
610 | 49 | |||
611 | 50 | context = { | ||
612 | 51 | 'packages_list': pkg_list_wrapper, | ||
613 | 52 | 'filters_pkg' : filters_pkg, | ||
614 | 53 | 'filters_opp' : filters_opp | ||
615 | 54 | } | ||
616 | 55 | |||
617 | 56 | return render( | ||
618 | 57 | 'opportunities/filter.html', | ||
619 | 58 | context, | ||
620 | 59 | context_instance=RequestContext(request)) | ||
621 | 60 | |||
622 | 61 | def single_package(request, package_name): | ||
623 | 62 | package = get_object_or_404(models.SourcePackage, name=package_name) | ||
624 | 63 | |||
625 | 64 | package_wrapper = PackageWrapper(request, package, visible_opportunities = package.opportunity_set) | ||
626 | 65 | |||
627 | 66 | context = { | ||
628 | 67 | 'package': package_wrapper | ||
629 | 68 | } | ||
630 | 69 | |||
631 | 70 | return render( | ||
632 | 71 | 'opportunities/single_package.html', | ||
633 | 72 | context, | ||
634 | 73 | context_instance=RequestContext(request)) | ||
635 | 74 | |||
636 | 19 | @login_required | 75 | @login_required |
638 | 20 | def opportunity_edit(request, opportunity_id): | 76 | def opportunity_edit(request, opportunity_id, template='opportunities/opportunity_edit.html'): |
639 | 21 | opportunity = get_object_or_404(models.Opportunity, id=opportunity_id) | 77 | opportunity = get_object_or_404(models.Opportunity, id=opportunity_id) |
640 | 22 | if request.method == "POST": | 78 | if request.method == "POST": |
641 | 23 | form = forms.OpportunityForm(data=request.POST, instance=opportunity) | 79 | form = forms.OpportunityForm(data=request.POST, instance=opportunity) |
642 | @@ -32,68 +88,41 @@ | |||
643 | 32 | models.log_action(request.user, | 88 | models.log_action(request.user, |
644 | 33 | action="changed experience to: %s" % form.cleaned_data["experience"], | 89 | action="changed experience to: %s" % form.cleaned_data["experience"], |
645 | 34 | opportunity=opportunity) | 90 | opportunity=opportunity) |
646 | 35 | if form.cleaned_data["comment"] != opportunity.comment: | ||
647 | 36 | if len(form.cleaned_data["comment"]) > 178: | ||
648 | 37 | action = "changed comment to: '%s'" % (form.cleaned_data["comment"][:177]+u"…") | ||
649 | 38 | else: | ||
650 | 39 | action = "changed comment to: '%s'" % form.cleaned_data["comment"] | ||
651 | 40 | models.log_action(request.user, action=action, | ||
652 | 41 | opportunity=opportunity) | ||
653 | 42 | form.save() | 91 | form.save() |
654 | 92 | |||
655 | 93 | #add a new note if input by the user | ||
656 | 94 | if form.cleaned_data["new_note"].strip() != '': | ||
657 | 95 | note_text = form.cleaned_data["new_note"] | ||
658 | 96 | |||
659 | 97 | note_log_text = note_text | ||
660 | 98 | if len(note_log_text) > 160: | ||
661 | 99 | note_log_text = note_log_text[:160]+u"\u2026" | ||
662 | 100 | models.log_action(request.user, | ||
663 | 101 | action="added note: '%s'" % note_log_text, | ||
664 | 102 | opportunity=opportunity) | ||
665 | 103 | |||
666 | 104 | note = Note(opportunity=opportunity, author=request.user, text=note_text) | ||
667 | 105 | note.save() | ||
668 | 106 | |||
669 | 43 | return HttpResponseRedirect(request.POST["next"]) | 107 | return HttpResponseRedirect(request.POST["next"]) |
670 | 44 | else: | 108 | else: |
671 | 45 | request.user.message_set.create(message=_('Opportunity details could not be saved.')) | 109 | request.user.message_set.create(message=_('Opportunity details could not be saved.')) |
672 | 46 | else: | 110 | else: |
673 | 47 | form = forms.OpportunityForm(instance=opportunity) | 111 | form = forms.OpportunityForm(instance=opportunity) |
675 | 48 | return render('opportunities/opportunity_edit.html', | 112 | |
676 | 113 | next = '/' | ||
677 | 114 | if 'next' in request.GET: next = request.GET['next'] | ||
678 | 115 | |||
679 | 116 | return render(template, | ||
680 | 49 | {'form': form, | 117 | {'form': form, |
681 | 50 | 'opportunity':opportunity, | 118 | 'opportunity':opportunity, |
682 | 51 | 'user':request.user, | 119 | 'user':request.user, |
684 | 52 | 'next': request.GET['next'], | 120 | 'next': next, |
685 | 53 | }, RequestContext(request)) | 121 | }, RequestContext(request)) |
686 | 54 | 122 | ||
687 | 55 | 123 | ||
729 | 56 | def _create_packages_list(request, filters_pkg, filters_opp): | 124 | |
730 | 57 | # XXX: rockstar: Eep! We shouldn't be storing the None as a string. We | 125 | def xhr_filter_results(request): |
690 | 58 | # should re-think this model relationship. | ||
691 | 59 | #sourcepackages_list = models.SourcePackage.objects.exclude(name='None') | ||
692 | 60 | |||
693 | 61 | sourcepackages_list = models.SourcePackage.objects.distinct() | ||
694 | 62 | sourcepackages_list = filters_pkg.process_queryset(sourcepackages_list) | ||
695 | 63 | |||
696 | 64 | #opportunities_list is filtered right away to only check opportunities belonging to selected packages | ||
697 | 65 | opportunities_list = models.Opportunity.objects.distinct().filter(sourcepackage__in=sourcepackages_list) | ||
698 | 66 | opportunities_list = filters_opp.process_queryset(opportunities_list) | ||
699 | 67 | |||
700 | 68 | #TODO: need to filter out opportunities with valid=False again | ||
701 | 69 | #TODO: would it be more efficient to group opportunities by their sourcepackages first, then run filters_opp.process_queryset() for each of those groups? | ||
702 | 70 | |||
703 | 71 | pkg_list_wrapper = PackageListWrapper(request, sourcepackages_list, opportunities_list) | ||
704 | 72 | |||
705 | 73 | return pkg_list_wrapper | ||
706 | 74 | |||
707 | 75 | |||
708 | 76 | def opportunities_filter(request): | ||
709 | 77 | filters = HarvestFilters() | ||
710 | 78 | filters.update_from_http(request) | ||
711 | 79 | filters_pkg = filters.find('pkg') | ||
712 | 80 | filters_opp = filters.find('opp') | ||
713 | 81 | |||
714 | 82 | pkg_list_wrapper = _create_packages_list(request, filters_pkg, filters_opp) | ||
715 | 83 | |||
716 | 84 | context = { | ||
717 | 85 | 'packages_list': pkg_list_wrapper, | ||
718 | 86 | 'filters_pkg' : filters_pkg, | ||
719 | 87 | 'filters_opp' : filters_opp | ||
720 | 88 | } | ||
721 | 89 | |||
722 | 90 | return render( | ||
723 | 91 | 'opportunities/filter.html', | ||
724 | 92 | context, | ||
725 | 93 | context_instance=RequestContext(request)) | ||
726 | 94 | |||
727 | 95 | |||
728 | 96 | def opportunities_xhr_filter_results(request): | ||
731 | 97 | filters = HarvestFilters() | 126 | filters = HarvestFilters() |
732 | 98 | filters.update_from_http(request) | 127 | filters.update_from_http(request) |
733 | 99 | 128 | ||
734 | @@ -108,12 +137,11 @@ | |||
735 | 108 | context, | 137 | context, |
736 | 109 | context_instance=RequestContext(request)) | 138 | context_instance=RequestContext(request)) |
737 | 110 | 139 | ||
740 | 111 | 140 | def xhr_package_details(request, package_id): | |
739 | 112 | def opportunities_xhr_package_details(request, package_id): | ||
741 | 113 | filters = HarvestFilters() | 141 | filters = HarvestFilters() |
742 | 114 | filters.update_from_http(request) | 142 | filters.update_from_http(request) |
743 | 115 | 143 | ||
745 | 116 | package = models.SourcePackage.objects.get(id=package_id) | 144 | package = get_object_or_404(models.SourcePackage, id=package_id) |
746 | 117 | 145 | ||
747 | 118 | opportunities_list = filters.find('opp').process_queryset(package.opportunity_set).all() | 146 | opportunities_list = filters.find('opp').process_queryset(package.opportunity_set).all() |
748 | 119 | 147 | ||
749 | @@ -128,18 +156,21 @@ | |||
750 | 128 | context, | 156 | context, |
751 | 129 | context_instance=RequestContext(request)) | 157 | context_instance=RequestContext(request)) |
752 | 130 | 158 | ||
757 | 131 | def single_package(request, package_name): | 159 | def xhr_opportunity_li(request, opportunity_id): |
758 | 132 | package = models.SourcePackage.objects.get(name=package_name) | 160 | opportunity = get_object_or_404(models.Opportunity, id=opportunity_id) |
755 | 133 | |||
756 | 134 | package_wrapper = PackageWrapper(request, package, visible_opportunities = package.opportunity_set) | ||
759 | 135 | 161 | ||
760 | 136 | context = { | 162 | context = { |
762 | 137 | 'package': package_wrapper | 163 | 'opportunity': opportunity |
763 | 138 | } | 164 | } |
764 | 139 | 165 | ||
765 | 140 | return render( | 166 | return render( |
767 | 141 | 'opportunities/single_package.html', | 167 | 'opportunities/xhr/opportunity_outer_li.html', |
768 | 142 | context, | 168 | context, |
769 | 143 | context_instance=RequestContext(request)) | 169 | context_instance=RequestContext(request)) |
770 | 144 | 170 | ||
771 | 171 | def xhr_opportunity_edit(request, opportunity_id): | ||
772 | 172 | return opportunity_edit( | ||
773 | 173 | request, | ||
774 | 174 | opportunity_id, | ||
775 | 175 | template='opportunities/xhr/opportunity_edit.html') | ||
776 | 145 | 176 | ||
777 | 146 | 177 | ||
778 | === modified file 'harvest/templates/base.html' | |||
779 | --- harvest/templates/base.html 2010-07-21 20:21:14 +0000 | |||
780 | +++ harvest/templates/base.html 2010-08-13 02:04:41 +0000 | |||
781 | @@ -26,7 +26,7 @@ | |||
782 | 26 | <div id="container"> | 26 | <div id="container"> |
783 | 27 | 27 | ||
784 | 28 | <div id="header"> | 28 | <div id="header"> |
786 | 29 | <span id="pagetitle"> | 29 | <span id="sitetitle"> |
787 | 30 | <img id="sitelogo" src="{{ MEDIA_URL }}img/logo_humanity-search-icon.png" /> | 30 | <img id="sitelogo" src="{{ MEDIA_URL }}img/logo_humanity-search-icon.png" /> |
788 | 31 | <h1 id="sitename">{% trans "Harvest" %}</h1> | 31 | <h1 id="sitename">{% trans "Harvest" %}</h1> |
789 | 32 | {% if harvest_version_name %}<span id="releasename">{{harvest_version_name}}</span>{% endif %} | 32 | {% if harvest_version_name %}<span id="releasename">{{harvest_version_name}}</span>{% endif %} |
790 | @@ -59,7 +59,7 @@ | |||
791 | 59 | 59 | ||
792 | 60 | <div id="footer"> | 60 | <div id="footer"> |
793 | 61 | <div id="footnav"><nav> | 61 | <div id="footnav"><nav> |
795 | 62 | <a class="title" href="{% url home %}" tabindex="2">{% trans "Harvest" %}</a> <a href="http://answers.launchpad.net/harvest" tabindex="2">{% trans "Help" %}</a> <a href="http://bugs.launchpad.net/harvest" tabindex="2">{% trans "Bugs" %}</a> <a href="http://launchpad.net/harvest" tabindex="2">{% trans "Code" %}</a> | 62 | <a class="title" href="{% url home %}" tabindex="2">{% trans "Harvest" %}</a> <a href="http://answers.launchpad.net/harvest" target="_blank" tabindex="2">{% trans "Help" %}</a> <a href="http://bugs.launchpad.net/harvest" target="_blank" tabindex="2">{% trans "Bugs" %}</a> <a href="http://launchpad.net/harvest" target="_blank" tabindex="2">{% trans "Code" %}</a> |
796 | 63 | </nav></div> | 63 | </nav></div> |
797 | 64 | 64 | ||
798 | 65 | <div id="smallprint" tabindex="3"> | 65 | <div id="smallprint" tabindex="3"> |
799 | 66 | 66 | ||
800 | === added file 'harvest/templates/one_column.html' | |||
801 | --- harvest/templates/one_column.html 1970-01-01 00:00:00 +0000 | |||
802 | +++ harvest/templates/one_column.html 2010-08-13 02:04:41 +0000 | |||
803 | @@ -0,0 +1,9 @@ | |||
804 | 1 | {% extends "base.html" %} | ||
805 | 2 | {% load i18n %} | ||
806 | 3 | |||
807 | 4 | {% block content %} | ||
808 | 5 | <h2 class="pagetitle">{% block pagetitle %}{% endblock %}</h2> | ||
809 | 6 | <div class="main"> | ||
810 | 7 | {% block content_main %}{% endblock %} | ||
811 | 8 | </div> | ||
812 | 9 | {% endblock %} | ||
813 | 0 | \ No newline at end of file | 10 | \ No newline at end of file |
814 | 1 | 11 | ||
815 | === modified file 'harvest/templates/opportunities/filter.html' | |||
816 | --- harvest/templates/opportunities/filter.html 2010-07-30 21:45:23 +0000 | |||
817 | +++ harvest/templates/opportunities/filter.html 2010-08-13 02:04:41 +0000 | |||
818 | @@ -10,7 +10,7 @@ | |||
819 | 10 | {{filters_opp.render}} | 10 | {{filters_opp.render}} |
820 | 11 | </div> | 11 | </div> |
821 | 12 | 12 | ||
823 | 13 | <div id="results-pane" data-results-url="{% url opportunities_xhr_filter_results %}"> | 13 | <div id="results-pane"> |
824 | 14 | <div id="results-status"></div> | 14 | <div id="results-status"></div> |
825 | 15 | <div id="results"> | 15 | <div id="results"> |
826 | 16 | {% include "opportunities/include/filter_results.html" %} | 16 | {% include "opportunities/include/filter_results.html" %} |
827 | 17 | 17 | ||
828 | === modified file 'harvest/templates/opportunities/include/filter_results.html' | |||
829 | --- harvest/templates/opportunities/include/filter_results.html 2010-07-30 08:23:24 +0000 | |||
830 | +++ harvest/templates/opportunities/include/filter_results.html 2010-08-13 02:04:41 +0000 | |||
831 | @@ -3,7 +3,7 @@ | |||
832 | 3 | {% if packages_list %} | 3 | {% if packages_list %} |
833 | 4 | <ul> | 4 | <ul> |
834 | 5 | {% for package in packages_list.get_visible_packages %} | 5 | {% for package in packages_list.get_visible_packages %} |
836 | 6 | <li data-results-packageid="{{ package.real.id }}" class="sourcepackage {% if package.expanded %}expanded{% else %}collapsed{% endif %}"> | 6 | <li data-package-id="{{ package.real.id }}" class="sourcepackage {% if package.expanded %}expanded{% else %}collapsed{% endif %}"> |
837 | 7 | <a class="sourcepackage-header" href="{{ package.get_expand_toggle_url }}"> | 7 | <a class="sourcepackage-header" href="{{ package.get_expand_toggle_url }}"> |
838 | 8 | <h2 class="sourcepackage-name">{{ package.real.name }}</h2> | 8 | <h2 class="sourcepackage-name">{{ package.real.name }}</h2> |
839 | 9 | <span class="status"></span> | 9 | <span class="status"></span> |
840 | 10 | 10 | ||
841 | === modified file 'harvest/templates/opportunities/include/opportunity.html' | |||
842 | --- harvest/templates/opportunities/include/opportunity.html 2010-07-30 21:45:23 +0000 | |||
843 | +++ harvest/templates/opportunities/include/opportunity.html 2010-08-13 02:04:41 +0000 | |||
844 | @@ -3,16 +3,14 @@ | |||
845 | 3 | <div class="opportunity-header"> | 3 | <div class="opportunity-header"> |
846 | 4 | <a href="{{opportunity.url}}" class="opportunity-description" target="_blank">{{ opportunity.description }}</a> | 4 | <a href="{{opportunity.url}}" class="opportunity-description" target="_blank">{{ opportunity.description }}</a> |
847 | 5 | {% if user.is_authenticated %} | 5 | {% if user.is_authenticated %} |
849 | 6 | <a href="{% url opportunity_edit opportunity.id %}?next={{request.get_full_path}}" class="opportunity-edit-button">edit</a> | 6 | <a href="{% url opportunity_edit opportunity.id %}?next={{request.get_full_path}}" target="_blank" class="opportunity-edit-button">edit</a> |
850 | 7 | {% endif %} | 7 | {% endif %} |
851 | 8 | <span class="opportunity-summary"> | 8 | <span class="opportunity-summary"> |
852 | 9 | {{ opportunity.summary|join:', ' }} | 9 | {{ opportunity.summary|join:', ' }} |
853 | 10 | </span> | 10 | </span> |
854 | 11 | </div> | 11 | </div> |
855 | 12 | <div class="opportunity-details"> | 12 | <div class="opportunity-details"> |
859 | 13 | {% if opportunity.comment %} | 13 | {% include "opportunities/include/opportunity_details.html" %} |
857 | 14 | <span class="opportunity-status">{{ opportunity.comment }}</span> | ||
858 | 15 | {% endif %} | ||
860 | 16 | </div> | 14 | </div> |
861 | 17 | <div class="bottom"></div> | 15 | <div class="bottom"></div> |
862 | 18 | 16 | ||
863 | 19 | 17 | ||
864 | === added file 'harvest/templates/opportunities/include/opportunity_details.html' | |||
865 | --- harvest/templates/opportunities/include/opportunity_details.html 1970-01-01 00:00:00 +0000 | |||
866 | +++ harvest/templates/opportunities/include/opportunity_details.html 2010-08-13 02:04:41 +0000 | |||
867 | @@ -0,0 +1,7 @@ | |||
868 | 1 | {% ifnotequal opportunity.note_set.count 0 %} | ||
869 | 2 | <div class="opportunity-notes"> | ||
870 | 3 | <ul> | ||
871 | 4 | {% include "opportunities/include/opportunity_notes_list.html" %} | ||
872 | 5 | </ul> | ||
873 | 6 | </div> | ||
874 | 7 | {% endifnotequal %} | ||
875 | 0 | \ No newline at end of file | 8 | \ No newline at end of file |
876 | 1 | 9 | ||
877 | === added file 'harvest/templates/opportunities/include/opportunity_details_edit.html' | |||
878 | --- harvest/templates/opportunities/include/opportunity_details_edit.html 1970-01-01 00:00:00 +0000 | |||
879 | +++ harvest/templates/opportunities/include/opportunity_details_edit.html 2010-08-13 02:04:41 +0000 | |||
880 | @@ -0,0 +1,48 @@ | |||
881 | 1 | {% load i18n %} | ||
882 | 2 | |||
883 | 3 | <form action="{{ request.path_info }}" method="POST"> | ||
884 | 4 | <div class="opportunity-notes"> | ||
885 | 5 | {% with form.new_note as field %} | ||
886 | 6 | <span class="separate-top {% if field.required %}required{% endif %} {% if field.errors %}error{% endif %}"> | ||
887 | 7 | {{field.errors}} | ||
888 | 8 | <input type="text" name="new_note" id="new_note" placeholder="{{field.label}}" {% if form.new_note.data %}value="{{form.new_note.data}}"{% endif %} /> | ||
889 | 9 | {% endwith %} | ||
890 | 10 | </span> | ||
891 | 11 | <ul> | ||
892 | 12 | {% include "opportunities/include/opportunity_notes_list.html" %} | ||
893 | 13 | </ul> | ||
894 | 14 | <div class="bottom"></div> | ||
895 | 15 | </div> | ||
896 | 16 | |||
897 | 17 | <ul class="opportunity-switches"> | ||
898 | 18 | {% with form.experience as field %} | ||
899 | 19 | <li class="separate-top {% if field.required %}required{% endif %} {% if field.errors %}error{% endif %}"> | ||
900 | 20 | {{field.errors}} | ||
901 | 21 | {{field}} | ||
902 | 22 | {{field.label_tag}} | ||
903 | 23 | </li> | ||
904 | 24 | {% endwith %} | ||
905 | 25 | |||
906 | 26 | {% with form.applied as field %} | ||
907 | 27 | <li class="{% if field.required %}required{% endif %} {% if field.errors %}error{% endif %}"> | ||
908 | 28 | {{field.errors}} | ||
909 | 29 | {{field}} | ||
910 | 30 | {{field.label_tag}} | ||
911 | 31 | </li> | ||
912 | 32 | {% endwith %} | ||
913 | 33 | |||
914 | 34 | {% with form.reviewed as field %} | ||
915 | 35 | <li class="{% if field.required %}required{% endif %} {% if field.errors %}error{% endif %}"> | ||
916 | 36 | {{field.errors}} | ||
917 | 37 | {{field}} | ||
918 | 38 | {{field.label_tag}} | ||
919 | 39 | </li> | ||
920 | 40 | {% endwith %} | ||
921 | 41 | </ul> | ||
922 | 42 | |||
923 | 43 | <div class="actions"> | ||
924 | 44 | <input type="hidden" name="next" value="{{ next }}" /> | ||
925 | 45 | <input type="submit" value="{% trans 'Apply changes' %}" /> | ||
926 | 46 | <a href="{{next}}" rel="deactivate"><button>Cancel</button></a> | ||
927 | 47 | </div> | ||
928 | 48 | </form> | ||
929 | 0 | 49 | ||
930 | === added file 'harvest/templates/opportunities/include/opportunity_li.html' | |||
931 | --- harvest/templates/opportunities/include/opportunity_li.html 1970-01-01 00:00:00 +0000 | |||
932 | +++ harvest/templates/opportunities/include/opportunity_li.html 2010-08-13 02:04:41 +0000 | |||
933 | @@ -0,0 +1,3 @@ | |||
934 | 1 | <li class="opportunity" data-opportunity-experience="{{opportunity.experience}}" {% if opportunity.reviewed %}data-opportunity-irrelevant{% endif %} {% if opportunity.applied %}data-opportunity-applied{% endif %}> | ||
935 | 2 | {% include "opportunities/include/opportunity.html" %} | ||
936 | 3 | </li> | ||
937 | 0 | 4 | ||
938 | === added file 'harvest/templates/opportunities/include/opportunity_notes_list.html' | |||
939 | --- harvest/templates/opportunities/include/opportunity_notes_list.html 1970-01-01 00:00:00 +0000 | |||
940 | +++ harvest/templates/opportunities/include/opportunity_notes_list.html 2010-08-13 02:04:41 +0000 | |||
941 | @@ -0,0 +1,9 @@ | |||
942 | 1 | {% load i18n %} | ||
943 | 2 | {% load humanize_timediff %} | ||
944 | 3 | |||
945 | 4 | {% for note in opportunity.note_set.all|dictsortreversed:"date" %} | ||
946 | 5 | <li> | ||
947 | 6 | {{ note.text }} | ||
948 | 7 | <span class="signature">{% blocktrans with note.date|humanize_timediff as timesince and note.author as author %}{{author}}, {{timesince}} ago{% endblocktrans %}</span> | ||
949 | 8 | </li> | ||
950 | 9 | {% endfor %} | ||
951 | 0 | 10 | ||
952 | === added file 'harvest/templates/opportunities/include/opportunity_outer_li.html' | |||
953 | --- harvest/templates/opportunities/include/opportunity_outer_li.html 1970-01-01 00:00:00 +0000 | |||
954 | +++ harvest/templates/opportunities/include/opportunity_outer_li.html 2010-08-13 02:04:41 +0000 | |||
955 | @@ -0,0 +1,3 @@ | |||
956 | 1 | <li class="opportunity" data-opportunity-id="{{opportunity.id}}" data-opportunity-experience="{{opportunity.experience}}" {% if opportunity.reviewed %}data-opportunity-irrelevant{% endif %} {% if opportunity.applied %}data-opportunity-applied{% endif %}> | ||
957 | 2 | {% include "opportunities/include/opportunity.html" %} | ||
958 | 3 | </li> | ||
959 | 0 | 4 | ||
960 | === modified file 'harvest/templates/opportunities/include/package_details.html' | |||
961 | --- harvest/templates/opportunities/include/package_details.html 2010-07-31 20:22:58 +0000 | |||
962 | +++ harvest/templates/opportunities/include/package_details.html 2010-08-13 02:04:41 +0000 | |||
963 | @@ -11,9 +11,7 @@ | |||
964 | 11 | <ul> | 11 | <ul> |
965 | 12 | {# FIXME: use |dictsort:'experience'|dictsort:'description' here (see comment in wrappers.py) #} | 12 | {# FIXME: use |dictsort:'experience'|dictsort:'description' here (see comment in wrappers.py) #} |
966 | 13 | {% for opportunity in opplist.list %} | 13 | {% for opportunity in opplist.list %} |
970 | 14 | <li class="opportunity" data-opportunity-experience="{{opportunity.experience}}" {% if opportunity.reviewed %}data-opportunity-irrelevant{% endif %} {% if opportunity.applied %}data-opportunity-applied{% endif %}> | 14 | {% include "opportunities/include/opportunity_outer_li.html" %} |
968 | 15 | {% include "opportunities/include/opportunity.html" %} | ||
969 | 16 | </li> | ||
971 | 17 | {% endfor %} | 15 | {% endfor %} |
972 | 18 | </ul> | 16 | </ul> |
973 | 19 | </div> | 17 | </div> |
974 | 20 | 18 | ||
975 | === modified file 'harvest/templates/opportunities/opportunity_edit.html' | |||
976 | --- harvest/templates/opportunities/opportunity_edit.html 2010-07-30 08:23:24 +0000 | |||
977 | +++ harvest/templates/opportunities/opportunity_edit.html 2010-08-13 02:04:41 +0000 | |||
978 | @@ -1,17 +1,14 @@ | |||
980 | 1 | {% extends "base.html" %} | 1 | {% extends "one_column.html" %} |
981 | 2 | {% load i18n %} | 2 | {% load i18n %} |
982 | 3 | 3 | ||
996 | 4 | {% block content %} | 4 | {% block title %}{{ block.super }}: {{ opportunity.description }}{% endblock %} |
997 | 5 | <div class="opportunity"> | 5 | |
998 | 6 | {% include "opportunities/include/opportunity.html" %} | 6 | {% block pagetitle %} |
999 | 7 | </div> | 7 | Opportunity <a href="{{opportunity.url}}" class="opportunity-description" target="_blank">{{ opportunity.description }}</a> in {% with opportunity.sourcepackage.name as pkgname %}<a href="{% url single_package pkgname %}">{{ pkgname }}</a>{% endwith %} |
1000 | 8 | <div id="form"><form action="{{ request.path_info }}" method="POST"> | 8 | {% endblock %} |
1001 | 9 | {% if form.errors %} | 9 | |
1002 | 10 | <p style="color: red;">{% trans "Please correct the error" %} {{ form.errors|pluralize }} below.</p> | 10 | {% block content_main %} |
1003 | 11 | {% endif %} | 11 | <div class="opportunity-details edit"> |
1004 | 12 | <table>{{ form.as_table }}</table> | 12 | {% include "opportunities/include/opportunity_details_edit.html" %} |
1005 | 13 | <td><input type="hidden" name="next" value="{{ next }}" /> </td> | 13 | </div> |
993 | 14 | <td><input type="submit" value='{% trans "Update Information Now!" %}' /></td> | ||
994 | 15 | <td><a href="{{next}}" rel="deactivate"><button>Cancel</button></a></td> | ||
995 | 16 | </form></div> | ||
1006 | 17 | {% endblock %} | 14 | {% endblock %} |
1007 | 18 | 15 | ||
1008 | === modified file 'harvest/templates/opportunities/single_package.html' | |||
1009 | --- harvest/templates/opportunities/single_package.html 2010-07-30 21:45:23 +0000 | |||
1010 | +++ harvest/templates/opportunities/single_package.html 2010-08-13 02:04:41 +0000 | |||
1011 | @@ -1,20 +1,13 @@ | |||
1013 | 1 | {% extends "base.html" %} | 1 | {% extends "one_column.html" %} |
1014 | 2 | {% load i18n %} | 2 | {% load i18n %} |
1015 | 3 | 3 | ||
1016 | 4 | {% block title %}{{ block.super }}: {{ package.real.name }}{% endblock %} | 4 | {% block title %}{{ block.super }}: {{ package.real.name }}{% endblock %} |
1017 | 5 | 5 | ||
1019 | 6 | {% block content %} | 6 | {% block pagetitle %}{{ package.real.name }}{% endblock %} |
1020 | 7 | 7 | ||
1030 | 8 | <div class="sourcepackage"> | 8 | {% block content_main %} |
1031 | 9 | <div class="sourcepackage-header"> | 9 | <div class="sourcepackage-details"> |
1032 | 10 | <h2 class="sourcepackage-name">{{ package.real.name }}</h2> | 10 | {% include "opportunities/include/package_details.html" %} |
1024 | 11 | {% comment %}<span class="sourcepackage-summary"></span>{% endcomment %} | ||
1025 | 12 | <div class="bottom"></div> | ||
1026 | 13 | </div> | ||
1027 | 14 | <div class="sourcepackage-details"> | ||
1028 | 15 | {% include "opportunities/include/package_details.html" %} | ||
1029 | 16 | </div> | ||
1033 | 17 | </div> | 11 | </div> |
1034 | 18 | |||
1035 | 19 | {% endblock %} | 12 | {% endblock %} |
1036 | 20 | 13 | ||
1037 | 21 | 14 | ||
1038 | === added file 'harvest/templates/opportunities/xhr/opportunity_edit.html' | |||
1039 | --- harvest/templates/opportunities/xhr/opportunity_edit.html 1970-01-01 00:00:00 +0000 | |||
1040 | +++ harvest/templates/opportunities/xhr/opportunity_edit.html 2010-08-13 02:04:41 +0000 | |||
1041 | @@ -0,0 +1,3 @@ | |||
1042 | 1 | {% load i18n %} | ||
1043 | 2 | {% include "opportunities/include/opportunity_details_edit.html" %} | ||
1044 | 3 | |||
1045 | 0 | 4 | ||
1046 | === added file 'harvest/templates/opportunities/xhr/opportunity_li.html' | |||
1047 | --- harvest/templates/opportunities/xhr/opportunity_li.html 1970-01-01 00:00:00 +0000 | |||
1048 | +++ harvest/templates/opportunities/xhr/opportunity_li.html 2010-08-13 02:04:41 +0000 | |||
1049 | @@ -0,0 +1,3 @@ | |||
1050 | 1 | {% load i18n %} | ||
1051 | 2 | {% include "opportunities/include/opportunity_li.html" %} | ||
1052 | 3 | |||
1053 | 0 | 4 | ||
1054 | === added file 'harvest/templates/opportunities/xhr/opportunity_outer_li.html' | |||
1055 | --- harvest/templates/opportunities/xhr/opportunity_outer_li.html 1970-01-01 00:00:00 +0000 | |||
1056 | +++ harvest/templates/opportunities/xhr/opportunity_outer_li.html 2010-08-13 02:04:41 +0000 | |||
1057 | @@ -0,0 +1,3 @@ | |||
1058 | 1 | {% load i18n %} | ||
1059 | 2 | {% include "opportunities/include/opportunity_outer_li.html" %} | ||
1060 | 3 |
Oh, I should mention: Note adds a new table to the database, and the Comment field in Opportunity is removed (replaced with note_set via the OneToMany relationship).
You'll need to run ./manage.py syncdb to get that all working right. It shouldn't cause any problems given that the comment field hasn't been used for anything so far.