datatracker/ietf/idtracker/models.py
2012-01-24 17:17:24 +00:00

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