# 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)