def test_notification(self): draft = make_test_data() clist = CommunityList.objects.create(user=User.objects.get(username="******")) clist.added_docs.add(draft) SearchRule.objects.create( community_list=clist, rule_type="name_contains", state=State.objects.get(type="draft", slug="active"), text="test", ) EmailSubscription.objects.create(community_list=clist, email=Email.objects.filter(person__user__username="******").first(), notify_on="significant") mailbox_before = len(outbox) active_state = State.objects.get(type="draft", slug="active") system = Person.objects.get(name="(System)") add_state_change_event(draft, system, None, active_state) self.assertEqual(len(outbox), mailbox_before) mailbox_before = len(outbox) rfc_state = State.objects.get(type="draft", slug="rfc") add_state_change_event(draft, system, active_state, rfc_state) self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue(draft.name in outbox[-1]["Subject"])
def change_state(request, name, option=None): """Change state of an IESG review for IETF conflicts in other stream's documents, notifying parties as necessary and logging the change as a comment.""" review = get_object_or_404(Document, type="conflrev", name=name) login = request.user.person if request.method == 'POST': form = ChangeStateForm(request.POST) if form.is_valid(): clean = form.cleaned_data new_state = clean['review_state'] comment = clean['comment'].rstrip() if comment: c = DocEvent(type="added_comment", doc=review, by=login) c.desc = comment c.save() prev_state = review.get_state() if new_state != prev_state: save_document_in_history(review) review.set_state(new_state) add_state_change_event(review, login, prev_state, new_state) review.time = datetime.datetime.now() review.save() if new_state.slug == "iesgeval": create_ballot_if_not_open(review, login, "conflrev") ballot = review.latest_event(BallotDocEvent, type="created_ballot") if has_role(request.user, "Area Director") and not review.latest_event(BallotPositionDocEvent, ad=login, ballot=ballot, type="changed_ballot_position"): # The AD putting a conflict review into iesgeval who doesn't already have a position is saying "yes" pos = BallotPositionDocEvent(doc=review, by=login) pos.ballot = ballot pos.type = "changed_ballot_position" pos.ad = login pos.pos_id = "yes" pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name()) pos.save() send_conflict_eval_email(request,review) return redirect('doc_view', name=review.name) else: s = review.get_state() init = dict(review_state=s.pk if s else None) form = ChangeStateForm(initial=init) return render_to_response('doc/change_state.html', dict(form=form, doc=review, login=login, help_url=reverse('state_help', kwargs=dict(type="conflict-review")), ), context_instance=RequestContext(request))
def approve(request, name): """Approve this conflict review, setting the appropriate state and send the announcement to the right parties.""" review = get_object_or_404(Document, type="conflrev", name=name) if review.get_state('conflrev').slug not in ('appr-reqnopub-pend','appr-noprob-pend'): raise Http404 login = request.user.person if request.method == 'POST': form = AnnouncementForm(request.POST) if form.is_valid(): prev_state = review.get_state() new_state_slug = 'appr-reqnopub-sent' if prev_state.slug == 'appr-reqnopub-pend' else 'appr-noprob-sent' new_state = State.objects.get(used=True, type="conflrev", slug=new_state_slug) save_document_in_history(review) review.set_state(new_state) add_state_change_event(review, login, prev_state, new_state) close_open_ballots(review, login) e = DocEvent(doc=review, by=login) e.type = "iesg_approved" e.desc = "IESG has approved the conflict review response" e.save() review.time = e.time review.save() # send announcement send_mail_preformatted(request, form.cleaned_data['announcement_text']) c = DocEvent(type="added_comment", doc=review, by=login) c.desc = "The following approval message was sent\n"+form.cleaned_data['announcement_text'] c.save() return HttpResponseRedirect(review.get_absolute_url()) else: init = { "announcement_text" : default_approval_text(review) } form = AnnouncementForm(initial=init) return render_to_response('doc/conflict_review/approve.html', dict( review = review, conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document, form = form, ), context_instance=RequestContext(request))
def do_undefer_ballot(request, doc): ''' Helper function to perform undefer of ballot. Takes the Request object, for use in logging, and the Document object. ''' login = request.user.person telechat_date = TelechatDate.objects.active().order_by("date")[0].date save_document_in_history(doc) new_state = doc.get_state() prev_tags = new_tags = [] if doc.type_id == 'draft': new_state = State.objects.get(used=True, type="draft-iesg", slug='iesg-eva') prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) elif doc.type_id in ['conflrev','statchg']: new_state = State.objects.get(used=True, type=doc.type_id, slug='iesgeval') prev_state = doc.get_state(new_state.type_id if new_state else None) doc.set_state(new_state) doc.tags.remove(*prev_tags) e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags) doc.time = (e and e.time) or datetime.datetime.now() doc.save() update_telechat(request, doc, login, telechat_date) email_state_changed(request, doc, e.desc) email_ballot_undeferred(request, doc, login.plain_name(), telechat_date)
def expire_draft(doc): # clean up files move_draft_files_to_archive(doc, doc.rev) system = Person.objects.get(name="(System)") # change the state save_document_in_history(doc) if doc.latest_event(type='started_iesg_process'): new_state = State.objects.get(used=True, type="draft-iesg", slug="dead") prev_state = doc.get_state(new_state.type_id) prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) if new_state != prev_state: doc.set_state(new_state) doc.tags.remove(*prev_tags) e = add_state_change_event(doc, system, prev_state, new_state, prev_tags=prev_tags, new_tags=[]) e = DocEvent(doc=doc, by=system) e.type = "expired_document" e.desc = "Document has expired" e.save() doc.set_state(State.objects.get(used=True, type="draft", slug="expired")) doc.time = datetime.datetime.now() doc.save()
def do_withdraw(draft, request): ''' Actions - change state to withdrawn - TODO move file to archive ''' withdraw_type = request.POST.get('withdraw_type') prev_state = draft.get_state("draft") new_state = None if withdraw_type == 'ietf': new_state = State.objects.get(type="draft", slug="ietf-rm") elif withdraw_type == 'author': new_state = State.objects.get(type="draft", slug="auth-rm") if not new_state: return draft.set_state(new_state) e = add_state_change_event(draft, request.user.person, prev_state, new_state) if e: draft.save_with_history([e]) # send announcement form = EmailForm(request.POST) announcement_from_form(form.data, by=request.user.person) return
def expire_last_call(doc): if doc.type_id == 'draft': new_state = State.objects.get(used=True, type="draft-iesg", slug="writeupw") e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") if e and "Relevant content can frequently be found in the abstract" not in e.text: # if boiler-plate text has been removed, we assume the # write-up has been written new_state = State.objects.get(used=True, type="draft-iesg", slug="goaheadw") elif doc.type_id == 'statchg': new_state = State.objects.get(used=True, type="statchg", slug="goahead") else: raise ValueError("Unexpected document type to expire_last_call(): %s" % doc.type) save_document_in_history(doc) prev_state = doc.get_state(new_state.type_id) doc.set_state(new_state) prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) doc.tags.remove(*prev_tags) system = Person.objects.get(name="(System)") e = add_state_change_event(doc, system, prev_state, new_state, prev_tags=prev_tags, new_tags=[]) doc.time = (e and e.time) or datetime.datetime.now() doc.save() email_last_call_expired(doc)
def change_state(request, name, option=None): """Change state of an IESG review for IETF conflicts in other stream's documents, notifying parties as necessary and logging the change as a comment.""" review = get_object_or_404(Document, type="conflrev", name=name) login = request.user.person if request.method == 'POST': form = ChangeStateForm(request.POST) if form.is_valid(): clean = form.cleaned_data new_state = clean['review_state'] comment = clean['comment'].rstrip() if comment: c = DocEvent(type="added_comment", doc=review, rev=review.rev, by=login) c.desc = comment c.save() prev_state = review.get_state() if new_state != prev_state: events = [] review.set_state(new_state) events.append(add_state_change_event(review, login, prev_state, new_state)) review.save_with_history(events) if new_state.slug == "iesgeval": create_ballot_if_not_open(review, login, "conflrev") ballot = review.latest_event(BallotDocEvent, type="created_ballot") if has_role(request.user, "Area Director") and not review.latest_event(BallotPositionDocEvent, ad=login, ballot=ballot, type="changed_ballot_position"): # The AD putting a conflict review into iesgeval who doesn't already have a position is saying "yes" pos = BallotPositionDocEvent(doc=review, rev=review.rev, by=login) pos.ballot = ballot pos.type = "changed_ballot_position" pos.ad = login pos.pos_id = "yes" pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name()) pos.save() # Consider mailing that position to 'ballot_saved' send_conflict_eval_email(request,review) return redirect('ietf.doc.views_doc.document_main', name=review.name) else: s = review.get_state() init = dict(review_state=s.pk if s else None) form = ChangeStateForm(initial=init) return render(request, 'doc/change_state.html', dict(form=form, doc=review, login=login, help_url=reverse('ietf.doc.views_help.state_help', kwargs=dict(type="conflict-review")), ))
def last_call(request, name): """Edit the Last Call Text for this status change and possibly request IETF LC""" status_change = get_object_or_404(Document, type="statchg", name=name) login = request.user.person last_call_event = status_change.latest_event(WriteupDocEvent, type="changed_last_call_text") if not last_call_event: last_call_event = generate_last_call_text(request, status_change) form = LastCallTextForm(initial=dict(last_call_text=last_call_event.text)) if request.method == 'POST': if "save_last_call_text" in request.POST or "send_last_call_request" in request.POST: form = LastCallTextForm(request.POST) if form.is_valid(): t = form.cleaned_data['last_call_text'] if t != last_call_event.text: e = WriteupDocEvent(doc=status_change, by=login) e.by = login e.type = "changed_last_call_text" e.desc = "Last call announcement was changed" e.text = t e.save() if "send_last_call_request" in request.POST: save_document_in_history(status_change) prev_state = status_change.get_state() new_state = State.objects.get(type='statchg', slug='lc-req') status_change.set_state(new_state) e = add_state_change_event(status_change, login, prev_state, new_state) status_change.time = (e and e.time) or datetime.datetime.now() status_change.save() request_last_call(request, status_change) return render_to_response('doc/draft/last_call_requested.html', dict(doc=status_change, url = status_change.get_absolute_url(), ), context_instance=RequestContext(request)) if "regenerate_last_call_text" in request.POST: e = generate_last_call_text(request,status_change) form = LastCallTextForm(initial=dict(last_call_text=e.text)) return render_to_response('doc/status_change/last_call.html', dict(doc=status_change, back_url = status_change.get_absolute_url(), last_call_event = last_call_event, last_call_form = form, ), context_instance = RequestContext(request))
def test_rfceditor_undo(self): draft = make_test_data() e1 = add_state_change_event( draft, Person.objects.get(name="(System)"), None, State.objects.get(used=True, type="draft-rfceditor", slug="auth")) e1.desc = "First" e1.save() e2 = add_state_change_event( draft, Person.objects.get(name="(System)"), None, State.objects.get(used=True, type="draft-rfceditor", slug="edit")) e2.desc = "Second" e2.save() url = urlreverse('ietf.sync.views.rfceditor_undo') login_testing_unauthorized(self, "rfc", url) # get r = self.client.get(url) self.assertEquals(r.status_code, 200) self.assertTrue(e2.doc_id in r.content) # delete e2 deleted_before = DeletedEvent.objects.count() r = self.client.post(url, dict(event=e2.id)) self.assertEquals(r.status_code, 302) self.assertEquals(StateDocEvent.objects.filter(id=e2.id).count(), 0) self.assertEquals(draft.get_state("draft-rfceditor").slug, "auth") self.assertEquals(DeletedEvent.objects.count(), deleted_before + 1) # delete e1 draft.state_cache = None r = self.client.post(url, dict(event=e1.id)) self.assertEquals(draft.get_state("draft-rfceditor"), None) # let's just test we can recover e = DeletedEvent.objects.all().order_by("-time", "-id")[0] e.content_type.model_class().objects.create(**json.loads(e.json)) self.assertTrue(StateDocEvent.objects.filter(desc="First", doc=draft))
def approve(request, name): """Approve this conflict review, setting the appropriate state and send the announcement to the right parties.""" review = get_object_or_404(Document, type="conflrev", name=name) if review.get_state('conflrev').slug not in ('appr-reqnopub-pend','appr-noprob-pend'): raise Http404 login = request.user.person if request.method == 'POST': form = AnnouncementForm(request.POST) if form.is_valid(): prev_state = review.get_state() events = [] new_state_slug = 'appr-reqnopub-sent' if prev_state.slug == 'appr-reqnopub-pend' else 'appr-noprob-sent' new_state = State.objects.get(used=True, type="conflrev", slug=new_state_slug) review.set_state(new_state) e = add_state_change_event(review, login, prev_state, new_state) events.append(e) close_open_ballots(review, login) e = DocEvent(doc=review, rev=review.rev, by=login) e.type = "iesg_approved" e.desc = "IESG has approved the conflict review response" e.save() events.append(e) review.save_with_history(events) # send announcement send_mail_preformatted(request, form.cleaned_data['announcement_text']) c = DocEvent(type="added_comment", doc=review, rev=review.rev, by=login) c.desc = "The following approval message was sent\n"+form.cleaned_data['announcement_text'] c.save() return HttpResponseRedirect(review.get_absolute_url()) else: init = { "announcement_text" : default_approval_text(review) } form = AnnouncementForm(initial=init) return render(request, 'doc/conflict_review/approve.html', dict( review = review, conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document, form = form, ))
def test_rfceditor_undo(self): draft = make_test_data() e1 = add_state_change_event(draft, Person.objects.get(name="(System)"), None, State.objects.get(used=True, type="draft-rfceditor", slug="auth")) e1.desc = "First" e1.save() e2 = add_state_change_event(draft, Person.objects.get(name="(System)"), None, State.objects.get(used=True, type="draft-rfceditor", slug="edit")) e2.desc = "Second" e2.save() url = urlreverse('ietf.sync.views.rfceditor_undo') login_testing_unauthorized(self, "rfc", url) # get r = self.client.get(url) self.assertEqual(r.status_code, 200) self.assertTrue(e2.doc_id in unicontent(r)) # delete e2 deleted_before = DeletedEvent.objects.count() r = self.client.post(url, dict(event=e2.id)) self.assertEqual(r.status_code, 302) self.assertEqual(StateDocEvent.objects.filter(id=e2.id).count(), 0) self.assertEqual(draft.get_state("draft-rfceditor").slug, "auth") self.assertEqual(DeletedEvent.objects.count(), deleted_before + 1) # delete e1 draft.state_cache = None r = self.client.post(url, dict(event=e1.id)) self.assertEqual(draft.get_state("draft-rfceditor"), None) # let's just test we can recover e = DeletedEvent.objects.all().order_by("-time", "-id")[0] e.content_type.model_class().objects.create(**json.loads(e.json)) self.assertTrue(StateDocEvent.objects.filter(desc="First", doc=draft))
def defer_ballot(request, name): """Signal post-pone of ballot, notifying relevant parties.""" doc = get_object_or_404(Document, docalias__name=name) if doc.type_id not in ('draft','conflrev','statchg'): raise Http404() interesting_state = dict(draft='draft-iesg',conflrev='conflrev',statchg='statchg') state = doc.get_state(interesting_state[doc.type_id]) if not state or state.slug=='defer' or not doc.telechat_date(): raise Http404() login = request.user.person telechat_date = TelechatDate.objects.active().order_by("date")[1].date if request.method == 'POST': save_document_in_history(doc) new_state = doc.get_state() prev_tags = new_tags = [] if doc.type_id == 'draft': new_state = State.objects.get(used=True, type="draft-iesg", slug='defer') prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) elif doc.type_id in ['conflrev','statchg']: new_state = State.objects.get(used=True, type=doc.type_id, slug='defer') prev_state = doc.get_state(new_state.type_id if new_state else None) doc.set_state(new_state) doc.tags.remove(*prev_tags) e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags) doc.time = (e and e.time) or datetime.datetime.now() doc.save() email_state_changed(request, doc, e.desc) update_telechat(request, doc, login, telechat_date) email_ballot_deferred(request, doc, login.plain_name(), telechat_date) return HttpResponseRedirect(doc.get_absolute_url()) return render_to_response('doc/ballot/defer_ballot.html', dict(doc=doc, telechat_date=telechat_date, back_url=doc.get_absolute_url()), context_instance=RequestContext(request))
def do_undefer_ballot(request, doc): ''' Helper function to perform undefer of ballot. Takes the Request object, for use in logging, and the Document object. ''' by = request.user.person telechat_date = TelechatDate.objects.active().order_by("date")[0].date new_state = doc.get_state() prev_tags = [] new_tags = [] if doc.type_id == 'draft': new_state = State.objects.get(used=True, type="draft-iesg", slug='iesg-eva') prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) elif doc.type_id in ['conflrev', 'statchg']: new_state = State.objects.get(used=True, type=doc.type_id, slug='iesgeval') prev_state = doc.get_state(new_state.type_id if new_state else None) doc.set_state(new_state) doc.tags.remove(*prev_tags) events = [] state_change_event = add_state_change_event(doc, by, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags) if state_change_event: events.append(state_change_event) e = update_telechat(request, doc, by, telechat_date) if e: events.append(e) if events: doc.save_with_history(events) email_ballot_undeferred(request, doc, by.plain_name(), telechat_date)
def expire_last_call(doc): if doc.type_id == 'draft': new_state = State.objects.get(used=True, type="draft-iesg", slug="writeupw") e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") if e and "Relevant content can frequently be found in the abstract" not in e.text: # if boiler-plate text has been removed, we assume the # write-up has been written new_state = State.objects.get(used=True, type="draft-iesg", slug="goaheadw") elif doc.type_id == 'statchg': new_state = State.objects.get(used=True, type="statchg", slug="goahead") else: raise ValueError("Unexpected document type to expire_last_call(): %s" % doc.type) prev_state = doc.get_state(new_state.type_id) doc.set_state(new_state) prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) doc.tags.remove(*prev_tags) system = Person.objects.get(name="(System)") e = add_state_change_event(doc, system, prev_state, new_state, prev_tags=prev_tags, new_tags=[]) if e: doc.save_with_history([e]) email_last_call_expired(doc)
def expire_draft(doc): # clean up files move_draft_files_to_archive(doc, doc.rev) system = Person.objects.get(name="(System)") events = [] # change the state if doc.latest_event(type='started_iesg_process'): new_state = State.objects.get(used=True, type="draft-iesg", slug="dead") prev_state = doc.get_state(new_state.type_id) prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) if new_state != prev_state: doc.set_state(new_state) doc.tags.remove(*prev_tags) e = add_state_change_event(doc, system, prev_state, new_state, prev_tags=prev_tags, new_tags=[]) if e: events.append(e) events.append(DocEvent.objects.create(doc=doc, rev=doc.rev, by=system, type="expired_document", desc="Document has expired")) doc.set_state(State.objects.get(used=True, type="draft", slug="expired")) doc.save_with_history(events)
def update_drafts_from_queue(drafts): tag_mapping = { 'IANA': DocTagName.objects.get(slug='iana'), 'REF': DocTagName.objects.get(slug='ref') } slookup = dict((s.slug, s) for s in State.objects.filter( used=True, type=StateType.objects.get(slug="draft-rfceditor"))) state_mapping = { 'AUTH': slookup['auth'], 'AUTH48': slookup['auth48'], 'AUTH48-DONE': slookup['auth48-done'], 'EDIT': slookup['edit'], 'IANA': slookup['iana'], 'IESG': slookup['iesg'], 'ISR': slookup['isr'], 'ISR-AUTH': slookup['isr-auth'], 'REF': slookup['ref'], 'RFC-EDITOR': slookup['rfc-edit'], 'TO': slookup['timeout'], 'MISSREF': slookup['missref'], } system = Person.objects.get(name="(System)") warnings = [] names = [t[0] for t in drafts] drafts_in_db = dict( (d.name, d) for d in Document.objects.filter(type="draft", docalias__name__in=names)) changed = set() for name, date_received, state, tags, missref_generation, stream, auth48, cluster, refs in drafts: if name not in drafts_in_db: warnings.append("unknown document %s" % name) continue if not state or state not in state_mapping: warnings.append("unknown state '%s'" % state) continue d = drafts_in_db[name] prev_state = d.get_state("draft-rfceditor") next_state = state_mapping[state] # check if we've noted it's been received if d.get_state_slug( "draft-iesg" ) == "ann" and not prev_state and not d.latest_event( DocEvent, type="rfc_editor_received_announcement"): e = DocEvent(doc=d, by=system, type="rfc_editor_received_announcement") e.desc = "Announcement was received by RFC Editor" e.save() send_mail_text( None, "*****@*****.**", None, '%s in RFC Editor queue' % d.name, 'The announcement for %s has been received by the RFC Editor.' % d.name) if prev_state != next_state: save_document_in_history(d) d.set_state(next_state) e = add_state_change_event(d, system, prev_state, next_state) if auth48: e.desc = re.sub(r"(<b>.*</b>)", "<a href=\"%s\">\\1</a>" % auth48, e.desc) e.save() changed.add(name) t = DocTagName.objects.filter(slug__in=tags) if set(t) != set(d.tags.all()): d.tags = t changed.add(name) # remove tags and states for those not in the queue anymore for d in Document.objects.exclude(docalias__name__in=names).filter( states__type="draft-rfceditor").distinct(): d.tags.remove(*tag_mapping.values()) d.unset_state("draft-rfceditor") # we do not add a history entry here - most likely we already # have something that explains what happened changed.add(name) return changed, warnings
def change_state(request, name, option=None): """Change state of an status-change document, notifying parties as necessary and logging the change as a comment.""" status_change = get_object_or_404(Document, type="statchg", name=name) login = request.user.person if request.method == 'POST': form = ChangeStateForm(request.POST) if form.is_valid(): clean = form.cleaned_data new_state = clean['new_state'] comment = clean['comment'].rstrip() if comment: c = DocEvent(type="added_comment", doc=status_change, by=login) c.desc = comment c.save() prev_state = status_change.get_state() if new_state != prev_state: save_document_in_history(status_change) status_change.set_state(new_state) e = add_state_change_event(status_change, login, prev_state, new_state) status_change.time = e.time status_change.save() if new_state.slug == "iesgeval": create_ballot_if_not_open(status_change, login, "statchg", e.time) ballot = status_change.latest_event(BallotDocEvent, type="created_ballot") if has_role(request.user, "Area Director") and not status_change.latest_event(BallotPositionDocEvent, ad=login, ballot=ballot, type="changed_ballot_position"): # The AD putting a status change into iesgeval who doesn't already have a position is saying "yes" pos = BallotPositionDocEvent(doc=status_change, by=login) pos.ballot = ballot pos.type = "changed_ballot_position" pos.ad = login pos.pos_id = "yes" pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.plain_name()) pos.save() send_status_change_eval_email(request,status_change) if new_state.slug == "lc-req": request_last_call(request, status_change) return render_to_response('doc/draft/last_call_requested.html', dict(doc=status_change, url = status_change.get_absolute_url(), ), context_instance=RequestContext(request)) return redirect('doc_view', name=status_change.name) else: s = status_change.get_state() init = dict(new_state=s.pk if s else None, type='statchg', label='Status Change Evaluation State', ) form = ChangeStateForm(initial=init) return render_to_response('doc/change_state.html', dict(form=form, doc=status_change, login=login, help_url=reverse('state_help', kwargs=dict(type="status-change")), ), context_instance=RequestContext(request))
def approve(request, name): """Approve this status change, setting the appropriate state and send the announcements to the right parties.""" status_change = get_object_or_404(Document, type="statchg", name=name) if status_change.get_state('statchg').slug not in ('appr-pend'): raise Http404 login = request.user.person AnnouncementFormSet = formset_factory(AnnouncementForm,extra=0) if request.method == 'POST': formset = AnnouncementFormSet(request.POST) if formset.is_valid(): save_document_in_history(status_change) prev_state = status_change.get_state() new_state = State.objects.get(type='statchg', slug='appr-sent') status_change.set_state(new_state) add_state_change_event(status_change, login, prev_state, new_state) close_open_ballots(status_change, login) e = DocEvent(doc=status_change, by=login) e.type = "iesg_approved" e.desc = "IESG has approved the status change" e.save() status_change.time = e.time status_change.save() for form in formset.forms: send_mail_preformatted(request,form.cleaned_data['announcement_text']) c = DocEvent(type="added_comment", doc=status_change, by=login) c.desc = "The following approval message was sent\n"+form.cleaned_data['announcement_text'] c.save() for rel in status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS): # Add a document event to each target c = DocEvent(type="added_comment", doc=rel.target.document, by=login) c.desc = "New status of %s approved by the IESG\n%s%s" % (newstatus(rel), settings.IDTRACKER_BASE_URL,reverse('doc_view', kwargs={'name': status_change.name})) c.save() return HttpResponseRedirect(status_change.get_absolute_url()) else: init = [] for rel in status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS): init.append({"announcement_text" : default_approval_text(status_change,rel), "label": "Announcement text for %s to %s"%(rel.target.document.canonical_name(),newstatus(rel)), }) formset = AnnouncementFormSet(initial=init) for form in formset.forms: form.fields['announcement_text'].label=form.label return render_to_response('doc/status_change/approve.html', dict( doc = status_change, formset = formset, ), context_instance=RequestContext(request))
def approve_ballot(request, name): """Approve ballot, sending out announcement, changing state.""" doc = get_object_or_404(Document, docalias__name=name) if not doc.get_state("draft-iesg"): raise Http404() login = request.user.person e = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") if not e: e = generate_approval_mail(request, doc) approval_text = e.text e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") if not e: e = generate_ballot_writeup(request, doc) ballot_writeup = e.text if "NOT be published" in approval_text: action = "do_not_publish" elif "To: RFC Editor" in approval_text: action = "to_rfc_editor" else: action = "to_announcement_list" # NOTE: according to Michelle Cotton <*****@*****.**> # (as per 2011-10-24) IANA is scraping these messages for # information so would like to know beforehand if the format # changes announcement = approval_text + "\n\n" + ballot_writeup if request.method == 'POST': if action == "do_not_publish": new_state = State.objects.get(used=True, type="draft-iesg", slug="dead") else: new_state = State.objects.get(used=True, type="draft-iesg", slug="ann") prev_state = doc.get_state("draft-iesg") prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) if new_state.slug == "ann" and new_state.slug != prev_state.slug and not request.REQUEST.get("skiprfceditorpost"): # start by notifying the RFC Editor import ietf.sync.rfceditor response, error = ietf.sync.rfceditor.post_approved_draft(settings.RFC_EDITOR_SYNC_NOTIFICATION_URL, doc.name) if error: return render_to_response('doc/draft/rfceditor_post_approved_draft_failed.html', dict(name=doc.name, response=response, error=error), context_instance=RequestContext(request)) save_document_in_history(doc) doc.set_state(new_state) doc.tags.remove(*prev_tags) # fixup document close_open_ballots(doc, login) e = DocEvent(doc=doc, by=login) if action == "do_not_publish": e.type = "iesg_disapproved" e.desc = "Do Not Publish note has been sent to the RFC Editor" else: e.type = "iesg_approved" e.desc = "IESG has approved the document" e.save() change_description = e.desc + " and state has been changed to %s" % doc.get_state("draft-iesg").name e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=[]) doc.time = (e and e.time) or datetime.datetime.now() doc.save() email_state_changed(request, doc, change_description) email_ad(request, doc, doc.ad, login, change_description) # send announcement send_mail_preformatted(request, announcement) if action == "to_announcement_list": send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc), override={ "To": "IANA <%s>"%settings.IANA_APPROVE_EMAIL, "CC": None, "Bcc": None, "Reply-To": None}) msg = infer_message(announcement) msg.by = login msg.save() msg.related_docs.add(doc) return HttpResponseRedirect(doc.get_absolute_url()) return render_to_response('doc/ballot/approve_ballot.html', dict(doc=doc, action=action, announcement=announcement), context_instance=RequestContext(request))
def make_last_call(request, name): """Make last call for Internet Draft, sending out announcement.""" doc = get_object_or_404(Document, docalias__name=name) if not (doc.get_state("draft-iesg") or doc.get_state("statchg")): raise Http404 login = request.user.person e = doc.latest_event(WriteupDocEvent, type="changed_last_call_text") if not e: if doc.type.slug != 'draft': raise Http404 e = generate_last_call_announcement(request, doc) announcement = e.text if request.method == 'POST': form = MakeLastCallForm(request.POST) if form.is_valid(): send_mail_preformatted(request, announcement) if doc.type.slug == 'draft': send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc), override={ "To": "IANA <*****@*****.**>", "CC": None, "Bcc": None, "Reply-To": None}) msg = infer_message(announcement) msg.by = login msg.save() msg.related_docs.add(doc) save_document_in_history(doc) new_state = doc.get_state() prev_tags = new_tags = [] if doc.type.slug == 'draft': new_state = State.objects.get(used=True, type="draft-iesg", slug='lc') prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) elif doc.type.slug == 'statchg': new_state = State.objects.get(used=True, type="statchg", slug='in-lc') prev_state = doc.get_state(new_state.type_id) doc.set_state(new_state) doc.tags.remove(*prev_tags) e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags) doc.time = (e and e.time) or datetime.datetime.now() doc.save() change_description = "Last call has been made for %s and state has been changed to %s" % (doc.name, new_state.name) email_state_changed(request, doc, change_description) email_ad(request, doc, doc.ad, login, change_description) e = LastCallDocEvent(doc=doc, by=login) e.type = "sent_last_call" e.desc = "The following Last Call announcement was sent out:<br><br>" e.desc += announcement if form.cleaned_data['last_call_sent_date'] != e.time.date(): e.time = datetime.datetime.combine(form.cleaned_data['last_call_sent_date'], e.time.time()) e.expires = form.cleaned_data['last_call_expiration_date'] e.save() # update IANA Review state if doc.type.slug == 'draft': prev_state = doc.get_state("draft-iana-review") if not prev_state: next_state = State.objects.get(used=True, type="draft-iana-review", slug="need-rev") doc.set_state(next_state) add_state_change_event(doc, login, prev_state, next_state) return HttpResponseRedirect(doc.get_absolute_url()) else: initial = {} initial["last_call_sent_date"] = datetime.date.today() if doc.type.slug == 'draft': # This logic is repeated in the code that edits last call text - why? expire_days = 14 if doc.group.type_id in ("individ", "area"): expire_days = 28 templ = 'doc/draft/make_last_call.html' else: expire_days=28 templ = 'doc/status_change/make_last_call.html' initial["last_call_expiration_date"] = datetime.date.today() + datetime.timedelta(days=expire_days) form = MakeLastCallForm(initial=initial) return render_to_response(templ, dict(doc=doc, form=form, announcement=announcement, ), context_instance=RequestContext(request))
def edit_material(request, name=None, acronym=None, action=None, doc_type=None): # the materials process is not very developed, so at the moment we # handle everything through the same view/form if action == "new": group = get_object_or_404(Group, acronym=acronym) if not group.features.has_materials: raise Http404 doc = None document_type = get_object_or_404(DocTypeName, slug=doc_type) else: doc = get_object_or_404(Document, name=name) group = doc.group document_type = doc.type if not can_manage_materials(request.user, group): return HttpResponseForbidden("You don't have permission to access this view") if request.method == 'POST': form = UploadMaterialForm(document_type, action, group, doc, request.POST, request.FILES) if form.is_valid(): if action == "new": doc = Document() doc.type = document_type doc.group = group doc.rev = "00" doc.name = form.cleaned_data["name"] prev_rev = None else: save_document_in_history(doc) prev_rev = doc.rev prev_title = doc.title prev_state = doc.get_state() if "title" in form.cleaned_data: doc.title = form.cleaned_data["title"] if "abstract" in form.cleaned_data: doc.abstract = form.cleaned_data["abstract"] doc.time = datetime.datetime.now() if "material" in form.fields: if action != "new": doc.rev = "%02d" % (int(doc.rev) + 1) f = form.cleaned_data["material"] file_ext = os.path.splitext(f.name)[1] with open(os.path.join(doc.get_file_path(), doc.name + "-" + doc.rev + file_ext), 'wb+') as dest: for chunk in f.chunks(): dest.write(chunk) doc.save() if action == "new": DocAlias.objects.get_or_create(name=doc.name, document=doc) if prev_rev != doc.rev: e = NewRevisionDocEvent(type="new_revision", doc=doc, rev=doc.rev) e.time = doc.time e.by = request.user.person e.desc = "New version available: <b>%s-%s</b>" % (doc.name, doc.rev) e.save() if prev_title != doc.title: e = DocEvent(doc=doc, by=request.user.person, type='changed_document') e.desc = u"Changed title to <b>%s</b>" % doc.title if prev_title: e.desc += u" from %s" % prev_title e.time = doc.time e.save() if "state" in form.cleaned_data and form.cleaned_data["state"] != prev_state: doc.set_state(form.cleaned_data["state"]) add_state_change_event(doc, request.user.person, prev_state, form.cleaned_data["state"]) return redirect("doc_view", name=doc.name) else: form = UploadMaterialForm(document_type, action, group, doc) return render(request, 'doc/material/edit_material.html', { 'group': group, 'form': form, 'action': action, 'document_type': document_type, 'doc_name': doc.name if doc else "", })
def change_state(request, name, option=None): """Change state of an status-change document, notifying parties as necessary and logging the change as a comment.""" status_change = get_object_or_404(Document, type="statchg", name=name) login = request.user.person if request.method == 'POST': form = ChangeStateForm(request.POST) if form.is_valid(): clean = form.cleaned_data new_state = clean['new_state'] comment = clean['comment'].rstrip() if comment: c = DocEvent(type="added_comment", doc=status_change, rev=status_change.rev, by=login) c.desc = comment c.save() prev_state = status_change.get_state() if new_state != prev_state: status_change.set_state(new_state) events = [] events.append( add_state_change_event(status_change, login, prev_state, new_state)) status_change.save_with_history(events) if new_state.slug == "iesgeval": create_ballot_if_not_open(status_change, login, "statchg", status_change.time) ballot = status_change.latest_event(BallotDocEvent, type="created_ballot") if has_role(request.user, "Area Director" ) and not status_change.latest_event( BallotPositionDocEvent, ad=login, ballot=ballot, type="changed_ballot_position"): # The AD putting a status change into iesgeval who doesn't already have a position is saying "yes" pos = BallotPositionDocEvent(doc=status_change, rev=status_change.rev, by=login) pos.ballot = ballot pos.type = "changed_ballot_position" pos.ad = login pos.pos_id = "yes" pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % ( pos.pos.name, pos.ad.plain_name()) pos.save() send_status_change_eval_email(request, status_change) if new_state.slug == "lc-req": request_last_call(request, status_change) return render( request, 'doc/draft/last_call_requested.html', dict( doc=status_change, url=status_change.get_absolute_url(), )) return redirect('ietf.doc.views_doc.document_main', name=status_change.name) else: s = status_change.get_state() init = dict( new_state=s.pk if s else None, type='statchg', label='Status Change Evaluation State', ) form = ChangeStateForm(initial=init) return render( request, 'doc/change_state.html', dict( form=form, doc=status_change, login=login, help_url=reverse('ietf.doc.views_help.state_help', kwargs=dict(type="status-change")), ))
def approve(request, name): """Approve charter, changing state, fixing revision, copying file to final location.""" charter = get_object_or_404(Document, type="charter", name=name) group = charter.group login = request.user.person e = charter.latest_event(WriteupDocEvent, type="changed_action_announcement") if not e: announcement = default_action_text(group, charter, login).text else: announcement = e.text if request.method == 'POST': new_charter_state = State.objects.get(used=True, type="charter", slug="approved") prev_charter_state = charter.get_state() save_document_in_history(charter) charter.set_state(new_charter_state) close_open_ballots(charter, login) # approve e = DocEvent(doc=charter, by=login) e.type = "iesg_approved" e.desc = "IESG has approved the charter" e.save() change_description = e.desc group_state_change_event = change_group_state_after_charter_approval(group, login) if group_state_change_event: change_description += " and group state has been changed to %s" % group.state.name add_state_change_event(charter, login, prev_charter_state, new_charter_state) fix_charter_revision_after_approval(charter, login) email_admin_re_charter(request, group, "Charter state changed to %s" % new_charter_state.name, change_description,'charter_state_edit_admin_needed') # move milestones over milestones_to_delete = list(group.groupmilestone_set.filter(state__in=("active", "review"))) for m in group.groupmilestone_set.filter(state="charter"): # see if we got this milestone already (i.e. it was copied # verbatim to the charter) found = False for i, o in enumerate(milestones_to_delete): if o.desc == m.desc and o.due == m.due and set(o.docs.all()) == set(m.docs.all()): found = True break if found: # keep existing, whack charter milestone if not o.state_id == "active": save_milestone_in_history(o) o.state_id = "active" o.save() MilestoneGroupEvent.objects.create( group=group, type="changed_milestone", by=login, desc="Changed milestone \"%s\", set state to active from review" % o.desc, milestone=o) del milestones_to_delete[i] # don't generate a DocEvent for this, it's implicit in the approval event save_milestone_in_history(m) m.state_id = "deleted" m.save() else: # move charter milestone save_milestone_in_history(m) m.state_id = "active" m.save() MilestoneGroupEvent.objects.create( group=group, type="changed_milestone", by=login, desc="Added milestone \"%s\", due %s, from approved charter" % (m.desc, m.due), milestone=m) for m in milestones_to_delete: save_milestone_in_history(m) m.state_id = "deleted" m.save() MilestoneGroupEvent.objects.create( group=group, type="changed_milestone", by=login, desc="Deleted milestone \"%s\", not present in approved charter" % m.desc, milestone=m) # send announcement send_mail_preformatted(request, announcement) return HttpResponseRedirect(charter.get_absolute_url()) return render_to_response('doc/charter/approve.html', dict(charter=charter, announcement=announcement), context_instance=RequestContext(request))
def change_state(request, name, option=None): """Change state of charter, notifying parties as necessary and logging the change as a comment.""" charter = get_object_or_404(Document, type="charter", name=name) group = charter.group if not can_manage_group_type(request.user, group.type_id): return HttpResponseForbidden("You don't have permission to access this view") chartering_type = get_chartering_type(charter) initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review") if charter.get_state_slug() != "infrev" or (initial_review and initial_review.expires < datetime.datetime.now()) or chartering_type == "rechartering": initial_review = None login = request.user.person if request.method == 'POST': form = ChangeStateForm(request.POST, group=group) if form.is_valid(): clean = form.cleaned_data charter_rev = charter.rev if option in ("initcharter", "recharter"): if group.type_id == "wg": charter_state = State.objects.get(used=True, type="charter", slug="infrev") else: charter_state = clean['charter_state'] # make sure we have the latest revision set, if we # abandoned a charter before, we could have reset the # revision to latest approved prev_revs = charter.history_set.order_by('-rev')[:1] if prev_revs and prev_revs[0].rev > charter_rev: charter_rev = prev_revs[0].rev if "-" not in charter_rev: charter_rev = charter_rev + "-00" elif option == "abandon": oldstate = group.state if oldstate.slug in ("proposed", "bof", "unknown"): charter_state = State.objects.get(used=True, type="charter", slug="notrev") #TODO : set an abandoned state and leave some comments here group.state = GroupStateName.objects.get(slug='abandon') group.save() e = ChangeStateGroupEvent(group=group, type="changed_state") e.time = group.time e.by = login e.state_id = group.state.slug e.desc = "Group state changed to %s from %s" % (group.state, oldstate) e.save() else: charter_state = State.objects.get(used=True, type="charter", slug="approved") charter_rev = approved_revision(charter.rev) else: charter_state = clean['charter_state'] comment = clean['comment'].rstrip() message = clean['message'] if charter_state != charter.get_state(): # Charter state changed save_document_in_history(charter) prev_state = charter.get_state() new_state = charter_state charter.set_state(new_state) charter.rev = charter_rev if option != "abandon": add_state_change_event(charter, login, prev_state, new_state) else: # kill hanging ballots close_open_ballots(charter, login) # Special log for abandoned efforts e = DocEvent(type="changed_document", doc=charter, by=login) e.desc = "IESG has abandoned the chartering effort" e.save() if comment: c = DocEvent(type="added_comment", doc=charter, by=login) c.desc = comment c.save() charter.time = datetime.datetime.now() charter.save() if charter_state.slug == 'intrev': email_charter_internal_review(request,charter) if message or charter_state.slug == "intrev" or charter_state.slug == "extrev": email_admin_re_charter(request, group, "Charter state changed to %s" % charter_state.name, message,'charter_state_edit_admin_needed') # TODO - do we need a seperate set of recipients for state changes to charters vrs other kind of documents email_state_changed(request, charter, "State changed to %s." % charter_state, 'doc_state_edited') if charter_state.slug == "intrev" and group.type_id == "wg": if request.POST.get("ballot_wo_extern"): create_ballot_if_not_open(charter, login, "r-wo-ext") else: create_ballot_if_not_open(charter, login, "r-extrev") default_review_text(group, charter, login) default_action_text(group, charter, login) elif charter_state.slug == "iesgrev": create_ballot_if_not_open(charter, login, "approve") elif charter_state.slug == "approved": change_group_state_after_charter_approval(group, login) fix_charter_revision_after_approval(charter, login) if charter_state.slug == "infrev" and clean["initial_time"] and clean["initial_time"] != 0: e = InitialReviewDocEvent(type="initial_review", by=login, doc=charter) e.expires = datetime.datetime.now() + datetime.timedelta(weeks=clean["initial_time"]) e.desc = "Initial review time expires %s" % e.expires.strftime("%Y-%m-%d") e.save() return redirect('doc_view', name=charter.name) else: hide = ['initial_time'] s = charter.get_state() init = dict(charter_state=s.pk if s and option != "recharter" else None) if option == "abandon": hide = ['initial_time', 'charter_state'] if group.type_id == "wg": if option == "recharter": hide = ['initial_time', 'charter_state', 'message'] init = dict() elif option == "initcharter": hide = ['charter_state'] init = dict(initial_time=1, message='%s has initiated chartering of the proposed %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym)) elif option == "abandon": hide = ['initial_time', 'charter_state'] init = dict(message='%s has abandoned the chartering effort on the %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym)) form = ChangeStateForm(hide=hide, initial=init, group=group) prev_charter_state = None 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 %s %s" % (group.acronym, group.type.name), "recharter": "Recharter %s %s" % (group.acronym, group.type.name), "abandon": "Abandon effort on %s %s" % (group.acronym, group.type.name), }.get(option) if not title: title = "Change chartering state of %s %s" % (group.acronym, group.type.name) def state_pk(slug): return State.objects.get(used=True, type="charter", slug=slug).pk info_msg = {} if group.type_id == "wg": info_msg[state_pk("infrev")] = 'The proposed charter for %s "%s" (%s) has been set to Informal IESG review by %s.' % (group.type.name, group.name, group.acronym, login.plain_name()) info_msg[state_pk("intrev")] = 'The proposed charter for %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat if it has not already been placed.' % (group.type.name, group.name, group.acronym, login.plain_name()) info_msg[state_pk("extrev")] = 'The proposed charter for %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (group.type.name, group.name, group.acronym, login.plain_name()) states_for_ballot_wo_extern = State.objects.none() if group.type_id == "wg": states_for_ballot_wo_extern = State.objects.filter(used=True, type="charter", slug="intrev").values_list("pk", flat=True) return render_to_response('doc/charter/change_state.html', dict(form=form, doc=group.charter, login=login, option=option, prev_charter_state=prev_charter_state, title=title, initial_review=initial_review, chartering_type=chartering_type, info_msg=json.dumps(info_msg), states_for_ballot_wo_extern=json.dumps(list(states_for_ballot_wo_extern)), ), context_instance=RequestContext(request))
def post_submission(request, submission): system = Person.objects.get(name="(System)") try: draft = Document.objects.get(name=submission.name) save_document_in_history(draft) except Document.DoesNotExist: draft = Document(name=submission.name) draft.intended_std_level = None prev_rev = draft.rev draft.type_id = "draft" draft.time = datetime.datetime.now() draft.title = submission.title group = submission.group or Group.objects.get(type="individ") if not (group.type_id == "individ" and draft.group and draft.group.type_id == "area"): # don't overwrite an assigned area if it's still an individual # submission draft.group_id = group.pk draft.rev = submission.rev draft.pages = submission.pages draft.abstract = submission.abstract was_rfc = draft.get_state_slug() == "rfc" if not draft.stream: stream_slug = None if draft.name.startswith("draft-iab-"): stream_slug = "iab" elif draft.name.startswith("draft-irtf-"): stream_slug = "irtf" elif draft.name.startswith("draft-ietf-") and (draft.group.type_id != "individ" or was_rfc): stream_slug = "ietf" if stream_slug: draft.stream = StreamName.objects.get(slug=stream_slug) draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) draft.save() submitter_parsed = submission.submitter_parsed() if submitter_parsed["name"] and submitter_parsed["email"]: submitter = ensure_person_email_info_exists(submitter_parsed["name"], submitter_parsed["email"]).person submitter_info = u'%s <%s>' % (submitter_parsed["name"], submitter_parsed["email"]) else: submitter = system submitter_info = system.name draft.set_state(State.objects.get(used=True, type="draft", slug="active")) DocAlias.objects.get_or_create(name=submission.name, document=draft) update_authors(draft, submission) trouble = rebuild_reference_relations(draft, filename=os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.txt' % (submission.name, submission.rev))) if trouble: log('Rebuild_reference_relations trouble: %s'%trouble) # new revision event e = NewRevisionDocEvent(type="new_revision", doc=draft, rev=draft.rev) e.time = draft.time #submission.submission_date e.by = submitter e.desc = "New version available: <b>%s-%s.txt</b>" % (draft.name, draft.rev) e.save() if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00": # automatically set state "WG Document" draft.set_state(State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="wg-doc")) if draft.get_state_slug("draft-iana-review") in ("ok-act", "ok-noact", "not-ok"): prev_state = draft.get_state("draft-iana-review") next_state = State.objects.get(used=True, type="draft-iana-review", slug="changed") draft.set_state(next_state) add_state_change_event(draft, submitter, prev_state, next_state) # clean up old files if prev_rev != draft.rev: from ietf.doc.expire import move_draft_files_to_archive move_draft_files_to_archive(draft, prev_rev) # automatic state changes state_change_msg = "" if not was_rfc and draft.tags.filter(slug="need-rev"): draft.tags.remove("need-rev") draft.tags.add("ad-f-up") e = DocEvent(type="changed_document", doc=draft) e.desc = "Sub state has been changed to <b>AD Followup</b> from <b>Revised ID Needed</b>" e.by = system e.save() state_change_msg = e.desc move_files_to_repository(submission) submission.state = DraftSubmissionStateName.objects.get(slug="posted") new_replaces, new_possibly_replaces = update_replaces_from_submission(request, submission, draft) announce_to_lists(request, submission) announce_new_version(request, submission, draft, state_change_msg) announce_to_authors(request, submission) if new_possibly_replaces: send_review_possibly_replaces_request(request, draft, submitter_info) submission.save()
def doc_detail(request, date, name): ''' This view displays the ballot information for the document, and lets the user make changes to ballot positions and document state. ''' doc = get_object_or_404(Document, docalias__name=name) # As of Datatracker v4.32, Conflict Review (conflrev) Document Types can # be added to the Telechat agenda. If Document.type_id == draft use draft-iesg # for state type state_type = doc.type_id if doc.type_id == 'draft': state_type = 'draft-iesg' login = request.user.person if doc.active_ballot(): ballots = doc.active_ballot().active_ad_positions() # returns dict of ad:ballotpositiondocevent else: ballots = [] # setup form initials initial_ballot = [] open_positions = 0 for key in sorted(ballots, key = lambda a: a.name_parts()[3]): initial_ballot.append({'name':key.name,'id':key.id,'position':ballots[key].pos.slug if ballots[key] else None}) if ballots[key] and ballots[key].pos.slug == 'norecord': open_positions += 1 elif not ballots[key]: open_positions += 1 tags = doc.tags.filter(slug__in=TELECHAT_TAGS) tag = tags[0].pk if tags else None writeup = get_doc_writeup(doc) initial_state = {'state':doc.get_state(state_type).pk, 'substate':tag} BallotFormset = formset_factory(BallotForm, extra=0) agenda = agenda_data(date=date) header = get_section_header(doc, agenda) # nav button logic doc_list = get_doc_list(agenda) nav_start = nav_end = False if doc == doc_list[0]: nav_start = True if doc == doc_list[-1]: nav_end = True if request.method == 'POST': button_text = request.POST.get('submit', '') # logic from doc/views_ballot.py EditPosition if button_text == 'update_ballot': formset = BallotFormset(request.POST, initial=initial_ballot) state_form = ChangeStateForm(initial=initial_state) has_changed = False for form in formset.forms: if form.is_valid() and form.changed_data: # create new BallotPositionDocEvent clean = form.cleaned_data ad = Person.objects.get(id=clean['id']) pos = BallotPositionDocEvent(doc=doc,by=login) pos.type = "changed_ballot_position" pos.ad = ad pos.ballot = doc.latest_event(BallotDocEvent, type="created_ballot") pos.pos = clean['position'] if form.initial['position'] == None: pos.desc = '[Ballot Position Update] New position, %s, has been recorded for %s by %s' % (pos.pos.name, ad.name, login.name) else: pos.desc = '[Ballot Position Update] Position for %s has been changed to %s by %s' % (ad.name, pos.pos.name, login.name) pos.save() has_changed = True if has_changed: messages.success(request,'Ballot position changed.') return redirect('telechat_doc_detail', date=date, name=name) # logic from doc/views_draft.py change_state elif button_text == 'update_state': formset = BallotFormset(initial=initial_ballot) state_form = ChangeStateForm(request.POST, initial=initial_state) if state_form.is_valid(): prev_state = doc.get_state(state_type) new_state = state_form.cleaned_data['state'] tag = state_form.cleaned_data['substate'] # tag handling is a bit awkward since the UI still works # as if IESG tags are a substate prev_tags = doc.tags.filter(slug__in=TELECHAT_TAGS) new_tags = [tag] if tag else [] if state_form.changed_data: save_document_in_history(doc) if 'state' in state_form.changed_data: doc.set_state(new_state) if 'substate' in state_form.changed_data: doc.tags.remove(*prev_tags) doc.tags.add(*new_tags) e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags) doc.time = (e and e.time) or datetime.datetime.now() doc.save() email_state_changed(request, doc, e.desc, 'doc_state_edited') if new_state.slug == "lc-req": request_last_call(request, doc) messages.success(request,'Document state updated') return redirect('telechat_doc_detail', date=date, name=name) else: formset = BallotFormset(initial=initial_ballot) state_form = ChangeStateForm(initial=initial_state) # if this is a conflict review document add referenced document if doc.type_id == 'conflrev': conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target.document else: conflictdoc = None return render_to_response('telechat/doc.html', { 'date': date, 'document': doc, 'conflictdoc': conflictdoc, 'agenda': agenda, 'formset': formset, 'header': header, 'open_positions': open_positions, 'state_form': state_form, 'writeup': writeup, 'nav_start': nav_start, 'nav_end': nav_end}, RequestContext(request, {}), )
def approve_ballot(request, name): """Approve ballot, sending out announcement, changing state.""" doc = get_object_or_404(Document, docalias__name=name) if not doc.get_state("draft-iesg"): raise Http404 login = request.user.person approval_mail_event = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text") if not approval_mail_event: approval_mail_event = generate_approval_mail(request, doc) approval_text = approval_mail_event.text ballot_writeup_event = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text") if not ballot_writeup_event: ballot_writeup_event = generate_ballot_writeup(request, doc) ballot_writeup = ballot_writeup_event.text error_duplicate_rfc_editor_note = False e = doc.latest_event(WriteupDocEvent, type="changed_rfc_editor_note_text") if e and (e.text != ""): if "RFC Editor Note" in ballot_writeup: error_duplicate_rfc_editor_note = True ballot_writeup += "\n\n" + e.text if error_duplicate_rfc_editor_note: return render(request, 'doc/draft/rfceditor_note_duplicate_error.html', {'doc': doc}) if "NOT be published" in approval_text: action = "do_not_publish" elif "To: RFC Editor" in approval_text: action = "to_rfc_editor" else: action = "to_announcement_list" # NOTE: according to Michelle Cotton <*****@*****.**> # (as per 2011-10-24) IANA is scraping these messages for # information so would like to know beforehand if the format # changes announcement = approval_text + "\n\n" + ballot_writeup if request.method == 'POST': if action == "do_not_publish": new_state = State.objects.get(used=True, type="draft-iesg", slug="dead") else: new_state = State.objects.get(used=True, type="draft-iesg", slug="ann") prev_state = doc.get_state("draft-iesg") prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) events = [] if approval_mail_event.pk == None: approval_mail_event.save() if ballot_writeup_event.pk == None: ballot_writeup_event.save() if new_state.slug == "ann" and new_state.slug != prev_state.slug and not request.POST.get( "skiprfceditorpost"): # start by notifying the RFC Editor import ietf.sync.rfceditor response, error = ietf.sync.rfceditor.post_approved_draft( settings.RFC_EDITOR_SYNC_NOTIFICATION_URL, doc.name) if error: return render( request, 'doc/draft/rfceditor_post_approved_draft_failed.html', dict(name=doc.name, response=response, error=error)) doc.set_state(new_state) doc.tags.remove(*prev_tags) # fixup document close_open_ballots(doc, login) e = DocEvent(doc=doc, rev=doc.rev, by=login) if action == "do_not_publish": e.type = "iesg_disapproved" e.desc = "Do Not Publish note has been sent to the RFC Editor" else: e.type = "iesg_approved" e.desc = "IESG has approved the document" e.save() events.append(e) e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=[]) if e: events.append(e) doc.save_with_history(events) # send announcement send_mail_preformatted(request, announcement) if action == "to_announcement_list": addrs = gather_address_lists( 'ballot_approved_ietf_stream_iana').as_strings(compact=False) send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc), override={ "To": addrs.to, "CC": addrs.cc, "Bcc": None, "Reply-To": None }) msg = infer_message(announcement) msg.by = login msg.save() msg.related_docs.add(doc) return HttpResponseRedirect(doc.get_absolute_url()) return render(request, 'doc/ballot/approve_ballot.html', dict(doc=doc, action=action, announcement=announcement))
def last_call(request, name): """Edit the Last Call Text for this status change and possibly request IETF LC""" status_change = get_object_or_404(Document, type="statchg", name=name) login = request.user.person last_call_event = status_change.latest_event(WriteupDocEvent, type="changed_last_call_text") if not last_call_event: last_call_event = generate_last_call_text(request, status_change) form = LastCallTextForm(initial=dict(last_call_text=last_call_event.text)) if request.method == 'POST': if "save_last_call_text" in request.POST or "send_last_call_request" in request.POST: form = LastCallTextForm(request.POST) if form.is_valid(): events = [] t = form.cleaned_data['last_call_text'] if t != last_call_event.text: e = WriteupDocEvent(doc=status_change, rev=status_change.rev, by=login) e.by = login e.type = "changed_last_call_text" e.desc = "Last call announcement was changed" e.text = t e.save() events.append(e) if "send_last_call_request" in request.POST: prev_state = status_change.get_state() new_state = State.objects.get(type='statchg', slug='lc-req') status_change.set_state(new_state) e = add_state_change_event(status_change, login, prev_state, new_state) if e: events.append(e) if events: status_change.save_with_history(events) request_last_call(request, status_change) return render( request, 'doc/draft/last_call_requested.html', dict( doc=status_change, url=status_change.get_absolute_url(), )) if "regenerate_last_call_text" in request.POST: e = generate_last_call_text(request, status_change) form = LastCallTextForm(initial=dict(last_call_text=e.text)) return render( request, 'doc/status_change/last_call.html', dict( doc=status_change, back_url=status_change.get_absolute_url(), last_call_event=last_call_event, last_call_form=form, ), )
def approve(request, name): """Approve this status change, setting the appropriate state and send the announcements to the right parties.""" status_change = get_object_or_404(Document, type="statchg", name=name) if status_change.get_state('statchg').slug not in ('appr-pend'): raise Http404 login = request.user.person AnnouncementFormSet = formset_factory(AnnouncementForm, extra=0) if request.method == 'POST': formset = AnnouncementFormSet(request.POST) if formset.is_valid(): prev_state = status_change.get_state() new_state = State.objects.get(type='statchg', slug='appr-sent') status_change.set_state(new_state) events = [] events.append( add_state_change_event(status_change, login, prev_state, new_state)) close_open_ballots(status_change, login) e = DocEvent(doc=status_change, rev=status_change.rev, by=login) e.type = "iesg_approved" e.desc = "IESG has approved the status change" e.save() events.append(e) status_change.save_with_history(events) for form in formset.forms: send_mail_preformatted(request, form.cleaned_data['announcement_text']) c = DocEvent(type="added_comment", doc=status_change, rev=status_change.rev, by=login) c.desc = "The following approval message was sent\n" + form.cleaned_data[ 'announcement_text'] c.save() for rel in status_change.relateddocument_set.filter( relationship__slug__in=STATUSCHANGE_RELATIONS): # Add a document event to each target c = DocEvent(type="added_comment", doc=rel.target.document, rev=rel.target.document.rev, by=login) c.desc = "New status of %s approved by the IESG\n%s%s" % ( newstatus(rel), settings.IDTRACKER_BASE_URL, reverse('ietf.doc.views_doc.document_main', kwargs={'name': status_change.name})) c.save() return HttpResponseRedirect(status_change.get_absolute_url()) else: init = [] for rel in status_change.relateddocument_set.filter( relationship__slug__in=STATUSCHANGE_RELATIONS): init.append({ "announcement_text": default_approval_text(status_change, rel), "label": "Announcement text for %s to %s" % (rel.target.document.canonical_name(), newstatus(rel)), }) formset = AnnouncementFormSet(initial=init) for form in formset.forms: form.fields['announcement_text'].label = form.label return render(request, 'doc/status_change/approve.html', dict( doc=status_change, formset=formset, ))
def perform_postREDESIGN(request, submission): system = Person.objects.get(name="(System)") group_id = submission.group_acronym_id or NONE_WG try: draft = Document.objects.get(name=submission.filename) save_document_in_history(draft) except Document.DoesNotExist: draft = Document(name=submission.filename) draft.intended_std_level = None prev_rev = draft.rev draft.type_id = "draft" draft.time = datetime.datetime.now() draft.title = submission.id_document_name if not (group_id == NONE_WG and draft.group and draft.group.type_id == "area"): # don't overwrite an assigned area if it's still an individual # submission draft.group_id = group_id draft.rev = submission.revision draft.pages = submission.txt_page_count draft.abstract = submission.abstract was_rfc = draft.get_state_slug() == "rfc" if not draft.stream: stream_slug = None if draft.name.startswith("draft-iab-"): stream_slug = "iab" elif draft.name.startswith("draft-irtf-"): stream_slug = "irtf" elif draft.name.startswith("draft-ietf-") and (draft.group.type_id != "individ" or was_rfc): stream_slug = "ietf" if stream_slug: draft.stream = StreamName.objects.get(slug=stream_slug) draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) draft.save() a = submission.tempidauthors_set.filter(author_order=0) if a: submitter = ensure_person_email_info_exists(a[0]).person else: submitter = system draft.set_state(State.objects.get(used=True, type="draft", slug="active")) DocAlias.objects.get_or_create(name=submission.filename, document=draft) update_authors(draft, submission) # new revision event e = NewRevisionDocEvent(type="new_revision", doc=draft, rev=draft.rev) e.time = draft.time #submission.submission_date e.by = submitter e.desc = "New version available: <b>%s-%s.txt</b>" % (draft.name, draft.rev) e.save() if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00": # automatically set state "WG Document" draft.set_state(State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="wg-doc")) if draft.get_state_slug("draft-iana-review") in ("ok-act", "ok-noact", "not-ok"): prev_state = draft.get_state("draft-iana-review") next_state = State.objects.get(used=True, type="draft-iana-review", slug="changed") draft.set_state(next_state) add_state_change_event(draft, submitter, prev_state, next_state) # clean up old files if prev_rev != draft.rev: from ietf.idrfc.expire import move_draft_files_to_archive move_draft_files_to_archive(draft, prev_rev) # automatic state changes state_change_msg = "" if not was_rfc and draft.tags.filter(slug="need-rev"): draft.tags.remove("need-rev") draft.tags.add("ad-f-up") e = DocEvent(type="changed_document", doc=draft) e.desc = "Sub state has been changed to <b>AD Followup</b> from <b>Revised ID Needed</b>" e.by = system e.save() state_change_msg = e.desc move_docs(submission) submission.status_id = POSTED announce_to_lists(request, submission) announce_new_version(request, submission, draft, state_change_msg) announce_to_authors(request, submission) submission.save()
def post_submission(request, submission, approvedDesc): system = Person.objects.get(name="(System)") submitter_parsed = submission.submitter_parsed() if submitter_parsed["name"] and submitter_parsed["email"]: submitter, _ = ensure_person_email_info_exists( submitter_parsed["name"], submitter_parsed["email"]) submitter_info = u'%s <%s>' % (submitter_parsed["name"], submitter_parsed["email"]) else: submitter = system submitter_info = system.name # update draft attributes try: draft = Document.objects.get(name=submission.name) except Document.DoesNotExist: draft = Document.objects.create(name=submission.name, type_id="draft") prev_rev = draft.rev draft.type_id = "draft" draft.title = submission.title group = submission.group or Group.objects.get(type="individ") if not (group.type_id == "individ" and draft.group and draft.group.type_id == "area"): # don't overwrite an assigned area if it's still an individual # submission draft.group = group draft.rev = submission.rev draft.pages = submission.pages draft.abstract = submission.abstract was_rfc = draft.get_state_slug() == "rfc" if not draft.stream: stream_slug = None if draft.name.startswith("draft-iab-"): stream_slug = "iab" elif draft.name.startswith("draft-irtf-"): stream_slug = "irtf" elif draft.name.startswith("draft-ietf-") and ( draft.group.type_id != "individ" or was_rfc): stream_slug = "ietf" if stream_slug: draft.stream = StreamName.objects.get(slug=stream_slug) draft.expires = datetime.datetime.now() + datetime.timedelta( settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) events = [] if draft.rev == '00': # Add all the previous submission events as docevents events += post_rev00_submission_events(draft, submission, submitter) # Add an approval docevent e = SubmissionDocEvent.objects.create( type="new_submission", doc=draft, by=system, desc=approvedDesc, submission=submission, rev=submission.rev, ) events.append(e) # new revision event e = NewRevisionDocEvent.objects.create( type="new_revision", doc=draft, rev=draft.rev, by=submitter, desc="New version available: <b>%s-%s.txt</b>" % (draft.name, draft.rev), ) events.append(e) # update related objects DocAlias.objects.get_or_create(name=submission.name, document=draft) draft.set_state(State.objects.get(used=True, type="draft", slug="active")) update_authors(draft, submission) draft.formal_languages = submission.formal_languages.all() trouble = rebuild_reference_relations( draft, filename=os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.txt' % (submission.name, submission.rev))) if trouble: log.log('Rebuild_reference_relations trouble: %s' % trouble) if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00": # automatically set state "WG Document" draft.set_state( State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="wg-doc")) # automatic state changes for IANA review if draft.get_state_slug("draft-iana-review") in ("ok-act", "ok-noact", "not-ok"): prev_state = draft.get_state("draft-iana-review") next_state = State.objects.get(used=True, type="draft-iana-review", slug="changed") draft.set_state(next_state) e = add_state_change_event(draft, system, prev_state, next_state) if e: events.append(e) state_change_msg = "" if not was_rfc and draft.tags.filter(slug="need-rev"): draft.tags.remove("need-rev") draft.tags.add("ad-f-up") e = DocEvent(type="changed_document", doc=draft, rev=draft.rev) e.desc = "Sub state has been changed to <b>AD Followup</b> from <b>Revised ID Needed</b>" e.by = system e.save() events.append(e) state_change_msg = e.desc if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00": # automatically set state "WG Document" draft.set_state( State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="wg-doc")) # save history now that we're done with changes to the draft itself draft.save_with_history(events) # clean up old files if prev_rev != draft.rev: from ietf.doc.expire import move_draft_files_to_archive move_draft_files_to_archive(draft, prev_rev) move_files_to_repository(submission) submission.state = DraftSubmissionStateName.objects.get(slug="posted") new_replaces, new_possibly_replaces = update_replaces_from_submission( request, submission, draft) update_name_contains_indexes_with_new_doc(draft) announce_to_lists(request, submission) announce_new_version(request, submission, draft, state_change_msg) announce_to_authors(request, submission) if new_possibly_replaces: send_review_possibly_replaces_request(request, draft, submitter_info) submission.draft = draft submission.save()
def edit_material(request, name=None, acronym=None, action=None, doc_type=None): # the materials process is not very developed, so at the moment we # handle everything through the same view/form if action == "new": group = get_object_or_404(Group, acronym=acronym) if not group.features.has_materials: raise Http404 doc = None document_type = get_object_or_404(DocTypeName, slug=doc_type) else: doc = get_object_or_404(Document, name=name) group = doc.group document_type = doc.type if document_type not in DocTypeName.objects.filter( slug__in=group.features.material_types ) and document_type.slug not in [ 'minutes', 'agenda', 'bluesheets', ]: raise Http404 if not can_manage_materials(request.user, group): return HttpResponseForbidden( "You don't have permission to access this view") if request.method == 'POST': form = UploadMaterialForm(document_type, action, group, doc, request.POST, request.FILES) if form.is_valid(): events = [] if action == "new": doc = Document.objects.create(type=document_type, group=group, rev="00", name=form.cleaned_data["name"]) prev_rev = None else: prev_rev = doc.rev prev_title = doc.title prev_state = doc.get_state() prev_abstract = doc.abstract if "title" in form.cleaned_data: doc.title = form.cleaned_data["title"] if "abstract" in form.cleaned_data: doc.abstract = form.cleaned_data["abstract"] if "material" in form.fields: if action != "new": doc.rev = "%02d" % (int(doc.rev) + 1) f = form.cleaned_data["material"] file_ext = os.path.splitext(f.name)[1] with open( os.path.join(doc.get_file_path(), doc.name + "-" + doc.rev + file_ext), 'wb+') as dest: for chunk in f.chunks(): dest.write(chunk) if action == "new": DocAlias.objects.get_or_create(name=doc.name, document=doc) if prev_rev != doc.rev: e = NewRevisionDocEvent(type="new_revision", doc=doc, rev=doc.rev) e.by = request.user.person e.desc = "New version available: <b>%s-%s</b>" % (doc.name, doc.rev) e.save() events.append(e) if prev_title != doc.title: e = DocEvent(doc=doc, rev=doc.rev, by=request.user.person, type='changed_document') e.desc = u"Changed title to <b>%s</b>" % doc.title if prev_title: e.desc += u" from %s" % prev_title e.save() events.append(e) if prev_abstract != doc.abstract: e = DocEvent(doc=doc, rev=doc.rev, by=request.user.person, type='changed_document') e.desc = u"Changed abstract to <b>%s</b>" % doc.abstract if prev_abstract: e.desc += u" from %s" % prev_abstract e.save() events.append(e) if "state" in form.cleaned_data and form.cleaned_data[ "state"] != prev_state: doc.set_state(form.cleaned_data["state"]) e = add_state_change_event(doc, request.user.person, prev_state, form.cleaned_data["state"]) events.append(e) if events: doc.save_with_history(events) return redirect("ietf.doc.views_doc.document_main", name=doc.name) else: form = UploadMaterialForm(document_type, action, group, doc) return render( request, 'doc/material/edit_material.html', { 'group': group, 'form': form, 'action': action, 'document_type': document_type, 'doc_name': doc.name if doc else "", })
def lastcalltext(request, name): """Editing of the last call text""" doc = get_object_or_404(Document, docalias__name=name) if not doc.get_state("draft-iesg"): raise Http404() login = request.user.person existing = doc.latest_event(WriteupDocEvent, type="changed_last_call_text") if not existing: existing = generate_last_call_announcement(request, doc) form = LastCallTextForm(initial=dict(last_call_text=existing.text)) if request.method == 'POST': if "save_last_call_text" in request.POST or "send_last_call_request" in request.POST: form = LastCallTextForm(request.POST) if form.is_valid(): t = form.cleaned_data['last_call_text'] if t != existing.text: e = WriteupDocEvent(doc=doc, by=login) e.by = login e.type = "changed_last_call_text" e.desc = "Last call announcement was changed" e.text = t e.save() if "send_last_call_request" in request.POST: save_document_in_history(doc) prev_state = doc.get_state("draft-iesg") new_state = State.objects.get(used=True, type="draft-iesg", slug='lc-req') prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) doc.set_state(new_state) doc.tags.remove(*prev_tags) e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=[]) doc.time = (e and e.time) or datetime.datetime.now() doc.save() email_state_changed(request, doc, e.desc) email_ad(request, doc, doc.ad, login, e.desc) request_last_call(request, doc) return render_to_response('doc/draft/last_call_requested.html', dict(doc=doc), context_instance=RequestContext(request)) if "regenerate_last_call_text" in request.POST: e = generate_last_call_announcement(request, doc) # make sure form has the updated text form = LastCallTextForm(initial=dict(last_call_text=e.text)) s = doc.get_state("draft-iesg") can_request_last_call = s.order < 27 can_make_last_call = s.order < 20 need_intended_status = "" if not doc.intended_std_level: need_intended_status = doc.file_tag() return render_to_response('doc/ballot/lastcalltext.html', dict(doc=doc, back_url=doc.get_absolute_url(), last_call_form=form, can_request_last_call=can_request_last_call, can_make_last_call=can_make_last_call, need_intended_status=need_intended_status, ), context_instance=RequestContext(request))
def approve(request, name): """Approve charter, changing state, fixing revision, copying file to final location.""" charter = get_object_or_404(Document, type="charter", name=name) group = charter.group by = request.user.person e = charter.latest_event(WriteupDocEvent, type="changed_action_announcement") if not e: announcement = default_action_text(group, charter, by).text else: announcement = e.text if request.method == 'POST': new_charter_state = State.objects.get(used=True, type="charter", slug="approved") prev_charter_state = charter.get_state() charter.set_state(new_charter_state) close_open_ballots(charter, by) events = [] # approve e = DocEvent(doc=charter, rev=charter.rev, by=by) e.type = "iesg_approved" e.desc = "IESG has approved the charter" e.save() events.append(e) change_description = e.desc group_state_change_event = change_group_state_after_charter_approval( group, by) if group_state_change_event: change_description += " and group state has been changed to %s" % group.state.name e = add_state_change_event(charter, by, prev_charter_state, new_charter_state) if e: events.append(e) fix_charter_revision_after_approval(charter, by) charter.save_with_history(events) email_admin_re_charter( request, group, "Charter state changed to \"%s\"" % new_charter_state.name, change_description, 'charter_state_edit_admin_needed') # move milestones over milestones_to_delete = list( group.groupmilestone_set.filter(state__in=("active", "review"))) for m in group.groupmilestone_set.filter(state="charter"): # see if we got this milestone already (i.e. it was copied # verbatim to the charter) found = False for i, o in enumerate(milestones_to_delete): if o.desc == m.desc and o.due == m.due and set( o.docs.all()) == set(m.docs.all()): found = True break if found: # keep existing, whack charter milestone if not o.state_id == "active": save_milestone_in_history(o) o.state_id = "active" o.save() MilestoneGroupEvent.objects.create( group=group, type="changed_milestone", by=by, desc= "Changed milestone \"%s\", set state to active from review" % o.desc, milestone=o) del milestones_to_delete[i] # don't generate a DocEvent for this, it's implicit in the approval event save_milestone_in_history(m) m.state_id = "deleted" m.save() else: # move charter milestone save_milestone_in_history(m) m.state_id = "active" m.save() MilestoneGroupEvent.objects.create( group=group, type="changed_milestone", by=by, desc="Added milestone \"%s\", due %s, from approved charter" % (m.desc, m.due), milestone=m) for m in milestones_to_delete: save_milestone_in_history(m) m.state_id = "deleted" m.save() MilestoneGroupEvent.objects.create( group=group, type="changed_milestone", by=by, desc="Deleted milestone \"%s\", not present in approved charter" % m.desc, milestone=m) # send announcement send_mail_preformatted(request, announcement) return HttpResponseRedirect(charter.get_absolute_url()) return render(request, 'doc/charter/approve.html', dict(charter=charter, announcement=announcement))
def change_state(request, name, option=None): """Change state of charter, notifying parties as necessary and logging the change as a comment.""" charter = get_object_or_404(Document, type="charter", name=name) group = charter.group if not can_manage_group_type(request.user, group): return HttpResponseForbidden( "You don't have permission to access this view") chartering_type = get_chartering_type(charter) initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review") if charter.get_state_slug() != "infrev" or ( initial_review and initial_review.expires < datetime.datetime.now()) or chartering_type == "rechartering": initial_review = None by = request.user.person if request.method == 'POST': form = ChangeStateForm(request.POST, group=group) if form.is_valid(): clean = form.cleaned_data charter_rev = charter.rev if option in ("initcharter", "recharter"): if group.type_id == "wg": charter_state = State.objects.get(used=True, type="charter", slug="infrev") else: charter_state = clean['charter_state'] # make sure we have the latest revision set, if we # abandoned a charter before, we could have reset the # revision to latest approved prev_revs = charter.history_set.order_by('-rev')[:1] if prev_revs and prev_revs[0].rev > charter_rev: charter_rev = prev_revs[0].rev if "-" not in charter_rev: charter_rev = charter_rev + "-00" elif option == "abandon": oldstate = group.state if oldstate.slug in ("proposed", "bof", "unknown"): charter_state = State.objects.get(used=True, type="charter", slug="notrev") #TODO : set an abandoned state and leave some comments here group.state = GroupStateName.objects.get(slug='abandon') group.save() e = ChangeStateGroupEvent(group=group, type="changed_state") e.time = group.time e.by = by e.state_id = group.state.slug e.desc = "Group state changed to \"%s\" from \"%s\"" % ( group.state, oldstate) e.save() else: charter_state = State.objects.get(used=True, type="charter", slug="approved") charter_rev = approved_revision(charter.rev) else: charter_state = clean['charter_state'] comment = clean['comment'].rstrip() message = clean['message'] if charter_state != charter.get_state(): events = [] prev_state = charter.get_state() new_state = charter_state charter.set_state(new_state) charter.rev = charter_rev if option != "abandon": e = add_state_change_event(charter, by, prev_state, new_state) if e: events.append(e) else: # kill hanging ballots close_open_ballots(charter, by) # Special log for abandoned efforts e = DocEvent(type="changed_document", doc=charter, rev=charter.rev, by=by) e.desc = "Chartering effort abandoned" e.save() events.append(e) if comment: events.append( DocEvent.objects.create(type="added_comment", doc=charter, rev=charter.rev, by=by, desc=comment)) charter.save_with_history(events) if charter_state.slug == 'intrev': email_charter_internal_review(request, charter) if message or charter_state.slug == "intrev" or charter_state.slug == "extrev": email_admin_re_charter( request, group, "Charter state changed to \"%s\"" % charter_state.name, message, 'charter_state_edit_admin_needed') # TODO - do we need a seperate set of recipients for state changes to charters vrs other kind of documents email_state_changed(request, charter, "State changed to %s." % charter_state, 'doc_state_edited') if charter_state.slug == "intrev" and group.type_id == "wg": if request.POST.get("ballot_wo_extern"): create_ballot_if_not_open(charter, by, "r-wo-ext") else: create_ballot_if_not_open(charter, by, "r-extrev") (e1, e2) = default_review_text(group, charter, by) e1.save() e2.save() e = default_action_text(group, charter, by) e.save() elif charter_state.slug in ["extrev", "iesgrev"]: create_ballot_if_not_open(charter, by, "approve") elif charter_state.slug == "approved": change_group_state_after_charter_approval(group, by) fix_charter_revision_after_approval(charter, by) if charter_state.slug == "infrev" and clean[ "initial_time"] and clean["initial_time"] != 0: e = InitialReviewDocEvent(type="initial_review", by=by, doc=charter, rev=charter.rev) e.expires = datetime.datetime.now() + datetime.timedelta( weeks=clean["initial_time"]) e.desc = "Initial review time expires %s" % e.expires.strftime( "%Y-%m-%d") e.save() return redirect('ietf.doc.views_doc.document_main', name=charter.name) else: hide = ['initial_time'] s = charter.get_state() init = dict( charter_state=s.pk if s and option != "recharter" else None) if option == "abandon": hide = ['initial_time', 'charter_state'] if group.type_id == "wg": if option == "recharter": hide = ['initial_time', 'charter_state', 'message'] init = dict() elif option == "initcharter": hide = ['charter_state'] init = dict( initial_time=1, message= '%s has initiated chartering of the proposed %s:\n "%s" (%s).' % (by.plain_name(), group.type.name, group.name, group.acronym)) elif option == "abandon": hide = ['initial_time', 'charter_state'] init = dict( message= '%s has abandoned the chartering effort on the %s:\n "%s" (%s).' % (by.plain_name(), group.type.name, group.name, group.acronym)) form = ChangeStateForm(hide=hide, initial=init, group=group) prev_charter_state = None 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 %s %s" % (group.acronym, group.type.name), "recharter": "Recharter %s %s" % (group.acronym, group.type.name), "abandon": "Abandon effort on %s %s" % (group.acronym, group.type.name), }.get(option) if not title: title = "Change chartering state of %s %s" % (group.acronym, group.type.name) def state_pk(slug): return State.objects.get(used=True, type="charter", slug=slug).pk info_msg = {} if group.type_id == "wg": info_msg[state_pk( "infrev" )] = 'The proposed charter for %s "%s" (%s) has been set to Informal IESG review by %s.' % ( group.type.name, group.name, group.acronym, by.plain_name()) info_msg[state_pk( "intrev" )] = 'The proposed charter for %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat if it has not already been placed.' % ( group.type.name, group.name, group.acronym, by.plain_name()) info_msg[state_pk( "extrev" )] = 'The proposed charter for %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % ( group.type.name, group.name, group.acronym, by.plain_name()) states_for_ballot_wo_extern = State.objects.none() if group.type_id == "wg": states_for_ballot_wo_extern = State.objects.filter( used=True, type="charter", slug="intrev").values_list("pk", flat=True) return render( request, 'doc/charter/change_state.html', dict( form=form, doc=group.charter, option=option, prev_charter_state=prev_charter_state, title=title, initial_review=initial_review, chartering_type=chartering_type, info_msg=json.dumps(info_msg), states_for_ballot_wo_extern=json.dumps( list(states_for_ballot_wo_extern)), ))
def defer_ballot(request, name): """Signal post-pone of ballot, notifying relevant parties.""" doc = get_object_or_404(Document, docalias__name=name) if doc.type_id not in ('draft', 'conflrev', 'statchg'): raise Http404 interesting_state = dict(draft='draft-iesg', conflrev='conflrev', statchg='statchg') state = doc.get_state(interesting_state[doc.type_id]) if not state or state.slug == 'defer' or not doc.telechat_date(): raise Http404 login = request.user.person telechat_date = TelechatDate.objects.active().order_by("date")[1].date if request.method == 'POST': new_state = doc.get_state() prev_tags = [] new_tags = [] if doc.type_id == 'draft': new_state = State.objects.get(used=True, type="draft-iesg", slug='defer') prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) elif doc.type_id in ['conflrev', 'statchg']: new_state = State.objects.get(used=True, type=doc.type_id, slug='defer') prev_state = doc.get_state(new_state.type_id if new_state else None) doc.set_state(new_state) doc.tags.remove(*prev_tags) events = [] state_change_event = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags) if state_change_event: events.append(state_change_event) e = update_telechat(request, doc, login, telechat_date) if e: events.append(e) doc.save_with_history(events) email_ballot_deferred(request, doc, login.plain_name(), telechat_date) return HttpResponseRedirect(doc.get_absolute_url()) return render( request, 'doc/ballot/defer_ballot.html', dict(doc=doc, telechat_date=telechat_date, back_url=doc.get_absolute_url()))
def update_history_with_changes(changes, send_email=True): """Take parsed changes from IANA and apply them. Note that we expect to get these chronologically sorted, otherwise the change descriptions generated may not be right.""" # build up state lookup states = {} slookup = dict((s.slug, s) for s in State.objects.filter(used=True, type=StateType.objects.get(slug="draft-iana-action"))) states["action"] = { "": slookup["newdoc"], "In Progress": slookup["inprog"], "Open": slookup["inprog"], "pre-approval In Progress": slookup["inprog"], "Waiting on Authors": slookup["waitauth"], "Author": slookup["waitauth"], "Waiting on ADs": slookup["waitad"], "Waiting on AD": slookup["waitad"], "AD": slookup["waitad"], "Waiting on WGC": slookup["waitwgc"], "WGC": slookup["waitwgc"], "Waiting on RFC-Editor": slookup["waitrfc"], "Waiting on RFC Editor": slookup["waitrfc"], "RFC-Editor": slookup["waitrfc"], "RFC-Ed-ACK": slookup["rfcedack"], "RFC-Editor-ACK": slookup["rfcedack"], "Completed": slookup["rfcedack"], "On Hold": slookup["onhold"], "No IC": slookup["noic"], } slookup = dict((s.slug, s) for s in State.objects.filter(used=True, type=StateType.objects.get(slug="draft-iana-review"))) states["review"] = { "IANA Review Needed": slookup["need-rev"], "IANA - Review Needed": slookup["need-rev"], "IANA OK - Actions Needed": slookup["ok-act"], "IANA OK - No Actions Needed": slookup["ok-noact"], "IANA Not OK": slookup["not-ok"], "IANA - Not OK": slookup["not-ok"], "Version Changed - Review Needed": slookup["changed"], } # so it turns out IANA has made a mistake and are including some # wrong states, we'll have to skip those wrong_action_states = ("Waiting on Reviewer", "Review Complete", "Last Call", "Last Call - Questions", "Evaluation", "Evaluation - Questions", "With Reviewer", "IESG Notification Received", "Watiing on Last Call", "IANA Comments Submitted", "Waiting on Last Call") system = Person.objects.get(name="(System)") added_events = [] warnings = [] for c in changes: docname = c['doc'] timestamp = datetime.datetime.strptime(c["time"], "%Y-%m-%d %H:%M:%S") timestamp = utc_to_local_timezone(timestamp) # timestamps are in UTC if c['type'] in ("iana_state", "iana_review"): if c['type'] == "iana_state": kind = "action" if c["state"] in wrong_action_states: warnings.append("Wrong action state '%s' encountered in changes from IANA" % c["state"]) continue else: kind = "review" if c["state"] not in states[kind]: warnings.append("Unknown IANA %s state %s (%s)" % (kind, c["state"], timestamp)) continue state = states[kind][c["state"]] state_type = "draft-iana-%s" % kind if state.slug in ("need-rev", "changed"): # the Datatracker is the ultimate source of these # states, so skip them continue e = StateDocEvent.objects.filter(type="changed_state", time=timestamp, state_type=state_type, state=state) if not e: try: doc = Document.objects.get(docalias__name=docname) except Document.DoesNotExist: warnings.append("Document %s not found" % docname) continue # the naive way of extracting prev_state here means # that we assume these changes are cronologically # applied prev_state = doc.get_state(state_type) e = add_state_change_event(doc, system, prev_state, state, timestamp) if e: # for logging purposes e.json = c added_events.append(e) if not StateDocEvent.objects.filter(doc=doc, time__gt=timestamp, state_type=state_type): save_document_in_history(doc) doc.set_state(state) if send_email and (state != prev_state): email_state_changed(None, doc, "IANA %s state changed to %s" % (kind, state.name)) email_owner(None, doc, doc.ad, system, "IANA %s state changed to %s" % (kind, state.name)) if doc.time < timestamp: doc.time = timestamp doc.save() return added_events, warnings
def lastcalltext(request, name): """Editing of the last call text""" doc = get_object_or_404(Document, docalias__name=name) if not doc.get_state("draft-iesg"): raise Http404 login = request.user.person existing = doc.latest_event(WriteupDocEvent, type="changed_last_call_text") if not existing: existing = generate_last_call_announcement(request, doc) form = LastCallTextForm(initial=dict(last_call_text=existing.text)) if request.method == 'POST': if "save_last_call_text" in request.POST or "send_last_call_request" in request.POST: form = LastCallTextForm(request.POST) if form.is_valid(): t = form.cleaned_data['last_call_text'] if t != existing.text: e = WriteupDocEvent(doc=doc, rev=doc.rev, by=login) e.by = login e.type = "changed_last_call_text" e.desc = "Last call announcement was changed" e.text = t e.save() elif existing.pk == None: existing.save() if "send_last_call_request" in request.POST: prev_state = doc.get_state("draft-iesg") new_state = State.objects.get(used=True, type="draft-iesg", slug='lc-req') prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) doc.set_state(new_state) doc.tags.remove(*prev_tags) e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=[]) if e: doc.save_with_history([e]) request_last_call(request, doc) return render(request, 'doc/draft/last_call_requested.html', dict(doc=doc)) if "regenerate_last_call_text" in request.POST: e = generate_last_call_announcement(request, doc) e.save() # make sure form has the updated text form = LastCallTextForm(initial=dict(last_call_text=e.text)) s = doc.get_state("draft-iesg") can_request_last_call = s.order < 27 can_make_last_call = s.order < 20 need_intended_status = "" if not doc.intended_std_level: need_intended_status = doc.file_tag() return render( request, 'doc/ballot/lastcalltext.html', dict( doc=doc, back_url=doc.get_absolute_url(), last_call_form=form, can_request_last_call=can_request_last_call, can_make_last_call=can_make_last_call, need_intended_status=need_intended_status, ))
def perform_postREDESIGN(request, submission): system = Person.objects.get(name="(System)") group_id = submission.group_acronym_id or NONE_WG try: draft = Document.objects.get(name=submission.filename) save_document_in_history(draft) except Document.DoesNotExist: draft = Document(name=submission.filename) draft.intended_std_level = None prev_rev = draft.rev draft.type_id = "draft" draft.time = datetime.datetime.now() draft.title = submission.id_document_name if not (group_id == NONE_WG and draft.group and draft.group.type_id == "area"): # don't overwrite an assigned area if it's still an individual # submission draft.group_id = group_id draft.rev = submission.revision draft.pages = submission.txt_page_count draft.abstract = submission.abstract was_rfc = draft.get_state_slug() == "rfc" if not draft.stream: stream_slug = None if draft.name.startswith("draft-iab-"): stream_slug = "iab" elif draft.name.startswith("draft-irtf-"): stream_slug = "irtf" elif draft.name.startswith("draft-ietf-") and ( draft.group.type_id != "individ" or was_rfc): stream_slug = "ietf" if stream_slug: draft.stream = StreamName.objects.get(slug=stream_slug) draft.expires = datetime.datetime.now() + datetime.timedelta( settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) draft.save() a = submission.tempidauthors_set.filter(author_order=0) if a: submitter = ensure_person_email_info_exists(a[0]).person else: submitter = system draft.set_state(State.objects.get(used=True, type="draft", slug="active")) DocAlias.objects.get_or_create(name=submission.filename, document=draft) update_authors(draft, submission) # new revision event e = NewRevisionDocEvent(type="new_revision", doc=draft, rev=draft.rev) e.time = draft.time #submission.submission_date e.by = submitter e.desc = "New version available: <b>%s-%s.txt</b>" % (draft.name, draft.rev) e.save() if draft.stream_id == "ietf" and draft.group.type_id == "wg" and draft.rev == "00": # automatically set state "WG Document" draft.set_state( State.objects.get(used=True, type="draft-stream-%s" % draft.stream_id, slug="wg-doc")) if draft.get_state_slug("draft-iana-review") in ("ok-act", "ok-noact", "not-ok"): prev_state = draft.get_state("draft-iana-review") next_state = State.objects.get(used=True, type="draft-iana-review", slug="changed") draft.set_state(next_state) add_state_change_event(draft, submitter, prev_state, next_state) # clean up old files if prev_rev != draft.rev: from ietf.idrfc.expire import move_draft_files_to_archive move_draft_files_to_archive(draft, prev_rev) # automatic state changes state_change_msg = "" if not was_rfc and draft.tags.filter(slug="need-rev"): draft.tags.remove("need-rev") draft.tags.add("ad-f-up") e = DocEvent(type="changed_document", doc=draft) e.desc = "Sub state has been changed to <b>AD Followup</b> from <b>Revised ID Needed</b>" e.by = system e.save() state_change_msg = e.desc move_docs(submission) submission.status_id = POSTED announce_to_lists(request, submission) announce_new_version(request, submission, draft, state_change_msg) announce_to_authors(request, submission) submission.save()
def make_last_call(request, name): """Make last call for Internet Draft, sending out announcement.""" doc = get_object_or_404(Document, docalias__name=name) if not (doc.get_state("draft-iesg") or doc.get_state("statchg")): raise Http404 login = request.user.person announcement_event = doc.latest_event(WriteupDocEvent, type="changed_last_call_text") if not announcement_event: if doc.type_id != 'draft': raise Http404 announcement_event = generate_last_call_announcement(request, doc) announcement = announcement_event.text if request.method == 'POST': form = MakeLastCallForm(request.POST) if form.is_valid(): if announcement_event.pk == None: announcement_event.save() send_mail_preformatted(request, announcement) if doc.type.slug == 'draft': addrs = gather_address_lists('last_call_issued_iana', doc=doc).as_strings(compact=False) send_mail_preformatted(request, announcement, extra=extra_automation_headers(doc), override={ "To": addrs.to, "CC": addrs.cc, "Bcc": None, "Reply-To": None }) msg = infer_message(announcement) msg.by = login msg.save() msg.related_docs.add(doc) new_state = doc.get_state() prev_tags = [] new_tags = [] events = [] if doc.type.slug == 'draft': new_state = State.objects.get(used=True, type="draft-iesg", slug='lc') prev_tags = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) elif doc.type.slug == 'statchg': new_state = State.objects.get(used=True, type="statchg", slug='in-lc') prev_state = doc.get_state(new_state.type_id) doc.set_state(new_state) doc.tags.remove(*prev_tags) e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags) if e: events.append(e) expiration_date = form.cleaned_data['last_call_expiration_date'] e = LastCallDocEvent(doc=doc, rev=doc.rev, by=login) e.type = "sent_last_call" e.desc = "The following Last Call announcement was sent out (ends %s):<br><br>" % expiration_date e.desc += announcement if form.cleaned_data['last_call_sent_date'] != e.time.date(): e.time = datetime.datetime.combine( form.cleaned_data['last_call_sent_date'], e.time.time()) e.expires = expiration_date e.save() events.append(e) # update IANA Review state if doc.type.slug == 'draft': prev_state = doc.get_state("draft-iana-review") if not prev_state: next_state = State.objects.get(used=True, type="draft-iana-review", slug="need-rev") doc.set_state(next_state) e = add_state_change_event(doc, login, prev_state, next_state) if e: events.append(e) doc.save_with_history(events) return HttpResponseRedirect(doc.get_absolute_url()) else: initial = {} initial["last_call_sent_date"] = datetime.date.today() if doc.type.slug == 'draft': # This logic is repeated in the code that edits last call text - why? expire_days = 14 if doc.group.type_id in ("individ", "area"): expire_days = 28 templ = 'doc/draft/make_last_call.html' else: expire_days = 28 templ = 'doc/status_change/make_last_call.html' initial["last_call_expiration_date"] = datetime.date.today( ) + datetime.timedelta(days=expire_days) form = MakeLastCallForm(initial=initial) return render(request, templ, dict( doc=doc, form=form, announcement=announcement, ))
def update_history_with_changes(changes, send_email=True): """Take parsed changes from IANA and apply them. Note that we expect to get these chronologically sorted, otherwise the change descriptions generated may not be right.""" # build up state lookup states = {} slookup = dict((s.slug, s) for s in State.objects.filter(used=True, type=StateType.objects.get(slug="draft-iana-action"))) states["action"] = { "": slookup["newdoc"], "In Progress": slookup["inprog"], "Open": slookup["inprog"], "pre-approval In Progress": slookup["inprog"], "Waiting on Authors": slookup["waitauth"], "Author": slookup["waitauth"], "Waiting on ADs": slookup["waitad"], "Waiting on AD": slookup["waitad"], "AD": slookup["waitad"], "Waiting on WGC": slookup["waitwgc"], "WGC": slookup["waitwgc"], "Waiting on RFC-Editor": slookup["waitrfc"], "Waiting on RFC Editor": slookup["waitrfc"], "RFC-Editor": slookup["waitrfc"], "RFC-Ed-ACK": slookup["rfcedack"], "RFC-Editor-ACK": slookup["rfcedack"], "Completed": slookup["rfcedack"], "On Hold": slookup["onhold"], "No IC": slookup["noic"], } slookup = dict((s.slug, s) for s in State.objects.filter(used=True, type=StateType.objects.get(slug="draft-iana-review"))) states["review"] = { "IANA Review Needed": slookup["need-rev"], "IANA - Review Needed": slookup["need-rev"], "IANA OK - Actions Needed": slookup["ok-act"], "IANA OK - No Actions Needed": slookup["ok-noact"], "IANA Not OK": slookup["not-ok"], "IANA - Not OK": slookup["not-ok"], "Version Changed - Review Needed": slookup["changed"], } # so it turns out IANA has made a mistake and are including some # wrong states, we'll have to skip those wrong_action_states = ("Waiting on Reviewer", "Review Complete", "Last Call", "Last Call - Questions", "Evaluation", "Evaluation - Questions", "With Reviewer", "IESG Notification Received", "Watiing on Last Call", "IANA Comments Submitted", "Waiting on Last Call") system = Person.objects.get(name="(System)") added_events = [] warnings = [] for c in changes: docname = c['doc'] timestamp = datetime.datetime.strptime(c["time"], "%Y-%m-%d %H:%M:%S") timestamp = utc_to_local_timezone(timestamp) # timestamps are in UTC if c['type'] in ("iana_state", "iana_review"): if c['type'] == "iana_state": kind = "action" if c["state"] in wrong_action_states: warnings.append("Wrong action state '%s' encountered in changes from IANA" % c["state"]) continue else: kind = "review" if c["state"] not in states[kind]: warnings.append("Unknown IANA %s state %s (%s)" % (kind, c["state"], timestamp)) continue state = states[kind][c["state"]] state_type = "draft-iana-%s" % kind if state.slug in ("need-rev", "changed"): # the Datatracker is the ultimate source of these # states, so skip them continue e = StateDocEvent.objects.filter(type="changed_state", time=timestamp, state_type=state_type, state=state) if not e: try: doc = Document.objects.get(docalias__name=docname) except Document.DoesNotExist: warnings.append("Document %s not found" % docname) continue # the naive way of extracting prev_state here means # that we assume these changes are cronologically # applied prev_state = doc.get_state(state_type) e = add_state_change_event(doc, system, prev_state, state, timestamp=timestamp) if e: # for logging purposes e.json = c added_events.append(e) if not StateDocEvent.objects.filter(doc=doc, time__gt=timestamp, state_type=state_type): doc.set_state(state) if e: doc.save_with_history([e]) if send_email and (state != prev_state): email_state_changed(None, doc, "IANA %s state changed to \"%s\"" % (kind, state.name),'doc_iana_state_changed') return added_events, warnings
def doc_detail(request, date, name): ''' This view displays the ballot information for the document, and lets the user make changes to ballot positions and document state. ''' doc = get_object_or_404(Document, docalias__name=name) if not is_doc_on_telechat(doc, date): messages.warning( request, 'Dcoument: {name} is not on the Telechat agenda for {date}'.format( name=doc.name, date=date)) return redirect('ietf.secr.telechat.views.doc', date=date) # As of Datatracker v4.32, Conflict Review (conflrev) Document Types can # be added to the Telechat agenda. If Document.type_id == draft use draft-iesg # for state type state_type = doc.type_id if doc.type_id == 'draft': state_type = 'draft-iesg' login = request.user.person if doc.active_ballot(): ballots = doc.active_ballot().active_ad_positions( ) # returns dict of ad:ballotpositiondocevent else: ballots = [] # setup form initials initial_ballot = [] open_positions = 0 for key in sorted(ballots, key=lambda a: a.name_parts()[3]): initial_ballot.append({ 'name': key.name, 'id': key.id, 'position': ballots[key].pos.slug if ballots[key] else None }) if ballots[key] and ballots[key].pos.slug == 'norecord': open_positions += 1 elif not ballots[key]: open_positions += 1 tags = doc.tags.filter(slug__in=TELECHAT_TAGS) tag = tags[0].pk if tags else None writeup = get_doc_writeup(doc) initial_state = {'state': doc.get_state(state_type).pk, 'substate': tag} # need to use curry here to pass custom variable to form init if doc.active_ballot(): ballot_type = doc.active_ballot().ballot_type else: ballot_type = BallotType.objects.get(doc_type='draft') BallotFormset = formset_factory(BallotForm, extra=0) BallotFormset.form.__init__ = curry(BallotForm.__init__, ballot_type=ballot_type) agenda = agenda_data(date=date) header = get_section_header(doc, agenda) # nav button logic doc_list = get_doc_list(agenda) nav_start = nav_end = False if doc == doc_list[0]: nav_start = True if doc == doc_list[-1]: nav_end = True if request.method == 'POST': button_text = request.POST.get('submit', '') # logic from doc/views_ballot.py EditPosition if button_text == 'update_ballot': formset = BallotFormset(request.POST, initial=initial_ballot) state_form = ChangeStateForm(initial=initial_state) has_changed = False for form in formset.forms: if form.is_valid() and form.changed_data: # create new BallotPositionDocEvent clean = form.cleaned_data ad = Person.objects.get(id=clean['id']) pos = BallotPositionDocEvent(doc=doc, rev=doc.rev, by=login) pos.type = "changed_ballot_position" pos.ad = ad pos.ballot = doc.latest_event(BallotDocEvent, type="created_ballot") pos.pos = clean['position'] if form.initial['position'] == None: pos.desc = '[Ballot Position Update] New position, %s, has been recorded for %s by %s' % ( pos.pos.name, ad.name, login.name) else: pos.desc = '[Ballot Position Update] Position for %s has been changed to %s by %s' % ( ad.name, pos.pos.name, login.name) pos.save() has_changed = True if has_changed: messages.success(request, 'Ballot position changed.') return redirect('ietf.secr.telechat.views.doc_detail', date=date, name=name) # logic from doc/views_draft.py change_state elif button_text == 'update_state': formset = BallotFormset(initial=initial_ballot) state_form = ChangeStateForm(request.POST, initial=initial_state) if state_form.is_valid(): prev_state = doc.get_state(state_type) new_state = state_form.cleaned_data['state'] tag = state_form.cleaned_data['substate'] # tag handling is a bit awkward since the UI still works # as if IESG tags are a substate prev_tags = doc.tags.filter(slug__in=TELECHAT_TAGS) new_tags = [tag] if tag else [] if state_form.changed_data: if 'state' in state_form.changed_data: doc.set_state(new_state) if 'substate' in state_form.changed_data: doc.tags.remove(*prev_tags) doc.tags.add(*new_tags) e = add_state_change_event(doc, login, prev_state, new_state, prev_tags=prev_tags, new_tags=new_tags) if e: doc.save_with_history([e]) email_state_changed(request, doc, e.desc, 'doc_state_edited') if new_state.slug == "lc-req": request_last_call(request, doc) messages.success(request, 'Document state updated') return redirect('ietf.secr.telechat.views.doc_detail', date=date, name=name) else: formset = BallotFormset(initial=initial_ballot) state_form = ChangeStateForm(initial=initial_state) # if this is a conflict review document add referenced document if doc.type_id == 'conflrev': conflictdoc = doc.relateddocument_set.get( relationship__slug='conflrev').target.document else: conflictdoc = None return render( request, 'telechat/doc.html', { 'ballot_type': ballot_type, 'date': date, 'document': doc, 'conflictdoc': conflictdoc, 'agenda': agenda, 'formset': formset, 'header': header, 'open_positions': open_positions, 'state_form': state_form, 'writeup': writeup, 'nav_start': nav_start, 'nav_end': nav_end }, )
def update_drafts_from_queue(drafts): tag_mapping = { 'IANA': DocTagName.objects.get(slug='iana'), 'REF': DocTagName.objects.get(slug='ref') } slookup = dict((s.slug, s) for s in State.objects.filter(used=True, type=StateType.objects.get(slug="draft-rfceditor"))) state_mapping = { 'AUTH': slookup['auth'], 'AUTH48': slookup['auth48'], 'AUTH48-DONE': slookup['auth48-done'], 'EDIT': slookup['edit'], 'IANA': slookup['iana'], 'IESG': slookup['iesg'], 'ISR': slookup['isr'], 'ISR-AUTH': slookup['isr-auth'], 'REF': slookup['ref'], 'RFC-EDITOR': slookup['rfc-edit'], 'TO': slookup['timeout'], 'MISSREF': slookup['missref'], } system = Person.objects.get(name="(System)") warnings = [] names = [t[0] for t in drafts] drafts_in_db = dict((d.name, d) for d in Document.objects.filter(type="draft", docalias__name__in=names)) changed = set() for name, date_received, state, tags, missref_generation, stream, auth48, cluster, refs in drafts: if name not in drafts_in_db: warnings.append("unknown document %s" % name) continue if not state or state not in state_mapping: warnings.append("unknown state '%s'" % state) continue d = drafts_in_db[name] prev_state = d.get_state("draft-rfceditor") next_state = state_mapping[state] # check if we've noted it's been received if d.get_state_slug("draft-iesg") == "ann" and not prev_state and not d.latest_event(DocEvent, type="rfc_editor_received_announcement"): e = DocEvent(doc=d, by=system, type="rfc_editor_received_announcement") e.desc = "Announcement was received by RFC Editor" e.save() send_mail_text(None, "*****@*****.**", None, '%s in RFC Editor queue' % d.name, 'The announcement for %s has been received by the RFC Editor.' % d.name) if prev_state != next_state: save_document_in_history(d) d.set_state(next_state) e = add_state_change_event(d, system, prev_state, next_state) if auth48: e.desc = re.sub(r"(<b>.*</b>)", "<a href=\"%s\">\\1</a>" % auth48, e.desc) e.save() changed.add(name) t = DocTagName.objects.filter(slug__in=tags) if set(t) != set(d.tags.all()): d.tags = t changed.add(name) # remove tags and states for those not in the queue anymore for d in Document.objects.exclude(docalias__name__in=names).filter(states__type="draft-rfceditor").distinct(): d.tags.remove(*tag_mapping.values()) d.unset_state("draft-rfceditor") # we do not add a history entry here - most likely we already # have something that explains what happened changed.add(name) return changed, warnings