datatracker/ietf/liaisons/models.py

249 lines
9.3 KiB
Python

# Copyright The IETF Trust 2007-2019, All Rights Reserved
# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals
from django.conf import settings
from django.urls import reverse as urlreverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.text import slugify
from ietf.person.models import Email, Person
from ietf.name.models import (LiaisonStatementPurposeName, LiaisonStatementState,
LiaisonStatementEventTypeName, LiaisonStatementTagName,
DocRelationshipName)
from ietf.doc.models import Document
from ietf.group.models import Group
from ietf.utils.models import ForeignKey
# maps (previous state id, new state id) to event type id
STATE_EVENT_MAPPING = {
('pending','approved'):'approved',
('pending','dead'):'killed',
('pending','posted'):'posted',
('approved','posted'):'posted',
('dead','pending'):'resurrected',
('pending','pending'):'submitted'
}
@python_2_unicode_compatible
class LiaisonStatement(models.Model):
title = models.CharField(max_length=255)
from_groups = models.ManyToManyField(Group, blank=True, related_name='liaisonstatement_from_set')
from_contact = ForeignKey(Email, blank=True, null=True)
to_groups = models.ManyToManyField(Group, blank=True, related_name='liaisonstatement_to_set')
to_contacts = models.CharField(max_length=2000, help_text="Contacts at recipient group")
response_contacts = models.CharField(blank=True, max_length=255, help_text="Where to send a response") # RFC4053
technical_contacts = models.CharField(blank=True, max_length=255, help_text="Who to contact for clarification") # RFC4053
action_holder_contacts = models.CharField(blank=True, max_length=255, help_text="Who makes sure action is completed") # incoming only?
cc_contacts = models.TextField(blank=True)
purpose = ForeignKey(LiaisonStatementPurposeName)
deadline = models.DateField(null=True, blank=True)
other_identifiers = models.TextField(blank=True, null=True) # Identifiers from other bodies
body = models.TextField(blank=True)
tags = models.ManyToManyField(LiaisonStatementTagName, blank=True)
attachments = models.ManyToManyField(Document, through='LiaisonStatementAttachment', blank=True)
state = ForeignKey(LiaisonStatementState, default='pending')
class Meta:
ordering = ['id']
def __str__(self):
return self.title or "<no title>"
def change_state(self,state_id=None,person=None):
'''Helper function to change state of liaison statement and create appropriate
event'''
previous_state_id = self.state_id
self.set_state(state_id)
event_type_id = STATE_EVENT_MAPPING[(previous_state_id,state_id)]
LiaisonStatementEvent.objects.create(
type_id=event_type_id,
by=person,
statement=self,
desc='Statement {}'.format(event_type_id.capitalize())
)
def get_absolute_url(self):
return settings.IDTRACKER_BASE_URL + urlreverse('ietf.liaisons.views.liaison_detail',kwargs={'object_id':self.id})
def is_outgoing(self):
return self.to_groups.first().type_id == 'sdo'
def latest_event(self, *args, **filter_args):
"""Get latest event of optional Python type and with filter
arguments, e.g. d.latest_event(type="xyz") returns an LiaisonStatementEvent
while d.latest_event(WriteupDocEvent, type="xyz") returns a
WriteupDocEvent event."""
model = args[0] if args else LiaisonStatementEvent
e = model.objects.filter(statement=self).filter(**filter_args).order_by('-time', '-id')[:1]
return e[0] if e else None
def name(self):
if self.from_groups.count():
frm = ', '.join([i.acronym or i.name for i in self.from_groups.all()])
else:
frm = self.from_contact.person.name
if self.to_groups.count():
to = ', '.join([i.acronym or i.name for i in self.to_groups.all()])
else:
to = self.to_contacts
return slugify("liaison" + " " + self.submitted.strftime("%Y-%m-%d") + " " + frm[:50] + " " + to[:50] + " " + self.title[:115])
@property
def posted(self):
if hasattr(self,'prefetched_posted_events') and self.prefetched_posted_events:
return self.prefetched_posted_events[0].time
else:
event = self.latest_event(type='posted')
if event:
return event.time
return None
@property
def submitted(self):
event = self.latest_event(type='submitted')
if event:
return event.time
return None
@property
def sort_date(self):
"""Returns the date to use for sorting, for posted statements this is post date,
for pending statements this is submitted date"""
if self.state_id == 'posted':
return self.posted
else:
return self.submitted
@property
def modified(self):
event = self.liaisonstatementevent_set.all().order_by('-time').first()
if event:
return event.time
return None
@property
def approved(self):
return self.state_id in ('approved','posted')
@property
def action_taken(self):
if hasattr(self,'prefetched_tags'):
return bool(self.prefetched_tags)
else:
return self.tags.filter(slug='taken').exists()
def active_attachments(self):
'''Returns attachments with removed ones filtered out'''
return self.attachments.exclude(liaisonstatementattachment__removed=True)
@property
def awaiting_action(self):
if getattr(self, '_awaiting_action', None) != None:
return bool(self._awaiting_action)
return self.tags.filter(slug='awaiting').exists()
def _get_group_display(self, groups):
'''Returns comma separated string of group acronyms, non-wg are uppercase'''
acronyms = []
for group in groups:
if group.type.slug == 'wg':
acronyms.append(group.acronym)
else:
acronyms.append(group.acronym.upper())
return ', '.join(acronyms)
@property
def from_groups_display(self):
'''Returns comma separated list of from_group names'''
if hasattr(self, 'prefetched_from_groups'):
return self._get_group_display(self.prefetched_from_groups)
else:
return self._get_group_display(self.from_groups.order_by('acronym'))
@property
def to_groups_display(self):
'''Returns comma separated list of to_group names'''
if hasattr(self, 'prefetched_to_groups'):
return self._get_group_display(self.prefetched_to_groups)
else:
return self._get_group_display(self.to_groups.order_by('acronym'))
def from_groups_short_display(self):
'''Returns comma separated list of from_group acronyms. For use in admin
interface'''
groups = self.to_groups.order_by('acronym').values_list('acronym',flat=True)
return ', '.join(groups)
from_groups_short_display.short_description = 'From Groups'
def set_state(self,slug):
try:
state = LiaisonStatementState.objects.get(slug=slug)
except LiaisonStatementState.DoesNotExist:
return
self.state = state
self.save()
def approver_emails(self):
'''Send mail requesting approval of pending liaison statement. Send mail to
the intersection of approvers for all from_groups
'''
approval_set = set()
if self.from_groups.first():
approval_set.update(self.from_groups.first().liaison_approvers())
if self.from_groups.count() > 1:
for group in self.from_groups.all():
approval_set.intersection_update(group.liaison_approvers())
return list(set([ r.email.address for r in approval_set ]))
@python_2_unicode_compatible
class LiaisonStatementAttachment(models.Model):
statement = ForeignKey(LiaisonStatement)
document = ForeignKey(Document)
removed = models.BooleanField(default=False)
def __str__(self):
return self.document.name
@python_2_unicode_compatible
class RelatedLiaisonStatement(models.Model):
source = ForeignKey(LiaisonStatement, related_name='source_of_set')
target = ForeignKey(LiaisonStatement, related_name='target_of_set')
relationship = ForeignKey(DocRelationshipName)
def __str__(self):
return "%s %s %s" % (self.source.title, self.relationship.name.lower(), self.target.title)
@python_2_unicode_compatible
class LiaisonStatementGroupContacts(models.Model):
group = ForeignKey(Group, unique=True, null=True)
contacts = models.CharField(max_length=255,blank=True)
cc_contacts = models.CharField(max_length=255,blank=True)
def __str__(self):
return "%s" % self.group.name
@python_2_unicode_compatible
class LiaisonStatementEvent(models.Model):
time = models.DateTimeField(auto_now_add=True)
type = ForeignKey(LiaisonStatementEventTypeName)
by = ForeignKey(Person)
statement = ForeignKey(LiaisonStatement)
desc = models.TextField()
def __str__(self):
return "%s %s by %s at %s" % (self.statement.title, self.type.slug, self.by.plain_name(), self.time)
class Meta:
ordering = ['-time', '-id']