datatracker/redesign/doc/models.py
2011-08-22 19:54:25 +00:00

307 lines
13 KiB
Python

# Copyright The IETF Trust 2007, All Rights Reserved
from django.db import models
from django.core.urlresolvers import reverse as urlreverse
from django.conf import settings
from redesign.group.models import *
from redesign.name.models import *
from redesign.person.models import Email, Person
from redesign.util import admin_link
import datetime, os
from ietf import settings
class DocumentInfo(models.Model):
"""Any kind of document. Draft, RFC, Charter, IPR Statement, Liaison Statement"""
time = models.DateTimeField(default=datetime.datetime.now) # should probably have auto_now=True
# Document related
type = models.ForeignKey(DocTypeName, blank=True, null=True) # Draft, Agenda, Minutes, Charter, Discuss, Guideline, Email, Review, Issue, Wiki, External ...
title = models.CharField(max_length=255)
# State
state = models.ForeignKey(DocStateName, blank=True, null=True) # Active/Expired/RFC/Replaced/Withdrawn
tags = models.ManyToManyField(DocInfoTagName, blank=True, null=True) # Revised ID Needed, ExternalParty, AD Followup, ...
stream = models.ForeignKey(DocStreamName, blank=True, null=True) # IETF, IAB, IRTF, Independent Submission
group = models.ForeignKey(Group, blank=True, null=True) # WG, RG, IAB, IESG, Edu, Tools
wg_state = models.ForeignKey(WgDocStateName, verbose_name="WG state", blank=True, null=True) # Not/Candidate/Active/Parked/LastCall/WriteUp/Submitted/Dead
iesg_state = models.ForeignKey(IesgDocStateName, verbose_name="IESG state", blank=True, null=True) #
iana_state = models.ForeignKey(IanaDocStateName, verbose_name="IANA state", blank=True, null=True)
rfc_state = models.ForeignKey(RfcDocStateName, verbose_name="RFC state", blank=True, null=True)
charter_state = models.ForeignKey(CharterDocStateName, verbose_name="IESG charter state", blank=True, null=True)
# Other
abstract = models.TextField()
rev = models.CharField(verbose_name="revision", max_length=16, blank=True)
pages = models.IntegerField(blank=True, null=True)
order = models.IntegerField(default=1)
intended_std_level = models.ForeignKey(IntendedStdLevelName, blank=True, null=True)
std_level = models.ForeignKey(StdLevelName, blank=True, null=True)
ad = models.ForeignKey(Person, verbose_name="area director", related_name='ad_%(class)s_set', blank=True, null=True)
shepherd = models.ForeignKey(Person, related_name='shepherd_%(class)s_set', blank=True, null=True)
notify = models.CharField(max_length=255, blank=True)
external_url = models.URLField(blank=True) # Should be set for documents with type 'External'.
note = models.TextField(blank=True)
internal_comments = models.TextField(blank=True)
def get_file_path(self):
if self.type_id == "draft":
return settings.INTERNET_DRAFT_PATH
elif self.type_id in ("agenda", "minutes", "slides"):
meeting = self.name.split("-")[1]
return os.path.join(settings.AGENDA_PATH, meeting, self.type_id) + "/"
elif self.type_id == "charter":
return settings.CHARTER_PATH
else:
raise NotImplemented
def get_txt_url(self):
if self.type_id == "charter":
return "http://www.ietf.org/charters/"
else:
raise NotImplemented
class Meta:
abstract = True
def author_list(self):
return ", ".join(email.address for email in self.authors.all())
class RelatedDocument(models.Model):
source = models.ForeignKey('Document')
target = models.ForeignKey('DocAlias')
relationship = models.ForeignKey(DocRelationshipName)
def action(self):
return self.relationship.name
def inverse_action():
infinitive = self.relationship.name[:-1]
return u"%sd by" % infinitive
def __unicode__(self):
return u"%s %s %s" % (self.source.name, self.relationship.name.lower(), self.target.name)
class DocumentAuthor(models.Model):
document = models.ForeignKey('Document')
author = models.ForeignKey(Email, help_text="Email address used by author for submission")
order = models.IntegerField(default=1)
def __unicode__(self):
return u"%s %s (%s)" % (self.document.name, self.author.get_name(), self.order)
class Meta:
ordering = ["document", "order"]
class Document(DocumentInfo):
name = models.CharField(max_length=255, primary_key=True) # immutable
related = models.ManyToManyField('DocAlias', through=RelatedDocument, blank=True, related_name="reversely_related_document_set")
authors = models.ManyToManyField(Email, through=DocumentAuthor, blank=True)
def __unicode__(self):
return self.name
def get_absolute_url(self):
name = self.name
if self.type_id == "charter":
return urlreverse('record_view', kwargs={ 'name': self.group.acronym })
else:
if self.state == "rfc":
aliases = self.docalias_set.filter(name__startswith="rfc")
if aliases:
name = aliases[0].name
return urlreverse('doc_view', kwargs={ 'name': name })
def file_tag(self):
return u"<%s>" % self.filename_with_rev()
def filename_with_rev(self):
# FIXME: compensate for tombstones?
return u"%s-%s.txt" % (self.name, self.rev)
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 DocEvent
while d.latest_event(WriteupDocEvent, type="xyz") returns a
WriteupDocEvent event."""
model = args[0] if args else DocEvent
e = model.objects.filter(doc=self).filter(**filter_args).order_by('-time', '-id')[:1]
return e[0] if e else None
def canonical_name(self):
name = self.name
if self.type_id == "draft" and self.state_id == "rfc":
a = self.docalias_set.filter(name__startswith="rfc")
if a:
name = a[0].name
return name
class RelatedDocHistory(models.Model):
source = models.ForeignKey('DocHistory')
target = models.ForeignKey('DocAlias', related_name="reversely_related_document_history_set")
relationship = models.ForeignKey(DocRelationshipName)
def __unicode__(self):
return u"%s %s %s" % (self.source.doc.name, self.relationship.name.lower(), self.target.name)
class DocHistoryAuthor(models.Model):
document = models.ForeignKey('DocHistory')
author = models.ForeignKey(Email)
order = models.IntegerField()
def __unicode__(self):
return u"%s %s (%s)" % (self.document.doc.name, self.author.get_name(), self.order)
class Meta:
ordering = ["document", "order"]
class DocHistory(DocumentInfo):
doc = models.ForeignKey(Document, related_name="history_set")
# Django 1.2 won't let us define these in the base class, so we have
# to repeat them
name = models.CharField(max_length=255) # We need to save the name for charters
related = models.ManyToManyField('DocAlias', through=RelatedDocHistory, blank=True)
authors = models.ManyToManyField(Email, through=DocHistoryAuthor, blank=True)
def __unicode__(self):
return unicode(self.doc.name)
def save_document_in_history(doc):
def get_model_fields_as_dict(obj):
return dict((field.name, getattr(obj, field.name))
for field in obj._meta.fields
if field is not obj._meta.pk)
# copy fields
fields = get_model_fields_as_dict(doc)
fields["doc"] = doc
fields["name"] = doc.name
dochist = DocHistory(**fields)
dochist.save()
# copy many to many
for field in doc._meta.many_to_many:
if not field.rel.through:
# just add the attributes
rel = getattr(dochist, field.name)
for item in getattr(doc, field.name).all():
rel.add(item)
# copy remaining tricky many to many
def transfer_fields(obj, HistModel):
mfields = get_model_fields_as_dict(item)
# map doc -> dochist
for k, v in mfields.iteritems():
if v == doc:
mfields[k] = dochist
HistModel.objects.create(**mfields)
for item in RelatedDocument.objects.filter(source=doc):
transfer_fields(item, RelatedDocHistory)
for item in DocumentAuthor.objects.filter(document=doc):
transfer_fields(item, DocHistoryAuthor)
return dochist
class DocAlias(models.Model):
"""This is used for documents that may appear under multiple names,
and in particular for RFCs, which for continuity still keep the
same immutable Document.name, in the tables, but will be referred
to by RFC number, primarily, after achieving RFC status.
"""
document = models.ForeignKey(Document)
name = models.CharField(max_length=255, db_index=True)
def __unicode__(self):
return "%s-->%s" % (self.name, self.document.name)
document_link = admin_link("document")
class Meta:
verbose_name = "document alias"
verbose_name_plural = "document aliases"
EVENT_TYPES = [
# core events
("new_revision", "Added new revision"),
("changed_document", "Changed document metadata"),
# misc document events
("added_comment", "Added comment"),
("expired_document", "Expired document"),
("requested_resurrect", "Requested resurrect"),
("completed_resurrect", "Completed resurrect"),
("published_rfc", "Published RFC"),
# Charter events
("initial_review", "Set initial review time"),
("changed_review_announcement", "Changed WG Review text"),
("changed_action_announcement", "Changed WG Action text"),
# IESG events
("started_iesg_process", "Started IESG process on document"),
("sent_ballot_announcement", "Sent ballot announcement"),
("changed_ballot_position", "Changed ballot position"),
("changed_ballot_approval_text", "Changed ballot approval text"),
("changed_ballot_writeup_text", "Changed ballot writeup text"),
("changed_last_call_text", "Changed last call text"),
("requested_last_call", "Requested last call"),
("sent_last_call", "Sent last call"),
("changed_status_date", "Changed status date"),
("scheduled_for_telechat", "Scheduled for telechat"),
("iesg_approved", "IESG approved document (no problem)"),
("iesg_disapproved", "IESG disapproved document (do not publish)"),
("approved_in_minute", "Approved in minute"),
]
class DocEvent(models.Model):
"""An occurrence for a document, used for tracking who, when and what."""
time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened")
type = models.CharField(max_length=50, choices=EVENT_TYPES)
by = models.ForeignKey(Person)
doc = models.ForeignKey('doc.Document')
desc = models.TextField()
def __unicode__(self):
return u"%s %s at %s" % (self.by.name, self.get_type_display().lower(), self.time)
class Meta:
ordering = ['-time', '-id']
class NewRevisionDocEvent(DocEvent):
rev = models.CharField(max_length=16)
# IESG events
class BallotPositionDocEvent(DocEvent):
ad = models.ForeignKey(Person)
pos = models.ForeignKey(BallotPositionName, verbose_name="position", default="norecord")
discuss = models.TextField(help_text="Discuss text if position is discuss", blank=True)
discuss_time = models.DateTimeField(help_text="Time discuss text was written", blank=True, null=True)
comment = models.TextField(help_text="Optional comment", blank=True)
comment_time = models.DateTimeField(help_text="Time optional comment was written", blank=True, null=True)
class WriteupDocEvent(DocEvent):
text = models.TextField(blank=True)
class StatusDateDocEvent(DocEvent):
date = models.DateField(blank=True, null=True)
class LastCallDocEvent(DocEvent):
expires = models.DateTimeField(blank=True, null=True)
class TelechatDocEvent(DocEvent):
telechat_date = models.DateField(blank=True, null=True)
returning_item = models.BooleanField(default=False)
# Charter ballot events
class GroupBallotPositionDocEvent(DocEvent):
ad = models.ForeignKey(Person)
pos = models.ForeignKey(GroupBallotPositionName, verbose_name="position", default="norecord")
block_comment = models.TextField(help_text="Blocking comment if position is comment", blank=True)
block_comment_time = models.DateTimeField(help_text="Blocking comment was written", blank=True, null=True)
comment = models.TextField(help_text="Non-blocking comment", blank=True)
comment_time = models.DateTimeField(help_text="Time non-blocking comment was written", blank=True, null=True)
class InitialReviewDocEvent(DocEvent):
expires = models.DateTimeField(blank=True, null=True)