1170 lines
47 KiB
Python
1170 lines
47 KiB
Python
# Copyright The IETF Trust 2007, All Rights Reserved
|
|
|
|
import os.path
|
|
import datetime
|
|
import re
|
|
|
|
from django.conf import settings
|
|
from django.db import models
|
|
from ietf.utils import FKAsOneToOne
|
|
from ietf.utils.broken_foreign_key import BrokenForeignKey
|
|
from ietf.utils.cached_lookup_field import CachedLookupField
|
|
from ietf.utils.admin import admin_link
|
|
|
|
class Acronym(models.Model):
|
|
INDIVIDUAL_SUBMITTER = 1027
|
|
|
|
acronym_id = models.AutoField(primary_key=True)
|
|
acronym = models.CharField(max_length=12)
|
|
name = models.CharField(max_length=100)
|
|
name_key = models.CharField(max_length=50, editable=False)
|
|
def save(self):
|
|
self.name_key = self.name.upper()
|
|
super(Acronym, self).save()
|
|
def __str__(self):
|
|
return self.acronym
|
|
class Meta:
|
|
db_table = "acronym"
|
|
|
|
class AreaStatus(models.Model):
|
|
status_id = models.AutoField(primary_key=True)
|
|
status = models.CharField(max_length=25, db_column='status_value')
|
|
def __str__(self):
|
|
return self.status
|
|
class Meta:
|
|
verbose_name = "Area Status"
|
|
verbose_name_plural = "Area Statuses"
|
|
db_table = 'area_status'
|
|
|
|
# I think equiv_group_flag is historical.
|
|
class IDState(models.Model):
|
|
PUBLICATION_REQUESTED = 10
|
|
LAST_CALL_REQUESTED = 15
|
|
IN_LAST_CALL = 16
|
|
WAITING_FOR_WRITEUP = 18
|
|
WAITING_FOR_AD_GO_AHEAD = 19
|
|
IESG_EVALUATION = 20
|
|
IESG_EVALUATION_DEFER = 21
|
|
APPROVED_ANNOUNCEMENT_SENT = 30
|
|
AD_WATCHING = 42
|
|
DEAD = 99
|
|
DO_NOT_PUBLISH_STATES = (33, 34)
|
|
|
|
document_state_id = models.AutoField(primary_key=True)
|
|
state = models.CharField(max_length=50, db_column='document_state_val')
|
|
equiv_group_flag = models.IntegerField(null=True, blank=True)
|
|
description = models.TextField(blank=True, db_column='document_desc')
|
|
def __str__(self):
|
|
return self.state
|
|
def choices():
|
|
return [(state.document_state_id, state.state) for state in IDState.objects.all()]
|
|
choices = staticmethod(choices)
|
|
class Meta:
|
|
db_table = 'ref_doc_states_new'
|
|
ordering = ['document_state_id']
|
|
|
|
class IDNextState(models.Model):
|
|
cur_state = models.ForeignKey(IDState, related_name='nextstate')
|
|
next_state = models.ForeignKey(IDState, related_name='prevstate')
|
|
condition = models.CharField(blank=True, max_length=255)
|
|
def __str__(self):
|
|
return "%s -> %s" % (self.cur_state.state, self.next_state.state )
|
|
class Meta:
|
|
db_table = 'ref_next_states_new'
|
|
|
|
class IDSubState(models.Model):
|
|
sub_state_id = models.AutoField(primary_key=True)
|
|
sub_state = models.CharField(max_length=55, db_column='sub_state_val')
|
|
description = models.TextField(blank=True, db_column='sub_state_desc')
|
|
def __str__(self):
|
|
return self.sub_state
|
|
class Meta:
|
|
db_table = 'sub_state'
|
|
ordering = ['sub_state_id']
|
|
|
|
class Area(models.Model):
|
|
ACTIVE=1
|
|
area_acronym = models.OneToOneField(Acronym, primary_key=True)
|
|
start_date = models.DateField(auto_now_add=True)
|
|
concluded_date = models.DateField(null=True, blank=True)
|
|
status = models.ForeignKey(AreaStatus)
|
|
comments = models.TextField(blank=True)
|
|
last_modified_date = models.DateField(auto_now=True)
|
|
extra_email_addresses = models.TextField(blank=True,null=True)
|
|
def __str__(self):
|
|
return self.area_acronym.acronym
|
|
def additional_urls(self):
|
|
return AreaWGURL.objects.filter(name=self.area_acronym.name)
|
|
def active_wgs(self):
|
|
return IETFWG.objects.filter(group_type=1,status=IETFWG.ACTIVE,areagroup__area=self).order_by('group_acronym__acronym')
|
|
def active_areas():
|
|
return Area.objects.filter(status=Area.ACTIVE).order_by('area_acronym__acronym')
|
|
active_areas = staticmethod(active_areas)
|
|
class Meta:
|
|
db_table = 'areas'
|
|
verbose_name="area"
|
|
|
|
class AreaWGURL(models.Model):
|
|
id = models.AutoField(primary_key=True, db_column='area_ID')
|
|
# For WGs, this is the WG acronym; for areas, it's the area name.
|
|
name = models.CharField(max_length=50, db_column='area_Name')
|
|
url = models.CharField(max_length=50)
|
|
description = models.CharField(max_length=50)
|
|
def __unicode__(self):
|
|
return u'%s (%s)' % (self.name, self.description)
|
|
class Meta:
|
|
ordering = ['name']
|
|
verbose_name = "Area/WG URL"
|
|
db_table = "wg_www_pages"
|
|
|
|
class IDStatus(models.Model):
|
|
status_id = models.AutoField(primary_key=True)
|
|
status = models.CharField(max_length=25, db_column='status_value')
|
|
def __str__(self):
|
|
return self.status
|
|
class Meta:
|
|
db_table = "id_status"
|
|
verbose_name="I-D Status"
|
|
verbose_name_plural="I-D Statuses"
|
|
|
|
class IDIntendedStatus(models.Model):
|
|
intended_status_id = models.AutoField(primary_key=True)
|
|
intended_status = models.CharField(max_length=25, db_column='status_value')
|
|
def __str__(self):
|
|
return self.intended_status
|
|
class Meta:
|
|
db_table = "id_intended_status"
|
|
verbose_name="I-D Intended Publication Status"
|
|
verbose_name_plural="I-D Intended Publication Statuses"
|
|
|
|
class InternetDraft(models.Model):
|
|
DAYS_TO_EXPIRE=185
|
|
id_document_tag = models.AutoField(primary_key=True)
|
|
title = models.CharField(max_length=255, db_column='id_document_name')
|
|
id_document_key = models.CharField(max_length=255, editable=False)
|
|
group = models.ForeignKey(Acronym, db_column='group_acronym_id')
|
|
filename = models.CharField(max_length=255, unique=True)
|
|
revision = models.CharField(max_length=2)
|
|
revision_date = models.DateField()
|
|
file_type = models.CharField(max_length=20)
|
|
txt_page_count = models.IntegerField()
|
|
local_path = models.CharField(max_length=255, blank=True, null=True)
|
|
start_date = models.DateField()
|
|
expiration_date = models.DateField(null=True)
|
|
abstract = models.TextField()
|
|
dunn_sent_date = models.DateField(null=True, blank=True)
|
|
extension_date = models.DateField(null=True, blank=True)
|
|
status = models.ForeignKey(IDStatus)
|
|
intended_status = models.ForeignKey(IDIntendedStatus)
|
|
lc_sent_date = models.DateField(null=True, blank=True)
|
|
lc_changes = models.CharField(max_length=3,null=True)
|
|
lc_expiration_date = models.DateField(null=True, blank=True)
|
|
b_sent_date = models.DateField(null=True, blank=True)
|
|
b_discussion_date = models.DateField(null=True, blank=True)
|
|
b_approve_date = models.DateField(null=True, blank=True)
|
|
wgreturn_date = models.DateField(null=True, blank=True)
|
|
rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
|
|
comments = models.TextField(blank=True,null=True)
|
|
last_modified_date = models.DateField()
|
|
replaced_by = BrokenForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
|
|
replaces = FKAsOneToOne('replaces', reverse=True)
|
|
review_by_rfc_editor = models.BooleanField()
|
|
expired_tombstone = models.BooleanField()
|
|
idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
|
|
shepherd = BrokenForeignKey('PersonOrOrgInfo', null=True, blank=True, null_values=(0, ))
|
|
def __str__(self):
|
|
return self.filename
|
|
def save(self, *args, **kwargs):
|
|
self.id_document_key = self.title.upper()
|
|
super(InternetDraft, self).save(*args, **kwargs)
|
|
def displayname(self):
|
|
return self.filename
|
|
def file_tag(self):
|
|
return "<%s>" % (self.filename_with_rev())
|
|
def filename_with_rev(self):
|
|
return "%s-%s.txt" % (self.filename, self.revision_display())
|
|
def name(self):
|
|
# small hack to make model forward-compatible with new schema
|
|
return self.filename
|
|
def group_acronym(self):
|
|
return self.group.acronym
|
|
def group_ml_archive(self):
|
|
return self.group.ietfwg.clean_email_archive()
|
|
def idstate(self):
|
|
idinternal = self.idinternal
|
|
if idinternal:
|
|
return idinternal.docstate()
|
|
else:
|
|
return "I-D Exists"
|
|
def revision_display(self):
|
|
r = int(self.revision)
|
|
if self.status.status != 'Active' and not self.expired_tombstone:
|
|
r = max(r - 1, 0)
|
|
return "%02d" % r
|
|
def expiration(self):
|
|
return self.revision_date + datetime.timedelta(self.DAYS_TO_EXPIRE)
|
|
def can_expire(self):
|
|
# Copying the logic from expire-ids-1 without thinking
|
|
# much about it.
|
|
if self.review_by_rfc_editor:
|
|
return False
|
|
idinternal = self.idinternal
|
|
if idinternal:
|
|
cur_state_id = idinternal.cur_state_id
|
|
# 42 is "AD is Watching"; this matches what's in the
|
|
# expire-ids-1 perl script.
|
|
# A better way might be to add a column to the table
|
|
# saying whether or not a document is prevented from
|
|
# expiring.
|
|
if cur_state_id < 42:
|
|
return False
|
|
return True
|
|
|
|
def clean_abstract(self):
|
|
# Cleaning based on what "id-abstracts-text" script does
|
|
a = self.abstract
|
|
a = re.sub(" *\r\n *", "\n", a) # get rid of DOS line endings
|
|
a = re.sub(" *\r *", "\n", a) # get rid of MAC line endings
|
|
a = re.sub("(\n *){3,}", "\n\n", a) # get rid of excessive vertical whitespace
|
|
a = re.sub("\f[\n ]*[^\n]*\n", "", a) # get rid of page headers
|
|
# Get rid of 'key words' boilerplate and anything which follows it:
|
|
# (No way that is part of the abstract...)
|
|
a = re.sub("(?s)(Conventions [Uu]sed in this [Dd]ocument|Requirements [Ll]anguage)?[\n ]*The key words \"MUST\", \"MUST NOT\",.*$", "", a)
|
|
# Get rid of status/copyright boilerplate
|
|
a = re.sub("(?s)\nStatus of [tT]his Memo\n.*$", "", a)
|
|
# wrap long lines without messing up formatting of Ok paragraphs:
|
|
while re.match("([^\n]{72,}?) +", a):
|
|
a = re.sub("([^\n]{72,}?) +([^\n ]*)(\n|$)", "\\1\n\\2 ", a)
|
|
# Remove leading and trailing whitespace
|
|
a = a.strip()
|
|
return a
|
|
|
|
class Meta:
|
|
db_table = "internet_drafts"
|
|
|
|
class PersonOrOrgInfo(models.Model):
|
|
person_or_org_tag = models.AutoField(primary_key=True)
|
|
record_type = models.CharField(blank=True, null=True, max_length=8)
|
|
name_prefix = models.CharField(blank=True, null=True, max_length=10)
|
|
first_name = models.CharField(blank=True, max_length=20)
|
|
first_name_key = models.CharField(blank=True, max_length=20, editable=False)
|
|
middle_initial = models.CharField(blank=True, null=True, max_length=4)
|
|
middle_initial_key = models.CharField(blank=True, null=True, max_length=4, editable=False)
|
|
last_name = models.CharField(blank=True, max_length=50)
|
|
last_name_key = models.CharField(blank=True, max_length=50, editable=False)
|
|
name_suffix = models.CharField(blank=True, null=True, max_length=10)
|
|
date_modified = models.DateField(null=True, blank=True, auto_now=True)
|
|
modified_by = models.CharField(blank=True, null=True, max_length=8)
|
|
date_created = models.DateField(auto_now_add=True, null=True)
|
|
created_by = models.CharField(blank=True, null=True, max_length=8)
|
|
address_type = models.CharField(blank=True, null=True, max_length=4)
|
|
def save(self, **kwargs):
|
|
self.first_name_key = self.first_name.upper()
|
|
self.middle_initial_key = self.middle_initial.upper()
|
|
self.last_name_key = self.last_name.upper()
|
|
super(PersonOrOrgInfo, self).save(**kwargs)
|
|
def __str__(self):
|
|
# For django.VERSION 0.96
|
|
if self.first_name == '' and self.last_name == '':
|
|
return "(Person #%s)" % self.person_or_org_tag
|
|
return "%s %s" % ( self.first_name or "<nofirst>", self.last_name or "<nolast>")
|
|
def __unicode__(self):
|
|
# For django.VERSION 1.x
|
|
if self.first_name == '' and self.last_name == '':
|
|
return u"(Person #%s)" % self.person_or_org_tag
|
|
return u"%s %s" % ( self.first_name or u"<nofirst>", self.last_name or u"<nolast>")
|
|
def email(self, priority=1, type=None):
|
|
name = unicode(self)
|
|
email = ''
|
|
addresses = self.emailaddress_set.filter(address__contains="@").order_by('priority')
|
|
if addresses:
|
|
email = addresses[0].address
|
|
for a in addresses:
|
|
if a.priority == priority:
|
|
email = a.address
|
|
return (name, email)
|
|
# Added by Sunny Lee to display person's affiliation - 5/26/2007
|
|
def affiliation(self, priority=1):
|
|
try:
|
|
postal = self.postaladdress_set.get(address_priority=priority)
|
|
except PostalAddress.DoesNotExist:
|
|
return "PersonOrOrgInfo with no postal address!"
|
|
except AssertionError:
|
|
return "PersonOrOrgInfo with multiple priority-%d addresses!" % priority
|
|
return "%s" % ( postal.affiliated_company or postal.department or "???" )
|
|
def full_name_as_key(self):
|
|
return self.first_name.lower() + "." + self.last_name.lower()
|
|
class Meta:
|
|
db_table = 'person_or_org_info'
|
|
ordering = ['last_name']
|
|
verbose_name="Rolodex Entry"
|
|
verbose_name_plural="Rolodex"
|
|
|
|
# could use a mapping for user_level
|
|
class IESGLogin(models.Model):
|
|
SECRETARIAT_LEVEL = 0
|
|
AD_LEVEL = 1
|
|
INACTIVE_AD_LEVEL = 2
|
|
|
|
USER_LEVEL_CHOICES = (
|
|
(SECRETARIAT_LEVEL, 'Secretariat'),
|
|
(AD_LEVEL, 'IESG'),
|
|
(INACTIVE_AD_LEVEL, 'ex-IESG'),
|
|
(3, 'Level 3'),
|
|
(4, 'Comment Only(?)'),
|
|
)
|
|
id = models.AutoField(primary_key=True)
|
|
login_name = models.CharField(blank=True, max_length=255)
|
|
password = models.CharField(max_length=25)
|
|
user_level = models.IntegerField(choices=USER_LEVEL_CHOICES)
|
|
first_name = models.CharField(blank=True, max_length=25)
|
|
last_name = models.CharField(blank=True, max_length=25)
|
|
# this could be a OneToOneField but the unique constraint is violated in the data (for person_or_org_tag=188)
|
|
person = BrokenForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag', unique=True, null_values=(0, 888888), null=True)
|
|
pgp_id = models.CharField(blank=True, null=True, max_length=20)
|
|
default_search = models.NullBooleanField()
|
|
def __str__(self):
|
|
#return "%s, %s" % ( self.last_name, self.first_name)
|
|
return "%s %s" % ( self.first_name, self.last_name)
|
|
def is_current_ad(self):
|
|
return self.user_level == 1
|
|
def active_iesg():
|
|
return IESGLogin.objects.filter(user_level=1,id__gt=1).order_by('last_name') #XXX hardcoded
|
|
active_iesg = staticmethod(active_iesg)
|
|
class Meta:
|
|
db_table = 'iesg_login'
|
|
|
|
class AreaDirector(models.Model):
|
|
area = models.ForeignKey(Area, db_column='area_acronym_id', null=True)
|
|
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
def __str__(self):
|
|
return "%s (%s)" % ( self.person, self.role() )
|
|
def role(self):
|
|
try:
|
|
return "%s AD" % self.area
|
|
except Area.DoesNotExist:
|
|
return "?%d? AD" % self.area_id
|
|
class Meta:
|
|
db_table = 'area_directors'
|
|
|
|
|
|
###
|
|
# RFC tables
|
|
|
|
class RfcIntendedStatus(models.Model):
|
|
NONE=5
|
|
intended_status_id = models.AutoField(primary_key=True)
|
|
status = models.CharField(max_length=25, db_column='status_value')
|
|
def __str__(self):
|
|
return self.status
|
|
class Meta:
|
|
db_table = 'rfc_intend_status'
|
|
verbose_name = 'RFC Intended Status Field'
|
|
|
|
class RfcStatus(models.Model):
|
|
status_id = models.AutoField(primary_key=True)
|
|
status = models.CharField(max_length=25, db_column='status_value')
|
|
def __str__(self):
|
|
return self.status
|
|
class Meta:
|
|
db_table = 'rfc_status'
|
|
verbose_name = 'RFC Status'
|
|
verbose_name_plural = 'RFC Statuses'
|
|
|
|
class Rfc(models.Model):
|
|
ONLINE_CHOICES=(('YES', 'Yes'), ('NO', 'No'))
|
|
rfc_number = models.IntegerField(primary_key=True)
|
|
title = models.CharField(max_length=200, db_column='rfc_name')
|
|
rfc_name_key = models.CharField(max_length=200, editable=False)
|
|
group_acronym = models.CharField(blank=True, max_length=8)
|
|
area_acronym = models.CharField(blank=True, max_length=8)
|
|
status = models.ForeignKey(RfcStatus, db_column="status_id")
|
|
intended_status = models.ForeignKey(RfcIntendedStatus, db_column="intended_status_id", default=RfcIntendedStatus.NONE)
|
|
fyi_number = models.CharField(blank=True, max_length=20)
|
|
std_number = models.CharField(blank=True, max_length=20)
|
|
txt_page_count = models.IntegerField(null=True, blank=True)
|
|
online_version = models.CharField(choices=ONLINE_CHOICES, max_length=3, default='YES')
|
|
rfc_published_date = models.DateField(null=True, blank=True)
|
|
proposed_date = models.DateField(null=True, blank=True)
|
|
draft_date = models.DateField(null=True, blank=True)
|
|
standard_date = models.DateField(null=True, blank=True)
|
|
historic_date = models.DateField(null=True, blank=True)
|
|
lc_sent_date = models.DateField(null=True, blank=True)
|
|
lc_expiration_date = models.DateField(null=True, blank=True)
|
|
b_sent_date = models.DateField(null=True, blank=True)
|
|
b_approve_date = models.DateField(null=True, blank=True)
|
|
comments = models.TextField(blank=True)
|
|
last_modified_date = models.DateField()
|
|
|
|
idinternal = CachedLookupField(lookup=lambda self: IDInternal.objects.get(draft=self.rfc_number, rfc_flag=1))
|
|
group = CachedLookupField(lookup=lambda self: Acronym.objects.get(acronym=self.group_acronym))
|
|
|
|
def __str__(self):
|
|
return "RFC%04d" % ( self.rfc_number )
|
|
def save(self):
|
|
self.rfc_name_key = self.title.upper()
|
|
self.last_modified_date = datetime.date.today()
|
|
super(Rfc, self).save()
|
|
def displayname(self):
|
|
return "%s.txt" % ( self.filename() )
|
|
def filename(self):
|
|
return "rfc%d" % ( self.rfc_number )
|
|
def name(self):
|
|
# small hack to make model forward-compatible with new schema
|
|
return self.filename()
|
|
def revision(self):
|
|
return "RFC"
|
|
def revision_display(self):
|
|
return "RFC"
|
|
def file_tag(self):
|
|
return "RFC %s" % self.rfc_number
|
|
|
|
# return set of RfcObsolete objects obsoleted or updated by this RFC
|
|
def obsoletes(self):
|
|
return RfcObsolete.objects.filter(rfc=self.rfc_number)
|
|
|
|
# return set of RfcObsolete objects obsoleting or updating this RFC
|
|
def obsoleted_by(self):
|
|
return RfcObsolete.objects.filter(rfc_acted_on=self.rfc_number)
|
|
|
|
class Meta:
|
|
db_table = 'rfcs'
|
|
verbose_name = 'RFC'
|
|
verbose_name_plural = 'RFCs'
|
|
|
|
class RfcAuthor(models.Model):
|
|
rfc = models.ForeignKey(Rfc, db_column='rfc_number', related_name='authors')
|
|
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
def __str__(self):
|
|
return "%s, %s" % ( self.person.last_name, self.person.first_name)
|
|
class Meta:
|
|
db_table = 'rfc_authors'
|
|
verbose_name = 'RFC Author'
|
|
|
|
class RfcObsolete(models.Model):
|
|
ACTION_CHOICES=(('Obsoletes', 'Obsoletes'), ('Updates', 'Updates'))
|
|
rfc = models.ForeignKey(Rfc, db_column='rfc_number', related_name='updates_or_obsoletes')
|
|
action = models.CharField(max_length=20, choices=ACTION_CHOICES)
|
|
rfc_acted_on = models.ForeignKey(Rfc, db_column='rfc_acted_on', related_name='updated_or_obsoleted_by')
|
|
def __str__(self):
|
|
return "RFC%04d %s RFC%04d" % (self.rfc_id, self.action, self.rfc_acted_on_id)
|
|
class Meta:
|
|
db_table = 'rfcs_obsolete'
|
|
verbose_name = 'RFC updates or obsoletes'
|
|
verbose_name_plural = verbose_name
|
|
|
|
## End RFC Tables
|
|
|
|
class BallotInfo(models.Model): # Added by Michael Lee
|
|
ballot = models.AutoField(primary_key=True, db_column='ballot_id')
|
|
active = models.BooleanField()
|
|
an_sent = models.BooleanField()
|
|
an_sent_date = models.DateField(null=True, blank=True)
|
|
an_sent_by = models.ForeignKey(IESGLogin, db_column='an_sent_by', related_name='ansent', null=True)
|
|
defer = models.BooleanField(blank=True)
|
|
defer_by = models.ForeignKey(IESGLogin, db_column='defer_by', related_name='deferred', null=True)
|
|
defer_date = models.DateField(null=True, blank=True)
|
|
approval_text = models.TextField(blank=True)
|
|
last_call_text = models.TextField(blank=True)
|
|
ballot_writeup = models.TextField(blank=True)
|
|
ballot_issued = models.IntegerField(null=True, blank=True)
|
|
def __str__(self):
|
|
try:
|
|
return "Ballot for %s" % self.drafts.get(primary_flag=1)
|
|
except IDInternal.DoesNotExist:
|
|
return "Ballot ID %d (no I-D?)" % (self.ballot)
|
|
def remarks(self):
|
|
remarks = list(self.discusses.all()) + list(self.comments.all())
|
|
return remarks
|
|
def active_positions(self):
|
|
'''Returns a list of dicts, with AD and Position tuples'''
|
|
active_iesg = IESGLogin.active_iesg()
|
|
ads = [ad.id for ad in active_iesg]
|
|
positions = {}
|
|
for position in self.positions.filter(ad__in=ads):
|
|
positions[position.ad_id] = position
|
|
ret = []
|
|
for ad in active_iesg:
|
|
ret.append({'ad': ad, 'pos': positions.get(ad.id, None)})
|
|
return ret
|
|
def needed(self, standardsTrack=True):
|
|
'''Returns text answering the question "what does this document
|
|
need to pass?". The return value is only useful if the document
|
|
is currently in IESG evaluation.'''
|
|
active_iesg = IESGLogin.active_iesg()
|
|
ads = [ad.id for ad in active_iesg]
|
|
yes = 0
|
|
noobj = 0
|
|
discuss = 0
|
|
recuse = 0
|
|
for position in self.positions.filter(ad__in=ads):
|
|
yes += 1 if position.yes > 0 else 0
|
|
noobj += 1 if position.noobj > 0 else 0
|
|
discuss += 1 if position.discuss > 0 else 0
|
|
recuse += 1 if position.recuse > 0 else 0
|
|
answer = ''
|
|
if yes < 1:
|
|
answer += "Needs a YES. "
|
|
if discuss > 0:
|
|
if discuss == 1:
|
|
answer += "Has a DISCUSS. "
|
|
else:
|
|
answer += "Has %d DISCUSSes. " % discuss
|
|
if standardsTrack:
|
|
# For standards-track, need positions from 2/3 of the
|
|
# non-recused current IESG.
|
|
needed = ( active_iesg.count() - recuse ) * 2 / 3
|
|
else:
|
|
# Info and experimental only need one position.
|
|
needed = 1
|
|
have = yes + noobj + discuss
|
|
if have < needed:
|
|
more = needed - have
|
|
if more == 1:
|
|
answer += "Needs %d more position. " % more
|
|
else:
|
|
answer += "Needs %d more positions. " % more
|
|
else:
|
|
answer += "Has enough positions to pass"
|
|
if discuss:
|
|
answer += " once DISCUSSes are resolved"
|
|
answer += ". "
|
|
|
|
return answer.rstrip()
|
|
|
|
class Meta:
|
|
db_table = 'ballot_info'
|
|
|
|
def format_document_state(state, substate):
|
|
if substate:
|
|
return state.state + "::" + substate.sub_state
|
|
else:
|
|
return state.state
|
|
|
|
class IDInternal(models.Model):
|
|
"""
|
|
An IDInternal represents a document that has been added to the
|
|
I-D tracker. It can be either an Internet Draft or an RFC.
|
|
The table has only a single primary key field, meaning that
|
|
there is the danger of RFC number collision with low-numbered
|
|
Internet Drafts.
|
|
|
|
Since it's most common to be an Internet Draft, the draft
|
|
field is defined as a FK to InternetDrafts. One side effect
|
|
of this is that select_related() will only work with
|
|
rfc_flag=0.
|
|
|
|
When searching where matches may be either I-Ds or RFCs,
|
|
you cannot use draft__ as that will cause an INNER JOIN
|
|
which will limit the responses to I-Ds.
|
|
"""
|
|
|
|
ACTIVE=1
|
|
PUBLISHED=3
|
|
EXPIRED=2
|
|
WITHDRAWN_SUBMITTER=4
|
|
REPLACED=5
|
|
WITHDRAWN_IETF=6
|
|
INACTIVE_STATES=[99,32,42]
|
|
|
|
draft = models.ForeignKey(InternetDraft, primary_key=True, unique=True, db_column='id_document_tag')
|
|
rfc_flag = models.IntegerField(null=True)
|
|
ballot = models.ForeignKey(BallotInfo, related_name='drafts', db_column="ballot_id")
|
|
primary_flag = models.IntegerField(blank=True, null=True)
|
|
group_flag = models.IntegerField(blank=True, default=0)
|
|
token_name = models.CharField(blank=True, max_length=25)
|
|
token_email = models.CharField(blank=True, max_length=255)
|
|
note = models.TextField(blank=True)
|
|
status_date = models.DateField(blank=True,null=True)
|
|
email_display = models.CharField(blank=True, max_length=50)
|
|
agenda = models.IntegerField(null=True, blank=True)
|
|
cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs')
|
|
prev_state = models.ForeignKey(IDState, db_column='prev_state', related_name='docs_prev')
|
|
assigned_to = models.CharField(blank=True, max_length=25)
|
|
mark_by = models.ForeignKey(IESGLogin, db_column='mark_by', related_name='marked')
|
|
job_owner = models.ForeignKey(IESGLogin, db_column='job_owner', related_name='documents')
|
|
event_date = models.DateField(null=True)
|
|
area_acronym = models.ForeignKey(Area)
|
|
cur_sub_state = BrokenForeignKey(IDSubState, related_name='docs', null=True, blank=True, null_values=(0, -1))
|
|
prev_sub_state = BrokenForeignKey(IDSubState, related_name='docs_prev', null=True, blank=True, null_values=(0, -1))
|
|
returning_item = models.IntegerField(null=True, blank=True)
|
|
telechat_date = models.DateField(null=True, blank=True)
|
|
via_rfc_editor = models.IntegerField(null=True, blank=True)
|
|
state_change_notice_to = models.CharField(blank=True, max_length=255)
|
|
dnp = models.IntegerField(null=True, blank=True)
|
|
dnp_date = models.DateField(null=True, blank=True)
|
|
noproblem = models.IntegerField(null=True, blank=True)
|
|
resurrect_requested_by = BrokenForeignKey(IESGLogin, db_column='resurrect_requested_by', related_name='docsresurrected', null=True, blank=True)
|
|
approved_in_minute = models.IntegerField(null=True, blank=True)
|
|
def __str__(self):
|
|
if self.rfc_flag:
|
|
return "RFC%04d" % ( self.draft_id )
|
|
else:
|
|
return self.draft.filename
|
|
def get_absolute_url(self):
|
|
if self.rfc_flag:
|
|
return "/doc/rfc%d/" % ( self.draft_id )
|
|
else:
|
|
return "/doc/%s/" % ( self.draft.filename )
|
|
_cached_rfc = None
|
|
def document(self):
|
|
if self.rfc_flag:
|
|
if self._cached_rfc is None:
|
|
self._cached_rfc = Rfc.objects.get(rfc_number=self.draft_id)
|
|
return self._cached_rfc
|
|
else:
|
|
return self.draft
|
|
def public_comments(self):
|
|
return self.comments().filter(public_flag=True)
|
|
def comments(self):
|
|
# would filter by rfc_flag but the database is broken. (see
|
|
# trac ticket #96) so this risks collisions.
|
|
# return self.documentcomment_set.all().order_by('-date','-time','-id')
|
|
#
|
|
# the obvious code above doesn't work with django.VERSION 1.0/1.1
|
|
# because "draft" isn't a true foreign key (when rfc_flag=1 the
|
|
# related InternetDraft object doesn't necessarily exist).
|
|
return DocumentComment.objects.filter(document=self.draft_id).order_by('-date','-time','-id')
|
|
def ballot_set(self):
|
|
return IDInternal.objects.filter(ballot=self.ballot_id).order_by('-primary_flag')
|
|
def ballot_primary(self):
|
|
return IDInternal.objects.filter(ballot=self.ballot_id,primary_flag=1)
|
|
def ballot_others(self):
|
|
return IDInternal.objects.filter(models.Q(primary_flag=0)|models.Q(primary_flag__isnull=True), ballot=self.ballot_id)
|
|
def docstate(self):
|
|
return format_document_state(self.cur_state, self.cur_sub_state)
|
|
def change_state(self, state, sub_state):
|
|
self.prev_state = self.cur_state
|
|
self.cur_state = state
|
|
self.prev_sub_state_id = self.cur_sub_state_id
|
|
self.cur_sub_state = sub_state
|
|
class Meta:
|
|
db_table = 'id_internal'
|
|
verbose_name = 'IDTracker Draft'
|
|
def draft_link(self):
|
|
try:
|
|
if self.rfc_flag:
|
|
return '<a href="http://tools.ietf.org/html/rfc%s">rfc%s</a>' % (self.draft.pk, self.draft.pk)
|
|
else:
|
|
return '<a href="http://tools.ietf.org/html/%s">%s</a>' % (self.draft.filename, self.draft.filename)
|
|
except Exception:
|
|
return ""
|
|
draft_link.allow_tags = True
|
|
def tracker_link(self):
|
|
try:
|
|
if self.rfc_flag:
|
|
return '<a href="http://datatracker.ietf.org/doc/rfc%s">rfc%s</a>' % (self.draft.pk, self.draft.pk)
|
|
else:
|
|
return '<a href="http://datatracker.ietf.org/doc/%s">%s</a>' % (self.draft.filename, self.draft.filename)
|
|
except Exception:
|
|
return ""
|
|
tracker_link.allow_tags = True
|
|
|
|
class DocumentComment(models.Model):
|
|
BALLOT_DISCUSS = 1
|
|
BALLOT_COMMENT = 2
|
|
BALLOT_CHOICES = (
|
|
(BALLOT_DISCUSS, 'discuss'),
|
|
(BALLOT_COMMENT, 'comment'),
|
|
)
|
|
document = models.ForeignKey(IDInternal)
|
|
# NOTE: This flag is often NULL, which complicates its correct use...
|
|
rfc_flag = models.IntegerField(null=True, blank=True)
|
|
public_flag = models.BooleanField()
|
|
date = models.DateField(db_column='comment_date', default=datetime.date.today)
|
|
time = models.CharField(db_column='comment_time', max_length=20, default=lambda: datetime.datetime.now().strftime("%H:%M:%S"))
|
|
version = models.CharField(blank=True, max_length=3)
|
|
comment_text = models.TextField(blank=True)
|
|
# NOTE: This is not a true foreign key -- it sometimes has values
|
|
# (like 999) that do not exist in IESGLogin. So using select_related()
|
|
# will break!
|
|
created_by = BrokenForeignKey(IESGLogin, db_column='created_by', null=True, null_values=(0, 999))
|
|
result_state = BrokenForeignKey(IDState, db_column='result_state', null=True, related_name="comments_leading_to_state", null_values=(0, 99))
|
|
origin_state = models.ForeignKey(IDState, db_column='origin_state', null=True, related_name="comments_coming_from_state")
|
|
ballot = models.IntegerField(null=True, choices=BALLOT_CHOICES)
|
|
def __str__(self):
|
|
return "\"%s...\" by %s" % (self.comment_text[:20], self.get_author())
|
|
def get_absolute_url(self):
|
|
# use self.document.rfc_flag, since
|
|
# self.rfc_flag is not always set properly.
|
|
if self.document.rfc_flag:
|
|
return "/idtracker/rfc%d/comment/%d/" % (self.document_id, self.id)
|
|
else:
|
|
return "/idtracker/%s/comment/%d/" % (self.document.draft.filename, self.id)
|
|
def get_author(self):
|
|
if self.created_by:
|
|
return str(self.created_by)
|
|
else:
|
|
return "(System)"
|
|
def get_username(self):
|
|
if self.created_by:
|
|
return self.created_by.login_name
|
|
else:
|
|
return "(System)"
|
|
def get_fullname(self):
|
|
if self.created_by:
|
|
return self.created_by.first_name + " " + self.created_by.last_name
|
|
else:
|
|
return "(System)"
|
|
def datetime(self):
|
|
# this is just a straightforward combination, except that the time is
|
|
# stored incorrectly in the database.
|
|
return datetime.datetime.combine( self.date, datetime.time( * [int(s) for s in self.time.split(":")] ) )
|
|
def doc_id(self):
|
|
return self.document_id
|
|
def doc_name(self):
|
|
return self.document.draft.filename
|
|
class Meta:
|
|
db_table = 'document_comments'
|
|
|
|
class Position(models.Model):
|
|
ballot = models.ForeignKey(BallotInfo, related_name='positions')
|
|
ad = models.ForeignKey(IESGLogin)
|
|
yes = models.IntegerField(db_column='yes_col')
|
|
noobj = models.IntegerField(db_column='no_col')
|
|
abstain = models.IntegerField()
|
|
approve = models.IntegerField(default=0) # doesn't appear to be used anymore?
|
|
discuss = models.IntegerField()
|
|
recuse = models.IntegerField()
|
|
def __str__(self):
|
|
return "Position for %s on %s" % ( self.ad, self.ballot )
|
|
def abstain_ind(self):
|
|
if self.recuse:
|
|
return 'R'
|
|
if self.abstain:
|
|
return 'X'
|
|
else:
|
|
return ' '
|
|
def name(self):
|
|
positions = {"yes":"Yes",
|
|
"noobj":"No Objection",
|
|
"discuss":"Discuss",
|
|
"abstain":"Abstain",
|
|
"recuse":"Recuse"}
|
|
p = None
|
|
for k,v in positions.iteritems():
|
|
if self.__dict__[k] > 0:
|
|
p = v
|
|
if not p:
|
|
p = "No Record"
|
|
return p
|
|
|
|
class Meta:
|
|
db_table = 'ballots'
|
|
unique_together = (('ballot', 'ad'), )
|
|
verbose_name = "IESG Ballot Position"
|
|
|
|
class IESGComment(models.Model):
|
|
ballot = models.ForeignKey(BallotInfo, related_name="comments")
|
|
ad = models.ForeignKey(IESGLogin)
|
|
date = models.DateField(db_column="comment_date")
|
|
revision = models.CharField(max_length=2)
|
|
active = models.IntegerField() # doesn't appear to be used
|
|
text = models.TextField(blank=True, db_column="comment_text")
|
|
def __str__(self):
|
|
return "Comment text by %s on %s" % ( self.ad, self.ballot )
|
|
def is_comment(self):
|
|
return True
|
|
class Meta:
|
|
db_table = 'ballots_comment'
|
|
unique_together = (('ballot', 'ad'), )
|
|
verbose_name = 'IESG Comment Text'
|
|
verbose_name_plural = 'IESG Comments'
|
|
|
|
class IESGDiscuss(models.Model):
|
|
ballot = models.ForeignKey(BallotInfo, related_name="discusses")
|
|
ad = models.ForeignKey(IESGLogin)
|
|
date = models.DateField(db_column="discuss_date")
|
|
revision = models.CharField(max_length=2)
|
|
active = models.IntegerField()
|
|
text = models.TextField(blank=True, db_column="discuss_text")
|
|
def __str__(self):
|
|
return "Discuss text by %s on %s" % ( self.ad, self.ballot )
|
|
def is_discuss(self):
|
|
return True
|
|
class Meta:
|
|
db_table = 'ballots_discuss'
|
|
unique_together = (('ballot', 'ad'), )
|
|
verbose_name = 'IESG Discuss Text'
|
|
verbose_name_plural = 'IESG Discusses'
|
|
|
|
class IDAuthor(models.Model):
|
|
document = models.ForeignKey(InternetDraft, db_column='id_document_tag', related_name='authors')
|
|
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
author_order = models.IntegerField()
|
|
def __str__(self):
|
|
return "%s authors %s" % ( self.person, self.document.filename )
|
|
def email(self):
|
|
addresses = self.person.emailaddress_set.filter(type='I-D',priority=self.document_id)
|
|
if len(addresses) == 0:
|
|
return None
|
|
else:
|
|
return addresses[0].address
|
|
def final_author_order(self):
|
|
# Unfortunately, multiple authors for the same draft can have
|
|
# the same value for author_order (although they should not).
|
|
# Sort by person_id in that case to get a deterministic ordering.
|
|
return "%08d%08d" % (self.author_order, self.person_id)
|
|
class Meta:
|
|
db_table = 'id_authors'
|
|
verbose_name = "I-D Author"
|
|
ordering = ['document','author_order']
|
|
|
|
# PostalAddress, EmailAddress and PhoneNumber are edited in
|
|
# the admin for the Rolodex.
|
|
# The unique_together constraint is commented out for now, because
|
|
# of a bug in oldforms and AutomaticManipulator which fails to
|
|
# create the isUniquefoo_bar method properly. Since django is
|
|
# moving away from oldforms, I have to assume that this is going
|
|
# to be fixed by moving admin to newforms.
|
|
# must decide which field is/are core.
|
|
class PostalAddress(models.Model):
|
|
address_type = models.CharField(max_length=4)
|
|
address_priority = models.IntegerField(null=True)
|
|
person_or_org = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
person_title = models.CharField(max_length=50, blank=True)
|
|
affiliated_company = models.CharField(max_length=70, blank=True)
|
|
aff_company_key = models.CharField(max_length=70, blank=True, editable=False)
|
|
department = models.CharField(max_length=100, blank=True)
|
|
staddr1 = models.CharField(max_length=40)
|
|
staddr2 = models.CharField(max_length=40, blank=True)
|
|
mail_stop = models.CharField(max_length=20, blank=True)
|
|
city = models.CharField(max_length=20, blank=True)
|
|
state_or_prov = models.CharField(max_length=20, blank=True)
|
|
postal_code = models.CharField(max_length=20, blank=True)
|
|
country = models.CharField(max_length=20, blank=True)
|
|
def save(self):
|
|
self.aff_company_key = self.affiliated_company.upper()
|
|
super(PostalAddress, self).save()
|
|
class Meta:
|
|
db_table = 'postal_addresses'
|
|
#unique_together = (('address_type', 'person_or_org'), )
|
|
verbose_name_plural = 'Postal Addresses'
|
|
|
|
class EmailAddress(models.Model):
|
|
person_or_org = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
type = models.CharField(max_length=4, db_column='email_type')
|
|
priority = models.IntegerField(db_column='email_priority')
|
|
address = models.CharField(max_length=255, db_column='email_address')
|
|
comment = models.CharField(blank=True, null=True, max_length=255, db_column='email_comment')
|
|
def __str__(self):
|
|
return self.address
|
|
person_link = admin_link('person_or_org')
|
|
def priority_link(self):
|
|
if self.type=="I-D":
|
|
return '<a href="/admin/idtracker/internetdraft/%s/">%s</a>' % (self.priority, self.priority)
|
|
else:
|
|
return self.priority
|
|
priority_link.allow_tags = True
|
|
class Meta:
|
|
db_table = 'email_addresses'
|
|
#unique_together = (('email_priority', 'person_or_org'), )
|
|
# with this, I get 'ChangeManipulator' object has no attribute 'isUniqueemail_priority_person_or_org'
|
|
verbose_name_plural = 'Email addresses'
|
|
|
|
class PhoneNumber(models.Model):
|
|
person_or_org = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
phone_type = models.CharField(max_length=3)
|
|
phone_priority = models.IntegerField()
|
|
phone_number = models.CharField(blank=True, max_length=255)
|
|
phone_comment = models.CharField(blank=True, max_length=255)
|
|
class Meta:
|
|
db_table = 'phone_numbers'
|
|
#unique_together = (('phone_priority', 'person_or_org'), )
|
|
|
|
### Working Groups
|
|
|
|
class WGType(models.Model):
|
|
group_type_id = models.AutoField(primary_key=True)
|
|
type = models.CharField(max_length=25, db_column='group_type')
|
|
def __str__(self):
|
|
return self.type
|
|
class Meta:
|
|
verbose_name = "WG Type"
|
|
db_table = 'g_type'
|
|
|
|
class WGStatus(models.Model):
|
|
status_id = models.AutoField(primary_key=True)
|
|
status = models.CharField(max_length=25, db_column='status_value')
|
|
def __str__(self):
|
|
return self.status
|
|
class Meta:
|
|
verbose_name = "WG Status"
|
|
verbose_name_plural = "WG Statuses"
|
|
db_table = 'g_status'
|
|
|
|
class IETFWG(models.Model):
|
|
ACTIVE = 1
|
|
group_acronym = models.OneToOneField(Acronym, primary_key=True, editable=False)
|
|
group_type = models.ForeignKey(WGType)
|
|
proposed_date = models.DateField(null=True, blank=True)
|
|
start_date = models.DateField(null=True, blank=True)
|
|
dormant_date = models.DateField(null=True, blank=True)
|
|
concluded_date = models.DateField(null=True, blank=True)
|
|
status = models.ForeignKey(WGStatus)
|
|
area_director = models.ForeignKey(AreaDirector, null=True)
|
|
meeting_scheduled = models.CharField(blank=True, max_length=3)
|
|
email_address = models.CharField(blank=True, max_length=60)
|
|
email_subscribe = models.CharField(blank=True, max_length=120)
|
|
email_keyword = models.CharField(blank=True, max_length=50)
|
|
email_archive = models.CharField(blank=True, max_length=95)
|
|
comments = models.TextField(blank=True)
|
|
last_modified_date = models.DateField()
|
|
meeting_scheduled_old = models.CharField(blank=True, max_length=3)
|
|
area = FKAsOneToOne('areagroup', reverse=True)
|
|
def __str__(self):
|
|
return self.group_acronym.acronym
|
|
def active_drafts(self):
|
|
return self.group_acronym.internetdraft_set.all().filter(status__status="Active")
|
|
def choices():
|
|
return [(wg.group_acronym_id, wg.group_acronym.acronym) for wg in IETFWG.objects.all().filter(group_type__type='WG').select_related().order_by('acronym.acronym')]
|
|
choices = staticmethod(choices)
|
|
def area_acronym(self):
|
|
areas = AreaGroup.objects.filter(group__exact=self.group_acronym)
|
|
if areas:
|
|
return areas[areas.count()-1].area.area_acronym
|
|
else:
|
|
return None
|
|
def area_directors(self):
|
|
areas = AreaGroup.objects.filter(group__exact=self.group_acronym)
|
|
if areas:
|
|
return areas[areas.count()-1].area.areadirector_set.all()
|
|
else:
|
|
return None
|
|
def chairs(self): # return a set of WGChair objects for this work group
|
|
return WGChair.objects.filter(group_acronym__exact=self.group_acronym)
|
|
def secretaries(self): # return a set of WGSecretary objects for this group
|
|
return WGSecretary.objects.filter(group_acronym__exact=self.group_acronym)
|
|
def milestones(self): # return a set of GoalMilestone objects for this group
|
|
return GoalMilestone.objects.filter(group_acronym__exact=self.group_acronym)
|
|
def rfcs(self): # return a set of Rfc objects for this group
|
|
return Rfc.objects.filter(group_acronym__exact=self.group_acronym)
|
|
def drafts(self): # return a set of Rfc objects for this group
|
|
return InternetDraft.objects.filter(group__exact=self.group_acronym)
|
|
def charter_text(self): # return string containing WG description read from file
|
|
# get file path from settings. Syntesize file name from path, acronym, and suffix
|
|
try:
|
|
filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, self.group_acronym.acronym) + ".desc.txt"
|
|
desc_file = open(filename)
|
|
desc = desc_file.read()
|
|
except BaseException:
|
|
desc = 'Error Loading Work Group Description'
|
|
return desc
|
|
def additional_urls(self):
|
|
return AreaWGURL.objects.filter(name=self.group_acronym.acronym)
|
|
def clean_email_archive(self):
|
|
x = self.email_archive
|
|
# remove "current/" and "maillist.html"
|
|
x = re.sub("^(http://www\.ietf\.org/mail-archive/web/)([^/]+/)(current/)?([a-z]+\.html)?$", "\\1\\2", x)
|
|
return x
|
|
chairs_link = admin_link('chairs')
|
|
class Meta:
|
|
db_table = 'groups_ietf'
|
|
ordering = ['?'] # workaround django wanting to sort by acronym but not joining with it
|
|
verbose_name = 'IETF Working Group'
|
|
|
|
class WGChair(models.Model):
|
|
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
group_acronym = models.ForeignKey(IETFWG)
|
|
def __str__(self):
|
|
return "%s (%s)" % ( self.person, self.role() )
|
|
def role(self):
|
|
return "%s %s Chair" % ( self.group_acronym, self.group_acronym.group_type )
|
|
person_link = admin_link('person')
|
|
group_link = admin_link('group_acronym')
|
|
class Meta:
|
|
db_table = 'g_chairs'
|
|
verbose_name = "WG Chair"
|
|
|
|
class WGEditor(models.Model):
|
|
group_acronym = models.ForeignKey(IETFWG)
|
|
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag', unique=True)
|
|
def __str__(self):
|
|
return "%s (%s)" % (self.person, self.role())
|
|
def role(self):
|
|
return "%s Editor" % self.group_acronym
|
|
class Meta:
|
|
db_table = 'g_editors'
|
|
verbose_name = "WG Editor"
|
|
|
|
# Note: there is an empty table 'g_secretary'.
|
|
# This uses the 'g_secretaries' table but is called 'GSecretary' to
|
|
# match the model naming scheme.
|
|
class WGSecretary(models.Model):
|
|
group_acronym = models.ForeignKey(IETFWG)
|
|
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
def __str__(self):
|
|
return "%s (%s)" % ( self.person, self.role() )
|
|
def role(self):
|
|
return "%s %s Secretary" % ( self.group_acronym, self.group_acronym.group_type )
|
|
class Meta:
|
|
db_table = 'g_secretaries'
|
|
verbose_name = "WG Secretary"
|
|
verbose_name_plural = "WG Secretaries"
|
|
|
|
class WGTechAdvisor(models.Model):
|
|
group_acronym = models.ForeignKey(IETFWG)
|
|
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
def __str__(self):
|
|
return "%s (%s)" % ( self.person, self.role() )
|
|
def role(self):
|
|
return "%s Technical Advisor" % self.group_acronym
|
|
class Meta:
|
|
db_table = 'g_tech_advisors'
|
|
verbose_name = "WG Technical Advisor"
|
|
|
|
class AreaGroup(models.Model):
|
|
area = models.ForeignKey(Area, db_column='area_acronym_id', related_name='areagroup')
|
|
group = models.ForeignKey(IETFWG, db_column='group_acronym_id', unique=True)
|
|
def __str__(self):
|
|
return "%s is in %s" % ( self.group, self.area )
|
|
class Meta:
|
|
db_table = 'area_group'
|
|
verbose_name = 'Area this group is in'
|
|
verbose_name_plural = 'Area to Group mappings'
|
|
|
|
class GoalMilestone(models.Model):
|
|
DONE_CHOICES = (
|
|
('Done', 'Done'),
|
|
('No', 'Not Done'),
|
|
)
|
|
gm_id = models.AutoField(primary_key=True)
|
|
group_acronym = models.ForeignKey(IETFWG)
|
|
description = models.TextField()
|
|
expected_due_date = models.DateField()
|
|
done_date = models.DateField(null=True, blank=True)
|
|
done = models.CharField(blank=True, choices=DONE_CHOICES, max_length=4)
|
|
last_modified_date = models.DateField()
|
|
def __str__(self):
|
|
return self.description
|
|
class Meta:
|
|
db_table = 'goals_milestones'
|
|
verbose_name = 'IETF WG Goal or Milestone'
|
|
verbose_name_plural = 'IETF WG Goals or Milestones'
|
|
ordering = ['expected_due_date']
|
|
|
|
|
|
#### end wg stuff
|
|
|
|
class Role(models.Model):
|
|
'''This table is named 'chairs' in the database, as its original
|
|
role was to store "who are IETF, IAB and IRTF chairs?". It has
|
|
since expanded to store roles, such as "IAB Exec Dir" and "IAD",
|
|
so the model is renamed.
|
|
'''
|
|
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
role_name = models.CharField(max_length=25, db_column='chair_name')
|
|
|
|
# Role values
|
|
IETF_CHAIR = 1
|
|
IAB_CHAIR = 2
|
|
NOMCOM_CHAIR = 3
|
|
IAB_EXCUTIVE_DIRECTOR = 4
|
|
IRTF_CHAIR = 5
|
|
IAD_CHAIR = 6
|
|
RSOC_CHAIR = 7
|
|
|
|
# This __str__ makes it odd to use as a ForeignKey.
|
|
def __str__(self):
|
|
return "%s (%s)" % (self.person, self.role())
|
|
def role(self):
|
|
if self.role_name in ('IETF', 'IAB', 'IRTF', 'NomCom'):
|
|
return "%s Chair" % self.role_name
|
|
else:
|
|
return self.role_name
|
|
class Meta:
|
|
db_table = 'chairs'
|
|
|
|
class ChairsHistory(models.Model):
|
|
chair_type = models.ForeignKey(Role)
|
|
present_chair = models.BooleanField()
|
|
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
start_year = models.IntegerField()
|
|
end_year = models.IntegerField(null=True, blank=True)
|
|
def __str__(self):
|
|
return str(self.person)
|
|
class Meta:
|
|
db_table = 'chairs_history'
|
|
|
|
#
|
|
# IRTF RG info
|
|
class IRTF(models.Model):
|
|
irtf_id = models.AutoField(primary_key=True)
|
|
acronym = models.CharField(blank=True, max_length=25, db_column='irtf_acronym')
|
|
name = models.CharField(blank=True, max_length=255, db_column='irtf_name')
|
|
charter_text = models.TextField(blank=True,null=True)
|
|
meeting_scheduled = models.BooleanField(blank=True)
|
|
def __str__(self):
|
|
return self.acronym
|
|
def chairs(self): # return a set of IRTFChair objects for this work group
|
|
return IRTFChair.objects.filter(irtf=self)
|
|
class Meta:
|
|
db_table = 'irtf'
|
|
verbose_name="IRTF Research Group"
|
|
|
|
class IRTFChair(models.Model):
|
|
irtf = models.ForeignKey(IRTF)
|
|
person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
|
|
def __str__(self):
|
|
return "%s is chair of %s" % (self.person, self.irtf)
|
|
class Meta:
|
|
db_table = 'irtf_chairs'
|
|
verbose_name="IRTF Research Group Chair"
|
|
|
|
class IDDates(models.Model):
|
|
FIRST_CUT_OFF = 1
|
|
SECOND_CUT_OFF = 2
|
|
IETF_MONDAY = 3
|
|
ALL_IDS_PROCESSED_BY = 4
|
|
IETF_MONDAY_AFTER = 5
|
|
APPROVED_V00_SUBMISSIONS = 6
|
|
|
|
date = models.DateField(db_column="id_date")
|
|
description = models.CharField(max_length=255, db_column="date_name")
|
|
f_name = models.CharField(max_length=255)
|
|
|
|
class Meta:
|
|
db_table = 'id_dates'
|
|
|
|
# Not a model, but it's related.
|
|
# This is used in the view to represent documents
|
|
# in "I-D Exists".
|
|
#
|
|
class DocumentWrapper(object):
|
|
'''A wrapper for a document, used to synthesize I-D Exists.'''
|
|
document = None
|
|
synthetic = True
|
|
job_owner = "Not Assigned Yet"
|
|
docstate = "I-D Exists"
|
|
cur_state = "I-D Exists"
|
|
cur_state_id = 100
|
|
primary_flag = 1
|
|
def __init__(self, document):
|
|
self.document = document
|
|
|
|
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
|
|
InternetDraftOld = InternetDraft
|
|
IDInternalOld = IDInternal
|
|
RfcOld = Rfc
|
|
BallotInfoOld = BallotInfo
|
|
IDStateOld = IDState
|
|
IDSubStateOld = IDSubState
|
|
AreaOld = Area
|
|
AcronymOld = Acronym
|
|
IESGLoginOld = IESGLogin
|
|
IETFWGOld = IETFWG
|
|
IRTFOld = IRTF
|
|
AreaGroupOld = AreaGroup
|
|
from ietf.doc.proxy import InternetDraft, IDInternal, BallotInfo, Rfc, IDState
|
|
from ietf.name.proxy import IDSubState
|
|
from ietf.group.proxy import Area, Acronym, IETFWG, IRTF, AreaGroup
|
|
from ietf.person.proxy import IESGLogin
|
|
|
|
|
|
# changes done by convert-096.py:changed maxlength to max_length
|
|
# removed core
|
|
# removed edit_inline
|
|
# removed max_num_in_admin
|
|
# removed num_in_admin
|
|
# removed raw_id_admin
|