# Copyright The IETF Trust 2011, All Rights Reserved import re, os, string, datetime from django.shortcuts import render_to_response, get_object_or_404, redirect from django.core.urlresolvers import reverse from django.template import RequestContext from django import forms from django.forms.util import ErrorList from utils import * from mails import email_secretariat from ietf.ietfauth.decorators import group_required from ietf.iesg.models import TelechatDate from ietf.doc.models import * from ietf.name.models import * from ietf.person.models import * from ietf.group.models import * from ietf.group.utils import save_group_in_history from views_search import json_emails class ChangeStateForm(forms.Form): charter_state = forms.ModelChoiceField(State.objects.filter(type="charter"), label="Charter state", empty_label=None, required=False) confirm_state = forms.BooleanField(widget=forms.HiddenInput, required=False, initial=True) initial_time = forms.IntegerField(initial=0, label="Review time", help_text="(in weeks)", required=False) message = forms.CharField(widget=forms.Textarea, help_text="Message to the secretariat", required=False) comment = forms.CharField(widget=forms.Textarea, help_text="Comment for the WG history", required=False) def __init__(self, *args, **kwargs): if 'queryset' in kwargs: qs = kwargs.pop('queryset') else: qs = None if 'hide' in kwargs: self.hide = kwargs.pop('hide') else: self.hide = None super(ChangeStateForm, self).__init__(*args, **kwargs) if qs: self.fields['charter_state'].queryset = qs # hide requested fields if self.hide: for f in self.hide: self.fields[f].widget = forms.HiddenInput @group_required('Area_Director','Secretariat') def change_state(request, name, option=None): """Change state of WG and charter, notifying parties as necessary and logging the change as a comment.""" # Get WG by acronym, redirecting if there's a newer acronym try: wg = Group.objects.get(acronym=name) charter = set_or_create_charter(wg) except Group.DoesNotExist: wglist = GroupHistory.objects.filter(acronym=name) if wglist: return redirect('wg_change_state', name=wglist[0].group.acronym) else: raise Http404() initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review") login = request.user.get_profile() if request.method == 'POST': form = ChangeStateForm(request.POST) if form.is_valid(): if initial_review and form.cleaned_data['charter_state'] and form.cleaned_data['charter_state'].slug != "infrev" and initial_review.expires > datetime.datetime.now() and not form.cleaned_data['confirm_state']: form._errors['charter_state'] = "warning" else: if option == "initcharter" or option == "recharter": charter_state = State.objects.get(type="charter", slug="infrev") charter_rev = charter.rev elif option == "abandon": if wg.state_id == "proposed": charter_state = State.objects.get(type="charter", slug="notrev") else: charter_state = State.objects.get(type="charter", slug="approved") charter_rev = approved_revision(charter.rev) else: charter_state = form.cleaned_data['charter_state'] charter_rev = charter.rev comment = form.cleaned_data['comment'] message = form.cleaned_data['message'] change = False if charter: # The WG has a charter if charter_state != charter.get_state(): # Charter state changed change = True save_charter_in_history(charter) prev = charter.get_state() charter.set_state(charter_state) charter.rev = charter_rev if option != "abandon": e = log_state_changed(request, charter, login, prev, comment) else: # Special log for abandoned efforts e = DocEvent(doc=charter, by=login) e.type = "changed_document" e.desc = "IESG has abandoned the chartering effort" if comment: e.desc += "
%s" % comment e.save() charter.time = datetime.datetime.now() charter.save() else: # WG does not yet have a charter if charter_state != "infrev": # This is an error raise Http404() if change and charter: messages = {} messages['extrev'] = "The WG has been set to External review by %s. Please schedule discussion for the next IESG telechat." % login.plain_name() if message: email_secretariat(request, wg, "state-%s" % charter_state.slug, message) if charter_state.slug == "extrev": email_secretariat(request, wg, "state-%s" % charter_state.slug, messages['extrev']) if charter_state.slug == "infrev": e = DocEvent() e.type = "started_iesg_process" e.by = login e.doc = charter e.desc = "IESG process started in state %s" % charter_state.name e.save() if charter_state.slug == "infrev" and form.cleaned_data["initial_time"] and form.cleaned_data["initial_time"] != 0: e = InitialReviewDocEvent() e.type = "initial_review" e.by = login e.doc = charter e.expires = datetime.datetime.now() + datetime.timedelta(weeks=form.cleaned_data["initial_time"]) e.desc = "Initial review time expires %s" % e.expires.strftime("%Y-%m-%d") e.save() return redirect('wg_view', name=wg.acronym) else: if option == "recharter": hide = ['charter_state'] init = dict(initial_time=1, message="%s has initiated a recharter effort on the WG %s (%s)" % (login.plain_name(), wg.name, wg.acronym)) elif option == "initcharter": hide = ['charter_state'] init = dict(initial_time=1, message="%s has initiated chartering of the proposed WG %s (%s)" % (login.plain_name(), wg.name, wg.acronym)) elif option == "abandon": hide = ['initial_time', 'charter_state'] init = dict(message="%s has abandoned the chartering effort on the WG %s (%s)" % (login.plain_name(), wg.name, wg.acronym)) else: hide = ['initial_time'] init = dict(charter_state=wg.charter.get_state_slug(), state=wg.state_id) states = State.objects.filter(type="charter", slug__in=["infrev", "intrev", "extrev", "iesgrev"]) form = ChangeStateForm(queryset=states, hide=hide, initial=init) group_hists = GroupHistory.objects.filter(group=wg).exclude(state=wg.state).order_by("-time")[:1] prev_charter_state = None if charter: charter_hists = DocHistory.objects.filter(doc=charter).exclude(states__type="charter", states__slug=charter.get_state_slug()).order_by("-time")[:1] if charter_hists: prev_charter_state = charter_hists[0].get_state() title = { "initcharter": "Initiate chartering of WG %s" % wg.acronym, "recharter": "Recharter WG %s" % wg.acronym, "abandon": "Abandon effort on WG %s" % wg.acronym, }.get(option) if not title: title = "Change state of WG %s" % wg.acronym return render_to_response('wgcharter/change_state.html', dict(form=form, wg=wg, login=login, option=option, prev_charter_state=prev_charter_state, title=title), context_instance=RequestContext(request)) def parse_emails_string(s): return Email.objects.filter(address__in=[x.strip() for x in s.split(",") if x.strip()]).select_related("person") class EditInfoForm(forms.Form): name = forms.CharField(max_length=255, label="WG Name", required=True) acronym = forms.CharField(max_length=8, label="WG Acronym", required=True) chairs = forms.CharField(max_length=255, label="WG Chairs", help_text="Type in a name", required=False) secretaries = forms.CharField(max_length=255, label="WG Secretaries", help_text="Type in a name", required=False) techadv = forms.CharField(max_length=255, label="WG Technical Advisors", help_text="Type in a name", required=False) ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), label="Shepherding AD", empty_label="-", required=False) parent = forms.ModelChoiceField(Group.objects.filter(type="area", state="active").order_by('name'), label="IETF Area", empty_label="-", required=False) list_email = forms.CharField(max_length=64, required=False) list_subscribe = forms.CharField(max_length=255, required=False) list_archive = forms.CharField(max_length=255, required=False) urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", help_text="Format: http://site/url (optional description). Separate multiple entries with newline.", required=False) telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False) def __init__(self, *args, **kwargs): self.cur_acronym = kwargs.pop('cur_acronym') if 'hide' in kwargs: self.hide = kwargs.pop('hide') else: self.hide = None super(self.__class__, self).__init__(*args, **kwargs) # hide requested fields if self.hide: for f in self.hide: self.fields[f].widget = forms.HiddenInput # if previous AD is now ex-AD, append that person to the list ad_pk = self.initial.get('ad') choices = self.fields['ad'].choices if ad_pk and ad_pk not in [pk for pk, name in choices]: self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())] # telechat choices dates = [d.date for d in TelechatDate.objects.active().order_by('date')] if 'telechat_date' in kwargs['initial']: init = kwargs['initial']['telechat_date'] if init and init not in dates: dates.insert(0, init) self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [(d, d.strftime("%Y-%m-%d")) for d in dates] def clean_acronym(self): acronym = self.cleaned_data['acronym'] if self.cur_acronym and acronym != self.cur_acronym: if Group.objects.filter(acronym=acronym): raise forms.ValidationError("Acronym used in a previous WG. Please pick another.") if GroupHistory.objects.filter(acronym=acronym): raise forms.ValidationError("Acronym used in a previous WG. Please pick another.") return acronym def clean_chairs(self): return parse_emails_string(self.cleaned_data["chairs"]) def clean_secretaries(self): return parse_emails_string(self.cleaned_data["secretaries"]) def clean_techadv(self): return parse_emails_string(self.cleaned_data["techadv"]) def clean_urls(self): return [x.strip() for x in self.cleaned_data["urls"].splitlines() if x.strip()] def format_urls(set, fs="\n"): ostr = "" for i,x in enumerate(set): if i != 0: ostr += fs if x.name: ostr += x.url + " (" + x.name + ")" else: ostr += x.url return ostr @group_required('Area_Director','Secretariat') def edit_info(request, name=None): """Edit or create a WG, notifying parties as necessary and logging changes as group events.""" import sys if request.path_info == reverse('wg_edit_info', kwargs={'name': name}): # Editing. Get group wg = get_object_or_404(Group, acronym=name) charter = set_or_create_charter(wg) new_wg = False elif request.path_info == reverse('wg_create'): wg = None new_wg = True login = request.user.get_profile() if not new_wg: e = charter.latest_event(TelechatDocEvent, type="scheduled_for_telechat") initial_telechat_date = e.telechat_date if e else None else: initial_telechat_date = None if request.method == 'POST': form = EditInfoForm(request.POST, initial=dict(telechat_date=initial_telechat_date), cur_acronym=wg.acronym if wg else None) if form.is_valid(): r = form.cleaned_data if not new_wg: gh = save_group_in_history(wg) else: # Create WG wg = Group(name=r["name"], acronym=r["acronym"], type=GroupTypeName.objects.get(name="WG"), state=GroupStateName.objects.get(name="Proposed")) wg.save() e = ChangeStateGroupEvent(group=wg, type="changed_state") e.time = datetime.datetime.now() e.by = login e.state_id = "proposed" e.desc = "Proposed group" e.save() if not wg.charter: # Create adjoined charter charter = set_or_create_charter(wg) charter.set_state(State.objects.get(type="charter", slug="infrev")) charter.save() e = DocEvent(doc=charter, type="started_iesg_process") e.time = datetime.datetime.now() e.by = login e.desc = "Started IESG process on charter" e.save() changes = [] def desc(attr, new, old): entry = "%(attr)s has been changed to %(new)s from %(old)s" if new_wg: entry = "%(attr)s has been changed to %(new)s" return entry % dict(attr=attr, new=new, old=old) def get_model_fields_as_dict(obj): return dict((field.name, getattr(obj, field.name)) for field in obj._meta.fields if field is not obj._meta.pk) def diff(attr, name): v = getattr(wg, attr) if r[attr] != v: changes.append(desc(name, r[attr], v)) setattr(wg, attr, r[attr]) if attr == "acronym": c = wg.charter save_charter_in_history(c) # and add a DocAlias DocAlias.objects.create( name = "charter-ietf-%s" % r['acronym'], document = charter ) # update the attributes, keeping track of what we're doing diff('name', "Name") diff('acronym', "Acronym") diff('ad', "Shepherding AD") diff('parent', "IETF Area") diff('list_email', "Mailing list email") diff('list_subscribe', "Mailing list subscribe address") diff('list_archive', "Mailing list archive") # update roles for attr, slug in [('chairs', 'chair'), ('secretaries', 'secr'), ('techadv', 'techadv')]: rname = RoleName.objects.get(slug=slug) new = r[attr] old = Email.objects.filter(role__group=wg, role__name=rname).select_related("person") if set(new) != set(old): changes.append(desc(rname.name, ",".join(x.get_name() for x in new), ",".join(x.get_name() for x in new))) wg.role_set.filter(name=rname).delete() for e in new: Role.objects.get_or_create(name=rname, email=e, group=wg, person=e.person) # update urls new_urls = r['urls'] old_urls = format_urls(wg.groupurl_set.order_by('url'), ", ") if ", ".join(sorted(new_urls)) != old_urls: changes.append(desc('urls', ", ".join(sorted(new_urls)), old_urls)) wg.groupurl_set.all().delete() # Add new ones for u in new_urls: m = re.search('(?P[\w\d:#@%/;$()~_?\+-=\\\.&]+)( \((?P.+)\))?', u) if m: if m.group('name'): url = GroupURL(url=m.group('url'), name=m.group('name'), group=wg) else: url = GroupURL(url=m.group('url'), name='', group=wg) url.save() wg.time = datetime.datetime.now() if changes and not new_wg: for c in changes: log_info_changed(request, wg, login, c) update_telechat(request, wg.charter, login, r['telechat_date']) wg.save() if new_wg: return redirect('wg_startstop_process', name=wg.acronym, option="initcharter") else: return redirect('wg_view', name=wg.acronym) else: # form.is_valid() if not new_wg: init = dict(name=wg.name, acronym=wg.acronym, chairs=json_emails([x.email for x in wg.role_set.filter(name="Chair")]), secretaries=json_emails([x.email for x in wg.role_set.filter(name="Secr")]), techadv=json_emails([x.email for x in wg.role_set.filter(name="Techadv")]), charter=wg.charter.name if wg.charter else None, ad=wg.ad.id if wg.ad else None, parent=wg.parent.id if wg.parent else None, list_email=wg.list_email if wg.list_email else None, list_subscribe=wg.list_subscribe if wg.list_subscribe else None, list_archive=wg.list_archive if wg.list_archive else None, urls=format_urls(wg.groupurl_set.all()), telechat_date=initial_telechat_date, ) hide = None else: init = dict(ad=login.id, ) hide = ['chairs', 'techadv', 'list_email', 'list_subscribe', 'list_archive', 'urls', 'telechat_date'] form = EditInfoForm(initial=init, cur_acronym=wg.acronym if wg else None, hide=hide) return render_to_response('wgcharter/edit_info.html', dict(wg=wg, form=form, user=request.user, login=login), context_instance=RequestContext(request)) class ConcludeForm(forms.Form): instructions = forms.CharField(widget=forms.Textarea, required=True) @group_required('Area_Director','Secretariat') def conclude(request, name): """Request the closing of a WG, prompting for instructions.""" try: wg = Group.objects.get(acronym=name) except Group.DoesNotExist: wglist = GroupHistory.objects.filter(acronym=name) if wglist: return redirect('wg_conclude', name=wglist[0].group.acronym) else: raise Http404 login = request.user.get_profile() # if state != wg.state: # # WG state changed # change = True # save_group_in_history(wg) # prev = wg.state # wg.state = state # ge = log_group_state_changed(request, wg, login, comment) # wg.save() if request.method == 'POST': form = ConcludeForm(request.POST) if form.is_valid(): instructions = form.cleaned_data['instructions'] email_secretariat(request, wg, "conclude", instructions) return redirect('wg_view', name=wg.acronym) else: form = ConcludeForm() return render_to_response('wgcharter/conclude.html', dict(form=form, wg=wg), context_instance=RequestContext(request)) class AddCommentForm(forms.Form): comment = forms.CharField(required=True, widget=forms.Textarea) @group_required('Area_Director','Secretariat') def add_comment(request, name): """Add comment to WG Record.""" wg = get_object_or_404(Group, acronym=name) login = request.user.get_profile() if request.method == 'POST': form = AddCommentForm(request.POST) if form.is_valid(): c = form.cleaned_data['comment'] add_wg_comment(request, wg, c) #email_owner(request, doc, doc.ad, login, # "A new comment added by %s" % login.name) return redirect('wg_view', name=wg.acronym) else: form = AddCommentForm() return render_to_response('wgcharter/add_comment.html', dict(wg=wg, form=form), context_instance=RequestContext(request))