diff --git a/ietf/idtracker/templatetags/ietf_filters.py b/ietf/idtracker/templatetags/ietf_filters.py index 321bc7d6a..ce216e8d7 100644 --- a/ietf/idtracker/templatetags/ietf_filters.py +++ b/ietf/idtracker/templatetags/ietf_filters.py @@ -431,6 +431,15 @@ def stable_dictsort(value, arg): decorated.sort(lambda a, b: cmp(a[0], b[0]) if a[0] and b[0] else -1 if b[0] else 1 if a[0] else 0) return [item[1] for item in decorated] +@register.filter +def ad_area(user): + if user and user.is_authenticated(): + from redesign.group.models import Group + g = Group.objects.filter(role__name="ad", role__person__user=user) + if g: + return g[0].acronym + return None + def _test(): import doctest doctest.testmod() diff --git a/ietf/ietfworkflows/forms.py b/ietf/ietfworkflows/forms.py index b6885729c..b63efc57f 100644 --- a/ietf/ietfworkflows/forms.py +++ b/ietf/ietfworkflows/forms.py @@ -22,6 +22,7 @@ from redesign.doc.utils import get_tags_for_stream_id from redesign.doc.models import save_document_in_history, DocEvent, Document from redesign.name.models import DocTagName, StreamName, RoleName from redesign.group.models import Group, GroupStateTransitions, Role +from redesign.group.utils import save_group_in_history from redesign.person.models import Person, Email class StreamDraftForm(forms.Form): @@ -351,9 +352,10 @@ class StreamDelegatesForm(forms.Form): def save(self): if settings.USE_DB_REDESIGN_PROXY_CLASSES: - # FIXME: should save group history here + stream_group = Group.objects.get(acronym=self.stream.slug) + save_group_in_history(stream_group) Role.objects.get_or_create(person=self.person, - group=Group.objects.get(acronym=self.stream.slug), + group=stream_group, name=RoleName.objects.get(slug="delegate"), email=Email.objects.get(address=self.cleaned_data.get('email'))) return diff --git a/ietf/ietfworkflows/views.py b/ietf/ietfworkflows/views.py index f24bd4d4e..49607fb31 100644 --- a/ietf/ietfworkflows/views.py +++ b/ietf/ietfworkflows/views.py @@ -19,6 +19,8 @@ from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream, is_chair_of_stream, can_adopt) from redesign.doc.utils import get_tags_for_stream_id from redesign.name.models import DocTagName +from redesign.group.utils import save_group_in_history +from redesign.group.models import Group, Role REDUCED_HISTORY_LEN = 20 @@ -125,9 +127,9 @@ def stream_delegates(request, stream_name): if request.POST.get('delete', False): pk_list = request.POST.getlist('remove_delegate') if settings.USE_DB_REDESIGN_PROXY_CLASSES: - # FIXME: should save group history here - from redesign.group.models import Role - Role.objects.filter(person__in=pk_list, group__acronym=stream.slug, name="delegate").delete() + stream_group = Group.objects.get(acronym=stream.slug) + save_group_in_history(stream_group) + Role.objects.filter(person__in=pk_list, group=stream_group, name="delegate").delete() else: StreamDelegate.objects.filter(stream=stream, person__pk__in=pk_list).delete() else: diff --git a/ietf/settings.py b/ietf/settings.py index 0e41fe8ce..e0c04dec3 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -156,6 +156,7 @@ INSTALLED_APPS = ( 'ietf.submit', 'ietf.ietfworkflows', 'ietf.wgchairs', + 'ietf.wgcharter', ) INTERNAL_IPS = ( @@ -187,6 +188,8 @@ MAX_WG_DELEGATES = 3 INTERNET_DRAFT_PATH = '/a/www/ietf-ftp/internet-drafts/' INTERNET_DRAFT_PDF_PATH = '/a/www/ietf-datatracker/pdf/' RFC_PATH = '/a/www/ietf-ftp/rfc/' +CHARTER_PATH = '/a/www/ietf-ftp/charters/' +CHARTER_TXT_URL = 'http://www.ietf.org/charters/' AGENDA_PATH = '/a/www/www6s/proceedings/' AGENDA_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/agenda/%(wg)s.%(ext)s' MINUTES_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/minutes/%(wg)s.%(ext)s' diff --git a/ietf/templates/base_leftmenu.html b/ietf/templates/base_leftmenu.html index 9eed4ec61..5d4c48db8 100644 --- a/ietf/templates/base_leftmenu.html +++ b/ietf/templates/base_leftmenu.html @@ -42,12 +42,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The comment will be added to the history trail.
+ + +{% endblock %} diff --git a/ietf/templates/wgcharter/announcement_text.html b/ietf/templates/wgcharter/announcement_text.html new file mode 100644 index 000000000..dda92b9f3 --- /dev/null +++ b/ietf/templates/wgcharter/announcement_text.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} + +{% block title %}WG {{ announcement }} announcement writeup for {{ charter.chartered_group.acronym }}{% endblock %} + +{% block morecss %} +form #id_announcement_text { + width: 700px; + height: 600px; +} +{% endblock %} + +{% block content %} +Ballot has been sent out.
+ ++Start new WG charter effort +
+ +{% if not recs %} +No WGs match your query.
+{% else %} +For help on the states, see the state table.
{% endif %} + + + +{% if prev_state %} ++ Please provide instructions regarding the disposition of each + active Internet-Draft (such as to withdraw the draft, move it to + another WG, convert it to an individual submission, and so on), + wording for the closure announcement, and the status of the WG + mailing list (will it remain open or should it be closed). +
+ + +{% endblock %} diff --git a/ietf/templates/wgcharter/date_column.html b/ietf/templates/wgcharter/date_column.html new file mode 100644 index 000000000..515e8d80a --- /dev/null +++ b/ietf/templates/wgcharter/date_column.html @@ -0,0 +1,5 @@ +{% comment %} +Copyright The IETF Trust 2011, All Rights Reserved +{% endcomment %} +{% load ietf_filters %}+Start new WG charter effort +
+ +{% if not recs %} +No WGs match your query.
+{% else %} +Too many WGs match the query! Returning partial result only.
+{% endif %} +{% if not recs %} +No WGs match your query.
+{% else %} +State | +Description | +
---|---|
{{ state.name|escape }} | +{{ state.desc|escape }} | +
The text will be submitted as charter-ietf-{{ wg.acronym }}-{{ next_rev }}
+ + +{% endblock %} diff --git a/ietf/templates/wgcharter/table_header.html b/ietf/templates/wgcharter/table_header.html new file mode 100644 index 000000000..604a92536 --- /dev/null +++ b/ietf/templates/wgcharter/table_header.html @@ -0,0 +1,15 @@ +{# Copyright The IETF Trust 2011, All Rights Reserved #} + +
+
+{% if user|in_group:"Area_Director,Secretariat" %}
+{% if user|in_group:"Area_Director" %}
+
+{% endif %}
+{% if user|in_group:"Secretariat" %}
+{% if not info.pos_block %}
+{% endif %}
+{% endif %}
+{% endif %}
+
+ Blocking Yes No Abstain No Record |
+
+
+Comments+ +{% for pos in info.positions %} +{% if pos.comment or pos.block_comment %} +{{pos.ad|escape}}+ +{% if pos.block_comment %} +Blocking ({{pos.block_comment_time}}) {{pos.block_comment|fill:"80"|escape }}+{% endif %} + +{% if pos.comment %} + Comment ({{pos.comment_time}}) {{pos.comment|fill:"80"|escape }}+{% endif %} +{% endif %} +{% endfor %} + |
Date | Version | By | Text | |||
---|---|---|---|---|---|---|
{{ c.date|date:"Y-m-d" }} | + +{% if c.is_rev %} +{{ c.info.version }} | +(System) | +New version available: charter-ietf-{{ c.group.acronym }}-{{ c.charter.rev }} {% if c.prev_charter %}(diff from -{{ c.prev_charter.rev }}){% endif %} | +{% endif %} + +{% if c.is_com %} +{{ c.info.version }} | +{{ c.info.by|escape }} | +{% if c.comment.ballot %}
+[Ballot {{ c.comment.get_ballot_display }}] +{% endif %} +{% if c.info.snipped %} + {{ c.info.textSnippet|safe }}
+[show all]
+
+{% else %}
+{{ c.info.text|fill:"80"|safe|urlize|linebreaksbr|keep_spacing|sanitize_html|safe }}
+{% endif %}
+ |
+{% endif %}
+
+
Other versions: plain text
+ ++
+{% endif %} + ++{{ info.review_text|escape|urlize }} ++
+
+{% endif %} + ++{{ info.action_text|escape|urlize }} ++ +
+
+{% endif %} + ++{{ info.ballot_text|escape|urlize }} ++ +
In addition to the charter maintained by the IETF Secretariat, there is additional information about this working group on the Web at: {% for url in wg.additional_urls %} -{{Â url.description}}{% if not forloop.last %}, {% endif %} +{{ url.name }}{% if not forloop.last %}, {% endif %} {% endfor %}
{% endif %} diff --git a/ietf/urls.py b/ietf/urls.py index ce66a6c8b..56285d3a8 100644 --- a/ietf/urls.py +++ b/ietf/urls.py @@ -9,6 +9,7 @@ from ietf.idtracker.feeds import DocumentComments, InLastCall from ietf.ipr.feeds import LatestIprDisclosures from ietf.proceedings.feeds import LatestWgProceedingsActivity from ietf.liaisons.feeds import Liaisons +from ietf.wgcharter.feeds import GroupComments from ietf.idtracker.sitemaps import IDTrackerMap, DraftMap from ietf.liaisons.sitemaps import LiaisonMap @@ -24,6 +25,7 @@ feeds = { 'iesg-agenda': IESGAgenda, 'last-call': InLastCall, 'comments': DocumentComments, + 'wgcomments': GroupComments, 'ipr': LatestIprDisclosures, 'liaison': Liaisons, 'wg-proceedings' : LatestWgProceedingsActivity @@ -59,6 +61,7 @@ urlpatterns = patterns('', (r'^accounts/', include('ietf.ietfauth.urls')), (r'^doc/', include('ietf.idrfc.urls')), (r'^wg/', include('ietf.wginfo.urls')), + (r'^wgcharter/', include('ietf.wgcharter.urls')), (r'^cookies/', include('ietf.cookies.urls')), (r'^submit/', include('ietf.submit.urls')), (r'^streams/', include('ietf.ietfworkflows.urls')), diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index e01cd207b..2e1f78d41 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -10,6 +10,14 @@ from redesign.group.models import * from redesign.person.models import * def make_test_data(): + # telechat dates + t = datetime.date.today() + old = TelechatDate.objects.create(date=t - datetime.timedelta(days=14)).date + date1 = TelechatDate.objects.create(date=t).date + date2 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14)).date + date3 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14 * 2)).date + date4 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14 * 3)).date + # groups secretariat = Group.objects.create( name="Secretariat", @@ -36,6 +44,13 @@ def make_test_data(): state_id="active", type_id="area", parent=ietf) + individ = Group.objects.create( + name="Individual submissions", + acronym="none", + state_id="active", + type_id="individ", + parent=None) + # mars WG group = Group.objects.create( name="Martian Special Interest Group", acronym="mars", @@ -43,13 +58,52 @@ def make_test_data(): type_id="wg", parent=area, ) - individ = Group.objects.create( - name="Individual submissions", - acronym="none", - state_id="active", - type_id="individ", - parent=None) - + charter = Document.objects.create( + name="charter-ietf-" + group.acronym, + type_id="charter", + title=group.name, + group=group, + rev="00", + ) + charter.set_state(State.objects.get(slug="approved", type="charter")) + group.charter = charter + group.save() + DocAlias.objects.create( + name=charter.name, + document=charter + ) + # ames WG + group = Group.objects.create( + name="Asteroid Mining Equipment Standardization Group", + acronym="ames", + state_id="proposed", + type_id="wg", + parent=area, + ) + charter = Document.objects.create( + name="charter-ietf-" + group.acronym, + type_id="charter", + title=group.name, + group=group, + rev="00", + ) + charter.set_state(State.objects.get(slug="infrev", type="charter")) + DocAlias.objects.create( + name=charter.name, + document=charter + ) + group.charter = charter + group.save() + WGAction.objects.create( + pk=group.pk, + note="", + status_date=datetime.date.today(), + agenda=1, + token_name="Aread", + category=13, + telechat_date=date2 + ) + # persons # system @@ -109,6 +163,20 @@ def make_test_data(): group=area, person=p, email=email) + else: + areahist = GroupHistory.objects.create( + group=area, + name=area.name, + acronym=area.acronym, + type_id=area.type_id, + state_id=area.state_id, + parent=area.parent + ) + RoleHistory.objects.create( + name_id="ad", + group=areahist, + person=p, + email=email) # group chair u = User.objects.create(username="marschairman") @@ -224,33 +292,7 @@ def make_test_data(): rev="00", ) - # telechat dates - t = datetime.date.today() - old = TelechatDate.objects.create(date=t - datetime.timedelta(days=14)).date - date1 = TelechatDate.objects.create(date=t).date - date2 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14)).date - date3 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14 * 2)).date - date4 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14 * 3)).date - - # WG Actions - group = Group.objects.create( - name="Asteroid Mining Equipment Standardization Group", - acronym="ames", - state_id="proposed", - type_id="wg", - parent=area, - ) - WGAction.objects.create( - pk=group.pk, - note="", - status_date=datetime.date.today(), - agenda=1, - token_name="Aread", - category=13, - telechat_date=date2 - ) - - # Meeting + # meeting Meeting.objects.create( number="42", type_id="ietf", diff --git a/ietf/wgchairs/forms.py b/ietf/wgchairs/forms.py index cc59a98bb..449c82aa1 100644 --- a/ietf/wgchairs/forms.py +++ b/ietf/wgchairs/forms.py @@ -21,6 +21,7 @@ from workflows.models import Transition from redesign.doc.models import WriteupDocEvent from redesign.person.models import Person, Email from redesign.group.models import Role, RoleName +from redesign.group.utils import save_group_in_history from redesign.name.models import DocTagName @@ -45,7 +46,7 @@ class RelatedWGForm(forms.Form): class TagForm(RelatedWGForm): - tags = forms.ModelMultipleChoiceField(AnnotationTag.objects.filter(wgworkflow__name='Default WG Workflow'), + tags = forms.ModelMultipleChoiceField(AnnotationTag.objects.filter(workflow__name='Default WG Workflow'), widget=forms.CheckboxSelectMultiple, required=False) def save(self): @@ -58,7 +59,7 @@ class TagForm(RelatedWGForm): class StateForm(RelatedWGForm): - states = forms.ModelMultipleChoiceField(State.objects.filter(wgworkflow__name='Default WG Workflow'), + states = forms.ModelMultipleChoiceField(State.objects.filter(workflow__name='Default WG Workflow'), widget=forms.CheckboxSelectMultiple, required=False) def update_transitions(self, workflow): @@ -103,7 +104,7 @@ class DeleteTransitionForm(RelatedWGForm): class TransitionForm(forms.ModelForm): - states = forms.ModelMultipleChoiceField(State.objects.filter(wgworkflow__name='Default WG Workflow')) + states = forms.ModelMultipleChoiceField(State.objects.filter(workflow__name='Default WG Workflow')) class Meta: model = Transition @@ -182,6 +183,7 @@ class RemoveDelegateForm(RelatedWGForm): def save(self): delegates = self.cleaned_data.get('delete') + save_group_in_history(self.wg) WGDelegate.objects.filter(pk__in=delegates).delete() self.set_message('success', 'Delegates removed') @@ -281,7 +283,9 @@ class AddDelegateForm(RelatedWGForm): created = False e = Email.objects.get(address=self.cleaned_data.get('email')) if not Role.objects.filter(name="delegate", group=self.wg, person=person, email=e): - delegate, created = Role.objects.get_or_create( + created = True + save_group_in_history(self.wg) + delegate, _ = Role.objects.get_or_create( name=RoleName.objects.get(slug="delegate"), group=self.wg, person=e.person, email=e) else: (delegate, created) = WGDelegate.objects.get_or_create(wg=self.wg, diff --git a/ietf/wgchairs/tests.py b/ietf/wgchairs/tests.py index ce46ccffe..464ae37a7 100644 --- a/ietf/wgchairs/tests.py +++ b/ietf/wgchairs/tests.py @@ -13,7 +13,7 @@ from ietf.utils.mail import outbox if settings.USE_DB_REDESIGN_PROXY_CLASSES: from redesign.person.models import Person, Email - from redesign.group.models import Group, Role, GroupStateTransitions + from redesign.group.models import Group, GroupHistory, Role, GroupStateTransitions from redesign.doc.models import Document, State, WriteupDocEvent from redesign.name.models import DocTagName @@ -87,6 +87,7 @@ class ManageDelegatesTestCase(django.test.TestCase): self.assertEquals(len(q('form input[name=email]')), 1) # add existing person + history_before = GroupHistory.objects.filter(acronym="mars").count() r = self.client.post(url, dict(email="plain@example.com", form_type="single")) @@ -94,7 +95,9 @@ class ManageDelegatesTestCase(django.test.TestCase): q = PyQuery(r.content) self.assertTrue("new delegate" in r.content) self.assertTrue(Email.objects.get(address="plain@example.com").person.name in r.content) - + self.assertEquals(Role.objects.filter(name="delegate", group__acronym="mars", email__address="plain@example.com").count(), 1) + self.assertEquals(history_before + 1, GroupHistory.objects.filter(acronym="mars").count()) + class ManageShepherdsTestCase(django.test.TestCase): fixtures = ['names'] diff --git a/ietf/wgcharter/__init__.py b/ietf/wgcharter/__init__.py new file mode 100644 index 000000000..792d60054 --- /dev/null +++ b/ietf/wgcharter/__init__.py @@ -0,0 +1 @@ +# diff --git a/ietf/wgcharter/feeds.py b/ietf/wgcharter/feeds.py new file mode 100644 index 000000000..faa5c7089 --- /dev/null +++ b/ietf/wgcharter/feeds.py @@ -0,0 +1,67 @@ +# Copyright The IETF Trust 2011, All Rights Reserved + +from django.conf import settings +from django.contrib.syndication.feeds import Feed, FeedDoesNotExist +from django.utils.feedgenerator import Atom1Feed +from django.core.exceptions import ObjectDoesNotExist +from django.core.urlresolvers import reverse +from ietf.utils.history import find_history_active_at + +from redesign.group.models import Group +from ietf.wgcharter.views import _get_history, _get_html +from wgcharter import markup_txt +import datetime +import re, os + +class GroupComments(Feed): + feed_type = Atom1Feed + title_template = "feeds/wg_charter_title.html" + description_template = "feeds/wg_charter_description.html" + def get_object(self, bits): + if len(bits) != 1: + raise ObjectDoesNotExist + return Group.objects.get(acronym=bits[0]) + + def title(self, obj): + return "WG Record changes for %s" % obj.acronym + + def link(self, obj): + if obj is None: + raise FeedDoesNotExist + return reverse('wg_view', kwargs={'name': obj.acronym}) + + def description(self, obj): + return self.title(obj) + + def items(self, obj): + history = _get_history(obj) + for h in history: + gh = find_history_active_at(obj, h['date']) + if gh: + h['chairs'] = [x.email.person.name for x in gh.rolehistory_set.filter(name__slug="chair")] + h['secr'] = [x.email.person.name for x in gh.rolehistory_set.filter(name__slug="secr")] + h['techadv'] = [x.email.person.name for x in gh.rolehistory_set.filter(name__slug="techadv")] + else: + h['chairs'] = [x.email.person.name for x in obj.role_set.filter(name__slug="chair")] + h['secr'] = [x.email.person.name for x in obj.role_set.filter(name__slug="secr")] + h['techadv'] = [x.email.person.name for x in obj.role_set.filter(name__slug="techadv")] + dh = find_history_active_at(obj.charter, h['date']) + if dh: + h['rev'] = dh.rev + h['charter'] = _get_html( + str(dh.name)+"-"+str(dh.rev)+",html", + os.path.join(dh.get_file_path(), dh.name+"-"+dh.rev+".txt")) + else: + h['rev'] = obj.charter.rev + h['charter'] = _get_html( + "charter-ietf-"+str(obj.acronym)+"-"+str(obj.charter.rev)+",html", + os.path.join(obj.charter.get_file_path(), "charter-ietf-"+obj.acronym+"-"+obj.charter.rev+".txt")) + return history + + def item_link(self, obj): + return reverse('wg_view', kwargs={'name': obj['group'].acronym}) + + def item_pubdate(self, obj): + return obj['date'] + + diff --git a/ietf/wgcharter/mails.py b/ietf/wgcharter/mails.py new file mode 100644 index 000000000..cab79d4c3 --- /dev/null +++ b/ietf/wgcharter/mails.py @@ -0,0 +1,114 @@ +# generation of mails + +import textwrap +from datetime import datetime, date, time, timedelta + +from django.template.loader import render_to_string +from django.utils.html import strip_tags +from django.conf import settings +from django.core.urlresolvers import reverse as urlreverse + +from ietf.utils.mail import send_mail, send_mail_text +from ietf.idtracker.models import * +from ietf.ipr.search import iprs_from_docs +from redesign.doc.models import WriteupDocEvent, DocAlias, GroupBallotPositionDocEvent +from redesign.person.models import Person + +# These become part of the subject of the email +types = {} +types['state'] = "State changed" +types['state-notrev'] = "State changed to Not currently under review" +types['state-infrev'] = "State changed to Informal review" +types['state-intrev'] = "State changed to Internal review" +types['state-extrev'] = "State changed to External review" +types['state-iesgrev'] = "State changed to IESG review" +types['state-approved'] = "Charter approved" +types['conclude'] = "Request closing of WG" + +def email_secretariat(request, wg, type, text): + to = ["iesg-secretary@ietf.org"] + + text = strip_tags(text) + send_mail(request, to, None, + "Regarding WG %s: %s" % (wg.acronym, types[type]), + "wgcharter/email_secretariat.txt", + dict(text=text, + url=settings.IDTRACKER_BASE_URL + urlreverse('wg_view', kwargs=dict(name=wg.acronym)))) + +def generate_ballot_writeup(request, doc): + e = WriteupDocEvent() + e.type = "changed_ballot_writeup_text" + e.by = request.user.get_profile() + e.doc = doc + e.desc = u"Ballot writeup was generated" + e.text = unicode(render_to_string("wgcharter/ballot_writeup.txt")) + e.save() + + return e + +def generate_issue_ballot_mail(request, charter): + active_ads = Person.objects.filter(email__role__name="ad", email__role__group__state="active").distinct() + + e = charter.latest_event(type="started_iesg_process") + seen = [] + positions = [] + for p in GroupBallotPositionDocEvent.objects.filter(doc=charter, type="changed_ballot_position", time__gte=e.time).order_by("-time", '-id').select_related('ad'): + if p.ad not in seen: + positions.append(p) + seen.append(p.ad) + + # format positions and setup blocking and non-blocking comments + ad_feedback = [] + seen = set() + active_ad_positions = [] + inactive_ad_positions = [] + for p in positions: + if p.ad in seen: + continue + + seen.add(p.ad) + + def formatted(val): + if val: + return "[ X ]" + else: + return "[ ]" + + fmt = u"%-21s%-6s%-6s%-8s%-7s" % ( + p.ad.name, + formatted(p.pos_id == "yes"), + formatted(p.pos_id == "no"), + formatted(p.pos_id == "block"), + formatted(p.pos_id == "abstain"), + ) + + if p.ad in active_ads: + active_ad_positions.append(fmt) + if not p.pos_id == "block": + p.block_comment = "" + if p.comment or p.block_comment: + ad_feedback.append(p) + else: + inactive_ad_positions.append(fmt) + + active_ad_positions.sort() + inactive_ad_positions.sort() + ad_feedback.sort(key=lambda p: p.ad.name) + + e = charter.latest_event(WriteupDocEvent, type="changed_action_announcement") + approval_text = e.text if e else "" + + e = charter.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") + ballot_writeup = e.text if e else "" + + return render_to_string("wgcharter/issue_ballot_mail.txt", + dict(charter=charter, + charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(), + active_ad_positions=active_ad_positions, + inactive_ad_positions=inactive_ad_positions, + ad_feedback=ad_feedback, + approval_text=approval_text, + ballot_writeup=ballot_writeup, + ) + ) + diff --git a/ietf/wgcharter/markup_txt.py b/ietf/wgcharter/markup_txt.py new file mode 100644 index 000000000..7fa7ff5c9 --- /dev/null +++ b/ietf/wgcharter/markup_txt.py @@ -0,0 +1,66 @@ +# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +# All rights reserved. Contact: Pasi Eronen"+content+"\n" diff --git a/ietf/wgcharter/models.py b/ietf/wgcharter/models.py new file mode 100644 index 000000000..e69de29bb diff --git a/ietf/wgcharter/templatetags/__init__.py b/ietf/wgcharter/templatetags/__init__.py new file mode 100644 index 000000000..792d60054 --- /dev/null +++ b/ietf/wgcharter/templatetags/__init__.py @@ -0,0 +1 @@ +# diff --git a/ietf/wgcharter/templatetags/wg_ballot_icon.py b/ietf/wgcharter/templatetags/wg_ballot_icon.py new file mode 100644 index 000000000..712e36e4f --- /dev/null +++ b/ietf/wgcharter/templatetags/wg_ballot_icon.py @@ -0,0 +1,124 @@ +# Copyright The IETF Trust 2011, All Rights Reserved + +from django import template +from django.core.urlresolvers import reverse as urlreverse +from django.conf import settings +from ietf.idtracker.templatetags.ietf_filters import in_group, timesince_days +from redesign.doc.models import GroupBallotPositionDocEvent +from redesign.person.models import Person +from redesign.group.models import Group + +register = template.Library() + +def get_user_adid(context): + if 'user' in context and in_group(context['user'], "Area_Director"): + return context['user'].get_profile().id + else: + return None + +def get_user_name(context): + if 'user' in context and context['user'].is_authenticated(): + from person.models import Person + try: + return context['user'].get_profile().name + except Person.DoesNotExist: + return None + +def render_ballot_icon(context, name): + wg = Group.objects.get(acronym=name) + doc = wg.charter + adId = get_user_adid(context) + red = 0 + green = 0 + yellow = 0 + gray = 0 + blank = 0 + my = None + + active_ads = list(Person.objects.filter(email__role__name="ad", + email__role__group__type="area", + email__role__group__state="active").distinct()) + started_process = doc.latest_event(type="started_iesg_process") + latest_positions = [] + for p in active_ads: + p_pos = list(GroupBallotPositionDocEvent.objects.filter(doc=doc, ad=p).order_by("-time")) + if p_pos != []: + latest_positions.append(p_pos[0]) + for p in latest_positions: + if not p.pos_id: + blank = blank + 1 + elif (p.pos_id == "yes"): + green = green + 1 + elif (p.pos_id == "no"): + green = green + 1 + elif (p.pos_id == "block"): + red = red + 1 + elif (p.pos_id == "abstain"): + yellow = yellow + 1 + else: + blank = blank + 1 + if adId and (p.ad_id == adId): + my = p.pos.name + return render_ballot_icon2(wg.acronym, red,yellow,green,gray,blank, my, adId)+"" + +def render_ballot_icon2(acronym, red,yellow,green,gray,blank, my, adId): + edit_position_url = urlreverse('wg_edit_position', kwargs=dict(name=acronym)) + if adId: + res_cm = ' oncontextmenu="editWGBallot(\''+str(edit_position_url)+'\');return false;"' + else: + res_cm = '' + res = '
' + my = None + else: + res = res + ' | ' + res = res + ' |