Merge lp:~mhall119/loco-team-portal/686268 into lp:loco-team-portal
- 686268
- Merge into 0.2
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Chris Johnston | ||||
Approved revision: | 369 | ||||
Merged at revision: | 369 | ||||
Proposed branch: | lp:~mhall119/loco-team-portal/686268 | ||||
Merge into: | lp:loco-team-portal | ||||
Diff against target: |
718 lines (+469/-33) 14 files modified
loco_directory/common/views.py (+10/-0) loco_directory/media/css/newstyle.css (+26/-0) loco_directory/meetings/forms.py (+49/-17) loco_directory/meetings/models.py (+24/-6) loco_directory/meetings/templatetags/recurse.py (+82/-0) loco_directory/meetings/urls.py (+3/-0) loco_directory/meetings/views.py (+119/-4) loco_directory/services/urls.py (+1/-0) loco_directory/services/views.py (+4/-1) loco_directory/templates/meetings/agenda_item_delete_confirm.html (+43/-0) loco_directory/templates/meetings/agenda_item_new.html (+36/-0) loco_directory/templates/meetings/agenda_item_update.html (+37/-0) loco_directory/templates/meetings/team_meeting_detail_agenda.inc.html (+19/-5) loco_directory/templates/site_search.html (+16/-0) |
||||
To merge this branch: | bzr merge lp:~mhall119/loco-team-portal/686268 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chris Johnston | Needs Fixing | ||
Review via email: mp+47162@code.launchpad.net |
Commit message
Description of the change
Added forms for adding/updating agenda items, as well as a template tag for recursively displaying them, and css to make it all look nice.
Dave Walker (davewalker) wrote : | # |
Michael Hall (mhall119) wrote : | # |
The migration script was part of a previous merge, this is just adding a front-end to it
- 366. By Chris Johnston
-
Fixes bug 639772. Props mhall119
Chris Johnston (cjohnston) wrote : | # |
IMO, the owner should be assigned automatically.
The time still shows up as "13:53:09.032017" - it really only needs HH:MM
Can we have a mouseover on the agenda item link say something like "Update <agenda item>" (i.e. when you hover over "Agenda item 1" in: Agenda item 1 - Chris Johnston @ 2011-01-23 13:53 it would read "Update Agenda item 1")
What do you think about indenting sub items a little more?
I also think that there should be some sort of end (if there isn't already, I've gone down 4 levels) to the subitems.
Michael Hall (mhall119) wrote : | # |
The owner is defaulted to the logged in user, but it's changeable. There have been times were I've added an item for someone else, or someone else has added one for me.
Agreed on the time, I'll fix that.
How much more indentation do you think it needs?
It would be more work to limit the depth of the tree than it would be to leave it up to the user. Anything over 3 levels deep will only get a point bullet though, rather than a number or letter.
- 367. By Michael Hall
-
Show appropriate timezone and localtimes on events and meetings, merge from Chris Johnston
- 368. By Michael Hall
-
Add link to meeting chair's lp profile, merge from Chris Johnston
- 369. By Michael Hall
-
Merge from trunk
Preview Diff
1 | === modified file 'loco_directory/common/views.py' |
2 | --- loco_directory/common/views.py 2010-11-30 12:54:39 +0000 |
3 | +++ loco_directory/common/views.py 2011-01-24 02:52:15 +0000 |
4 | @@ -68,6 +68,7 @@ |
5 | global_events = [] |
6 | team_events = [] |
7 | venues = [] |
8 | + meetings = [] |
9 | q = None |
10 | form = SiteSearchForm(data=request.GET) |
11 | if form.is_valid(): |
12 | @@ -77,11 +78,13 @@ |
13 | global_events = search_global_events(q) |
14 | team_events = search_team_events(q) |
15 | venues = search_venues(q) |
16 | + meetings = search_meetings(q) |
17 | context = { |
18 | 'teams': teams, |
19 | 'global_events': global_events, |
20 | 'team_events': team_events, |
21 | 'venues': venues, |
22 | + 'meetings': meetings, |
23 | 'q': q, |
24 | 'colcycle' : simple_iterator('col_left', 'col_right'), |
25 | } |
26 | @@ -114,5 +117,12 @@ |
27 | venue_list = Venue.objects.filter(Q(name__icontains=q) | Q(country__name__icontains=q) | Q(city__icontains=q) | Q(address__icontains=q)).order_by('name').distinct() |
28 | return venue_list |
29 | |
30 | +def search_meetings(q): |
31 | + from meetings.models import TeamMeeting |
32 | + from django.db.models import Q |
33 | + meeting_list = TeamMeeting.objects.next_meetings() |
34 | + meeting_list = meeting_list.filter(Q(name__icontains=q) | Q(teams__name__icontains=q) | Q(agenda__title__icontains=q)).distinct() |
35 | + return meeting_list |
36 | + |
37 | |
38 | |
39 | |
40 | === modified file 'loco_directory/media/css/newstyle.css' |
41 | --- loco_directory/media/css/newstyle.css 2011-01-02 18:45:45 +0000 |
42 | +++ loco_directory/media/css/newstyle.css 2011-01-24 02:52:15 +0000 |
43 | @@ -434,3 +434,29 @@ |
44 | .attendee-mugshot { |
45 | vertical-align: middle; |
46 | } |
47 | + |
48 | +.agenda-list { |
49 | + list-style-type: decimal; |
50 | +} |
51 | + |
52 | +.agenda-list .agenda-list { |
53 | + margin-left: 20px; |
54 | + list-style-type: lower-alpha; |
55 | +} |
56 | + |
57 | +.agenda-list .agenda-list .agenda-list { |
58 | + list-style-type: lower-roman; |
59 | +} |
60 | + |
61 | +.agenda-list .agenda-list .agenda-list .agenda-list { |
62 | + list-style-type: circle; |
63 | +} |
64 | + |
65 | +.agenda-title { |
66 | + font-weight: bold; |
67 | +} |
68 | + |
69 | +.agenda-description { |
70 | + margin-left: 30px; |
71 | + font-size: 0.9em; |
72 | +} |
73 | |
74 | === modified file 'loco_directory/meetings/forms.py' |
75 | --- loco_directory/meetings/forms.py 2011-01-24 02:44:41 +0000 |
76 | +++ loco_directory/meetings/forms.py 2011-01-24 02:52:15 +0000 |
77 | @@ -4,13 +4,23 @@ |
78 | from django.utils.translation import ugettext as _ |
79 | from django.core.urlresolvers import reverse |
80 | |
81 | -from models import BaseMeeting, TeamMeeting |
82 | +from models import BaseMeeting, TeamMeeting, AgendaItem |
83 | from common.forms import RenderableMixin |
84 | from userprofiles.models import UserProfile |
85 | |
86 | import pytz |
87 | import urllib |
88 | |
89 | +def grouped_user_list(teams): |
90 | + other_members, team_members = [], [] |
91 | + for profile in UserProfile.objects.filter(user__groups__name__in=teams): |
92 | + team_members.append((profile.id, str(profile))) |
93 | + for profile in UserProfile.objects.all().exclude(pk__in=[m[0] for m in team_members]): |
94 | + other_members.append((profile.id, str(profile))) |
95 | + |
96 | + return [('', '---------'), |
97 | + (_('Team members'), team_members), |
98 | + (_('Other users'), other_members)] |
99 | |
100 | class BaseMeetingForm(forms.ModelForm, RenderableMixin): |
101 | """ |
102 | @@ -60,22 +70,10 @@ |
103 | def __init__(self, teams=None, *args, **kargs): |
104 | super(TeamMeetingForm, self).__init__(*args, **kargs) |
105 | if teams: |
106 | - self.fields['chair'].choices = self.grouped_user_list(teams) |
107 | - self.fields['channel'].initial = teams[0].irc_chan |
108 | - elif self.instance: |
109 | - self.fields['chair'].choices = self.grouped_user_list(self.instance.teams.iterator()) |
110 | + self.fields['chair'].choices = grouped_user_list(teams) |
111 | + elif self.instance.teams: |
112 | + self.fields['chair'].choices = grouped_user_list([team.lp_name for team in self.instance.teams.iterator()]) |
113 | |
114 | - def grouped_user_list(self, teams): |
115 | - print dir(teams) |
116 | - other_members, team_members = [], [] |
117 | - for profile in UserProfile.objects.filter(user__groups__name__in=teams): |
118 | - team_members.append((profile.id, str(profile))) |
119 | - for profile in UserProfile.objects.all().exclude(user__groups__name__in=teams): |
120 | - other_members.append((profile.id, str(profile))) |
121 | - return [('', '---------'), |
122 | - (_('Team members'), team_members), |
123 | - (_('Other users'), other_members)] |
124 | - |
125 | def save(self): |
126 | start_date = self.cleaned_data['date_begin'] |
127 | self.instance.logs = 'http://irclogs.ubuntu.com/%(date)s/%(channel)s.html#t%(time)s' % {'date': start_date.strftime('%Y/%m/%d'), |
128 | @@ -83,4 +81,38 @@ |
129 | 'time': start_date.strftime('%H:%M')} |
130 | return super(TeamMeetingForm, self).save() |
131 | |
132 | - |
133 | +class AgendaItemForm(forms.ModelForm, RenderableMixin): |
134 | + |
135 | + class Meta: |
136 | + model = AgendaItem |
137 | + exclude = ('meeting','created_date') |
138 | + |
139 | + def __init__(self, *args, **kargs): |
140 | + super(AgendaItemForm, self).__init__(*args, **kargs) |
141 | + parent_choices = AgendaItem.objects.filter(meeting=self.instance.meeting).exclude(pk=self.instance.id) |
142 | + self.fields['parent'].choices = [('', '')]+[(item.id, unicode(item)) for item in parent_choices if item not in self.instance.descendents] |
143 | + if self.instance: |
144 | + meeting = self.instance.meeting.teammeeting |
145 | + self.fields['owner'].choices = grouped_user_list(meeting.teams.iterator()) |
146 | + |
147 | + |
148 | +class AgendaItemFormSet(forms.models.BaseInlineFormSet): |
149 | + |
150 | + def __init__(self, *args, **kargs): |
151 | + super(AgendaItemFormSet, self).__init__(*args, **kargs) |
152 | + self.node_tree = {} |
153 | + |
154 | + def add_fields(self, form, index): |
155 | + super(AgendaItemFormSet, self).add_fields(form, index) |
156 | + self.fields['node_id'] = IntegerField(label=_(u'Node'), |
157 | + initial=index, |
158 | + required=False) |
159 | + self.node_tree[form.instance.id] = index |
160 | + self.fields['parent_node'] = IntegerField(label=_(u'Parent Node'), |
161 | + initial=self.node_tree.get(form.instance.parent, None), |
162 | + required=False) |
163 | + |
164 | + def save(self): |
165 | + pass |
166 | + def as_tree(self): |
167 | + pass |
168 | |
169 | === modified file 'loco_directory/meetings/models.py' |
170 | --- loco_directory/meetings/models.py 2011-01-10 16:08:59 +0000 |
171 | +++ loco_directory/meetings/models.py 2011-01-24 02:52:15 +0000 |
172 | @@ -92,6 +92,9 @@ |
173 | |
174 | class AgendaItemManager(models.Manager): |
175 | |
176 | + def top(self): |
177 | + return self.filter(parent__isnull=True).order_by('order') |
178 | + |
179 | def as_tree(self): |
180 | cache = {} |
181 | tree = [] |
182 | @@ -109,27 +112,42 @@ |
183 | class AgendaItem(models.Model): |
184 | |
185 | class Meta: |
186 | - ordering = ('parent__id', 'order') |
187 | + ordering = ('parent__id', 'order', 'created_date') |
188 | |
189 | meeting = models.ForeignKey(BaseMeeting, verbose_name=_('Meeting'), related_name='agenda', help_text=_('meeting during which this agenda item is to be discussed')) |
190 | - parent = models.ForeignKey('self', verbose_name=_('Parent Agenda Item'), related_name='children', help_text=_('agenda item that contains this item'), blank=True, null=True) |
191 | - order = models.PositiveIntegerField(verbose_name=_('Order'), help_text=_('index number of where this item falls in the agenda')) |
192 | + title = models.CharField(verbose_name=_('Title'), max_length = 150, help_text=_('descriptive name for this item')) |
193 | owner = models.ForeignKey(UserProfile, verbose_name=_('Owner'), help_text=_('person proposing or responsible for this item')) |
194 | created_date = models.DateTimeField(verbose_name=_('Created Date'), auto_now_add=True, help_text=_('timestamp of when this item was created')) |
195 | - title = models.CharField(verbose_name=_('Title'), max_length = 150, help_text=_('descriptive name for this item')) |
196 | description = models.TextField(verbose_name=_('Description'), help_text=_('detailed description of this item'), blank=True, null=True) |
197 | + parent = models.ForeignKey('self', verbose_name=_('Parent Agenda Item'), related_name='children', help_text=_('agenda item that contains this item'), blank=True, null=True) |
198 | + order = models.PositiveIntegerField(verbose_name=_('Order'), help_text=_('index number of where this item falls in the agenda'), default=1) |
199 | log = models.URLField(verbose_name=_('Log URL'), max_length=200, verify_exists=False, help_text=_('URL to this item\'s discussion'), blank=True, null=True) |
200 | |
201 | objects = AgendaItemManager() |
202 | |
203 | + def get_descendents(self): |
204 | + descendents = [] |
205 | + if self.id is None: |
206 | + return descendents |
207 | + for child in self.children.all(): |
208 | + descendents.append(child) |
209 | + descendents.extend(child.descendents) |
210 | + return descendents |
211 | + descendents = property(get_descendents) |
212 | + |
213 | def get_sig(self): |
214 | - return '<a href="http://launchpad.net/~%s">%s</a> %s' % (self.owner.user.username, self.owner.realname, self.created_date) |
215 | + return '<a href="http://launchpad.net/~%s" target="launchpaduser">%s</a> %s' % (self.owner.user.username, self.owner.realname, self.created_date) |
216 | |
217 | sig = property(get_sig) |
218 | |
219 | + def save(self, *args, **kargs): |
220 | + if not self.created_date: |
221 | + self.created_date = datetime.datetime.now() |
222 | + return super(AgendaItem, self).save(*args, **kargs) |
223 | + |
224 | def __unicode__(self): |
225 | if self.parent is None: |
226 | - return '%s: %s' % (self.meeting, self.title) |
227 | + return '%s' % self.title |
228 | else: |
229 | return '%s->%s' % (self.parent, self.title) |
230 | |
231 | |
232 | === added directory 'loco_directory/meetings/templatetags' |
233 | === added file 'loco_directory/meetings/templatetags/__init__.py' |
234 | === added file 'loco_directory/meetings/templatetags/recurse.py' |
235 | --- loco_directory/meetings/templatetags/recurse.py 1970-01-01 00:00:00 +0000 |
236 | +++ loco_directory/meetings/templatetags/recurse.py 2011-01-24 02:52:15 +0000 |
237 | @@ -0,0 +1,82 @@ |
238 | +############################################################################### |
239 | +# Recurse template tag for Django v1.1 |
240 | +# Copyright (C) 2008 Lucas Murray |
241 | +# http://www.undefinedfire.com |
242 | +# |
243 | +# This program is free software: you can redistribute it and/or modify |
244 | +# it under the terms of the GNU Lesser General Public License as published |
245 | +# by the Free Software Foundation, either version 3 of the License, or |
246 | +# (at your option) any later version. |
247 | +# |
248 | +# This program is distributed in the hope that it will be useful, |
249 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
250 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
251 | +# GNU Lesser General Public License for more details. |
252 | +############################################################################### |
253 | + |
254 | +from django import template |
255 | + |
256 | +register = template.Library() |
257 | + |
258 | +class RecurseNode(template.Node): |
259 | + def __init__(self, var, name, child, nodeList): |
260 | + self.var = var |
261 | + self.name = name |
262 | + self.child = child |
263 | + self.nodeList = nodeList |
264 | + |
265 | + def __repr__(self): |
266 | + return '<RecurseNode>' |
267 | + |
268 | + def renderCallback(self, context, vals, level): |
269 | + output = [] |
270 | + try: |
271 | + if len(vals): |
272 | + pass |
273 | + except: |
274 | + vals = [vals] |
275 | + if len(vals): |
276 | + if 'loop' in self.nodeList: |
277 | + output.append(self.nodeList['loop'].render(context)) |
278 | + for val in vals: |
279 | + context.push() |
280 | + context['level'] = level |
281 | + context[self.name] = val |
282 | + if 'child' in self.nodeList: |
283 | + output.append(self.nodeList['child'].render(context)) |
284 | + child = self.child.resolve(context) |
285 | + if child: |
286 | + output.append(self.renderCallback(context, child, level + 1)) |
287 | + if 'endloop' in self.nodeList: |
288 | + output.append(self.nodeList['endloop'].render(context)) |
289 | + else: |
290 | + output.append(self.nodeList['endrecurse'].render(context)) |
291 | + context.pop() |
292 | + if 'endloop' in self.nodeList: |
293 | + output.append(self.nodeList['endrecurse'].render(context)) |
294 | + return ''.join(output) |
295 | + |
296 | + def render(self, context): |
297 | + vals = self.var.resolve(context) |
298 | + output = self.renderCallback(context, vals, 1) |
299 | + return output |
300 | + |
301 | +def do_recurse(parser, token): |
302 | + bits = list(token.split_contents()) |
303 | + if len(bits) != 6 and bits[2] != 'with' and bits[4] != 'as': |
304 | + raise template.TemplateSyntaxError, "Invalid tag syxtax expected '{% recurse [childVar] with [parents] as [parent] %}'" |
305 | + child = parser.compile_filter(bits[1]) |
306 | + var = parser.compile_filter(bits[3]) |
307 | + name = bits[5] |
308 | + |
309 | + nodeList = {} |
310 | + while len(nodeList) < 4: |
311 | + temp = parser.parse(('child','loop','endloop','endrecurse')) |
312 | + tag = parser.tokens[0].contents |
313 | + nodeList[tag] = temp |
314 | + parser.delete_first_token() |
315 | + if tag == 'endrecurse': |
316 | + break |
317 | + |
318 | + return RecurseNode(var, name, child, nodeList) |
319 | +do_recurse = register.tag('recurse', do_recurse) |
320 | \ No newline at end of file |
321 | |
322 | === modified file 'loco_directory/meetings/urls.py' |
323 | --- loco_directory/meetings/urls.py 2010-12-02 09:12:53 +0000 |
324 | +++ loco_directory/meetings/urls.py 2011-01-24 02:52:15 +0000 |
325 | @@ -16,4 +16,7 @@ |
326 | url(r'^team/(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/ical/$', 'meetings.views.team_meeting_list_ical', name='team-meeting-list-ical'), |
327 | url(r'^team/add/$', 'meetings.views.team_meeting_select', name='team-meeting-select'), |
328 | |
329 | + url(r'^team/(?P<team_meeting_id>\d+)/agenda/(?P<agenda_item_id>\d+)/delete/$', 'meetings.views.agenda_item_delete', name='agenda-item-delete'), |
330 | + url(r'^team/(?P<team_meeting_id>\d+)/agenda/(?P<agenda_item_id>\d+)/update/$', 'meetings.views.agenda_item_update', name='agenda-item-update'), |
331 | + url(r'^team/(?P<team_meeting_id>\d+)/agenda/add/$', 'meetings.views.agenda_item_new', name='agenda-item-new'), |
332 | ) |
333 | |
334 | === modified file 'loco_directory/meetings/views.py' |
335 | --- loco_directory/meetings/views.py 2011-01-18 20:44:47 +0000 |
336 | +++ loco_directory/meetings/views.py 2011-01-24 02:52:15 +0000 |
337 | @@ -6,15 +6,16 @@ |
338 | from django.utils.translation import ugettext as _ |
339 | from django.core.urlresolvers import reverse |
340 | |
341 | -from meetings.models import TeamMeeting |
342 | +from meetings.models import TeamMeeting, AgendaItem |
343 | from teams.models import Team |
344 | |
345 | -from forms import TeamMeetingForm |
346 | +from forms import TeamMeetingForm, AgendaItemForm |
347 | from django.db.models import Q |
348 | |
349 | from common.utils import redirect, simple_iterator |
350 | from common import launchpad |
351 | |
352 | +from userprofiles.models import UserProfile |
353 | import datetime |
354 | |
355 | def meeting_list(request): |
356 | @@ -172,16 +173,18 @@ |
357 | is_on_lc = launchpad.is_user_on_loco_council(request.user) |
358 | is_member = launchpad.is_team_member(request.user, team_object) |
359 | |
360 | + team_meeting_object = TeamMeeting(chair=team_object.owner_profile, |
361 | + channel=team_object.irc_chan) |
362 | if is_on_lc or is_member: |
363 | if request.method == 'POST': |
364 | - form = TeamMeetingForm(data=request.POST, teams=[team_object]) |
365 | + form = TeamMeetingForm(data=request.POST, teams=[team_object], instance=team_meeting_object) |
366 | if form.is_valid(): |
367 | team_meeting = form.save() |
368 | team_meeting.teams.add(team_object) |
369 | team_meeting_id = team_meeting.id |
370 | return HttpResponseRedirect(reverse('team-meeting-detail', kwargs={'team_meeting_id': team_meeting_id})) |
371 | else: |
372 | - form = TeamMeetingForm(teams=[team_object]) |
373 | + form = TeamMeetingForm(teams=[team_object], instance=team_meeting_object) |
374 | |
375 | context = { |
376 | 'team_object': team_object, |
377 | @@ -262,3 +265,115 @@ |
378 | else: |
379 | request.user.message_set.create(message=_('You can not update this team meeting. You are not member of the team or on the LoCo Council.')) |
380 | return redirect( team_meeting_object ) |
381 | + |
382 | +@login_required |
383 | +def agenda_item_new(request, team_meeting_id): |
384 | + """ |
385 | + new agenda item |
386 | + """ |
387 | + team_meeting_object = get_object_or_404(TeamMeeting, pk=team_meeting_id) |
388 | + try: |
389 | + user = UserProfile.objects.get(user=request.user) |
390 | + agenda_item_object = AgendaItem(meeting=team_meeting_object, |
391 | + owner=user, |
392 | + created_date=datetime.datetime.now()) |
393 | + except UserProfile.DoesNotExist: |
394 | + agenda_item_object = AgendaItem(meeting=team_meeting_object, |
395 | + created_date=datetime.datetime.now()) |
396 | + is_member = False |
397 | + for team in team_meeting_object.teams.all(): |
398 | + if launchpad.is_team_member(request.user, team): |
399 | + is_member = True |
400 | + break |
401 | + is_on_lc = launchpad.is_user_on_loco_council(request.user) |
402 | + |
403 | + if is_on_lc or is_member: |
404 | + if request.method == 'POST': |
405 | + form = AgendaItemForm(data=request.POST, instance=agenda_item_object) |
406 | + if form.is_valid(): |
407 | + agenda_item_object = form.save() |
408 | + request.user.message_set.create(message=_('Meeting agenda updated.')) |
409 | + return redirect( team_meeting_object ) |
410 | + else: |
411 | + form = AgendaItemForm(instance=agenda_item_object) |
412 | + |
413 | + context = { |
414 | + 'team_meeting_object': team_meeting_object, |
415 | + 'form': form, |
416 | + } |
417 | + return render_to_response('meetings/agenda_item_new.html', |
418 | + context, RequestContext(request)) |
419 | + else: |
420 | + # XXX: Once we move to a new ACL system, this needs fixing. |
421 | + request.user.message_set.create(message=_('You can not add a new agenda item for this team meeting. You are not member of the team or on the LoCo Council.')) |
422 | + return redirect( team_meeting_object ) |
423 | + |
424 | +@login_required |
425 | +def agenda_item_update(request, team_meeting_id, agenda_item_id): |
426 | + """ |
427 | + update agenda item |
428 | + """ |
429 | + team_meeting_object = get_object_or_404(TeamMeeting, pk=team_meeting_id) |
430 | + agenda_item_object = get_object_or_404(AgendaItem, pk=agenda_item_id) |
431 | + #check if user is admin or owner of a team |
432 | + is_member = False |
433 | + for team in team_meeting_object.teams.all(): |
434 | + if launchpad.is_team_member(request.user, team): |
435 | + is_member = True |
436 | + break |
437 | + |
438 | + is_on_lc = launchpad.is_user_on_loco_council(request.user) |
439 | + |
440 | + if is_on_lc or is_member: |
441 | + if request.method == 'POST': |
442 | + form = AgendaItemForm(data=request.POST, instance=agenda_item_object) |
443 | + if form.is_valid(): |
444 | + form.save() |
445 | + request.user.message_set.create(message=_('Meeting agenda updated.')) |
446 | + return redirect( team_meeting_object ) |
447 | + else: |
448 | + form = AgendaItemForm(instance=agenda_item_object) |
449 | + |
450 | + context = { |
451 | + 'team_meeting_object': team_meeting_object, |
452 | + 'agenda_item_object': agenda_item_object, |
453 | + 'form': form, |
454 | + } |
455 | + return render_to_response('meetings/agenda_item_update.html', |
456 | + context, RequestContext(request)) |
457 | + else: |
458 | + request.user.message_set.create(message=_('You can not update this team meeting agenda. You are not member of the team or on the LoCo Council.')) |
459 | + return redirect( team_meeting_object ) |
460 | + |
461 | +@login_required |
462 | +def agenda_item_delete(request, team_meeting_id, agenda_item_id): |
463 | + """ |
464 | + delete an agenda item |
465 | + """ |
466 | + team_meeting_object = get_object_or_404(TeamMeeting, pk=team_meeting_id) |
467 | + agenda_item_object = get_object_or_404(AgendaItem, pk=agenda_item_id) |
468 | + #check if user is admin or owner of a team |
469 | + is_member = False |
470 | + for team in team_meeting_object.teams.all(): |
471 | + if launchpad.is_team_member(request.user, team): |
472 | + is_member = True |
473 | + break |
474 | + |
475 | + is_on_lc = launchpad.is_user_on_loco_council(request.user) |
476 | + |
477 | + if is_on_lc or is_member: |
478 | + if request.method == 'POST': |
479 | + agenda_item_object.delete() |
480 | + request.user.message_set.create(message=_('Agenda Item removed.')) |
481 | + return redirect( team_meeting_object ) |
482 | + else: |
483 | + context = { |
484 | + 'team_meeting_object': team_meeting_object, |
485 | + 'agenda_item_object': agenda_item_object} |
486 | + return render_to_response('meetings/agenda_item_delete_confirm.html', context, RequestContext(request)) |
487 | + |
488 | + else: |
489 | + request.user.message_set.create(message=_('You can not remove this agenda item. You are not admin/owner of the Launchpad team or on the LoCo Council.')) |
490 | + return redirect( team_meeting_object ) |
491 | + |
492 | + |
493 | |
494 | === modified file 'loco_directory/services/urls.py' |
495 | --- loco_directory/services/urls.py 2010-12-02 07:34:24 +0000 |
496 | +++ loco_directory/services/urls.py 2011-01-24 02:52:15 +0000 |
497 | @@ -12,6 +12,7 @@ |
498 | url(r'^continents/(.*)$', 'services.views.continent_service', name='continent_service'), |
499 | url(r'^events/(.*)$', 'services.views.team_event_service', name='team_event_service'), |
500 | url(r'^meeting/(.*)$', 'services.views.meeting_service', name='meeting_service'), |
501 | + url(r'^agenda/(.*)$', 'services.views.meeting_agenda_service', name='meeting_agenda_service'), |
502 | url(r'^global/(.*)$', 'services.views.global_event_service', name='global_event_service'), |
503 | url(r'^comments/(.*)$', 'services.views.event_comment_service', name='event_comment_service'), |
504 | url(r'^attendees/(.*)$', 'services.views.event_attendee_service', name='event_attendee_service'), |
505 | |
506 | === modified file 'loco_directory/services/views.py' |
507 | --- loco_directory/services/views.py 2010-12-19 19:20:44 +0000 |
508 | +++ loco_directory/services/views.py 2011-01-24 02:52:15 +0000 |
509 | @@ -1,6 +1,6 @@ |
510 | from teams.models import Team, Continent, Country, Language |
511 | from events.models import GlobalEvent, TeamEvent, TeamEventComment, Attendee |
512 | -from meetings.models import TeamMeeting |
513 | +from meetings.models import TeamMeeting, AgendaItem |
514 | from venues.models import Venue |
515 | from userprofiles.models import UserProfile |
516 | from django.contrib.auth.models import User, Group |
517 | @@ -22,6 +22,9 @@ |
518 | def meeting_service(request, url): |
519 | return model_service(TeamMeeting, request, url) |
520 | |
521 | +def meeting_agenda_service(request, url): |
522 | + return model_service(AgendaItem, request, url) |
523 | + |
524 | def global_event_service(request, url): |
525 | return model_service(GlobalEvent, request, url) |
526 | |
527 | |
528 | === added file 'loco_directory/templates/meetings/agenda_item_delete_confirm.html' |
529 | --- loco_directory/templates/meetings/agenda_item_delete_confirm.html 1970-01-01 00:00:00 +0000 |
530 | +++ loco_directory/templates/meetings/agenda_item_delete_confirm.html 2011-01-24 02:52:15 +0000 |
531 | @@ -0,0 +1,43 @@ |
532 | +{% extends "base.html" %} |
533 | +{% load i18n %} |
534 | +{% load recurse %} |
535 | + |
536 | +{% block title %}{% trans "Delete Agenda Item" %} | {% trans "Ubuntu LoCo Team Directory" %} {% endblock %} |
537 | + |
538 | +{% block sub_nav_links %} |
539 | +<a class="sub-nav-item" href="{% url team-meeting-detail team_meeting_object.id %}">{% trans "Back to Meeting Details" %}</a> |
540 | +{% endblock %} |
541 | + |
542 | +{% block extrahead %}{{ block.super }} |
543 | +{{form.media}} |
544 | +{% endblock %} |
545 | + |
546 | +{% block content %} |
547 | +<article class="main-content"> |
548 | +<h2>{% trans "Delete Agenda Item" %}</h2> |
549 | + |
550 | +{% if agenda_item_object.children.all %} |
551 | +<p> |
552 | +Deleting this Agenda Item will also delete the following Agenda Items: |
553 | +</p> |
554 | +{% recurse item.children.all with agenda_item_object.children.all as item %} |
555 | + <ol class="agenda-list"> |
556 | + {% loop %} |
557 | + <li class="agenda-item"> |
558 | + <a class="agenda-title" title="{% trans 'Update Agenda Item:' %} {{item.title}}" href="{% url agenda-item-update team_meeting_object.id item.id %}">{{ item.title }}</a> |
559 | + - <a class="agenda-sig" target="launchpaduser" href="https://launchpad.net/~{{ item.owner.user.username}}">{{ item.owner.realname }}</a> |
560 | + @ {{ item.created_date|date:"D, d N Y H:i T" }} |
561 | + </li> |
562 | + {% child %} |
563 | + {% endloop %} |
564 | + </ol> |
565 | +{% endrecurse %} |
566 | +{% endif %} |
567 | + |
568 | +<p>{% blocktrans with agenda_item_object.title as itemname %}Do you really want to delete the Agenda Item: {{itemname}}?{% endblocktrans %}</p> |
569 | + |
570 | +<form action="." method="post"> |
571 | + <p><input type="submit" value="Submit" /> <a href="{{team_meeting_object.get_absolute_url}}">{% trans "Cancel" %}</a></p> |
572 | +</form> |
573 | +</article> |
574 | +{% endblock %} |
575 | |
576 | === added file 'loco_directory/templates/meetings/agenda_item_new.html' |
577 | --- loco_directory/templates/meetings/agenda_item_new.html 1970-01-01 00:00:00 +0000 |
578 | +++ loco_directory/templates/meetings/agenda_item_new.html 2011-01-24 02:52:15 +0000 |
579 | @@ -0,0 +1,36 @@ |
580 | +{% extends "base.html" %} |
581 | +{% load i18n %} |
582 | + |
583 | +{% block title %}{% trans "New Agenda Item" %} | {% trans "Ubuntu LoCo Team Directory" %} {% endblock %} |
584 | + |
585 | +{% block sub_nav_links %} |
586 | +<a class="sub-nav-item" href="{% url team-meeting-detail team_meeting_object.id %}">{% trans "Back to Meeting Details" %}</a> |
587 | +{% endblock %} |
588 | + |
589 | +{% block extrahead %}{{ block.super }} |
590 | +{{form.media}} |
591 | +{% endblock %} |
592 | + |
593 | +{% block extrafooter %} |
594 | +<script type="text/javascript"><!-- |
595 | +$(document).ready(function(){ |
596 | + $('span[rel*=help]').colorTip({color:'orange'}); |
597 | +}); |
598 | +--></script> |
599 | +{% endblock %} |
600 | + |
601 | +{% block content %} |
602 | +<article class="main-content"> |
603 | +<h2>{% trans "Add new Agenda Item for " %}{{ team_meeting_object.name}}</h2> |
604 | +<form action="." method="post"> |
605 | + <div class="form" style="width:auto;"> |
606 | + {{ form.as_template }} |
607 | + <div> |
608 | + {% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %} |
609 | + <input type="submit" name="submit" value="{% trans "Submit" %}" class="submit-button" /> |
610 | + </div> |
611 | + </div> |
612 | +</form> |
613 | +</article> |
614 | + |
615 | +{% endblock %} |
616 | |
617 | === added file 'loco_directory/templates/meetings/agenda_item_update.html' |
618 | --- loco_directory/templates/meetings/agenda_item_update.html 1970-01-01 00:00:00 +0000 |
619 | +++ loco_directory/templates/meetings/agenda_item_update.html 2011-01-24 02:52:15 +0000 |
620 | @@ -0,0 +1,37 @@ |
621 | +{% extends "base.html" %} |
622 | +{% load i18n admin_modify adminmedia %} |
623 | + |
624 | +{% block title %}{% trans "Update Agenda Item" %} | {% trans "Ubuntu LoCo Team Directory" %} {% endblock %} |
625 | + |
626 | +{% block sub_nav_links %} |
627 | +<a class="sub-nav-item" href="{% url team-meeting-detail team_meeting_object.id %}">{% trans "Back to Meeting Details" %}</a> |
628 | +<a class="sub-nav-item" href="{% url agenda-item-delete team_meeting_object.id agenda_item_object.id %}">{% trans "Delete Agenda Item" %}</a> |
629 | +{% endblock %} |
630 | + |
631 | +{% block extrahead %}{{ block.super }} |
632 | +{{form.media}} |
633 | +{% endblock %} |
634 | + |
635 | +{% block extrafooter %} |
636 | +<script type="text/javascript"><!-- |
637 | +$(document).ready(function(){ |
638 | + $('span[rel*=help]').colorTip({color:'orange'}); |
639 | +}); |
640 | +--></script> |
641 | +{% endblock %} |
642 | + |
643 | +{% block content %} |
644 | +<article class="main-content"> |
645 | +<h2>{% trans "Update Agenda Item" %}</h2> |
646 | +<form action="." method="post"> |
647 | + <div class="form" style="width:auto;"> |
648 | + {{ form.as_template }} |
649 | + <div> |
650 | + {% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %} |
651 | + <input type="submit" name="submit" value="{% trans "Submit" %}" class="submit-button" /> |
652 | + </div> |
653 | + </div> |
654 | +</form> |
655 | +</article> |
656 | + |
657 | +{% endblock %} |
658 | |
659 | === modified file 'loco_directory/templates/meetings/team_meeting_detail_agenda.inc.html' |
660 | --- loco_directory/templates/meetings/team_meeting_detail_agenda.inc.html 2010-12-16 13:01:35 +0000 |
661 | +++ loco_directory/templates/meetings/team_meeting_detail_agenda.inc.html 2011-01-24 02:52:15 +0000 |
662 | @@ -1,10 +1,24 @@ |
663 | {% load i18n %} |
664 | +{% load recurse %} |
665 | + <p> |
666 | + <a href="{% url agenda-item-new team_meeting_object.id %}">{% trans 'Add Agenda Item' %}</a> |
667 | + </p> |
668 | {% if team_meeting_object.agenda %} |
669 | -<ol class="agenda-list"> |
670 | -{% for item in team_meeting_object.agenda.as_tree %} |
671 | -{{item.as_ol|safe}} |
672 | -{% endfor %} |
673 | -</ol> |
674 | + {% recurse item.children.all with team_meeting_object.agenda.top as item %} |
675 | + <ol class="agenda-list"> |
676 | + {% loop %} |
677 | + <li class="agenda-item"> |
678 | + <a class="agenda-title" title="{% trans 'Update Agenda Item:' %} {{item.title}}" href="{% url agenda-item-update team_meeting_object.id item.id %}">{{ item.title }}</a> |
679 | + - <a class="agenda-sig" target="launchpaduser" href="https://launchpad.net/~{{ item.owner.user.username}}">{{ item.owner.realname }}</a> |
680 | + @ {{ item.created_date|date:"D, d N Y H:i T" }} |
681 | + {% if item.description %} |
682 | + <div class="agenda-description">{{ item.description|linebreaks }}</div> |
683 | + {% endif %} |
684 | + </li> |
685 | + {% child %} |
686 | + {% endloop %} |
687 | + </ol> |
688 | + {% endrecurse %} |
689 | {% else %} |
690 | <p>{% trans "There are currently no items on the agenda." %}</p> |
691 | {% endif %} |
692 | |
693 | === modified file 'loco_directory/templates/site_search.html' |
694 | --- loco_directory/templates/site_search.html 2010-11-20 18:33:39 +0000 |
695 | +++ loco_directory/templates/site_search.html 2011-01-24 02:52:15 +0000 |
696 | @@ -57,6 +57,22 @@ |
697 | <hr class="divide" /> |
698 | {% endif %} |
699 | |
700 | +{% if meetings %} |
701 | +<article class="main-content"> |
702 | + <h2>{% trans "Meetings" %}</h2> |
703 | + {{colcycle.reset}} |
704 | + <ul> |
705 | + {% for meeting in meetings %} |
706 | + <li class="{{colcycle.next}}"><a href="{{ meeting.get_absolute_url }}">{{ meeting.name }}</a> |
707 | + {{ meeting.date_begin|date:"D, d N Y H:i T" }} |
708 | + - {% for team in meeting.teams.all %}<a title="{% trans "more information about this team" %}" href="{{ team.get_absolute_url }}">{{ team.name }}</a>{% endfor %} |
709 | + </li> |
710 | + {% endfor %} |
711 | + </ul> |
712 | +</article> |
713 | +<hr class="divide" /> |
714 | +{% endif %} |
715 | + |
716 | {% else %} |
717 | <article class="main-content alone"> |
718 | <center> |
Visual review looks good, but does it not require a migration script for the model changes?