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 do_replace(draft, request): 'Perform document replace' save_document_in_history(draft) replaced = request.session['data']['replaced'] # a DocAlias replaced_by = request.session['data']['replaced_by'] # a Document # change state and update last modified draft.set_state(State.objects.get(type="draft", slug="repl")) draft.time = datetime.datetime.now() draft.save() # create relationship RelatedDocument.objects.create(source=replaced_by, target=replaced, relationship=DocRelationshipName.objects.get(slug='replaces')) # create DocEvent # no replace DocEvent at this time, Jan 2012 # move replaced document to archive archive_draft_files(replaced.document.name + '-' + replaced.document.rev) # send announcement announcement_from_form(request.session['email'],by=request.user.person) return
def edit_notify(request, name): doc = get_object_or_404(Document, type="charter", name=name) login = request.user.person init = {'notify': doc.notify} if request.method == "POST": form = NotifyForm(request.POST, initial=init) if form.is_valid(): n = form.cleaned_data["notify"] if n != doc.notify: save_document_in_history(doc) e = DocEvent(doc=doc, by=login) e.desc = "Notification list changed to %s" % (escape(n) or "none") if doc.notify: e.desc += " from %s" % escape(doc.notify) e.type = "changed_document" e.save() doc.notify = n doc.time = e.time doc.save() return redirect("doc_view", name=doc.name) else: form = NotifyForm(initial=init) return render_to_response('doc/charter/edit_notify.html', dict(doc=doc, form=form, user=request.user, login=login), context_instance=RequestContext(request))
def test_document_primary_and_history_views(self): make_test_data() # Ensure primary views of both current and historic versions of documents works for docname in ["draft-imaginary-independent-submission", "conflict-review-imaginary-irtf-submission", "status-change-imaginary-mid-review", "charter-ietf-mars", "agenda-42-mars", "minutes-42-mars", "slides-42-mars-1", ]: doc = Document.objects.get(name=docname) # give it some history save_document_in_history(doc) doc.rev="01" doc.save() r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name))) self.assertEqual(r.status_code, 200) self.assertTrue("%s-01"%docname in unicontent(r)) r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name,rev="01"))) self.assertEqual(r.status_code, 302) r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name,rev="00"))) self.assertEqual(r.status_code, 200) self.assertTrue("%s-00"%docname in unicontent(r))
def expire_draft(doc): # clean up files move_draft_files_to_archive(doc, doc.rev) # change the state system = Person.objects.get(name="(System)") save_document_in_history(doc) if doc.latest_event(type='started_iesg_process'): dead_state = State.objects.get(used=True, type="draft-iesg", slug="dead") prev = doc.get_state("draft-iesg") prev_tag = doc.tags.filter(slug__in=IESG_SUBSTATE_TAGS) prev_tag = prev_tag[0] if prev_tag else None if prev != dead_state: doc.set_state(dead_state) if prev_tag: doc.tags.remove(prev_tag) log_state_changed(None, doc, system, prev, prev_tag) 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 assign_shepherd(user, internetdraft, shepherd): if internetdraft.shepherd == shepherd: return from ietf.doc.models import save_document_in_history, DocEvent, Document # saving the proxy object is a bit of a mess, so convert it to a # proper document doc = Document.objects.get(name=internetdraft.name) save_document_in_history(doc) doc.time = datetime.datetime.now() doc.shepherd = shepherd doc.save() e = DocEvent(type="changed_document") e.time = doc.time e.doc = doc e.by = user.get_profile() if not shepherd: e.desc = u"Unassigned shepherd" else: e.desc = u"Changed shepherd to %s" % shepherd.plain_name() e.save() # update proxy too internetdraft.shepherd = shepherd
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.get_profile() if request.method == 'POST': form = ChangeStateForm(request.POST) if form.is_valid(): clean = form.cleaned_data review_state = clean['review_state'] comment = clean['comment'].rstrip() if comment: c = DocEvent(type="added_comment", doc=review, by=login) c.desc = comment c.save() if review_state != review.get_state(): save_document_in_history(review) old_description = review.friendly_state() review.set_state(review_state) new_description = review.friendly_state() log_state_changed(request, review, login, new_description, old_description) review.time = datetime.datetime.now() review.save() if review_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('help_conflict_review_states'), ), 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 edit_ad(request, name): """Change the responsible Area Director for this charter.""" charter = get_object_or_404(Document, type="charter", name=name) login = request.user.person if request.method == 'POST': form = AdForm(request.POST) if form.is_valid(): new_ad = form.cleaned_data['ad'] if new_ad != charter.ad: save_document_in_history(charter) e = DocEvent(doc=charter, by=login) e.desc = "Responsible AD changed to %s" % new_ad.plain_name() if charter.ad: e.desc += " from %s" % charter.ad.plain_name() e.type = "changed_document" e.save() charter.ad = new_ad charter.time = e.time charter.save() return redirect('doc_view', name=charter.name) else: init = { "ad" : charter.ad_id } form = AdForm(initial=init) return render_to_response('doc/charter/change_ad.html', {'form': form, 'charter': charter, }, context_instance = RequestContext(request))
def do_revision(draft, request): ''' This function handles adding a new revision of an existing Internet-Draft. Prerequisites: draft must be active Input: title, revision_date, pages, abstract, file input fields to upload new draft document(s) Actions - move current doc(s) to archive directory - upload new docs to live dir - save doc in history - increment revision - reset expires - create DocEvent - handle sub-state - schedule notification ''' # TODO this behavior may change with archive strategy archive_draft_files(draft.name + '-' + draft.rev) save_document_in_history(draft) # save form data form = BaseRevisionModelForm(request.session['data'],instance=draft) if form.is_valid(): new_draft = form.save() else: raise Exception(form.errors) raise Exception('Problem with input data %s' % form.data) # set revision and expires new_draft.rev = request.session['filename'][-2:] new_draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) new_draft.time = datetime.datetime.now() new_draft.save() # create DocEvent NewRevisionDocEvent.objects.create(type='new_revision', by=request.user.person, doc=draft, rev=new_draft.rev, desc='New revision available', time=draft.time) handle_substate(new_draft) # move uploaded files to production directory promote_files(new_draft, request.session['file_type']) # save the submission record post_submission(request) # send announcement if we are in IESG process if new_draft.get_state('draft-iesg'): announcement_from_form(request.session['email'],by=request.user.person) return
def update_tags(request, obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[]): if settings.USE_DB_REDESIGN_PROXY_CLASSES: doc = Document.objects.get(pk=obj.pk) save_document_in_history(doc) obj.tags.remove(*reset_tags) obj.tags.add(*set_tags) doc.time = datetime.datetime.now() e = DocEvent(type="changed_document", time=doc.time, by=person, doc=doc) l = [] if set_tags: l.append(u"Annotation tag%s %s set." % (pluralize(set_tags), ", ".join(x.name for x in set_tags))) if reset_tags: l.append(u"Annotation tag%s %s cleared." % (pluralize(reset_tags), ", ".join(x.name for x in reset_tags))) e.desc = " ".join(l) e.save() receivers = get_notification_receivers(doc, extra_notify) send_mail(request, receivers, settings.DEFAULT_FROM_EMAIL, u"Annotations tags changed for draft %s" % doc.name, 'ietfworkflows/annotation_tags_updated_mail.txt', dict(doc=doc, entry=dict(setted=", ".join(x.name for x in set_tags), unsetted=", ".join(x.name for x in reset_tags), change_date=doc.time, person=person, comment=comment))) return ctype = ContentType.objects.get_for_model(obj) setted = [] resetted = [] for tag in set_tags: if isinstance(tag, basestring): if set_tag_by_name(obj, tag): setted.append(tag) else: if set_tag(obj, tag): setted.append(tag.name) for tag in reset_tags: if isinstance(tag, basestring): if reset_tag_by_name(obj, tag): resetted.append(tag) else: if reset_tag(obj, tag): resetted.append(tag.name) entry = ObjectAnnotationTagHistoryEntry.objects.create( content_type=ctype, content_id=obj.pk, setted=','.join(setted), unsetted=','.join(resetted), date=datetime.datetime.now(), comment=comment, person=person) notify_tag_entry(entry, extra_notify)
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.get_profile() 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) old_description = status_change.friendly_state() status_change.set_state(State.objects.get(type='statchg', slug='lc-req')) new_description = status_change.friendly_state() e = log_state_changed(request, status_change, login, new_description, old_description) status_change.time = e.time status_change.save() request_last_call(request, status_change) return render_to_response('idrfc/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 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.get_profile() if request.method == 'POST': form = AnnouncementForm(request.POST) if form.is_valid(): new_state_slug = 'appr-reqnopub-sent' if review.get_state('conflrev').slug=='appr-reqnopub-pend' else 'appr-noprob-sent' new_review_state = State.objects.get(used=True, type="conflrev", slug=new_state_slug) save_document_in_history(review) old_description = review.friendly_state() review.set_state(new_review_state) new_description = review.friendly_state() log_state_changed(request, review, login, new_description, old_description) 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 edit(request, id): ''' Since there's a lot going on in this function we are summarizing in the docstring. Also serves as a record of requirements. if revision number increases add document_comments and send notify-revision if revision date changed and not the number return error check if using restricted words (?) send notification based on check box revision date = now if a new status box checked add_id5.cfm (notify_[resurrection,revision,updated,extended]) if rfcnum="" rfcnum=0 if status != 2, expired_tombstone="0" if new revision move current txt and ps files to archive directory (add_id5.cfm) if status > 3 create tombstone, else send revision notification (EmailIDRevision.cfm) ''' draft = get_object_or_404(Document, name=id) if request.method == 'POST': button_text = request.POST.get('submit', '') if button_text == 'Cancel': return redirect('drafts_view', id=id) form = EditModelForm(request.POST, instance=draft) if form.is_valid(): if form.changed_data: save_document_in_history(draft) DocEvent.objects.create(type='changed_document', by=request.user.person, doc=draft, desc='Changed field(s): %s' % ','.join(form.changed_data)) # see EditModelForm.save() for detailed logic form.save() messages.success(request, 'Draft modified successfully!') return redirect('drafts_view', id=id) else: #assert False, form.errors pass else: form = EditModelForm(instance=draft) return render_to_response('drafts/edit.html', { 'form': form, 'draft': draft}, RequestContext(request, {}), )
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_update(draft,request): ''' Actions - increment revision # - reset expires - create DocEvent - do substate check - change state to Active ''' save_document_in_history(draft) # save form data form = BaseRevisionModelForm(request.session['data'],instance=draft) if form.is_valid(): new_draft = form.save() else: raise Exception('Problem with input data %s' % form.data) handle_substate(new_draft) # update draft record new_draft.rev = os.path.splitext(request.session['data']['filename'])[0][-2:] new_draft.expires = datetime.datetime.now() + datetime.timedelta(settings.INTERNET_DRAFT_DAYS_TO_EXPIRE) new_draft.time = datetime.datetime.now() new_draft.save() new_draft.set_state(State.objects.get(type="draft", slug="active")) # create DocEvent NewRevisionDocEvent.objects.create(type='new_revision', by=request.user.person, doc=new_draft, rev=new_draft.rev, desc='New revision available', time=new_draft.time) # move uploaded files to production directory promote_files(new_draft, request.session['file_type']) # save the submission record post_submission(request) # send announcement announcement_from_form(request.session['email'],by=request.user.person) return
def change_title(request, name, option=None): """Change title of charter, notifying parties as necessary and logging the title 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") login = request.user.person if request.method == 'POST': form = ChangeTitleForm(request.POST, charter=charter) if form.is_valid(): clean = form.cleaned_data charter_rev = charter.rev new_title = clean['charter_title'] comment = clean['comment'].rstrip() message = clean['message'] prev_title = charter.title if new_title != prev_title: # Charter title changed save_document_in_history(charter) charter.title=new_title charter.rev = charter_rev if not comment: comment = "Changed charter title from '%s' to '%s'." % (prev_title, new_title) event = DocEvent(type="added_comment", doc=charter, by=login) event.desc = comment event.save() charter.time = datetime.datetime.now() charter.save() if message: email_admin_re_charter(request, group, "Charter title changed to %s" % new_title, message,'charter_state_edit_admin_needed') email_state_changed(request, charter, "Title changed to %s." % new_title,'doc_state_edited') return redirect('doc_view', name=charter.name) else: form = ChangeTitleForm(charter=charter) title = "Change charter title of %s %s" % (group.acronym, group.type.name) return render_to_response('doc/charter/change_title.html', dict(form=form, doc=group.charter, login=login, title=title, ), context_instance=RequestContext(request))
def update_stream(request, doc, comment, person, to_stream, extra_notify=[]): if settings.USE_DB_REDESIGN_PROXY_CLASSES: doc = Document.objects.get(pk=doc.pk) save_document_in_history(doc) doc.time = datetime.datetime.now() from_stream = doc.stream doc.stream = to_stream doc.save() e = DocEvent(type="changed_stream", time=doc.time, by=person, doc=doc) e.desc = u"Stream changed to <b>%s</b>" % to_stream.name if from_stream: e.desc += u"from %s" % from_stream.name e.save() receivers = get_notification_receivers(doc, extra_notify) send_mail(request, receivers, settings.DEFAULT_FROM_EMAIL, u"Stream changed for draft %s" % doc.name, 'ietfworkflows/stream_updated_mail.txt', dict(doc=doc, entry=dict(from_stream=from_stream, to_stream=to_stream, transition_date=doc.time, person=person, comment=comment))) return ctype = ContentType.objects.get_for_model(doc) from_stream = get_stream_from_draft(doc) to_stream = set_stream_for_draft(doc, to_stream) entry = ObjectStreamHistoryEntry.objects.create( content_type=ctype, content_id=doc.pk, from_stream=from_stream and from_stream.name or '', to_stream=to_stream and to_stream.name or '', date=datetime.datetime.now(), comment=comment, person=person) notify_stream_entry(entry, extra_notify)
def do_extend(draft, request): ''' Actions: - update revision_date - set extension_date ''' save_document_in_history(draft) draft.expires = request.session['data']['expiration_date'] draft.time = datetime.datetime.now() draft.save() DocEvent.objects.create(type='changed_document', by=request.user.person, doc=draft, time=draft.time, desc='extend_expiry') # save scheduled announcement announcement_from_form(request.session['email'],by=request.user.person) return
def make_notify_changed_event(request, doc, by, new_notify, time=None): # FOR REVIEW: This preserves the behavior from when # drafts and charters had separate edit_notify # functions. If it should be unified, there should # also be a migration function cause historic # events to match if doc.type.slug=='charter': event_type = 'changed_document' save_document_in_history(doc) else: event_type = 'added_comment' e = DocEvent(type=event_type, doc=doc, by=by) e.desc = "Notification list changed to %s" % (escape(new_notify) or "none") if doc.notify: e.desc += " from %s" % escape(doc.notify) if time: e.time = time e.save() return e
def test_document_ballot(self): doc = make_test_data() ballot = doc.active_ballot() save_document_in_history(doc) pos = BallotPositionDocEvent.objects.create( doc=doc, ballot=ballot, type="changed_ballot_position", pos_id="yes", comment="Looks fine to me", comment_time=datetime.datetime.now(), ad=Person.objects.get(user__username="******"), by=Person.objects.get(name="(System)")) r = self.client.get(urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc.name))) self.assertEqual(r.status_code, 200) self.assertTrue(pos.comment in unicontent(r)) # test with ballot_id r = self.client.get(urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc.name, ballot_id=ballot.pk))) self.assertEqual(r.status_code, 200) self.assertTrue(pos.comment in unicontent(r)) # test popup too while we're at it r = self.client.get(urlreverse("ietf.doc.views_doc.ballot_popup", kwargs=dict(name=doc.name, ballot_id=ballot.pk))) self.assertEqual(r.status_code, 200) # Now simulate a new revision and make sure positions on older revisions are marked as such oldrev = doc.rev e = NewRevisionDocEvent.objects.create(doc=doc,rev='%02d'%(int(doc.rev)+1),type='new_revision',by=Person.objects.get(name="(System)")) save_document_in_history(doc) doc.rev = e.rev doc.save() r = self.client.get(urlreverse("ietf.doc.views_doc.document_ballot", kwargs=dict(name=doc.name))) self.assertEqual(r.status_code, 200) self.assertTrue( '(%s for -%s)' % (pos.comment_time.strftime('%Y-%m-%d'), oldrev) in unicontent(r))
def submit(request, name): doc = get_object_or_404(Document, type="statchg", name=name) login = request.user.get_profile() path = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev)) not_uploaded_yet = doc.rev == "00" and not os.path.exists(path) if not_uploaded_yet: # this case is special - the status change text document doesn't actually exist yet next_rev = doc.rev else: next_rev = "%02d" % (int(doc.rev)+1) if request.method == 'POST': if "submit_response" in request.POST: form = UploadForm(request.POST, request.FILES) if form.is_valid(): save_document_in_history(doc) doc.rev = next_rev e = NewRevisionDocEvent(doc=doc, by=login, type="new_revision") e.desc = "New version available: <b>%s-%s.txt</b>" % (doc.canonical_name(), doc.rev) e.rev = doc.rev e.save() # Save file on disk form.save(doc) doc.time = datetime.datetime.now() doc.save() return HttpResponseRedirect(reverse('doc_view', kwargs={'name': doc.name})) elif "reset_text" in request.POST: init = { "content": render_to_string("doc/status_change/initial_template.txt",dict())} form = UploadForm(initial=init) # Protect against handcrufted malicious posts else: form = None else: form = None if not form: init = { "content": ""} if not_uploaded_yet: init["content"] = render_to_string("doc/status_change/initial_template.txt", dict(), ) else: filename = os.path.join(settings.STATUS_CHANGE_PATH, '%s-%s.txt' % (doc.canonical_name(), doc.rev)) try: with open(filename, 'r') as f: init["content"] = f.read() except IOError: pass form = UploadForm(initial=init) return render_to_response('doc/status_change/submit.html', {'form': form, 'next_rev': next_rev, 'doc' : doc, }, context_instance=RequestContext(request))
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 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 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 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 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.get_profile() AnnouncementFormSet = formset_factory(AnnouncementForm, extra=0) if request.method == 'POST': formset = AnnouncementFormSet(request.POST) if formset.is_valid(): save_document_in_history(status_change) old_description = status_change.friendly_state() status_change.set_state( State.objects.get(type='statchg', slug='appr-sent')) new_description = status_change.friendly_state() log_state_changed(request, status_change, login, new_description, old_description) 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=RELATION_SLUGS): # 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=RELATION_SLUGS): 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 update_docs_from_rfc_index(data, skip_older_than_date=None): std_level_mapping = { "Standard": StdLevelName.objects.get(slug="std"), "Internet Standard": StdLevelName.objects.get(slug="std"), "Draft Standard": StdLevelName.objects.get(slug="ds"), "Proposed Standard": StdLevelName.objects.get(slug="ps"), "Informational": StdLevelName.objects.get(slug="inf"), "Experimental": StdLevelName.objects.get(slug="exp"), "Best Current Practice": StdLevelName.objects.get(slug="bcp"), "Historic": StdLevelName.objects.get(slug="hist"), "Unknown": StdLevelName.objects.get(slug="unkn"), } stream_mapping = { "IETF": StreamName.objects.get(slug="ietf"), "INDEPENDENT": StreamName.objects.get(slug="ise"), "IRTF": StreamName.objects.get(slug="irtf"), "IAB": StreamName.objects.get(slug="iab"), "Legacy": StreamName.objects.get(slug="legacy"), } tag_has_errata = DocTagName.objects.get(slug='errata') relationship_obsoletes = DocRelationshipName.objects.get(slug="obs") relationship_updates = DocRelationshipName.objects.get(slug="updates") system = Person.objects.get(name="(System)") results = [] new_rfcs = [] for rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats, pages, abstract in data: if skip_older_than_date and rfc_published_date < skip_older_than_date: # speed up the process by skipping old entries continue # we assume two things can happen: we get a new RFC, or an # attribute has been updated at the RFC Editor (RFC Editor # attributes take precedence over our local attributes) # make sure we got the document and alias doc = None name = "rfc%s" % rfc_number a = DocAlias.objects.filter(name=name).select_related("document") if a: doc = a[0].document else: if draft: try: doc = Document.objects.get(name=draft) except Document.DoesNotExist: pass if not doc: results.append("created document %s" % name) doc = Document.objects.create(name=name, type=DocTypeName.objects.get(slug="draft")) # add alias DocAlias.objects.get_or_create(name=name, document=doc) results.append("created alias %s to %s" % (name, doc.name)) # check attributes changed_attributes = {} changed_states = [] created_relations = [] other_changes = False if title != doc.title: changed_attributes["title"] = title if abstract and abstract != doc.abstract: changed_attributes["abstract"] = abstract if pages and int(pages) != doc.pages: changed_attributes["pages"] = int(pages) if std_level_mapping[current_status] != doc.std_level: changed_attributes["std_level"] = std_level_mapping[current_status] if doc.get_state_slug() != "rfc": changed_states.append(State.objects.get(used=True, type="draft", slug="rfc")) move_draft_files_to_archive(doc, doc.rev) if doc.stream != stream_mapping[stream]: changed_attributes["stream"] = stream_mapping[stream] if not doc.group: # if we have no group assigned, check if RFC Editor has a suggestion if wg: changed_attributes["group"] = Group.objects.get(acronym=wg) else: changed_attributes["group"] = Group.objects.get(type="individ") if not doc.latest_event(type="published_rfc"): e = DocEvent(doc=doc, type="published_rfc") # unfortunately, rfc_published_date doesn't include the correct day # at the moment because the data only has month/year, so # try to deduce it d = datetime.datetime.combine(rfc_published_date, datetime.time()) synthesized = datetime.datetime.now() if abs(d - synthesized) > datetime.timedelta(days=60): synthesized = d else: direction = -1 if (d - synthesized).total_seconds() < 0 else +1 while synthesized.month != d.month or synthesized.year != d.year: synthesized += datetime.timedelta(days=direction) e.time = synthesized e.by = system e.desc = "RFC published" e.save() other_changes = True results.append("Added RFC published event: %s" % e.time.strftime("%Y-%m-%d")) new_rfcs.append(doc) for t in ("draft-iesg", "draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"): slug = doc.get_state_slug(t) if slug and slug != "pub": changed_states.append(State.objects.get(used=True, type=t, slug="pub")) def parse_relation_list(l): res = [] for x in l: if x[:3] in ("NIC", "IEN", "STD", "RTR"): # try translating this to RFCs that we can handle # sensibly; otherwise we'll have to ignore them l = DocAlias.objects.filter(name__startswith="rfc", document__docalias__name=x.lower()) else: l = DocAlias.objects.filter(name=x.lower()) for a in l: if a not in res: res.append(a) return res for x in parse_relation_list(obsoletes): if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_obsoletes): created_relations.append(RelatedDocument(source=doc, target=x, relationship=relationship_obsoletes)) for x in parse_relation_list(updates): if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_updates): created_relations.append(RelatedDocument(source=doc, target=x, relationship=relationship_updates)) if also: for a in also: a = a.lower() if not DocAlias.objects.filter(name=a): DocAlias.objects.create(name=a, document=doc) other_changes = True results.append("Created alias %s to %s" % (a, doc.name)) if has_errata: if not doc.tags.filter(pk=tag_has_errata.pk): changed_attributes["tags"] = list(doc.tags.all()) + [tag_has_errata] else: if doc.tags.filter(pk=tag_has_errata.pk): changed_attributes["tags"] = set(doc.tags.all()) - set([tag_has_errata]) if changed_attributes or changed_states or created_relations or other_changes: # apply changes save_document_in_history(doc) for k, v in changed_attributes.iteritems(): setattr(doc, k, v) results.append("Changed %s to %s on %s" % (k, v, doc.name)) for s in changed_states: doc.set_state(s) results.append("Set state %s on %s" % (s, doc.name)) for o in created_relations: o.save() results.append("Created %s" % o) doc.time = datetime.datetime.now() doc.save() return results, new_rfcs
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.get_profile() 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) old_description = status_change.friendly_state() status_change.set_state( State.objects.get(type='statchg', slug='lc-req')) new_description = status_change.friendly_state() e = log_state_changed(request, status_change, login, new_description, old_description) status_change.time = e.time status_change.save() request_last_call(request, status_change) return render_to_response( 'idrfc/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 submit(request, name): review = get_object_or_404(Document, type="conflrev", name=name) login = request.user.get_profile() path = os.path.join(settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (review.canonical_name(), review.rev)) not_uploaded_yet = review.rev == "00" and not os.path.exists(path) if not_uploaded_yet: # this case is special - the conflict review text document doesn't actually exist yet next_rev = review.rev else: next_rev = "%02d" % (int(review.rev) + 1) if request.method == 'POST': if "submit_response" in request.POST: form = UploadForm(request.POST, request.FILES) if form.is_valid(): save_document_in_history(review) review.rev = next_rev e = NewRevisionDocEvent(doc=review, by=login, type="new_revision") e.desc = "New version available: <b>%s-%s.txt</b>" % ( review.canonical_name(), review.rev) e.rev = review.rev e.save() # Save file on disk form.save(review) review.time = datetime.datetime.now() review.save() return HttpResponseRedirect( reverse('doc_view', kwargs={'name': review.name})) elif "reset_text" in request.POST: init = { "content": render_to_string("doc/conflict_review/review_choices.txt", dict()) } form = UploadForm(initial=init) # Protect against handcrufted malicious posts else: form = None else: form = None if not form: init = {"content": ""} if not_uploaded_yet: init["content"] = render_to_string( "doc/conflict_review/review_choices.txt", dict(), ) else: filename = os.path.join( settings.CONFLICT_REVIEW_PATH, '%s-%s.txt' % (review.canonical_name(), review.rev)) try: with open(filename, 'r') as f: init["content"] = f.read() except IOError: pass form = UploadForm(initial=init) return render_to_response('doc/conflict_review/submit.html', { 'form': form, 'next_rev': next_rev, 'review': review, 'conflictdoc': review.relateddocument_set.get( relationship__slug='conflrev').target.document, }, context_instance=RequestContext(request))
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. We need to check the document type here # and set the state_type for use later in the view if doc.type_id == 'draft': state_type = 'draft-iesg' elif doc.type_id == 'conflrev': state_type = 'conflrev' elif doc.type_id == 'charter': state_type = 'charter' started_process = doc.latest_event(type="started_iesg_process") login = request.user.get_profile() 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(request, date=date) header = get_section_header(name, agenda) if name else '' # 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 idrfc/views_ballot.py EditPositionRedesign 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.') url = reverse('telechat_doc_detail', kwargs={ 'date': date, 'name': name }) return HttpResponseRedirect(url) # logic from idrfc/views_edit.py change_stateREDESIGN elif button_text == 'update_state': formset = BallotFormset(initial=initial_ballot) state_form = ChangeStateForm(request.POST, initial=initial_state) if state_form.is_valid(): state = state_form.cleaned_data['state'] tag = state_form.cleaned_data['substate'] prev = doc.get_state(state_type) # tag handling is a bit awkward since the UI still works # as if IESG tags are a substate prev_tag = doc.tags.filter(slug__in=(TELECHAT_TAGS)) prev_tag = prev_tag[0] if prev_tag else None #if state != prev or tag != prev_tag: if state_form.changed_data: save_document_in_history(doc) old_description = doc.friendly_state() if 'state' in state_form.changed_data: doc.set_state(state) if 'substate' in state_form.changed_data: if prev_tag: doc.tags.remove(prev_tag) if tag: doc.tags.add(tag) new_description = doc.friendly_state() e = log_state_changed(request, doc, login, new_description, old_description) doc.time = e.time doc.save() email_state_changed(request, doc, e.desc) email_owner(request, doc, doc.ad, login, e.desc) if state.slug == "lc-req": request_last_call(request, doc) messages.success(request, 'Document state updated') url = reverse('telechat_doc_detail', kwargs={ 'date': date, 'name': name }) return HttpResponseRedirect(url) 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(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.get_profile() AnnouncementFormSet = formset_factory(AnnouncementForm,extra=0) if request.method == 'POST': formset = AnnouncementFormSet(request.POST) if formset.is_valid(): save_document_in_history(status_change) old_description = status_change.friendly_state() status_change.set_state(State.objects.get(type='statchg', slug='appr-sent')) new_description = status_change.friendly_state() log_state_changed(request, status_change, login, new_description, old_description) 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=RELATION_SLUGS): # 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=RELATION_SLUGS): 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(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.get_profile() if request.method == 'POST': form = AnnouncementForm(request.POST) if form.is_valid(): new_state_slug = 'appr-reqnopub-sent' if review.get_state( 'conflrev' ).slug == 'appr-reqnopub-pend' else 'appr-noprob-sent' new_review_state = State.objects.get(used=True, type="conflrev", slug=new_state_slug) save_document_in_history(review) old_description = review.friendly_state() review.set_state(new_review_state) new_description = review.friendly_state() log_state_changed(request, review, login, new_description, old_description) 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 save(self): comment = self.cleaned_data.get('comment').strip() weeks = self.cleaned_data.get('weeks') group = IETFWG.objects.get(pk=self.cleaned_data.get('group')) estimated_date = None if weeks: now = datetime.date.today() estimated_date = now + datetime.timedelta(weeks=weeks) if settings.USE_DB_REDESIGN_PROXY_CLASSES: # do changes on real Document object instead of proxy to avoid trouble doc = Document.objects.get(pk=self.draft.pk) save_document_in_history(doc) doc.time = datetime.datetime.now() if group.type.slug == "rg": new_stream = StreamName.objects.get(slug="irtf") else: new_stream = StreamName.objects.get(slug="ietf") if doc.stream != new_stream: e = DocEvent(type="changed_stream") e.time = doc.time e.by = self.user.get_profile() e.doc = doc e.desc = u"Changed to <b>%s</b>" % new_stream.name if doc.stream: e.desc += u" from %s" % doc.stream.name e.save() doc.stream = new_stream if doc.group.pk != group.pk: e = DocEvent(type="changed_group") e.time = doc.time e.by = self.user.get_profile() e.doc = doc e.desc = u"Changed group to <b>%s (%s)</b>" % ( group.name, group.acronym.upper()) if doc.group.type_id != "individ": e.desc += " from %s (%s)" % (doc.group.name, doc.group.acronym) e.save() doc.group_id = group.pk doc.save() self.draft = InternetDraft.objects.get( pk=doc.pk) # make sure proxy object is updated else: workflow = get_workflow_for_wg(wg) set_workflow_for_object(self.draft, workflow) stream = get_stream_by_name(IETF_STREAM) streamed = get_streamed_draft(self.draft) if not streamed: set_stream_for_draft(self.draft, stream) streamed = get_streamed_draft(self.draft) streamed.stream = stream streamed.group = wg streamed.save() if settings.USE_DB_REDESIGN_PROXY_CLASSES: from ietf.doc.models import State if self.draft.stream_id == "irtf": to_state = State.objects.get(used=True, slug="active", type="draft-stream-irtf") else: to_state = State.objects.get(used=True, slug="c-adopt", type="draft-stream-%s" % self.draft.stream_id) else: to_state = get_state_by_name(CALL_FOR_ADOPTION) update_state(self.request, self.draft, comment=comment, person=self.person, to_state=to_state, estimated_date=estimated_date) if settings.USE_DB_REDESIGN_PROXY_CLASSES: if comment: e = DocEvent(type="added_comment") e.time = self.draft.time e.by = self.person e.doc_id = self.draft.pk e.desc = comment e.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 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 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.get_profile() if request.method == 'POST': form = ChangeStateForm(request.POST) if form.is_valid(): clean = form.cleaned_data review_state = clean['review_state'] comment = clean['comment'].rstrip() if comment: c = DocEvent(type="added_comment", doc=review, by=login) c.desc = comment c.save() if review_state != review.get_state(): save_document_in_history(review) old_description = review.friendly_state() review.set_state(review_state) new_description = review.friendly_state() log_state_changed(request, review, login, new_description, old_description) review.time = datetime.datetime.now() review.save() if review_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('help_conflict_review_states'), ), context_instance=RequestContext(request))
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. We need to check the document type here # and set the state_type for use later in the view if doc.type_id == 'draft': state_type = 'draft-iesg' elif doc.type_id == 'conflrev': state_type = 'conflrev' elif doc.type_id == 'charter': state_type = 'charter' started_process = doc.latest_event(type="started_iesg_process") login = request.user.get_profile() 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(request, date=date) header = get_section_header(name,agenda) if name else '' # 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 idrfc/views_ballot.py EditPositionRedesign 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.') url = reverse('telechat_doc_detail', kwargs={'date':date,'name':name}) return HttpResponseRedirect(url) # logic from idrfc/views_edit.py change_stateREDESIGN elif button_text == 'update_state': formset = BallotFormset(initial=initial_ballot) state_form = ChangeStateForm(request.POST, initial=initial_state) if state_form.is_valid(): state = state_form.cleaned_data['state'] tag = state_form.cleaned_data['substate'] prev = doc.get_state(state_type) # tag handling is a bit awkward since the UI still works # as if IESG tags are a substate prev_tag = doc.tags.filter(slug__in=(TELECHAT_TAGS)) prev_tag = prev_tag[0] if prev_tag else None #if state != prev or tag != prev_tag: if state_form.changed_data: save_document_in_history(doc) old_description = doc.friendly_state() if 'state' in state_form.changed_data: doc.set_state(state) if 'substate' in state_form.changed_data: if prev_tag: doc.tags.remove(prev_tag) if tag: doc.tags.add(tag) new_description = doc.friendly_state() e = log_state_changed(request, doc, login, new_description, old_description) doc.time = e.time doc.save() email_state_changed(request, doc, e.desc) email_owner(request, doc, doc.ad, login, e.desc) if state.slug == "lc-req": request_last_call(request, doc) messages.success(request,'Document state updated') url = reverse('telechat_doc_detail', kwargs={'date':date,'name':name}) return HttpResponseRedirect(url) 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 update_state(request, doc, comment, person, to_state, estimated_date=None, extra_notify=[]): if settings.USE_DB_REDESIGN_PROXY_CLASSES: doc = Document.objects.get(pk=doc.pk) save_document_in_history(doc) doc.time = datetime.datetime.now() from_state = doc.get_state("draft-stream-%s" % doc.stream_id) doc.set_state(to_state) e = DocEvent(type="changed_document", time=doc.time, by=person, doc=doc) e.desc = u"%s changed to <b>%s</b> from %s" % (to_state.type.label, to_state, from_state) e.save() # reminder reminder_type = DocReminderTypeName.objects.get(slug="stream-s") try: reminder = DocReminder.objects.get(event__doc=doc, type=reminder_type, active=True) except DocReminder.DoesNotExist: reminder = None if estimated_date: if not reminder: reminder = DocReminder(type=reminder_type) reminder.event = e reminder.due = estimated_date reminder.active = True reminder.save() elif reminder: reminder.active = False reminder.save() receivers = get_notification_receivers(doc, extra_notify) send_mail(request, receivers, settings.DEFAULT_FROM_EMAIL, u"State changed for draft %s" % doc.name, 'ietfworkflows/state_updated_mail.txt', dict(doc=doc, entry=dict(from_state=from_state, to_state=to_state, transition_date=doc.time, person=person, comment=comment))) if (to_state.slug=='sub-pub'): receivers = get_pubreq_receivers(doc, extra_notify) cc_receivers = get_pubreq_cc_receivers(doc) send_mail(request, receivers, settings.DEFAULT_FROM_EMAIL, u"Publication has been requested for draft %s" % doc.name, 'ietfworkflows/state_updated_mail.txt', dict(doc=doc, entry=dict(from_state=from_state, to_state=to_state, transition_date=doc.time, person=person, comment=comment)), cc=cc_receivers) return ctype = ContentType.objects.get_for_model(doc) from_state = get_state_for_draft(doc) to_state = set_state_for_draft(doc, to_state, estimated_date) if not to_state: return False entry = ObjectWorkflowHistoryEntry.objects.create( content_type=ctype, content_id=doc.pk, from_state=from_state and from_state.name or '', to_state=to_state and to_state.name or '', date=datetime.datetime.now(), comment=comment, person=person) notify_state_entry(entry, extra_notify)