Merge lp:~dylanmccall/harvest/opportunity-edit into lp:harvest

Proposed by Dylan McCall
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
Reviewer Review Type Date Requested Status
Daniel Holbach Approve
Review via email: mp+32544@code.launchpad.net

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://people.ubuntu.com/~dylanmccall/harvest/media/gsoc-week12/opportunity-edit.png

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.

To post a comment you must log in.
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

Revision history for this message
Dylan McCall (dylanmccall) wrote :

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.

Revision history for this message
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.

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

Nevermind, can't reproduce the issue I had with duplicated opportunity entries.

Preview Diff

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

Subscribers

People subscribed via source and target branches

to all changes: