Adds dependency graphs for drafts belonging to a group.
Removes links to Bill's dependency tools. Fixes bug #536. Commit ready to merge. - Legacy-Id: 7445
This commit is contained in:
parent
a0311b7ee8
commit
74e4ff72e9
|
@ -177,10 +177,13 @@ class RelatedDocument(models.Model):
|
|||
if self.source.get_state().slug == 'rfc':
|
||||
source_lvl = self.source.std_level.slug
|
||||
elif self.source.intended_std_level:
|
||||
source_lvl = self.source.intended_std_level.slug
|
||||
source_lvl = self.source.intended_std_level and self.source.intended_std_level.slug
|
||||
else:
|
||||
source_lvl = None
|
||||
|
||||
if not source_lvl:
|
||||
return None
|
||||
|
||||
if source_lvl not in ['bcp','ps','ds','std']:
|
||||
return None
|
||||
|
||||
|
|
|
@ -366,6 +366,10 @@ IDSUBMIT_MAX_DAILY_SAME_GROUP_SIZE = 450 # in MB
|
|||
IDSUBMIT_MAX_DAILY_SUBMISSIONS = 1000
|
||||
IDSUBMIT_MAX_DAILY_SUBMISSIONS_SIZE = 2000 # in MB
|
||||
|
||||
DOT_BINARY = '/usr/bin/dot'
|
||||
UNFLATTEN_BINARY= '/usr/bin/unflatten'
|
||||
PS2PDF_BINARY = '/usr/bin/ps2pdf'
|
||||
|
||||
# Account settings
|
||||
DAYS_TO_EXPIRE_REGISTRATION_LINK = 3
|
||||
HTPASSWD_COMMAND = "/usr/bin/htpasswd2"
|
||||
|
|
|
@ -232,7 +232,6 @@
|
|||
<div class="links">
|
||||
<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&id={{ doc.name }}" rel="nofollow">IPR Disclosures{% if doc.related_ipr %} ({{doc.related_ipr|length}}){% 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://www.ietf.org/tools/idnits?url=http://www.ietf.org/archive/id/{{ doc.filename_with_rev }}" rel="nofollow" target="_blank">Check nits</a>
|
||||
|
|
88
ietf/templates/wginfo/dot.txt
Normal file
88
ietf/templates/wginfo/dot.txt
Normal file
|
@ -0,0 +1,88 @@
|
|||
{% load mail_filters %}{% autoescape off %}
|
||||
digraph draftdeps {
|
||||
graph [fontame=Helvetica];
|
||||
node [fontname=Helvetica];
|
||||
edge [fontname=Helvetica];
|
||||
subgraph cluster_key {
|
||||
graph [label=Key,
|
||||
rankdir=LR,
|
||||
margin=.5,
|
||||
fontname=Helvetica
|
||||
];
|
||||
subgraph key_a {
|
||||
graph [rank=same];
|
||||
key_colors [color=white,
|
||||
fontcolor=black,
|
||||
label="Colors in\nthis row"];
|
||||
key_wgdoc [color="#0AFE47",
|
||||
label="Product of\nthis WG",
|
||||
style=filled,
|
||||
wg=this];
|
||||
key_otherwgdoc [color="#9999FF",
|
||||
label="Product of\nother WG",
|
||||
style=filled,
|
||||
wg=blort];
|
||||
key_individual [color="#FF800D",
|
||||
label="Individual\nsubmission",
|
||||
style=filled,
|
||||
wg=individual];
|
||||
}
|
||||
subgraph key_b {
|
||||
graph [rank=same];
|
||||
key_shapes [color=white,
|
||||
fontcolor=black,
|
||||
label="Shapes in\nthis row"];
|
||||
key_active [color="#9999FF",
|
||||
label="Active\ndocument",
|
||||
style=filled];
|
||||
key_iesg [color="#9999FF",
|
||||
label="IESG or\nRFC Queue",
|
||||
shape=parallelogram,
|
||||
style=filled];
|
||||
key_rfc [color="#9999FF",
|
||||
label="RFC\nPublished",
|
||||
shape=box,
|
||||
style=filled];
|
||||
key_expired [color="#9999FF",
|
||||
label="Expired!",
|
||||
peripheries=3,
|
||||
shape=house,
|
||||
style=solid];
|
||||
key_replaced [color="#9999FF",
|
||||
label="Replaced",
|
||||
peripheries=3,
|
||||
shape=ellipse];
|
||||
}
|
||||
key_colors -> key_shapes [color=white,
|
||||
fontcolor=black,
|
||||
label="Line\ncolor\nreflects\nReference\ntype"];
|
||||
key_wgdoc -> key_active [color=orange,
|
||||
label="Orange link:\nUnsplit"];
|
||||
key_otherwgdoc -> key_active [color=green,
|
||||
label="Green link:\nInformative"];
|
||||
key_individual -> key_iesg [color=blue,
|
||||
label="Blue link:\nNormative"];
|
||||
key_otherwgdoc -> key_expired [label="Black link:\nUnknown",
|
||||
style=dashed];
|
||||
key_wgdoc -> key_rfc [color=red,
|
||||
label="Red link:\nDownref!",
|
||||
arrowhead=normalnormal];
|
||||
key_individual -> key_replaced [color=pink,
|
||||
label="Pink link:\nReplaces",
|
||||
style=dashed,
|
||||
arrowhead=diamond];
|
||||
}
|
||||
|
||||
{% for node in nodes %}
|
||||
{{ node.nodename }} [ status="{{ node.get_state.slug }}",
|
||||
wg="{{ node.group.acronym }}",{% for key,value in node.styles.items %}
|
||||
{{ key }}={{ value }},{% endfor %}
|
||||
];
|
||||
{% endfor %}
|
||||
|
||||
{% for edge in edges%}
|
||||
{{ edge.sourcename }} -> {{ edge.targetname }} {% if edge.styles %}[ {% for key,value in edge.styles.items %}{{ key }}={{ value }}{% if not forloop.last %}, {% endif %}{% endfor %} ] {% endif %};{% endfor %}
|
||||
|
||||
|
||||
}
|
||||
{% endautoescape %}
|
|
@ -69,6 +69,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
<a {% if selected == "documents" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.group_documents" acronym=group.acronym %}"{% endif %}>Documents</a> |
|
||||
<a {% if selected == "charter" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.group_charter" acronym=group.acronym %}"{% endif %}>Charter</a> |
|
||||
<a {% if selected == "history" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.history" acronym=group.acronym %}"{% endif %}>History</a>
|
||||
| <a href="{% url 'ietf.wginfo.views.dependencies_pdf' acronym=group.acronym %}">Dependency Graph</a>
|
||||
{% if group.list_archive|startswith:"http:" or group.list_archive|startswith:"https:" or group.list_archive|startswith:"ftp:" %}
|
||||
| <a href="{{ group.list_archive }}">List Archive »</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -23,6 +23,8 @@ urlpatterns = patterns('',
|
|||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/charter/$', views.group_charter, None, 'group_charter'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/init-charter/', edit.submit_initial_charter, None, "wg_init_charter"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/history/$', views.history),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/deps/dot/$', views.dependencies_dot),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/deps/pdf/$', views.dependencies_pdf),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/edit/$', edit.edit, {'action': "edit"}, "group_edit"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/conclude/$', edit.conclude, None, "wg_conclude"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "wg_edit_milestones"),
|
||||
|
|
|
@ -35,10 +35,12 @@
|
|||
import itertools
|
||||
|
||||
from django.shortcuts import get_object_or_404, render_to_response
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import RequestContext
|
||||
from django.http import HttpResponse
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.db.models import Q
|
||||
|
||||
from ietf.doc.views_search import SearchForm, retrieve_search_results
|
||||
from ietf.group.models import Group, GroupURL, Role
|
||||
|
@ -48,6 +50,10 @@ from ietf.group.utils import get_charter_text
|
|||
from ietf.doc.templatetags.ietf_filters import clean_whitespace
|
||||
from ietf.ietfauth.utils import has_role
|
||||
|
||||
from ietf.utils.pipe import pipe
|
||||
from tempfile import mkstemp
|
||||
import os
|
||||
|
||||
def roles(group, role_name):
|
||||
return Role.objects.filter(group=group, name=role_name).select_related("email", "person")
|
||||
|
||||
|
@ -286,3 +292,163 @@ def history(request, acronym):
|
|||
construct_group_menu_context(request, group, "history", {
|
||||
"events": events,
|
||||
}), RequestContext(request))
|
||||
|
||||
|
||||
def nodename(name):
|
||||
return name.replace('-','_')
|
||||
|
||||
class Edge(object):
|
||||
def __init__(self,relateddocument):
|
||||
self.relateddocument=relateddocument
|
||||
|
||||
def __hash__(self):
|
||||
return hash("|".join([str(hash(nodename(self.relateddocument.source.name))),
|
||||
str(hash(nodename(self.relateddocument.target.document.name))),
|
||||
self.relateddocument.relationship.slug]))
|
||||
|
||||
def __eq__(self,other):
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
def sourcename(self):
|
||||
return nodename(self.relateddocument.source.name)
|
||||
|
||||
def targetname(self):
|
||||
return nodename(self.relateddocument.target.document.name)
|
||||
|
||||
def styles(self):
|
||||
|
||||
# Note that the old style=dotted, color=red styling is never used
|
||||
|
||||
if self.relateddocument.is_downref():
|
||||
return { 'color':'red','arrowhead':'normalnormal' }
|
||||
else:
|
||||
styles = { 'refnorm' : { 'color':'blue' },
|
||||
'refinfo' : { 'color':'green' },
|
||||
'refold' : { 'color':'orange' },
|
||||
'refunk' : { 'style':'dashed' },
|
||||
'replaces': { 'color':'pink', 'style':'dashed', 'arrowhead':'diamond' },
|
||||
}
|
||||
return styles[self.relateddocument.relationship.slug]
|
||||
|
||||
def get_node_styles(node,group):
|
||||
|
||||
styles=dict()
|
||||
|
||||
# Shape and style (note that old diamond shape is never used
|
||||
|
||||
styles['style'] = 'filled'
|
||||
|
||||
if node.get_state('draft').slug == 'rfc':
|
||||
styles['shape'] = 'box'
|
||||
elif node.get_state('draft-iesg') and not node.get_state('draft-iesg').slug in ['watching','dead']:
|
||||
styles['shape'] = 'parallelogram'
|
||||
elif node.get_state('draft').slug == 'expired':
|
||||
styles['shape'] = 'house'
|
||||
styles['style'] ='solid'
|
||||
styles['peripheries'] = 3
|
||||
elif node.get_state('draft').slug == 'repl':
|
||||
styles['shape'] = 'ellipse'
|
||||
styles['style'] ='solid'
|
||||
styles['peripheries'] = 3
|
||||
else:
|
||||
pass # quieter form of styles['shape'] = 'ellipse'
|
||||
|
||||
# Color (note that the old 'Flat out red' is never used
|
||||
if node.group.acronym == 'none':
|
||||
styles['color'] = '"#FF800D"' # orangeish
|
||||
elif node.group == group:
|
||||
styles['color'] = '"#0AFE47"' # greenish
|
||||
else:
|
||||
styles['color'] = '"#9999FF"' # blueish
|
||||
|
||||
# Label
|
||||
label = node.name
|
||||
if label.startswith('draft-'):
|
||||
if label.startswith('draft-ietf-'):
|
||||
label=label[11:]
|
||||
else:
|
||||
label=label[6:]
|
||||
try:
|
||||
t=label.index('-')
|
||||
label="%s\\n%s" % (label[:t],label[t+1:])
|
||||
except:
|
||||
pass
|
||||
if node.group.acronym != 'none' and node.group != group:
|
||||
label = "(%s) %s"%(node.group.acronym,label)
|
||||
if node.get_state('draft').slug == 'rfc':
|
||||
label = "%s\\n(%s)"%(label,node.canonical_name())
|
||||
styles['label'] = '"%s"'%label
|
||||
|
||||
return styles
|
||||
|
||||
def make_dot(group):
|
||||
|
||||
references = Q(source__group=group,source__type='draft',relationship__slug__startswith='ref')
|
||||
both_rfcs = Q(source__states__slug='rfc',target__document__states__slug='rfc')
|
||||
inactive = Q(source__states__slug__in=['expired','repl'])
|
||||
attractor = Q(target__name__in=['rfc5000','rfc5741'])
|
||||
removed = Q(source__states__slug__in=['auth-rm','ietf-rm'])
|
||||
relations = RelatedDocument.objects.filter(references).exclude(both_rfcs).exclude(inactive).exclude(attractor).exclude(removed)
|
||||
|
||||
edges = set()
|
||||
for x in relations:
|
||||
target_state = x.target.document.get_state_slug('draft')
|
||||
if target_state!='rfc' or x.is_downref():
|
||||
edges.add(Edge(x))
|
||||
|
||||
replacements = RelatedDocument.objects.filter(relationship__slug='replaces',target__document__in=[x.relateddocument.target.document for x in edges])
|
||||
|
||||
for x in replacements:
|
||||
edges.add(Edge(x))
|
||||
|
||||
nodes = set([x.relateddocument.source for x in edges]).union([x.relateddocument.target.document for x in edges])
|
||||
|
||||
for node in nodes:
|
||||
node.nodename=nodename(node.name)
|
||||
node.styles = get_node_styles(node,group)
|
||||
|
||||
return render_to_string('wginfo/dot.txt',
|
||||
dict( nodes=nodes, edges=edges )
|
||||
)
|
||||
|
||||
def dependencies_dot(request, acronym):
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
||||
return HttpResponse(make_dot(group),
|
||||
content_type='text/plain; charset=UTF-8'
|
||||
)
|
||||
|
||||
def dependencies_pdf(request, acronym):
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
||||
dothandle,dotname = mkstemp()
|
||||
os.close(dothandle)
|
||||
dotfile = open(dotname,"w")
|
||||
dotfile.write(make_dot(group))
|
||||
dotfile.close()
|
||||
|
||||
unflathandle,unflatname = mkstemp()
|
||||
os.close(unflathandle)
|
||||
|
||||
pshandle,psname = mkstemp()
|
||||
os.close(pshandle)
|
||||
|
||||
pdfhandle,pdfname = mkstemp()
|
||||
os.close(pdfhandle)
|
||||
|
||||
pipe("%s -f -l 10 -o %s %s" % (settings.UNFLATTEN_BINARY,unflatname,dotname))
|
||||
pipe("%s -Tps -Gsize=10.5,8.0 -Gmargin=0.25 -Gratio=auto -Grotate=90 -o %s %s" % (settings.DOT_BINARY,psname,unflatname))
|
||||
pipe("%s %s %s" % (settings.PS2PDF_BINARY,psname,pdfname))
|
||||
|
||||
pdfhandle = open(pdfname,"r")
|
||||
pdf = pdfhandle.read()
|
||||
pdfhandle.close()
|
||||
|
||||
os.unlink(pdfname)
|
||||
os.unlink(psname)
|
||||
os.unlink(unflatname)
|
||||
os.unlink(dotname)
|
||||
|
||||
return HttpResponse(pdf, content_type='application/pdf')
|
||||
|
|
Loading…
Reference in a new issue