Adds pages to show what a given document refers to and what refers to it.

Fixes bug #1194
commit ready for merge
 - Legacy-Id: 6701
This commit is contained in:
Robert Sparks 2013-11-11 23:12:53 +00:00
parent a677a70df3
commit d19967ac93
9 changed files with 232 additions and 0 deletions

View file

@ -167,6 +167,34 @@ class RelatedDocument(models.Model):
def __unicode__(self):
return u"%s %s %s" % (self.source.name, self.relationship.name.lower(), self.target.name)
def is_downref(self):
if self.source.type.slug!='draft' or self.relationship.slug not in ['refnorm','refold','refunk']:
return None
if self.source.get_state().slug == 'rfc':
source_lvl = self.source.std_level.slug
else:
source_lvl = self.source.intended_std_level.slug
if source_lvl not in ['bcp','ps','ds','std']:
return None
if self.target.document.get_state().slug == 'rfc':
target_lvl = self.target.document.std_level.slug
else:
target_lvl = self.target.document.intended_std_level.slug
rank = { 'ps':1, 'ds':2, 'std':3, 'bcp':3 }
if ( target_lvl not in rank ) or ( rank[target_lvl] < rank[source_lvl] ):
if self.relationship.slug == 'refnorm' and target_lvl!='unkn':
return "Downref"
else:
return "Possible Downref"
return None
class DocumentAuthor(models.Model):
document = models.ForeignKey('Document')
author = models.ForeignKey(Email, help_text="Email address used by author for submission")

View file

@ -295,3 +295,21 @@ class TemplateTagTest(unittest.TestCase):
from ietf.doc.templatetags import ietf_filters
failures, tests = doctest.testmod(ietf_filters)
self.assertEqual(failures, 0)
class ReferencesTest(TestCase):
perma_fixtures = ['names']
def test_references(self):
make_test_data()
doc1 = Document.objects.get(name='draft-ietf-mars-test')
doc2 = DocAlias.objects.get(name='draft-imaginary-independent-submission')
RelatedDocument.objects.get_or_create(source=doc1,target=doc2,relationship=DocRelationshipName.objects.get(slug='refnorm'))
url = urlreverse('doc_references', kwargs=dict(name=doc1.name))
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
self.assertTrue(doc2.name in r.content)
url = urlreverse('doc_referenced_by', kwargs=dict(name=doc2.name))
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
self.assertTrue(doc1.name in r.content)

View file

@ -60,6 +60,8 @@ urlpatterns += patterns('',
url(r'^(?P<name>[A-Za-z0-9._+-]+)/history/$', views_doc.document_history, name="doc_history"),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/writeup/$', views_doc.document_writeup, name="doc_writeup"),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/shepherdwriteup/$', views_doc.document_shepherd_writeup, name="doc_shepherd_writeup"),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/references/$', views_doc.document_references, name="doc_references"),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/referencedby/$', views_doc.document_referenced_by, name="doc_referenced_by"),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot/(?P<ballot_id>[0-9]+)/position/$', views_ballot.edit_position),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot/(?P<ballot_id>[0-9]+)/emailposition/$', views_ballot.send_ballot_comment, name='doc_send_ballot_comment'),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot/(?P<ballot_id>[0-9]+)/$', views_doc.document_ballot, name="doc_ballot"),
@ -97,6 +99,8 @@ urlpatterns += patterns('',
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/makelastcall/$', views_ballot.make_last_call, name='doc_make_last_call'),
url(r'^help/state/(?P<type>[\w-]+)/$', 'ietf.doc.views_help.state_help', name="state_help"),
url(r'^help/relationships/$', 'ietf.doc.views_help.relationship_help', name="relationship_help"),
url(r'^help/relationships/(?P<subset>\w+)/$', 'ietf.doc.views_help.relationship_help', name="relationship_subset_help"),
(r'^(?P<name>charter-[A-Za-z0-9._+-]+)/', include('ietf.wgcharter.urls')),
(r'^(?P<name>[A-Za-z0-9._+-]+)/conflict-review/', include('ietf.doc.urls_conflict_review')),

View file

@ -609,6 +609,29 @@ def document_shepherd_writeup(request, name):
),
context_instance=RequestContext(request))
def document_references(request, name):
doc = get_object_or_404(Document,docalias__name=name)
refs = doc.relations_that_doc(['refnorm','refinfo','refunk','refold'])
return render_to_response("doc/document_references.html",dict(doc=doc,refs=sorted(refs,key=lambda x:x.target.name),),context_instance=RequestContext(request))
def document_referenced_by(request, name):
doc = get_object_or_404(Document,docalias__name=name)
refs = doc.relations_that(['refnorm','refinfo','refunk','refold']).filter(source__states__type__slug='draft',source__states__slug__in=['rfc','active'])
full = ( request.GET.get('full') != None )
numdocs = refs.count()
if not full and numdocs>250:
refs=refs[:250]
else:
numdocs=None
refs=sorted(refs,key=lambda x:(['refnorm','refinfo','refunk','refold'].index(x.relationship.slug),x.source.canonical_name()))
return render_to_response("doc/document_referenced_by.html",
dict(alias_name=name,
doc=doc,
numdocs=numdocs,
refs=refs,
),
context_instance=RequestContext(request))
def document_ballot_content(request, doc, ballot_id, editable=True):
"""Render HTML string with content of ballot page."""
all_ballots = list(BallotDocEvent.objects.filter(doc=doc, type="created_ballot").order_by("time"))

View file

@ -1,6 +1,7 @@
from django import forms
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.http import Http404
from ietf.doc.models import *
@ -44,3 +45,17 @@ def state_help(request, type):
"tags": tags,
},
context_instance=RequestContext(request))
def relationship_help(request,subset=None):
subsets = { "reference": ['refnorm','refinfo','refunk','refold'],
"status" : ['tops','tois','tohist','toinf','tobcp','toexp'],
}
if subset and subset not in subsets:
raise Http404()
rels = DocRelationshipName.objects.filter(used=True)
if subset:
rels = rels.filter(slug__in=subsets[subset])
return render_to_response("doc/relationship_help.html", {
"relations": rels
},
context_instance=RequestContext(request))

View file

@ -228,6 +228,8 @@
<a href="mailto:{{ doc.name }}@tools.ietf.org?subject=Mail%20regarding%20{{ doc.name }}" rel="nofollow">Email Authors</a>
| <a href="{% url ipr_search %}?option=document_search&amp;id={{ doc.name }}" rel="nofollow">IPR Disclosures{% if doc.related_ipr %} ({{doc.related_ipr.count}}){% endif %}</a>
| <a href="http://www.fenron.net/~fenner/ietf/deps/index.cgi?dep={{ name }}" rel="nofollow">Dependencies to this document</a>
| <a href="{% url doc_references doc.canonical_name %}" rel="nofollow">References</a>
| <a href="{% url doc_referenced_by doc.canonical_name %}" rel="nofollow">Referenced By</a>
| <a href="http://tools.ietf.org/idnits?url=http://tools.ietf.org/id/{{ doc.filename_with_rev }}" rel="nofollow" target="_blank">Check nits</a>
| <a href="/feed/document-changes/{{ name }}/">History feed</a>
| <a href="http://www.google.com/search?as_q={{ doc.name }}&as_sitesearch={{ search_archive }}" rel="nofollow" target="_blank">Search Mailing Lists</a>

View file

@ -0,0 +1,59 @@
{% extends "base.html" %}
{% block title %}
References to {{alias_name}}
{% endblock %}
{% block morecss %}
.referencetable {margin-top:1em;}
.referenctable .references {width:100%;}
.referencetable .references .reference .source {width:30%;}
.referencetable .references .reference .title {width:35%;}
.referencetable .references .reference .level {width:15%;}
.referencetable .references .reference .reftype {width:15%;}
.referencetable .references .reference .downref {width:5%;}
.reflinks {font-size:80%;float:right;}
.showmore {margin-top:1em;}
{% endblock %}
{% block content %}
<h1>References to {{alias_name}}</h1>
<div class="info-message-warning">
This is an experimental product. These dependencies are extracted using heuristics looking for strings with particular prefixes. Notably, this means that references to I-Ds by title only are not reflected here. If it's really important, please inspect the documents' references sections directly.
</div>
<div>
Showing RFCs and active Internet-Drafts, sorted by <a href="{% url relationship_subset_help subset='reference' %}">reference type</a>, then document name.
</div>
{% if numdocs %}
<div class="showmore">
Results restricted to the first 250 of {{ numdocs }} documents. <a href="?full=True">Show All</a>
</div>
{% endif %}
<div class="referencetable">
<table class="references ietf-table">
<tr><th>Document</th><th>Title</th><th>Status</th><th>Reference Type</th><th>Downref</th></tr>
{% for ref in refs %}
<tr class="reference {% cycle 'evenrow' 'oddrow' %}">
<td class="source">
{% with ref.source.canonical_name as name %}
<a href="{% url doc_view name=name %}">{{name}}</a> {% if ref.target.name != alias_name %}(as {{ref.target.name}}){% endif %} <span class="reflinks">(<a href="{% url doc_references name %}">refs</a>, <a href="{% url doc_referenced_by name %}">refby</a>)</span>
{% endwith %}
</td>
<td class="title">
{{ref.source.title}}
</td>
<td class="level">
{% ifequal ref.source.get_state.slug 'rfc' %}
{% with ref.source.std_level as lvl %}{% if lvl %}{{lvl}}{% endif %}{%endwith%}
{% else %}
{% with ref.source.intended_std_level as lvl %}{% if lvl %}{{lvl}}{% endif %}{%endwith%}
{% endifequal %}
</td>
<td class="reftype">
{{ref.relationship.name}}
</td>
<td class="downref">
{{ref.is_downref|default:''}}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}

View file

@ -0,0 +1,54 @@
{% extends "base.html" %}
{% block title %}
References from {{doc.canonical_name}}
{% endblock %}
{% block morecss %}
.referencetable {margin-top:1em;}
.referenctable .references {width:100%;}
.referencetable .references .reference .source {width:30%;}
.referencetable .references .reference .title {width:35%;}
.referencetable .references .reference .level {width:15%;}
.referencetable .references .reference .reftype {width:15%;}
.referencetable .references .reference .downref {width:5%;}
.reflinks {font-size:80%;float:right;}
{% endblock %}
{% block content %}
<h1>References from {{doc.canonical_name}}</h1>
<div class="info-message-warning">
This is an experimental product. These dependencies are extracted using heuristics looking for strings with particular prefixes. Notably, this means that references to I-Ds by title only are not reflected here. If it's really important, please inspect the documents' references sections directly.
</div>
<div>
Sorted by document name.
<a href="{% url relationship_subset_help subset='reference' %}">Reference type help</a>
</div>
<div class="referencetable">
<table class="references ietf-table">
<tr><th>Document</th><th>Title</th><th>Status</th><th>Reference Type</th><th>Downref</th></tr>
{% for ref in refs %}
<tr class="reference {% cycle 'evenrow' 'oddrow' %}">
<td class="target">
{% with ref.target.name as name %}
<a href="{% url doc_view name=name %}">{{name}}</a> <span class="reflinks">(<a href="{% url doc_references name %}">refs</a>, <a href="{% url doc_referenced_by name %}">refby</a>)</span>
{% endwith %}
</td>
<td class="title">
{{ref.target.document.title}}
</td>
<td class="level">
{% ifequal ref.target.document.get_state.slug 'rfc' %}
{% with ref.target.document.std_level as lvl %}{% if lvl %}{{lvl}}{% endif %}{%endwith%}
{% else %}
{% with ref.target.document.intended_std_level as lvl %}{% if lvl %}{{lvl}}{% endif %}{%endwith%}
{% endifequal %}
</td>
<td class="reftype">
{{ref.relationship.name}}
</td>
<td class="downref">
{{ref.is_downref|default:''}}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}

View file

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}Document Relationships{% endblock %}
{% block morecss %}
.ietf-table .name { white-space: nowrap; padding-right: 1em; }
.ietf-table .desc { max-width: 35em; }
{% endblock %}
{% block content %}
<h1>Document Relationships</h1>
<table class="ietf-table">
<tr>
<th>Relationship</th>
<th>Description</th>
<th>Inverse Relationship</th>
</tr>
{% for rel in relations %}
<tr id="{{ rel.slug }}" class="{{ forloop.counter|divisibleby:2|yesno:"evenrow,oddrow" }}">
<td class="name">{{ rel.name }}</td>
<td class="desc">{{ rel.desc|linebreaksbr }}</td>
<td class="revname">{{ rel.revname }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}