import copy import datetime from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.mail import EmailMessage from django.template.loader import render_to_string from django.template.defaultfilters import pluralize from workflows.models import State, StateObjectRelation from workflows.utils import (get_workflow_for_object, set_workflow_for_object, get_state, set_state) from ietf.ietfworkflows.streams import (get_streamed_draft, get_stream_from_draft, set_stream_for_draft) from ietf.ietfworkflows.models import (WGWorkflow, AnnotationTagObjectRelation, AnnotationTag, ObjectAnnotationTagHistoryEntry, ObjectHistoryEntry, StateObjectRelationMetadata, ObjectWorkflowHistoryEntry, ObjectStreamHistoryEntry) from ietf.idtracker.models import InternetDraft from ietf.utils.mail import send_mail from ietf.doc.models import Document, DocEvent, save_document_in_history, DocReminder, DocReminderTypeName from ietf.group.models import Role WAITING_WRITEUP = 'WG Consensus: Waiting for Write-Up' FOLLOWUP_TAG = 'Doc Shepherd Follow-up Underway' def get_default_workflow_for_wg(): try: workflow = WGWorkflow.objects.get(name='Default WG Workflow') return workflow except WGWorkflow.DoesNotExist: return None def clone_transition(transition): new = copy.copy(transition) new.pk = None new.save() # Reference original initial states for state in transition.states.all(): new.states.add(state) return new def clone_workflow(workflow, name): new = WGWorkflow.objects.create(name=name, initial_state=workflow.initial_state) # Reference default states for state in workflow.states.all(): new.selected_states.add(state) # Reference default annotation tags for tag in workflow.annotation_tags.all(): new.selected_tags.add(tag) # Reference cloned transitions for transition in workflow.transitions.all(): new.transitions.add(clone_transition(transition)) return new def get_workflow_for_wg(wg, default=None): workflow = get_workflow_for_object(wg) try: workflow = workflow and workflow.wgworkflow except WGWorkflow.DoesNotExist: workflow = None if not workflow: if default: workflow = default else: workflow = get_default_workflow_for_wg() if not workflow: return None workflow = clone_workflow(workflow, name='%s workflow' % wg) set_workflow_for_object(wg, workflow) return workflow def get_workflow_for_draft(draft): if settings.USE_DB_REDESIGN_PROXY_CLASSES: return True if get_streamed_draft(draft) else None workflow = get_workflow_for_object(draft) try: workflow = workflow and workflow.wgworkflow except WGWorkflow.DoesNotExist: workflow = None if not workflow: streamed_draft = get_streamed_draft(draft) if not streamed_draft or not streamed_draft.stream: return None stream = streamed_draft.stream if stream.document_group_attribute: group = streamed_draft.get_group() if not group: return None else: workflow = get_workflow_for_wg(group, streamed_draft.stream.workflow) else: workflow = stream.workflow set_workflow_for_object(draft, workflow) return workflow def get_workflow_history_for_draft(draft, entry_type=None): if settings.USE_DB_REDESIGN_PROXY_CLASSES: from ietf.doc.proxy import ObjectHistoryEntryProxy return ObjectHistoryEntryProxy.objects.filter(doc=draft).order_by('-time', '-id').select_related('by') ctype = ContentType.objects.get_for_model(draft) filter_param = {'content_type': ctype, 'content_id': draft.pk} if entry_type: filter_param.update({'%s__isnull' % entry_type: False}) history = ObjectHistoryEntry.objects.filter(**filter_param).\ select_related('objectworkflowhistoryentry', 'objectannotationtaghistoryentry', 'objectstreamhistoryentry') return history def get_annotation_tags_for_draft(draft): if settings.USE_DB_REDESIGN_PROXY_CLASSES: from ietf.name.proxy import AnnotationTagObjectRelationProxy from ietf.doc.utils import get_tags_for_stream_id return AnnotationTagObjectRelationProxy.objects.filter(document=draft.pk, slug__in=get_tags_for_stream_id(draft.stream_id)) ctype = ContentType.objects.get_for_model(draft) tags = AnnotationTagObjectRelation.objects.filter(content_type=ctype, content_id=draft.pk) return tags def get_state_for_draft(draft): if settings.USE_DB_REDESIGN_PROXY_CLASSES: return draft.get_state("draft-stream-%s" % draft.stream_id) return get_state(draft) def get_state_by_name(state_name): try: return State.objects.get(name=state_name) except State.DoesNotExist: return None def get_annotation_tag_by_name(tag_name): try: return AnnotationTag.objects.get(name=tag_name) except AnnotationTag.DoesNotExist: return None def set_tag(obj, tag): ctype = ContentType.objects.get_for_model(obj) (relation, created) = AnnotationTagObjectRelation.objects.get_or_create( content_type=ctype, content_id=obj.pk, annotation_tag=tag) return relation def set_tag_by_name(obj, tag_name): try: tag = AnnotationTag.objects.get(name=tag_name) return set_tag(obj, tag) except AnnotationTag.DoesNotExist: return None def reset_tag(obj, tag): ctype = ContentType.objects.get_for_model(obj) try: tag_relation = AnnotationTagObjectRelation.objects.get( content_type=ctype, content_id=obj.pk, annotation_tag=tag) tag_relation.delete() return True except AnnotationTagObjectRelation.DoesNotExist: return False def reset_tag_by_name(obj, tag_name): try: tag = AnnotationTag.objects.get(name=tag_name) return reset_tag(obj, tag) except AnnotationTag.DoesNotExist: return False def set_state_for_draft(draft, state, estimated_date=None): workflow = get_workflow_for_draft(draft) if state in workflow.get_states(): set_state(draft, state) relation = StateObjectRelation.objects.get( content_type=ContentType.objects.get_for_model(draft), content_id=draft.pk) metadata = StateObjectRelationMetadata.objects.get_or_create(relation=relation)[0] metadata.from_date = datetime.date.today() metadata.to_date = estimated_date metadata.save() return state return False def notify_entry(entry, template, extra_notify=[]): doc = entry.content wg = doc.group.ietfwg mail_list = set(['%s <%s>' % i.person.email() for i in wg.wgchair_set.all() if i.person.email()]) mail_list = mail_list.union(['%s <%s>' % i.person.email() for i in wg.wgdelegate_set.all() if i.person.email()]) mail_list = mail_list.union(['%s <%s>' % i.person.email() for i in doc.authors.all() if i.person.email()]) mail_list = mail_list.union(extra_notify) mail_list = list(mail_list) subject = 'Annotation tags have changed for draft %s' % doc body = render_to_string(template, {'doc': doc, 'entry': entry, }) mail = EmailMessage(subject=subject, body=body, to=mail_list, from_email=settings.DEFAULT_FROM_EMAIL) # Only send emails if we are not debug mode if not settings.DEBUG: mail.send() return mail def notify_tag_entry(entry, extra_notify=[]): return notify_entry(entry, 'ietfworkflows/annotation_tags_updated_mail.txt', extra_notify) def notify_state_entry(entry, extra_notify=[]): return notify_entry(entry, 'ietfworkflows/state_updated_mail.txt', extra_notify) def notify_stream_entry(entry, extra_notify=[]): return notify_entry(entry, 'ietfworkflows/stream_updated_mail.txt', extra_notify) def get_notification_receivers(doc, extra_notify): persons = set() res = [] for r in Role.objects.filter(group=doc.group, name__in=("chair", "delegate")): res.append(u'"%s" <%s>' % (r.person.plain_name(), r.email.address)) persons.add(r.person) for email in doc.authors.all(): if email.person not in persons: res.append(email.formatted_email()) persons.add(email.person) for x in extra_notify: if not x in res: res.append(x) return res 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 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 %s 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))) 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) 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 %s from %s" % (to_stream.name, 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 get_full_info_for_draft(draft): return dict( streamed=get_streamed_draft(draft), stream=get_stream_from_draft(draft), workflow=get_workflow_for_draft(draft), tags=[i.annotation_tag for i in get_annotation_tags_for_draft(draft)], state=get_state_for_draft(draft), shepherd=draft.shepherd if draft.shepherd_id else None, )