Michael Hall has proposed merging lp:~mhall119/loco-directory/686268 into
lp:loco-directory.
Requested reviews:
loco-directory-dev (loco-directory-dev)
Related bugs:
#686268 Add the ability to add agenda items
https://bugs.launchpad.net/bugs/686268
For more details, see:
https://code.launchpad.net/~mhall119/loco-directory/686268/+merge/47162
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.
--
https://code.launchpad.net/~mhall119/loco-directory/686268/+merge/47162
Your team loco-directory-dev is requested to review the proposed merge of
lp:~mhall119/loco-directory/686268 into lp:loco-directory.
=== modified file 'loco_directory/media/css/newstyle.css'
--- loco_directory/media/css/newstyle.css 2011-01-02 18:45:45 +0000
+++ loco_directory/media/css/newstyle.css 2011-01-22 16:08:54 +0000
@@ -434,3 +434,28 @@
.attendee-mugshot {
vertical-align: middle;
}
+
+.agenda-list {
+ list-style-type: decimal;
+}
+
+.agenda-list .agenda-list {
+ list-style-type: lower-alpha;
+}
+
+.agenda-list .agenda-list .agenda-list {
+ list-style-type: lower-roman;
+}
+
+.agenda-list .agenda-list .agenda-list .agenda-list {
+ list-style-type: circle;
+}
+
+.agenda-title {
+ font-weight: bold;
+}
+
+.agenda-description {
+ margin-left: 50px;
+ font-size: 0.9em;
+}
=== modified file 'loco_directory/meetings/forms.py'
--- loco_directory/meetings/forms.py 2011-01-18 21:13:08 +0000
+++ loco_directory/meetings/forms.py 2011-01-22 16:08:54 +0000
@@ -4,13 +4,23 @@
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
-from models import BaseMeeting, TeamMeeting
+from models import BaseMeeting, TeamMeeting, AgendaItem
from common.forms import RenderableMixin
from userprofiles.models import UserProfile
import pytz
import urllib
+def grouped_user_list(teams):
+ other_members, team_members = [], []
+ for profile in UserProfile.objects.filter(user__groups__name__in=teams):
+ team_members.append((profile.id, str(profile)))
+ for profile in UserProfile.objects.all().exclude(pk__in=[m[0] for m in team_members]):
+ other_members.append((profile.id, str(profile)))
+
+ return [('', '---------'),
+ (_('Team members'), team_members),
+ (_('Other users'), other_members)]
class BaseMeetingForm(forms.ModelForm, RenderableMixin):
"""
@@ -60,22 +70,11 @@
def __init__(self, teams=None, *args, **kargs):
super(TeamMeetingForm, self).__init__(*args, **kargs)
if teams:
- self.fields['chair'].choices = self.grouped_user_list(teams)
+ self.fields['chair'].choices = grouped_user_list(teams)
self.fields['channel'].initial = teams[0].irc_chan
elif self.instance:
- self.fields['chair'].choices = self.grouped_user_list(teams.iterator())
+ self.fields['chair'].choices = grouped_user_list([team.lp_name for team in self.instance.teams.iterator()])
- def grouped_user_list(self, teams):
- print dir(teams)
- other_members, team_members = [], []
- for profile in UserProfile.objects.filter(user__groups__name__in=teams):
- team_members.append((profile.id, str(profile)))
- for profile in UserProfile.objects.all().exclude(user__groups__name__in=teams):
- other_members.append((profile.id, str(profile)))
- return [('', '---------'),
- (_('Team members'), team_members),
- (_('Other users'), other_members)]
-
def save(self):
start_date = self.cleaned_data['date_begin']
self.instance.logs = 'http://irclogs.ubuntu.com/%(date)s/%(channel)s.html#t%(time)s' % {'date': start_date.strftime('%Y/%m/%d'),
@@ -83,4 +82,38 @@
'time': start_date.strftime('%H:%M')}
return super(TeamMeetingForm, self).save()
-
+class AgendaItemForm(forms.ModelForm, RenderableMixin):
+
+ class Meta:
+ model = AgendaItem
+ exclude = ('meeting','created_date')
+
+ def __init__(self, *args, **kargs):
+ super(AgendaItemForm, self).__init__(*args, **kargs)
+ parent_choices = AgendaItem.objects.filter(meeting=self.instance.meeting).exclude(pk=self.instance.id)
+ self.fields['parent'].choices = [('', '')]+[(item.id, str(item)) for item in parent_choices]
+ if self.instance:
+ meeting = self.instance.meeting.teammeeting
+ print dir(meeting)
+ self.fields['owner'].choices = grouped_user_list(meeting.teams.iterator())
+
+class AgendaItemFormSet(forms.models.BaseInlineFormSet):
+
+ def __init__(self, *args, **kargs):
+ super(AgendaItemFormSet, self).__init__(*args, **kargs)
+ self.node_tree = {}
+
+ def add_fields(self, form, index):
+ super(AgendaItemFormSet, self).add_fields(form, index)
+ self.fields['node_id'] = IntegerField(label=_(u'Node'),
+ initial=index,
+ required=False)
+ self.node_tree[form.instance.id] = index
+ self.fields['parent_node'] = IntegerField(label=_(u'Parent Node'),
+ initial=self.node_tree.get(form.instance.parent, None),
+ required=False)
+
+ def save(self):
+ pass
+ def as_tree(self):
+ pass
=== modified file 'loco_directory/meetings/models.py'
--- loco_directory/meetings/models.py 2011-01-10 16:08:59 +0000
+++ loco_directory/meetings/models.py 2011-01-22 16:08:54 +0000
@@ -92,6 +92,9 @@
class AgendaItemManager(models.Manager):
+ def top(self):
+ return self.filter(parent__isnull=True).order_by('order')
+
def as_tree(self):
cache = {}
tree = []
@@ -109,27 +112,32 @@
class AgendaItem(models.Model):
class Meta:
- ordering = ('parent__id', 'order')
+ ordering = ('parent__id', 'order', 'created_date')
meeting = models.ForeignKey(BaseMeeting, verbose_name=_('Meeting'), related_name='agenda', help_text=_('meeting during which this agenda item is to be discussed'))
- parent = models.ForeignKey('self', verbose_name=_('Parent Agenda Item'), related_name='children', help_text=_('agenda item that contains this item'), blank=True, null=True)
- order = models.PositiveIntegerField(verbose_name=_('Order'), help_text=_('index number of where this item falls in the agenda'))
+ title = models.CharField(verbose_name=_('Title'), max_length = 150, help_text=_('descriptive name for this item'))
owner = models.ForeignKey(UserProfile, verbose_name=_('Owner'), help_text=_('person proposing or responsible for this item'))
created_date = models.DateTimeField(verbose_name=_('Created Date'), auto_now_add=True, help_text=_('timestamp of when this item was created'))
- title = models.CharField(verbose_name=_('Title'), max_length = 150, help_text=_('descriptive name for this item'))
description = models.TextField(verbose_name=_('Description'), help_text=_('detailed description of this item'), blank=True, null=True)
+ parent = models.ForeignKey('self', verbose_name=_('Parent Agenda Item'), related_name='children', help_text=_('agenda item that contains this item'), blank=True, null=True)
+ order = models.PositiveIntegerField(verbose_name=_('Order'), help_text=_('index number of where this item falls in the agenda'), default=1)
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)
objects = AgendaItemManager()
def get_sig(self):
- return '<a href="http://launchpad.net/~%s">%s</a> %s' % (self.owner.user.username, self.owner.realname, self.created_date)
+ return '<a href="http://launchpad.net/~%s" target="launchpaduser">%s</a> %s' % (self.owner.user.username, self.owner.realname, self.created_date)
sig = property(get_sig)
+ def save(self, *args, **kargs):
+ if not self.created_date:
+ self.created_date = datetime.datetime.now()
+ return super(AgendaItem, self).save(*args, **kargs)
+
def __unicode__(self):
if self.parent is None:
- return '%s: %s' % (self.meeting, self.title)
+ return '%s' % self.title
else:
return '%s->%s' % (self.parent, self.title)
=== added directory 'loco_directory/meetings/templatetags'
=== added file 'loco_directory/meetings/templatetags/__init__.py'
=== added file 'loco_directory/meetings/templatetags/recurse.py'
--- loco_directory/meetings/templatetags/recurse.py 1970-01-01 00:00:00 +0000
+++ loco_directory/meetings/templatetags/recurse.py 2011-01-22 16:08:54 +0000
@@ -0,0 +1,82 @@
+###############################################################################
+# Recurse template tag for Django v1.1
+# Copyright (C) 2008 Lucas Murray
+# http://www.undefinedfire.com
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+###############################################################################
+
+from django import template
+
+register = template.Library()
+
+class RecurseNode(template.Node):
+ def __init__(self, var, name, child, nodeList):
+ self.var = var
+ self.name = name
+ self.child = child
+ self.nodeList = nodeList
+
+ def __repr__(self):
+ return '<RecurseNode>'
+
+ def renderCallback(self, context, vals, level):
+ output = []
+ try:
+ if len(vals):
+ pass
+ except:
+ vals = [vals]
+ if len(vals):
+ if 'loop' in self.nodeList:
+ output.append(self.nodeList['loop'].render(context))
+ for val in vals:
+ context.push()
+ context['level'] = level
+ context[self.name] = val
+ if 'child' in self.nodeList:
+ output.append(self.nodeList['child'].render(context))
+ child = self.child.resolve(context)
+ if child:
+ output.append(self.renderCallback(context, child, level + 1))
+ if 'endloop' in self.nodeList:
+ output.append(self.nodeList['endloop'].render(context))
+ else:
+ output.append(self.nodeList['endrecurse'].render(context))
+ context.pop()
+ if 'endloop' in self.nodeList:
+ output.append(self.nodeList['endrecurse'].render(context))
+ return ''.join(output)
+
+ def render(self, context):
+ vals = self.var.resolve(context)
+ output = self.renderCallback(context, vals, 1)
+ return output
+
+def do_recurse(parser, token):
+ bits = list(token.split_contents())
+ if len(bits) != 6 and bits[2] != 'with' and bits[4] != 'as':
+ raise template.TemplateSyntaxError, "Invalid tag syxtax expected '{% recurse [childVar] with [parents] as [parent] %}'"
+ child = parser.compile_filter(bits[1])
+ var = parser.compile_filter(bits[3])
+ name = bits[5]
+
+ nodeList = {}
+ while len(nodeList) < 4:
+ temp = parser.parse(('child','loop','endloop','endrecurse'))
+ tag = parser.tokens[0].contents
+ nodeList[tag] = temp
+ parser.delete_first_token()
+ if tag == 'endrecurse':
+ break
+
+ return RecurseNode(var, name, child, nodeList)
+do_recurse = register.tag('recurse', do_recurse)
\ No newline at end of file
=== modified file 'loco_directory/meetings/urls.py'
--- loco_directory/meetings/urls.py 2010-12-02 09:12:53 +0000
+++ loco_directory/meetings/urls.py 2011-01-22 16:08:54 +0000
@@ -16,4 +16,6 @@
url(r'^team/(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/ical/$', 'meetings.views.team_meeting_list_ical', name='team-meeting-list-ical'),
url(r'^team/add/$', 'meetings.views.team_meeting_select', name='team-meeting-select'),
+ url(r'^team/(?P<team_meeting_id>\d+)/agenda/(?P<agenda_item_id>\d+)/$', 'meetings.views.agenda_item_update', name='agenda-item-update'),
+ url(r'^team/(?P<team_meeting_id>\d+)/agenda/add/$', 'meetings.views.agenda_item_new', name='agenda-item-new'),
)
=== modified file 'loco_directory/meetings/views.py'
--- loco_directory/meetings/views.py 2011-01-18 20:44:47 +0000
+++ loco_directory/meetings/views.py 2011-01-22 16:08:54 +0000
@@ -6,15 +6,16 @@
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
-from meetings.models import TeamMeeting
+from meetings.models import TeamMeeting, AgendaItem
from teams.models import Team
-from forms import TeamMeetingForm
+from forms import TeamMeetingForm, AgendaItemForm
from django.db.models import Q
from common.utils import redirect, simple_iterator
from common import launchpad
+from userprofiles.models import UserProfile
import datetime
def meeting_list(request):
@@ -262,3 +263,81 @@
else:
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.'))
return redirect( team_meeting_object )
+
+@login_required
+def agenda_item_new(request, team_meeting_id):
+ """
+ new agenda item
+ """
+ team_meeting_object = get_object_or_404(TeamMeeting, pk=team_meeting_id)
+ try:
+ user = UserProfile.objects.get(user=request.user)
+ agenda_item_object = AgendaItem(meeting=team_meeting_object,
+ owner=user,
+ created_date=datetime.datetime.now())
+ except UserProfile.DoesNotExist:
+ agenda_item_object = AgendaItem(meeting=team_meeting_object,
+ created_date=datetime.datetime.now())
+ is_member = False
+ for team in team_meeting_object.teams.all():
+ if launchpad.is_team_member(request.user, team):
+ is_member = True
+ break
+ is_on_lc = launchpad.is_user_on_loco_council(request.user)
+
+ if is_on_lc or is_member:
+ if request.method == 'POST':
+ form = AgendaItemForm(data=request.POST, instance=agenda_item_object)
+ if form.is_valid():
+ agenda_item_object = form.save()
+ request.user.message_set.create(message=_('Meeting agenda updated.'))
+ return redirect( team_meeting_object )
+ else:
+ form = AgendaItemForm(instance=agenda_item_object)
+
+ context = {
+ 'team_meeting_object': team_meeting_object,
+ 'form': form,
+ }
+ return render_to_response('meetings/agenda_item_new.html',
+ context, RequestContext(request))
+ else:
+ # XXX: Once we move to a new ACL system, this needs fixing.
+ 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.'))
+ return redirect( team_meeting_object )
+
+@login_required
+def agenda_item_update(request, team_meeting_id, agenda_item_id):
+ """
+ update agenda item
+ """
+ team_meeting_object = get_object_or_404(TeamMeeting, pk=team_meeting_id)
+ agenda_item_object = get_object_or_404(AgendaItem, pk=agenda_item_id)
+ #check if user is admin or owner of a team
+ is_member = False
+ for team in team_meeting_object.teams.all():
+ if launchpad.is_team_member(request.user, team):
+ is_member = True
+ break
+
+ is_on_lc = launchpad.is_user_on_loco_council(request.user)
+
+ if is_on_lc or is_member:
+ if request.method == 'POST':
+ form = AgendaItemForm(data=request.POST, instance=agenda_item_object)
+ if form.is_valid():
+ form.save()
+ request.user.message_set.create(message=_('Meeting agenda updated.'))
+ return redirect( team_meeting_object )
+ else:
+ form = AgendaItemForm(instance=agenda_item_object)
+
+ context = {
+ 'team_meeting_object': team_meeting_object,
+ 'form': form,
+ }
+ return render_to_response('meetings/agenda_item_update.html',
+ context, RequestContext(request))
+ else:
+ 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.'))
+ return redirect( team_meeting_object )
=== modified file 'loco_directory/services/urls.py'
--- loco_directory/services/urls.py 2010-12-02 07:34:24 +0000
+++ loco_directory/services/urls.py 2011-01-22 16:08:54 +0000
@@ -12,6 +12,7 @@
url(r'^continents/(.*)$', 'services.views.continent_service', name='continent_service'),
url(r'^events/(.*)$', 'services.views.team_event_service', name='team_event_service'),
url(r'^meeting/(.*)$', 'services.views.meeting_service', name='meeting_service'),
+ url(r'^agenda/(.*)$', 'services.views.meeting_agenda_service', name='meeting_agenda_service'),
url(r'^global/(.*)$', 'services.views.global_event_service', name='global_event_service'),
url(r'^comments/(.*)$', 'services.views.event_comment_service', name='event_comment_service'),
url(r'^attendees/(.*)$', 'services.views.event_attendee_service', name='event_attendee_service'),
=== modified file 'loco_directory/services/views.py'
--- loco_directory/services/views.py 2010-12-19 19:20:44 +0000
+++ loco_directory/services/views.py 2011-01-22 16:08:54 +0000
@@ -1,6 +1,6 @@
from teams.models import Team, Continent, Country, Language
from events.models import GlobalEvent, TeamEvent, TeamEventComment, Attendee
-from meetings.models import TeamMeeting
+from meetings.models import TeamMeeting, AgendaItem
from venues.models import Venue
from userprofiles.models import UserProfile
from django.contrib.auth.models import User, Group
@@ -22,6 +22,9 @@
def meeting_service(request, url):
return model_service(TeamMeeting, request, url)
+def meeting_agenda_service(request, url):
+ return model_service(AgendaItem, request, url)
+
def global_event_service(request, url):
return model_service(GlobalEvent, request, url)
=== added file 'loco_directory/templates/meetings/agenda_item_new.html'
--- loco_directory/templates/meetings/agenda_item_new.html 1970-01-01 00:00:00 +0000
+++ loco_directory/templates/meetings/agenda_item_new.html 2011-01-22 16:08:54 +0000
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block title %}{% trans "New Agenda Item" %} | {% trans "Ubuntu LoCo Team Directory" %} {% endblock %}
+
+{% block sub_nav_links %}
+<a class="sub-nav-item" href="{% url team-meeting-detail team_meeting_object.id %}">{% trans "Back to Meeting Details" %}</a>
+{% endblock %}
+
+{% block extrahead %}{{ block.super }}
+{{form.media}}
+{% endblock %}
+
+{% block extrafooter %}
+<script type="text/javascript"><!--
+$(document).ready(function(){
+ $('span[rel*=help]').colorTip({color:'orange'});
+});
+--></script>
+{% endblock %}
+
+{% block content %}
+<article class="main-content">
+<h2>{% trans "Add new Agenda Item for " %}{{ team_meeting_object.name}}</h2>
+<form action="." method="post">
+ <div class="form" style="width:auto;">
+ {{ form.as_template }}
+ <div>
+ {% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
+ <input type="submit" name="submit" value="{% trans "Submit" %}" class="submit-button" />
+ </div>
+ </div>
+</form>
+</article>
+
+{% endblock %}
=== added file 'loco_directory/templates/meetings/agenda_item_update.html'
--- loco_directory/templates/meetings/agenda_item_update.html 1970-01-01 00:00:00 +0000
+++ loco_directory/templates/meetings/agenda_item_update.html 2011-01-22 16:08:54 +0000
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+{% load i18n admin_modify adminmedia %}
+
+{% block title %}{% trans "Update Agenda Item" %} | {% trans "Ubuntu LoCo Team Directory" %} {% endblock %}
+
+{% block sub_nav_links %}
+<a class="sub-nav-item" href="{% url team-meeting-detail team_meeting_object.id %}">{% trans "Back to Meeting Details" %}</a>
+{% endblock %}
+
+{% block extrahead %}{{ block.super }}
+{{form.media}}
+{% endblock %}
+
+{% block extrafooter %}
+<script type="text/javascript"><!--
+$(document).ready(function(){
+ $('span[rel*=help]').colorTip({color:'orange'});
+});
+--></script>
+{% endblock %}
+
+{% block content %}
+<article class="main-content">
+<h2>{% trans "Update Agenda Item" %}</h2>
+<form action="." method="post">
+ <div class="form" style="width:auto;">
+ {{ form.as_template }}
+ <div>
+ {% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
+ <input type="submit" name="submit" value="{% trans "Submit" %}" class="submit-button" />
+ </div>
+ </div>
+</form>
+</article>
+
+{% endblock %}
=== modified file 'loco_directory/templates/meetings/team_meeting_detail_agenda.inc.html'
--- loco_directory/templates/meetings/team_meeting_detail_agenda.inc.html 2010-12-16 13:01:35 +0000
+++ loco_directory/templates/meetings/team_meeting_detail_agenda.inc.html 2011-01-22 16:08:54 +0000
@@ -1,10 +1,24 @@
{% load i18n %}
+{% load recurse %}
+ <p>
+ <a href="{% url agenda-item-new team_meeting_object.id %}">{% trans 'Add Agenda Item' %}</a>
+ </p>
{% if team_meeting_object.agenda %}
-<ol class="agenda-list">
-{% for item in team_meeting_object.agenda.as_tree %}
-{{item.as_ol|safe}}
-{% endfor %}
-</ol>
+ {% recurse item.children.all with team_meeting_object.agenda.top as item %}
+ <ol class="agenda-list">
+ {% loop %}
+ <li class="agenda-item">
+ <a class="agenda-title" href="{% url agenda-item-update team_meeting_object.id item.id %}">{{ item.title }}</a>
+ - <a class="agenda-sig" href="https://launchpad.net/~{{ item.owner.username}}">{{ item.owner.realname }}</a>
+ @ {{ item.created_date }}
+ {% if item.description %}
+ <div class="agenda-description">{{ item.description|linebreaks }}</div>
+ {% endif %}
+ </li>
+ {% child %}
+ {% endloop %}
+ </ol>
+ {% endrecurse %}
{% else %}
<p>{% trans "There are currently no items on the agenda." %}</p>
{% endif %}
_______________________________________________
Mailing list: https://launchpad.net/~loco-directory-dev
Post to : [email protected]
Unsubscribe : https://launchpad.net/~loco-directory-dev
More help : https://help.launchpad.net/ListHelp