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
=== modified file 'EXTERNALS'
--- EXTERNALS 2010-07-14 15:34:17 +0000
+++ EXTERNALS 2010-08-13 02:04:41 +0000
@@ -10,6 +10,10 @@
10 By "metajack"10 By "metajack"
11 <http://djangosnippets.org/snippets/797/>11 <http://djangosnippets.org/snippets/797/>
1212
13./harvest/common/templatetags/humanize_timediff.py
14 By "supsupmo"
15 <http://djangosnippets.org/snippets/412/>
16
13./harvest/media/css/reset.css17./harvest/media/css/reset.css
14 By Eric Meyer18 By Eric Meyer
15 <http://meyerweb.com/eric/tools/css/reset/>19 <http://meyerweb.com/eric/tools/css/reset/>
1620
=== added file 'harvest/common/templatetags/humanize_timediff.py'
--- harvest/common/templatetags/humanize_timediff.py 1970-01-01 00:00:00 +0000
+++ harvest/common/templatetags/humanize_timediff.py 2010-08-13 02:04:41 +0000
@@ -0,0 +1,51 @@
1#Template filter to provide a string similar to the built in timesince
2#filter, but slightly fuzzier.
3#From <http://djangosnippets.org/snippets/412/> with localization added
4
5from django.utils.translation import ungettext
6from django import template
7
8register = template.Library()
9
10@register.filter
11def humanize_timediff(timestamp = None):
12 """
13 Returns a humanized string representing time difference
14 between now() and the input timestamp.
15
16 The output rounds up to days, hours, minutes, or seconds.
17 4 days 5 hours returns '4 days'
18 0 days 4 hours 3 minutes returns '4 hours', etc...
19 """
20 import datetime
21
22 timeDiff = datetime.datetime.now() - timestamp
23 days = timeDiff.days
24 hours = timeDiff.seconds/3600
25 minutes = timeDiff.seconds%3600/60
26 seconds = timeDiff.seconds%3600%60
27
28 str = ""
29 tStr = ""
30 if days > 0:
31 str = ungettext('%(days)d day', '%(days)d days', days) % {
32 'days' : days
33 }
34 return str
35 elif hours > 0:
36 str = ungettext('%(hours)d hour', '%(hours)d hours', hours) % {
37 'hours' : hours
38 }
39 return str
40 elif minutes > 0:
41 str = ungettext('%(minutes)d minute', '%(minutes)d minutes', minutes) % {
42 'minutes' : minutes
43 }
44 return str
45 elif seconds > 0:
46 str = ungettext('%(seconds)d second', '%(seconds)d seconds', seconds) % {
47 'seconds' : seconds
48 }
49 return str
50 else:
51 return None
052
=== removed file 'harvest/common/templatetags/list_to_columns.py'
--- harvest/common/templatetags/list_to_columns.py 2009-07-08 11:31:32 +0000
+++ harvest/common/templatetags/list_to_columns.py 1970-01-01 00:00:00 +0000
@@ -1,30 +0,0 @@
1from django import template
2
3register = template.Library()
4
5class SplitListNode(template.Node):
6 def __init__(self, list, cols, new_list):
7 self.list = list
8 self.cols = cols
9 self.new_list = new_list
10
11 def split_seq(self, list, cols=2):
12 start = 0
13 for i in xrange(cols):
14 stop = start + len(list[i::cols])
15 yield list[start:stop]
16 start = stop
17
18 def render(self, context):
19 context[self.new_list] = self.split_seq(context[self.list],
20 int(self.cols))
21 return ''
22
23def list_to_columns(parser, token):
24 bits = token.contents.split()
25 if len(bits) != 5:
26 raise template.TemplateSyntaxError, "list_to_columns list as new_list 2"
27 if bits[2] != 'as':
28 raise template.TemplateSyntaxError, "second argument to the list_to_columns tag must be 'as'"
29 return SplitListNode(bits[1], bits[4], bits[3])
30list_to_columns = register.tag(list_to_columns)
310
=== modified file 'harvest/media/css/style.css'
--- harvest/media/css/style.css 2010-08-05 17:20:01 +0000
+++ harvest/media/css/style.css 2010-08-13 02:04:41 +0000
@@ -43,6 +43,17 @@
43 color:rgb(180,180,180);43 color:rgb(180,180,180);
44}44}
4545
46form label {
47 font-weight:bold;
48}
49form .error input, form .error textarea, form .error input#new_note {
50 border:solid 2px rgb(140,0,0);
51}
52form ul.errorlist {
53 font-size:smaller;
54 color:rgb(140,0,0);
55}
56
4657
4758
48#container {59#container {
@@ -59,34 +70,33 @@
59 display:block;70 display:block;
60 width:100%;71 width:100%;
61 min-height:80px;72 min-height:80px;
62
63 padding-top:12px;73 padding-top:12px;
64 padding-bottom:12px;
65}74}
66#header a {75#header a {
67 color:rgb(0,0,0);76 color:rgb(0,0,0);
68}77}
69#header #pagetitle {78#header #sitetitle {
70 display:inline;79 display:inline;
71 position:static;80 position:static;
72 margin-left:80px;81 margin-left:40px;
73 margin-right:40px;82 margin-right:40px;
83 margin-bottom:10px;
74 84
75 text-transform:lowercase;85 text-transform:lowercase;
76 color:rgb(0,0,0);86 color:rgb(0,0,0);
77 font-family:'Molengo', 'Bitstream Vera Sans', 'DejaVu Sans', sans-serif;87 font-family:'Molengo', 'Bitstream Vera Sans', 'DejaVu Sans', sans-serif;
78}88}
79#header #pagetitle #sitelogo {89#header #sitetitle #sitelogo {
80 width:auto; /* line up with #sitename font-size */90 width:auto; /* line up with #sitename font-size */
81 height:36px;91 height:36px;
82}92}
83#header #pagetitle #sitename {93#header #sitetitle #sitename {
84 display:inline;94 display:inline;
85 position:static;95 position:static;
86 96
87 font-size:36px;97 font-size:36px;
88}98}
89#header #pagetitle #releasename {99#header #sitetitle #releasename {
90 display:inline;100 display:inline;
91 position:static;101 position:static;
92 102
@@ -112,6 +122,30 @@
112 padding-bottom:140px;122 padding-bottom:140px;
113}123}
114124
125#content > #messages {
126 display:block;
127 margin:10px 0px;
128 padding:5px 10px;
129 font-size:larger;
130 background-color:rgb(242,151,93);
131 color:rgb(255,255,255);
132}
133
134#content > .pagetitle {
135 margin:10px 0px;
136 max-width:30em;
137 padding:0px 10px;
138 letter-spacing:-1px;
139 font-size:18px;
140 line-height:1em;
141}
142
143#content > .main {
144 margin:10px 0px;
145 padding:0px 20px;
146 max-width:60em;
147}
148
115149
116150
117#filters {151#filters {
@@ -120,7 +154,7 @@
120 float:left;154 float:left;
121 min-width:160px;155 min-width:160px;
122 156
123 padding:0px 20px 20px 20px;157 padding:0px 20px;
124 font-size:12px;158 font-size:12px;
125 line-height:1.4em;159 line-height:1.4em;
126 color:rgb(51,51,51);160 color:rgb(51,51,51);
@@ -207,9 +241,9 @@
207#filters .editfilter input {241#filters .editfilter input {
208 width:8em;242 width:8em;
209 border:none;243 border:none;
244 border-bottom:1px solid rgb(180,180,180);
245
210 padding:0px 2px 0px 2px;246 padding:0px 2px 0px 2px;
211 border-bottom:1px solid rgb(180,180,180);
212
213 background-color:inherit;247 background-color:inherit;
214 color:inherit;248 color:inherit;
215 font:inherit;249 font:inherit;
@@ -265,48 +299,43 @@
265 -moz-border-radius:3px 3px 0px 0px;299 -moz-border-radius:3px 3px 0px 0px;
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 */
267}301}
268.sourcepackage > a.sourcepackage-header {302a.sourcepackage-header {
269 color:inherit;303 color:inherit;
270 text-decoration:none;304 text-decoration:none;
271}305}
272.sourcepackage > .sourcepackage-header > .sourcepackage-name {306.sourcepackage-header > .sourcepackage-name {
273 display:inline;307 display:inline;
274 margin-right:10px;308 margin-right:10px;
275 color:rgb(0,0,0);309 color:rgb(0,0,0);
276 font-size:16px;310 font-size:16px;
277}311}
278.sourcepackage > .sourcepackage-header > .status {312.sourcepackage-header > .status {
279 display:none; /* harvest.js will override this where necessary */313 display:none; /* harvest.js will override this where necessary */
280}314}
281.sourcepackage > .sourcepackage-header > .status img {315.sourcepackage-header > .status img {
282 width:16px;316 width:16px;
283 height:16px;317 height:16px;
284}318}
285.sourcepackage > .sourcepackage-header > .sourcepackage-summary {319.sourcepackage-header > .sourcepackage-summary {
286 display:inline;320 display:inline;
287 float:right;321 float:right;
288 text-align:right;322 text-align:right;
289 font-size:10px;323 font-size:10px;
290}324}
291/* prelights */325/* prelights */
292.sourcepackage > a.sourcepackage-header:hover,326a.sourcepackage-header:hover,
293.sourcepackage > a.sourcepackage-header:focus {327a.sourcepackage-header:focus {
294 /* use the opportunity count or experience measure here */
295 background-color:rgb(240,255,243);328 background-color:rgb(240,255,243);
296}329}
297.sourcepackage > a.sourcepackage-header:hover .sourcepackage-name,330a.sourcepackage-header:hover .sourcepackage-name,
298.sourcepackage > a.sourcepackage-header:focus .sourcepackage-name {331a.sourcepackage-header:focus .sourcepackage-name {
299 text-decoration:underline;332 text-decoration:underline;
300}333}
301334
302.sourcepackage > .sourcepackage-details {335.sourcepackage-details {
303 display:block;336 display:block;
304 padding-top:20px;
305 /* inner padding should be left:20px, right:20px and bottom:5px */337 /* inner padding should be left:20px, right:20px and bottom:5px */
306}338}
307li.sourcepackage > .sourcepackage-details {
308 padding-top:0px;
309}
310339
311/* collapsed state. (expanded is the default) */340/* collapsed state. (expanded is the default) */
312li.sourcepackage.collapsed {341li.sourcepackage.collapsed {
@@ -334,7 +363,7 @@
334}363}
335364
336365
337.sourcepackage-details > .opportunity-list {366li.sourcepackage > .sourcepackage-details > .opportunity-list {
338 padding-left:20px;367 padding-left:20px;
339 margin-bottom:5px;368 margin-bottom:5px;
340}369}
@@ -394,17 +423,71 @@
394 font-size:smaller;423 font-size:smaller;
395}424}
396425
397.opportunity > .opportunity-details {426li.opportunity > .opportunity-details {
398 display:block;427 display:block;
399 margin-left:10px;428 margin-left:10px;
400}429}
401.opportunity-details > .opportunity-status {430li.opportunity > .opportunity-details.edit {
431 background-color:rgb(255,255,240);
432}
433
434.opportunity-notes {
402 display:block;435 display:block;
403 max-width:30em;436 width:100%;
437 max-width:35em;
404 border-left:dashed 1px rgb(180,180,180);438 border-left:dashed 1px rgb(180,180,180);
405 padding-left:5px;439 padding:0px 5px 0px 5px;
406 font-size:12px;440 font-size:12px;
407}441}
442.opportunity-notes input#new_note {
443 width:100%;
444 border:none;
445 border-bottom:1px solid rgb(180,180,180);
446
447 padding:2px 5px 2px 5px;
448 background-color:inherit;
449 color:inherit;
450 font:inherit;
451}
452.opportunity-notes > ul {
453 margin:0.5em 0;
454 max-height:10em;
455}
456.opportunity-notes > ul > li {
457 line-height:1.2em;
458 margin-bottom:0.5em;
459}
460.opportunity-notes > ul > li > .signature {
461 margin-left:1em;
462 vertical-align:sub;
463 color:rgb(180,180,180);
464 font-style:italic;
465 font-size:smaller;
466}
467
468
469
470.opportunity-details.edit > form > div.opportunity-notes {
471 float:left;
472 margin-right:4em;
473 margin-bottom:2em;
474}
475
476.opportunity-details.edit > form > ul.opportunity-switches {
477 float:left;
478}
479.opportunity-details.edit > form > ul.opportunity-switches > li {
480 white-space:nowrap;
481 margin-bottom:0.5em;
482}
483.opportunity-details.edit > form > ul.opportunity-switches > li.separate-top {
484 margin-bottom:2em;
485}
486
487.opportunity-details.edit > form > .actions {
488 clear:both;
489 padding-top:1em;
490}
408491
409492
410493
411494
=== modified file 'harvest/media/js/harvest.js'
--- harvest/media/js/harvest.js 2010-07-21 20:59:34 +0000
+++ harvest/media/js/harvest.js 2010-08-13 02:04:41 +0000
@@ -52,6 +52,10 @@
52var harvest = new function () {52var harvest = new function () {
5353
54/* Globals and constants */54/* Globals and constants */
55var XHR_ROOT = '/opportunities/xhr/'; /* TODO: would be nice to get this from Django */
56var RESULTS_URL = XHR_ROOT + 'results/'
57var OPPORTUNITIES_URL = XHR_ROOT + 'opportunity/';
58
55var MEDIA_PATH = '/media/' /* we should get this from Django somehow */59var MEDIA_PATH = '/media/' /* we should get this from Django somehow */
56var MEDIA = { 'pkg' : { 'loading' : MEDIA_PATH+'img/pkg-status-loading.gif' },60var MEDIA = { 'pkg' : { 'loading' : MEDIA_PATH+'img/pkg-status-loading.gif' },
57 'results' : { 'waiting' : MEDIA_PATH+'img/results-status-waiting.gif',61 'results' : { 'waiting' : MEDIA_PATH+'img/results-status-waiting.gif',
@@ -312,13 +316,13 @@
312}316}
313317
314318
315function Package (dom_node, details_url, opps_query, expanded_cb, collapsed_cb) {319function Package (dom_node, opps_query, expanded_cb, collapsed_cb) {
316 /* Created for each package inside the #results element */320 /* Created for each package inside the #results element */
317 321
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 */
319 var pkg = this;323 var pkg = this;
320 324
321 this.id = $(dom_node).attr('data-results-packageid');325 this.id = $(dom_node).attr('data-package-id');
322 this.details = $(dom_node).children('.sourcepackage-details');326 this.details = $(dom_node).children('.sourcepackage-details');
323 327
324 this.loading_xhr = null;328 this.loading_xhr = null;
@@ -370,7 +374,7 @@
370 374
371 this.loading_xhr = $.ajax({375 this.loading_xhr = $.ajax({
372 type: "GET",376 type: "GET",
373 url: details_url + this.id, dataType: 'html',377 url: RESULTS_URL + this.id, dataType: 'html',
374 data: opps_query,378 data: opps_query,
375 complete: function (xhr, status) {379 complete: function (xhr, status) {
376 pkg.hide_status('loading');380 pkg.hide_status('loading');
@@ -452,7 +456,6 @@
452 this.future_query = {};456 this.future_query = {};
453 457
454 this.container = $(dom_node);458 this.container = $(dom_node);
455 this.query_url = $(dom_node).attr('data-results-url');
456 this.output = $(dom_node).children('#results');459 this.output = $(dom_node).children('#results');
457 this.status_bubble = $(dom_node).children('#results-status');460 this.status_bubble = $(dom_node).children('#results-status');
458 461
@@ -488,10 +491,9 @@
488 491
489 results_packages.each(function () {492 results_packages.each(function () {
490 var dom_node = $(this);493 var dom_node = $(this);
491 var details_url = results.query_url + '/';
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: */
493 495
494 var pkg = new Package(dom_node, details_url, opps_query,496 var pkg = new Package(dom_node, opps_query,
495 pkg_expanded_cb, pkg_collapsed_cb);497 pkg_expanded_cb, pkg_collapsed_cb);
496 results.packages[pkg.id] = pkg;498 results.packages[pkg.id] = pkg;
497 499
@@ -545,7 +547,7 @@
545 547
546 this.loading_xhr = $.ajax({548 this.loading_xhr = $.ajax({
547 type: "GET",549 type: "GET",
548 url: this.query_url, dataType: 'html',550 url: RESULTS_URL, dataType: 'html',
549 data: load_query,551 data: load_query,
550 complete: function (xhr, status) {552 complete: function (xhr, status) {
551 results.hide_status('loading');553 results.hide_status('loading');
552554
=== modified file 'harvest/opportunities/admin.py'
--- harvest/opportunities/admin.py 2009-08-30 14:53:53 +0000
+++ harvest/opportunities/admin.py 2010-08-13 02:04:41 +0000
@@ -1,9 +1,15 @@
1from django.contrib import admin1from django.contrib import admin
2from opportunities.models import Opportunity, OpportunityList, SourcePackage2from opportunities.models import Opportunity, Note, OpportunityList, SourcePackage
3
4class NoteInline(admin.TabularInline):
5 model = Note
6 extra = 1
7 raw_id_fields = ('author', )
38
4class OpportunityAdmin(admin.ModelAdmin):9class OpportunityAdmin(admin.ModelAdmin):
5 list_display = ('description', 'sourcepackage', 'opportunitylist', 'last_updated')10 list_display = ('description', 'sourcepackage', 'opportunitylist', 'last_updated', 'note_set')
6 list_filter = ('opportunitylist', 'last_updated')11 list_filter = ('opportunitylist', 'last_updated')
12 inlines = [NoteInline]
713
8class OpportunityListAdmin(admin.ModelAdmin):14class OpportunityListAdmin(admin.ModelAdmin):
9 list_display = ('name', 'description', 'last_updated', 'active')15 list_display = ('name', 'description', 'last_updated', 'active')
1016
=== modified file 'harvest/opportunities/forms.py'
--- harvest/opportunities/forms.py 2010-03-08 16:53:44 +0000
+++ harvest/opportunities/forms.py 2010-08-13 02:04:41 +0000
@@ -1,9 +1,13 @@
1from django import forms1from django import forms
2
3from models import Opportunity2from models import Opportunity
3from django.utils.translation import ugettext as _
44
5class OpportunityForm(forms.ModelForm):5class OpportunityForm(forms.ModelForm):
6 new_note = forms.CharField(label=_("Enter a new note here"),
7 required=False,
8 max_length=250) #from models.Note.text
9
6 class Meta:10 class Meta:
7 model = Opportunity11 model = Opportunity
8 exclude = ('description', 'url', 'last_updated', 'since', 'sourcepackage', 'opportunitylist', 'valid')12 fields = ('experience', 'applied', 'reviewed')
913
1014
=== modified file 'harvest/opportunities/models.py'
--- harvest/opportunities/models.py 2010-08-05 17:20:01 +0000
+++ harvest/opportunities/models.py 2010-08-13 02:04:41 +0000
@@ -8,10 +8,10 @@
8PACKAGE_RED_THRESHOLD = 208PACKAGE_RED_THRESHOLD = 20
99
10EXPERIENCE_CHOICES = (10EXPERIENCE_CHOICES = (
11 (0, '---'),11 (0, ""),
12 (1, 'Easy'),12 (1, _("Easy")),
13 (2, 'Medium'),13 (2, _("Medium")),
14 (3, 'Hard'),14 (3, _("Hard")),
15)15)
1616
17class PackageSet(models.Model):17class PackageSet(models.Model):
@@ -83,9 +83,8 @@
83 applied = models.BooleanField(_("Applied"), default=False, blank=True)83 applied = models.BooleanField(_("Applied"), default=False, blank=True)
84 sourcepackage = models.ForeignKey(SourcePackage)84 sourcepackage = models.ForeignKey(SourcePackage)
85 opportunitylist = models.ForeignKey(OpportunityList)85 opportunitylist = models.ForeignKey(OpportunityList)
86 comment = models.TextField(_("Comment"), blank=True)
87 valid = models.BooleanField(_("Valid"), default=True)86 valid = models.BooleanField(_("Valid"), default=True)
88 experience = models.IntegerField(_("Required Experience"), choices=EXPERIENCE_CHOICES, default=0,87 experience = models.IntegerField(_("Difficulty"), choices=EXPERIENCE_CHOICES, default=0,
89 help_text=_("Level of experience required for this specific opportunity."))88 help_text=_("Level of experience required for this specific opportunity."))
9089
91 class Meta:90 class Meta:
@@ -108,6 +107,20 @@
108 summary_list.append(_("Invalid"))107 summary_list.append(_("Invalid"))
109 return summary_list108 return summary_list
110109
110
111class Note(models.Model):
112 opportunity = models.ForeignKey(Opportunity)
113 date = models.DateTimeField(auto_now_add=True)
114 author = models.ForeignKey(User)
115 text = models.CharField(max_length=250)
116
117 def __unicode__(self):
118 text = self.text
119 if len(text) > 40:
120 text = text[:40]+u"\u2026"
121 return '%s: %s' % (self.author, text)
122
123
111class ActionLogEntry(models.Model):124class ActionLogEntry(models.Model):
112 timestamp = models.DateTimeField(_("Timestamp"))125 timestamp = models.DateTimeField(_("Timestamp"))
113 who = models.ForeignKey(User)126 who = models.ForeignKey(User)
114127
=== modified file 'harvest/opportunities/urls.py'
--- harvest/opportunities/urls.py 2010-07-30 21:45:23 +0000
+++ harvest/opportunities/urls.py 2010-08-13 02:04:41 +0000
@@ -1,12 +1,28 @@
1from django.conf.urls.defaults import *1from django.conf.urls.defaults import *
22
3urlpatterns = patterns('',3urlpatterns = patterns('',
4 url(r'^opportunity/(?P<opportunity_id>[\d]+)/edit$', 'opportunities.views.opportunity_edit', name='opportunity_edit'),4 url(r'^$',
5 5 'opportunities.views.filter',
6 url(r'^filter$', 'opportunities.views.opportunities_filter', name='opportunities_filter'),6 name='filter'),
7 7
8 url(r'^package/(?P<package_name>.+)', 'opportunities.views.single_package', name='single_package'),8 url(r'^package/(?P<package_name>.+)/$',
9 9 'opportunities.views.single_package',
10 url(r'^xhr/results$', 'opportunities.views.opportunities_xhr_filter_results', name='opportunities_xhr_filter_results'),10 name='single_package'),
11 url(r'^xhr/results/(?P<package_id>[\d]+)$', 'opportunities.views.opportunities_xhr_package_details', name='opportunities_xhr_package_details'),11
12 url(r'^opportunity/(?P<opportunity_id>[\d]+)/edit/$',
13 'opportunities.views.opportunity_edit',
14 name='opportunity_edit'),
15
16
17 url(r'^xhr/results/$',
18 'opportunities.views.xhr_filter_results'),
19
20 url(r'^xhr/results/(?P<package_id>[\d]+)/$',
21 'opportunities.views.xhr_package_details'),
22
23 url(r'xhr/opportunity/(?P<opportunity_id>[\d]+)/$',
24 'opportunities.views.xhr_opportunity_li'),
25
26 url(r'^xhr/opportunity/(?P<opportunity_id>[\d]+)/edit/$',
27 'opportunities.views.xhr_opportunity_edit'),
12)28)
1329
=== modified file 'harvest/opportunities/views.py'
--- harvest/opportunities/views.py 2010-07-30 21:45:23 +0000
+++ harvest/opportunities/views.py 2010-08-13 02:04:41 +0000
@@ -13,11 +13,67 @@
13import models13import models
14import forms14import forms
1515
16from models import Opportunity, Note # for form processing
17
16from filters import HarvestFilters18from filters import HarvestFilters
17from wrappers import PackageWrapper, PackageListWrapper19from wrappers import PackageWrapper, PackageListWrapper
1820
21def _create_packages_list(request, filters_pkg, filters_opp):
22 # XXX: rockstar: Eep! We shouldn't be storing the None as a string. We
23 # should re-think this model relationship.
24 #sourcepackages_list = models.SourcePackage.objects.exclude(name='None')
25
26 sourcepackages_list = models.SourcePackage.objects.distinct()
27 sourcepackages_list = filters_pkg.process_queryset(sourcepackages_list)
28
29 #opportunities_list is filtered right away to only check opportunities belonging to selected packages
30 opportunities_list = models.Opportunity.objects.distinct().filter(sourcepackage__in=sourcepackages_list)
31 opportunities_list = filters_opp.process_queryset(opportunities_list)
32
33 #TODO: need to filter out opportunities with valid=False again
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?
35
36 pkg_list_wrapper = PackageListWrapper(request, sourcepackages_list, opportunities_list)
37
38 return pkg_list_wrapper
39
40
41
42def filter(request):
43 filters = HarvestFilters()
44 filters.update_from_http(request)
45 filters_pkg = filters.find('pkg')
46 filters_opp = filters.find('opp')
47
48 pkg_list_wrapper = _create_packages_list(request, filters_pkg, filters_opp)
49
50 context = {
51 'packages_list': pkg_list_wrapper,
52 'filters_pkg' : filters_pkg,
53 'filters_opp' : filters_opp
54 }
55
56 return render(
57 'opportunities/filter.html',
58 context,
59 context_instance=RequestContext(request))
60
61def single_package(request, package_name):
62 package = get_object_or_404(models.SourcePackage, name=package_name)
63
64 package_wrapper = PackageWrapper(request, package, visible_opportunities = package.opportunity_set)
65
66 context = {
67 'package': package_wrapper
68 }
69
70 return render(
71 'opportunities/single_package.html',
72 context,
73 context_instance=RequestContext(request))
74
19@login_required75@login_required
20def opportunity_edit(request, opportunity_id):76def opportunity_edit(request, opportunity_id, template='opportunities/opportunity_edit.html'):
21 opportunity = get_object_or_404(models.Opportunity, id=opportunity_id)77 opportunity = get_object_or_404(models.Opportunity, id=opportunity_id)
22 if request.method == "POST":78 if request.method == "POST":
23 form = forms.OpportunityForm(data=request.POST, instance=opportunity)79 form = forms.OpportunityForm(data=request.POST, instance=opportunity)
@@ -32,68 +88,41 @@
32 models.log_action(request.user, 88 models.log_action(request.user,
33 action="changed experience to: %s" % form.cleaned_data["experience"],89 action="changed experience to: %s" % form.cleaned_data["experience"],
34 opportunity=opportunity)90 opportunity=opportunity)
35 if form.cleaned_data["comment"] != opportunity.comment:
36 if len(form.cleaned_data["comment"]) > 178:
37 action = "changed comment to: '%s'" % (form.cleaned_data["comment"][:177]+u"…")
38 else:
39 action = "changed comment to: '%s'" % form.cleaned_data["comment"]
40 models.log_action(request.user, action=action,
41 opportunity=opportunity)
42 form.save()91 form.save()
92
93 #add a new note if input by the user
94 if form.cleaned_data["new_note"].strip() != '':
95 note_text = form.cleaned_data["new_note"]
96
97 note_log_text = note_text
98 if len(note_log_text) > 160:
99 note_log_text = note_log_text[:160]+u"\u2026"
100 models.log_action(request.user,
101 action="added note: '%s'" % note_log_text,
102 opportunity=opportunity)
103
104 note = Note(opportunity=opportunity, author=request.user, text=note_text)
105 note.save()
106
43 return HttpResponseRedirect(request.POST["next"])107 return HttpResponseRedirect(request.POST["next"])
44 else:108 else:
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.'))
46 else:110 else:
47 form = forms.OpportunityForm(instance=opportunity)111 form = forms.OpportunityForm(instance=opportunity)
48 return render('opportunities/opportunity_edit.html', 112
113 next = '/'
114 if 'next' in request.GET: next = request.GET['next']
115
116 return render(template,
49 {'form': form, 117 {'form': form,
50 'opportunity':opportunity,118 'opportunity':opportunity,
51 'user':request.user, 119 'user':request.user,
52 'next': request.GET['next'],120 'next': next,
53 }, RequestContext(request))121 }, RequestContext(request))
54122
55123
56def _create_packages_list(request, filters_pkg, filters_opp):124
57 # XXX: rockstar: Eep! We shouldn't be storing the None as a string. We125def xhr_filter_results(request):
58 # should re-think this model relationship.
59 #sourcepackages_list = models.SourcePackage.objects.exclude(name='None')
60
61 sourcepackages_list = models.SourcePackage.objects.distinct()
62 sourcepackages_list = filters_pkg.process_queryset(sourcepackages_list)
63
64 #opportunities_list is filtered right away to only check opportunities belonging to selected packages
65 opportunities_list = models.Opportunity.objects.distinct().filter(sourcepackage__in=sourcepackages_list)
66 opportunities_list = filters_opp.process_queryset(opportunities_list)
67
68 #TODO: need to filter out opportunities with valid=False again
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?
70
71 pkg_list_wrapper = PackageListWrapper(request, sourcepackages_list, opportunities_list)
72
73 return pkg_list_wrapper
74
75
76def opportunities_filter(request):
77 filters = HarvestFilters()
78 filters.update_from_http(request)
79 filters_pkg = filters.find('pkg')
80 filters_opp = filters.find('opp')
81
82 pkg_list_wrapper = _create_packages_list(request, filters_pkg, filters_opp)
83
84 context = {
85 'packages_list': pkg_list_wrapper,
86 'filters_pkg' : filters_pkg,
87 'filters_opp' : filters_opp
88 }
89
90 return render(
91 'opportunities/filter.html',
92 context,
93 context_instance=RequestContext(request))
94
95
96def opportunities_xhr_filter_results(request):
97 filters = HarvestFilters()126 filters = HarvestFilters()
98 filters.update_from_http(request)127 filters.update_from_http(request)
99 128
@@ -108,12 +137,11 @@
108 context,137 context,
109 context_instance=RequestContext(request))138 context_instance=RequestContext(request))
110139
111140def xhr_package_details(request, package_id):
112def opportunities_xhr_package_details(request, package_id):
113 filters = HarvestFilters()141 filters = HarvestFilters()
114 filters.update_from_http(request)142 filters.update_from_http(request)
115 143
116 package = models.SourcePackage.objects.get(id=package_id)144 package = get_object_or_404(models.SourcePackage, id=package_id)
117 145
118 opportunities_list = filters.find('opp').process_queryset(package.opportunity_set).all()146 opportunities_list = filters.find('opp').process_queryset(package.opportunity_set).all()
119 147
@@ -128,18 +156,21 @@
128 context,156 context,
129 context_instance=RequestContext(request))157 context_instance=RequestContext(request))
130158
131def single_package(request, package_name):159def xhr_opportunity_li(request, opportunity_id):
132 package = models.SourcePackage.objects.get(name=package_name)160 opportunity = get_object_or_404(models.Opportunity, id=opportunity_id)
133
134 package_wrapper = PackageWrapper(request, package, visible_opportunities = package.opportunity_set)
135 161
136 context = {162 context = {
137 'package': package_wrapper163 'opportunity': opportunity
138 }164 }
139 165
140 return render(166 return render(
141 'opportunities/single_package.html',167 'opportunities/xhr/opportunity_outer_li.html',
142 context,168 context,
143 context_instance=RequestContext(request))169 context_instance=RequestContext(request))
144170
171def xhr_opportunity_edit(request, opportunity_id):
172 return opportunity_edit(
173 request,
174 opportunity_id,
175 template='opportunities/xhr/opportunity_edit.html')
145176
146177
=== modified file 'harvest/templates/base.html'
--- harvest/templates/base.html 2010-07-21 20:21:14 +0000
+++ harvest/templates/base.html 2010-08-13 02:04:41 +0000
@@ -26,7 +26,7 @@
26<div id="container">26<div id="container">
2727
28<div id="header">28<div id="header">
29 <span id="pagetitle">29 <span id="sitetitle">
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" />
31 <h1 id="sitename">{% trans "Harvest" %}</h1>31 <h1 id="sitename">{% trans "Harvest" %}</h1>
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 %}
@@ -59,7 +59,7 @@
5959
60<div id="footer">60<div id="footer">
61 <div id="footnav"><nav>61 <div id="footnav"><nav>
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>
63 </nav></div>63 </nav></div>
64 64
65 <div id="smallprint" tabindex="3">65 <div id="smallprint" tabindex="3">
6666
=== added file 'harvest/templates/one_column.html'
--- harvest/templates/one_column.html 1970-01-01 00:00:00 +0000
+++ harvest/templates/one_column.html 2010-08-13 02:04:41 +0000
@@ -0,0 +1,9 @@
1{% extends "base.html" %}
2{% load i18n %}
3
4{% block content %}
5<h2 class="pagetitle">{% block pagetitle %}{% endblock %}</h2>
6<div class="main">
7 {% block content_main %}{% endblock %}
8</div>
9{% endblock %}
0\ No newline at end of file10\ No newline at end of file
111
=== modified file 'harvest/templates/opportunities/filter.html'
--- harvest/templates/opportunities/filter.html 2010-07-30 21:45:23 +0000
+++ harvest/templates/opportunities/filter.html 2010-08-13 02:04:41 +0000
@@ -10,7 +10,7 @@
10 {{filters_opp.render}}10 {{filters_opp.render}}
11</div>11</div>
1212
13<div id="results-pane" data-results-url="{% url opportunities_xhr_filter_results %}">13<div id="results-pane">
14 <div id="results-status"></div>14 <div id="results-status"></div>
15 <div id="results">15 <div id="results">
16 {% include "opportunities/include/filter_results.html" %}16 {% include "opportunities/include/filter_results.html" %}
1717
=== modified file 'harvest/templates/opportunities/include/filter_results.html'
--- harvest/templates/opportunities/include/filter_results.html 2010-07-30 08:23:24 +0000
+++ harvest/templates/opportunities/include/filter_results.html 2010-08-13 02:04:41 +0000
@@ -3,7 +3,7 @@
3{% if packages_list %}3{% if packages_list %}
4<ul>4<ul>
5 {% for package in packages_list.get_visible_packages %}5 {% for package in packages_list.get_visible_packages %}
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 %}">
7 <a class="sourcepackage-header" href="{{ package.get_expand_toggle_url }}">7 <a class="sourcepackage-header" href="{{ package.get_expand_toggle_url }}">
8 <h2 class="sourcepackage-name">{{ package.real.name }}</h2>8 <h2 class="sourcepackage-name">{{ package.real.name }}</h2>
9 <span class="status"></span>9 <span class="status"></span>
1010
=== modified file 'harvest/templates/opportunities/include/opportunity.html'
--- harvest/templates/opportunities/include/opportunity.html 2010-07-30 21:45:23 +0000
+++ harvest/templates/opportunities/include/opportunity.html 2010-08-13 02:04:41 +0000
@@ -3,16 +3,14 @@
3<div class="opportunity-header">3<div class="opportunity-header">
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>
5 {% if user.is_authenticated %}5 {% if user.is_authenticated %}
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>
7 {% endif %}7 {% endif %}
8 <span class="opportunity-summary">8 <span class="opportunity-summary">
9 {{ opportunity.summary|join:', ' }}9 {{ opportunity.summary|join:', ' }}
10 </span>10 </span>
11</div>11</div>
12<div class="opportunity-details">12<div class="opportunity-details">
13 {% if opportunity.comment %}13 {% include "opportunities/include/opportunity_details.html" %}
14 <span class="opportunity-status">{{ opportunity.comment }}</span>
15 {% endif %}
16</div>14</div>
17<div class="bottom"></div>15<div class="bottom"></div>
1816
1917
=== added file 'harvest/templates/opportunities/include/opportunity_details.html'
--- harvest/templates/opportunities/include/opportunity_details.html 1970-01-01 00:00:00 +0000
+++ harvest/templates/opportunities/include/opportunity_details.html 2010-08-13 02:04:41 +0000
@@ -0,0 +1,7 @@
1{% ifnotequal opportunity.note_set.count 0 %}
2<div class="opportunity-notes">
3 <ul>
4 {% include "opportunities/include/opportunity_notes_list.html" %}
5 </ul>
6</div>
7{% endifnotequal %}
0\ No newline at end of file8\ No newline at end of file
19
=== added file 'harvest/templates/opportunities/include/opportunity_details_edit.html'
--- harvest/templates/opportunities/include/opportunity_details_edit.html 1970-01-01 00:00:00 +0000
+++ harvest/templates/opportunities/include/opportunity_details_edit.html 2010-08-13 02:04:41 +0000
@@ -0,0 +1,48 @@
1{% load i18n %}
2
3<form action="{{ request.path_info }}" method="POST">
4 <div class="opportunity-notes">
5 {% with form.new_note as field %}
6 <span class="separate-top {% if field.required %}required{% endif %} {% if field.errors %}error{% endif %}">
7 {{field.errors}}
8 <input type="text" name="new_note" id="new_note" placeholder="{{field.label}}" {% if form.new_note.data %}value="{{form.new_note.data}}"{% endif %} />
9 {% endwith %}
10 </span>
11 <ul>
12 {% include "opportunities/include/opportunity_notes_list.html" %}
13 </ul>
14 <div class="bottom"></div>
15 </div>
16
17 <ul class="opportunity-switches">
18 {% with form.experience as field %}
19 <li class="separate-top {% if field.required %}required{% endif %} {% if field.errors %}error{% endif %}">
20 {{field.errors}}
21 {{field}}
22 {{field.label_tag}}
23 </li>
24 {% endwith %}
25
26 {% with form.applied as field %}
27 <li class="{% if field.required %}required{% endif %} {% if field.errors %}error{% endif %}">
28 {{field.errors}}
29 {{field}}
30 {{field.label_tag}}
31 </li>
32 {% endwith %}
33
34 {% with form.reviewed as field %}
35 <li class="{% if field.required %}required{% endif %} {% if field.errors %}error{% endif %}">
36 {{field.errors}}
37 {{field}}
38 {{field.label_tag}}
39 </li>
40 {% endwith %}
41 </ul>
42
43 <div class="actions">
44 <input type="hidden" name="next" value="{{ next }}" />
45 <input type="submit" value="{% trans 'Apply changes' %}" />
46 <a href="{{next}}" rel="deactivate"><button>Cancel</button></a>
47 </div>
48</form>
049
=== added file 'harvest/templates/opportunities/include/opportunity_li.html'
--- harvest/templates/opportunities/include/opportunity_li.html 1970-01-01 00:00:00 +0000
+++ harvest/templates/opportunities/include/opportunity_li.html 2010-08-13 02:04:41 +0000
@@ -0,0 +1,3 @@
1<li class="opportunity" data-opportunity-experience="{{opportunity.experience}}" {% if opportunity.reviewed %}data-opportunity-irrelevant{% endif %} {% if opportunity.applied %}data-opportunity-applied{% endif %}>
2 {% include "opportunities/include/opportunity.html" %}
3</li>
04
=== added file 'harvest/templates/opportunities/include/opportunity_notes_list.html'
--- harvest/templates/opportunities/include/opportunity_notes_list.html 1970-01-01 00:00:00 +0000
+++ harvest/templates/opportunities/include/opportunity_notes_list.html 2010-08-13 02:04:41 +0000
@@ -0,0 +1,9 @@
1{% load i18n %}
2{% load humanize_timediff %}
3
4{% for note in opportunity.note_set.all|dictsortreversed:"date" %}
5<li>
6 {{ note.text }}
7 <span class="signature">{% blocktrans with note.date|humanize_timediff as timesince and note.author as author %}{{author}}, {{timesince}} ago{% endblocktrans %}</span>
8</li>
9{% endfor %}
010
=== added file 'harvest/templates/opportunities/include/opportunity_outer_li.html'
--- harvest/templates/opportunities/include/opportunity_outer_li.html 1970-01-01 00:00:00 +0000
+++ harvest/templates/opportunities/include/opportunity_outer_li.html 2010-08-13 02:04:41 +0000
@@ -0,0 +1,3 @@
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 %}>
2 {% include "opportunities/include/opportunity.html" %}
3</li>
04
=== modified file 'harvest/templates/opportunities/include/package_details.html'
--- harvest/templates/opportunities/include/package_details.html 2010-07-31 20:22:58 +0000
+++ harvest/templates/opportunities/include/package_details.html 2010-08-13 02:04:41 +0000
@@ -11,9 +11,7 @@
11<ul>11<ul>
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) #}
13 {% for opportunity in opplist.list %}13 {% for opportunity in opplist.list %}
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" %}
15 {% include "opportunities/include/opportunity.html" %}
16 </li>
17 {% endfor %}15 {% endfor %}
18</ul>16</ul>
19</div>17</div>
2018
=== modified file 'harvest/templates/opportunities/opportunity_edit.html'
--- harvest/templates/opportunities/opportunity_edit.html 2010-07-30 08:23:24 +0000
+++ harvest/templates/opportunities/opportunity_edit.html 2010-08-13 02:04:41 +0000
@@ -1,17 +1,14 @@
1{% extends "base.html" %}1{% extends "one_column.html" %}
2{% load i18n %}2{% load i18n %}
33
4{% block content %}4{% block title %}{{ block.super }}: {{ opportunity.description }}{% endblock %}
5 <div class="opportunity">5
6 {% include "opportunities/include/opportunity.html" %}6{% block pagetitle %}
7 </div>7Opportunity <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 %}
8 <div id="form"><form action="{{ request.path_info }}" method="POST">8{% endblock %}
9 {% if form.errors %}9
10 <p style="color: red;">{% trans "Please correct the error" %} {{ form.errors|pluralize }} below.</p>10{% block content_main %}
11 {% endif %}11<div class="opportunity-details edit">
12 <table>{{ form.as_table }}</table>12 {% include "opportunities/include/opportunity_details_edit.html" %}
13 <td><input type="hidden" name="next" value="{{ next }}" /> </td>13</div>
14 <td><input type="submit" value='{% trans "Update Information Now!" %}' /></td>
15 <td><a href="{{next}}" rel="deactivate"><button>Cancel</button></a></td>
16 </form></div>
17{% endblock %}14{% endblock %}
1815
=== modified file 'harvest/templates/opportunities/single_package.html'
--- harvest/templates/opportunities/single_package.html 2010-07-30 21:45:23 +0000
+++ harvest/templates/opportunities/single_package.html 2010-08-13 02:04:41 +0000
@@ -1,20 +1,13 @@
1{% extends "base.html" %}1{% extends "one_column.html" %}
2{% load i18n %}2{% load i18n %}
33
4{% block title %}{{ block.super }}: {{ package.real.name }}{% endblock %}4{% block title %}{{ block.super }}: {{ package.real.name }}{% endblock %}
55
6{% block content %}6{% block pagetitle %}{{ package.real.name }}{% endblock %}
77
8<div class="sourcepackage">8{% block content_main %}
9 <div class="sourcepackage-header">9<div class="sourcepackage-details">
10 <h2 class="sourcepackage-name">{{ package.real.name }}</h2>10 {% include "opportunities/include/package_details.html" %}
11 {% comment %}<span class="sourcepackage-summary"></span>{% endcomment %}
12 <div class="bottom"></div>
13 </div>
14 <div class="sourcepackage-details">
15 {% include "opportunities/include/package_details.html" %}
16 </div>
17</div>11</div>
18
19{% endblock %}12{% endblock %}
2013
2114
=== added file 'harvest/templates/opportunities/xhr/opportunity_edit.html'
--- harvest/templates/opportunities/xhr/opportunity_edit.html 1970-01-01 00:00:00 +0000
+++ harvest/templates/opportunities/xhr/opportunity_edit.html 2010-08-13 02:04:41 +0000
@@ -0,0 +1,3 @@
1{% load i18n %}
2{% include "opportunities/include/opportunity_details_edit.html" %}
3
04
=== added file 'harvest/templates/opportunities/xhr/opportunity_li.html'
--- harvest/templates/opportunities/xhr/opportunity_li.html 1970-01-01 00:00:00 +0000
+++ harvest/templates/opportunities/xhr/opportunity_li.html 2010-08-13 02:04:41 +0000
@@ -0,0 +1,3 @@
1{% load i18n %}
2{% include "opportunities/include/opportunity_li.html" %}
3
04
=== added file 'harvest/templates/opportunities/xhr/opportunity_outer_li.html'
--- harvest/templates/opportunities/xhr/opportunity_outer_li.html 1970-01-01 00:00:00 +0000
+++ harvest/templates/opportunities/xhr/opportunity_outer_li.html 2010-08-13 02:04:41 +0000
@@ -0,0 +1,3 @@
1{% load i18n %}
2{% include "opportunities/include/opportunity_outer_li.html" %}
3

Subscribers

People subscribed via source and target branches

to all changes: