Merged in [8846] from rjsparks@nostrum.com:

Added tests for document urls that provide a revision for all the document types the view code currently handles. Refactored parts of Document and DocHistory into DocumentInfo to get the tests to pass.  (but careful review is probably warranted).
 - Legacy-Id: 8880
Note: SVN reference [8846] has been migrated to Git commit ce5bda1835b32640d17136d09262f654fade94d4
This commit is contained in:
Henrik Levkowetz 2015-01-21 19:01:47 +00:00
parent a6e4618db6
commit f15ba3346b
3 changed files with 157 additions and 101 deletions

View file

@ -168,6 +168,53 @@ class DocumentInfo(models.Model):
else:
return None
def friendly_state(self):
""" Return a concise text description of the document's current state."""
state = self.get_state()
if not state:
return "Unknown state"
if self.type_id == 'draft':
iesg_state = self.get_state("draft-iesg")
iesg_state_summary = None
if iesg_state:
iesg_substate = [t for t in self.tags.all() if t.slug in IESG_SUBSTATE_TAGS]
# There really shouldn't be more than one tag in iesg_substate, but this will do something sort-of-sensible if there is
iesg_state_summary = iesg_state.name
if iesg_substate:
iesg_state_summary = iesg_state_summary + "::"+"::".join(tag.name for tag in iesg_substate)
if state.slug == "rfc":
return "RFC %s (%s)" % (self.rfc_number(), self.std_level)
elif state.slug == "repl":
rs = self.related_that("replaces")
if rs:
return mark_safe("Replaced by " + ", ".join("<a href=\"%s\">%s</a>" % (urlreverse('doc_view', kwargs=dict(name=alias.document)), alias.document) for alias in rs))
else:
return "Replaced"
elif state.slug == "active":
if iesg_state:
if iesg_state.slug == "dead":
# Many drafts in the draft-iesg "Dead" state are not dead
# in other state machines; they're just not currently under
# IESG processing. Show them as "I-D Exists (IESG: Dead)" instead...
return "I-D Exists (IESG: %s)" % iesg_state_summary
elif iesg_state.slug == "lc":
e = self.latest_event(LastCallDocEvent, type="sent_last_call")
if e:
return iesg_state_summary + " (ends %s)" % e.expires.date().isoformat()
return iesg_state_summary
else:
return "I-D Exists"
else:
if iesg_state and iesg_state.slug == "dead":
return state.name + " (IESG: %s)" % iesg_state_summary
# Expired/Withdrawn by Submitter/IETF
return state.name
else:
return state.name
def author_list(self):
return ", ".join(email.address for email in self.authors.all())
@ -195,6 +242,69 @@ class DocumentInfo(models.Model):
else:
return False
def relations_that(self, relationship):
"""Return the related-document objects that describe a given relationship targeting self."""
if isinstance(relationship, str):
relationship = [ relationship ]
if isinstance(relationship, tuple):
relationship = list(relationship)
if not isinstance(relationship, list):
raise TypeError("Expected a string, tuple or list, received %s" % type(relationship))
if isinstance(self, Document):
return RelatedDocument.objects.filter(target__document=self, relationship__in=relationship).select_related('source')
elif isinstance(self, DocHistory):
return RelatedDocHistory.objects.filter(target__document=self, relationship__in=relationship).select_related('source')
else:
return RelatedDocument.objects.none()
def all_relations_that(self, relationship, related=None):
if not related:
related = []
rels = self.relations_that(relationship)
for r in rels:
if not r in related:
related += [ r ]
related = r.source.all_relations_that(relationship, related)
return related
def relations_that_doc(self, relationship):
"""Return the related-document objects that describe a given relationship from self to other documents."""
if isinstance(relationship, str):
relationship = [ relationship ]
if isinstance(relationship, tuple):
relationship = list(relationship)
if not isinstance(relationship, list):
raise TypeError("Expected a string, tuple or list, received %s" % type(relationship))
if isinstance(self, Document):
return RelatedDocument.objects.filter(source=self, relationship__in=relationship).select_related('target__document')
elif isinstance(self, DocHistory):
return RelatedDocHistory.objects.filter(source=self, relationship__in=relationship).select_related('target__document')
else:
return RelatedDocument.objects.none()
def all_relations_that_doc(self, relationship, related=None):
if not related:
related = []
rels = self.relations_that_doc(relationship)
for r in rels:
if not r in related:
related += [ r ]
related = r.target.document.all_relations_that_doc(relationship, related)
return related
def related_that(self, relationship):
return list(set([x.source.docalias_set.get(name=x.source.name) for x in self.relations_that(relationship)]))
def all_related_that(self, relationship, related=None):
return list(set([x.source.docalias_set.get(name=x.source.name) for x in self.all_relations_that(relationship)]))
def related_that_doc(self, relationship):
return list(set([x.target for x in self.relations_that_doc(relationship)]))
def all_related_that_doc(self, relationship, related=None):
return list(set([x.target for x in self.all_relations_that_doc(relationship)]))
class Meta:
abstract = True
@ -324,57 +434,6 @@ class Document(DocumentInfo):
name = name.upper()
return name
def relations_that(self, relationship):
"""Return the related-document objects that describe a given relationship targeting self."""
if isinstance(relationship, str):
relationship = [ relationship ]
if isinstance(relationship, tuple):
relationship = list(relationship)
if not isinstance(relationship, list):
raise TypeError("Expected a string, tuple or list, received %s" % type(relationship))
return RelatedDocument.objects.filter(target__document=self, relationship__in=relationship).select_related('source')
def all_relations_that(self, relationship, related=None):
if not related:
related = []
rels = self.relations_that(relationship)
for r in rels:
if not r in related:
related += [ r ]
related = r.source.all_relations_that(relationship, related)
return related
def relations_that_doc(self, relationship):
"""Return the related-document objects that describe a given relationship from self to other documents."""
if isinstance(relationship, str):
relationship = [ relationship ]
if isinstance(relationship, tuple):
relationship = list(relationship)
if not isinstance(relationship, list):
raise TypeError("Expected a string, tuple or list, received %s" % type(relationship))
return RelatedDocument.objects.filter(source=self, relationship__in=relationship).select_related('target__document')
def all_relations_that_doc(self, relationship, related=None):
if not related:
related = []
rels = self.relations_that_doc(relationship)
for r in rels:
if not r in related:
related += [ r ]
related = r.target.document.all_relations_that_doc(relationship, related)
return related
def related_that(self, relationship):
return list(set([x.source.docalias_set.get(name=x.source.name) for x in self.relations_that(relationship)]))
def all_related_that(self, relationship, related=None):
return list(set([x.source.docalias_set.get(name=x.source.name) for x in self.all_relations_that(relationship)]))
def related_that_doc(self, relationship):
return list(set([x.target for x in self.relations_that_doc(relationship)]))
def all_related_that_doc(self, relationship, related=None):
return list(set([x.target for x in self.all_relations_that_doc(relationship)]))
def telechat_date(self, e=None):
if not e:
@ -427,53 +486,6 @@ class Document(DocumentInfo):
n = self.canonical_name()
return n[3:] if n.startswith("rfc") else None
def friendly_state(self):
""" Return a concise text description of the document's current state."""
state = self.get_state()
if not state:
return "Unknown state"
if self.type_id == 'draft':
iesg_state = self.get_state("draft-iesg")
iesg_state_summary = None
if iesg_state:
iesg_substate = [t for t in self.tags.all() if t.slug in IESG_SUBSTATE_TAGS]
# There really shouldn't be more than one tag in iesg_substate, but this will do something sort-of-sensible if there is
iesg_state_summary = iesg_state.name
if iesg_substate:
iesg_state_summary = iesg_state_summary + "::"+"::".join(tag.name for tag in iesg_substate)
if state.slug == "rfc":
return "RFC %s (%s)" % (self.rfc_number(), self.std_level)
elif state.slug == "repl":
rs = self.related_that("replaces")
if rs:
return mark_safe("Replaced by " + ", ".join("<a href=\"%s\">%s</a>" % (urlreverse('doc_view', kwargs=dict(name=alias.document)), alias.document) for alias in rs))
else:
return "Replaced"
elif state.slug == "active":
if iesg_state:
if iesg_state.slug == "dead":
# Many drafts in the draft-iesg "Dead" state are not dead
# in other state machines; they're just not currently under
# IESG processing. Show them as "I-D Exists (IESG: Dead)" instead...
return "I-D Exists (IESG: %s)" % iesg_state_summary
elif iesg_state.slug == "lc":
e = self.latest_event(LastCallDocEvent, type="sent_last_call")
if e:
return iesg_state_summary + " (ends %s)" % e.expires.date().isoformat()
return iesg_state_summary
else:
return "I-D Exists"
else:
if iesg_state and iesg_state.slug == "dead":
return state.name + " (IESG: %s)" % iesg_state_summary
# Expired/Withdrawn by Submitter/IETF
return state.name
else:
return state.name
def ipr(self,states=('posted','removed')):
"""Returns the IPR disclosures against this document (as a queryset over IprDocRel)."""
from ietf.ipr.models import IprDocRel
@ -547,6 +559,10 @@ class DocHistory(DocumentInfo):
def last_presented(self):
return self.doc.last_presented()
@property
def groupmilestone_set(self):
return self.doc.groupmilestone_set
class Meta:
verbose_name = "document history"
verbose_name_plural = "document histories"
@ -593,6 +609,8 @@ def save_document_in_history(doc):
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

View file

@ -9,7 +9,7 @@ from pyquery import PyQuery
from django.core.urlresolvers import reverse as urlreverse
from ietf.doc.models import ( Document, DocAlias, DocRelationshipName, RelatedDocument, State,
DocEvent, BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent )
DocEvent, BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, save_document_in_history )
from ietf.group.models import Group
from ietf.person.models import Person
from ietf.utils.mail import outbox
@ -205,6 +205,35 @@ class DocTestCase(TestCase):
r = self.client.get(urlreverse("doc_view", kwargs=dict(name="draft-xyz123")))
self.assertEqual(r.status_code, 404)
def test_document_primary_and_history_views(self):
make_test_data()
# Ensure primary views of both current and historic versions of documents works
for docname in ["draft-imaginary-independent-submission",
"conflict-review-imaginary-irtf-submission",
"status-change-imaginary-mid-review",
"charter-ietf-mars",
"agenda-42-mars",
"minutes-42-mars",
"slides-42-mars-1",
]:
doc = Document.objects.get(name=docname)
# give it some history
save_document_in_history(doc)
doc.rev="01"
doc.save()
r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name)))
self.assertEqual(r.status_code, 200)
self.assertTrue("%s-01"%docname in r.content)
r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name,rev="01")))
self.assertEqual(r.status_code, 302)
r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name,rev="00")))
self.assertEqual(r.status_code, 200)
self.assertTrue("%s-00"%docname in r.content)
def test_document_charter(self):
make_test_data()

View file

@ -263,12 +263,12 @@ def make_test_data():
)
# an independent submission before review
doc = Document.objects.create(name='draft-imaginary-independent-submission',type_id='draft')
doc = Document.objects.create(name='draft-imaginary-independent-submission',type_id='draft',rev='00')
doc.set_state(State.objects.get(used=True, type="draft", slug="active"))
DocAlias.objects.create(name=doc.name, document=doc)
# an irtf submission mid review
doc = Document.objects.create(name='draft-imaginary-irtf-submission', type_id='draft')
doc = Document.objects.create(name='draft-imaginary-irtf-submission', type_id='draft',rev='00')
docalias = DocAlias.objects.create(name=doc.name, document=doc)
doc.stream = StreamName.objects.get(slug='irtf')
doc.save()
@ -298,4 +298,13 @@ def make_test_data():
rfc_for_status_change_test_factory('draft-ietf-random-otherthing',9998,'inf')
rfc_for_status_change_test_factory('draft-was-never-issued',14,'unkn')
# Instances of the remaining document types
# (Except liaison, liai-att, and recording which the code in ietf.doc does not use...)
def other_doc_factory(type_id,name):
doc = Document.objects.create(type_id=type_id,name=name,rev='00',group=mars_wg)
DocAlias.objects.create(name=name,document=doc)
other_doc_factory('agenda','agenda-42-mars')
other_doc_factory('minutes','minutes-42-mars')
other_doc_factory('slides','slides-42-mars-1')
return draft