Skip to content

Commit 641d92c

Browse files
committed
Add many-to-many field with formal languages to Document and add formal
language statistics - Legacy-Id: 12658
1 parent aebfe44 commit 641d92c

9 files changed

Lines changed: 179 additions & 12 deletions

File tree

ietf/doc/migrations/0020_auto_20170112_0753.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
class Migration(migrations.Migration):
88

99
dependencies = [
10+
('name', '0017_formallanguagename'),
1011
('doc', '0019_auto_20161207_1036'),
1112
]
1213

@@ -21,4 +22,14 @@ class Migration(migrations.Migration):
2122
name='words',
2223
field=models.IntegerField(null=True, blank=True),
2324
),
25+
migrations.AddField(
26+
model_name='dochistory',
27+
name='formal_languages',
28+
field=models.ManyToManyField(help_text=b'Formal languages used in document', to='name.FormalLanguageName', blank=True),
29+
),
30+
migrations.AddField(
31+
model_name='document',
32+
name='formal_languages',
33+
field=models.ManyToManyField(help_text=b'Formal languages used in document', to='name.FormalLanguageName', blank=True),
34+
),
2435
]

ietf/doc/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from ietf.group.models import Group
1717
from ietf.name.models import ( DocTypeName, DocTagName, StreamName, IntendedStdLevelName, StdLevelName,
18-
DocRelationshipName, DocReminderTypeName, BallotPositionName, ReviewRequestStateName )
18+
DocRelationshipName, DocReminderTypeName, BallotPositionName, ReviewRequestStateName, FormalLanguageName )
1919
from ietf.person.models import Email, Person
2020
from ietf.utils.admin import admin_link
2121

@@ -76,6 +76,7 @@ class DocumentInfo(models.Model):
7676
rev = models.CharField(verbose_name="revision", max_length=16, blank=True)
7777
pages = models.IntegerField(blank=True, null=True)
7878
words = models.IntegerField(blank=True, null=True)
79+
formal_languages = models.ManyToManyField(FormalLanguageName, blank=True, help_text="Formal languages used in document")
7980
order = models.IntegerField(default=1, blank=True) # This is probably obviated by SessionPresentaion.order
8081
intended_std_level = models.ForeignKey(IntendedStdLevelName, verbose_name="Intended standardization level", blank=True, null=True)
8182
std_level = models.ForeignKey(StdLevelName, verbose_name="Standardization level", blank=True, null=True)

ietf/name/admin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from ietf.name.models import (
44
BallotPositionName, ConstraintName, DBTemplateTypeName, DocRelationshipName,
55
DocReminderTypeName, DocTagName, DocTypeName, DraftSubmissionStateName,
6-
FeedbackTypeName, GroupMilestoneStateName, GroupStateName, GroupTypeName,
6+
FeedbackTypeName, FormalLanguageName, GroupMilestoneStateName, GroupStateName, GroupTypeName,
77
IntendedStdLevelName, IprDisclosureStateName, IprEventTypeName, IprLicenseTypeName,
88
LiaisonStatementEventTypeName, LiaisonStatementPurposeName, LiaisonStatementState,
99
LiaisonStatementTagName, MeetingTypeName, NomineePositionStateName,
@@ -32,6 +32,7 @@ class GroupTypeNameAdmin(NameAdmin):
3232
admin.site.register(DocReminderTypeName, NameAdmin)
3333
admin.site.register(DocTagName, NameAdmin)
3434
admin.site.register(DraftSubmissionStateName, NameAdmin)
35+
admin.site.register(FormalLanguageName, NameAdmin)
3536
admin.site.register(FeedbackTypeName, NameAdmin)
3637
admin.site.register(GroupMilestoneStateName, NameAdmin)
3738
admin.site.register(GroupStateName, NameAdmin)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('name', '0016_auto_20161013_1010'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='FormalLanguageName',
16+
fields=[
17+
('slug', models.CharField(max_length=32, serialize=False, primary_key=True)),
18+
('name', models.CharField(max_length=255)),
19+
('desc', models.TextField(blank=True)),
20+
('used', models.BooleanField(default=True)),
21+
('order', models.IntegerField(default=0)),
22+
],
23+
options={
24+
'ordering': ['order', 'name'],
25+
'abstract': False,
26+
},
27+
),
28+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import migrations
5+
6+
def insert_initial_formal_language_names(apps, schema_editor):
7+
FormalLanguageName = apps.get_model("name", "FormalLanguageName")
8+
FormalLanguageName.objects.get_or_create(slug="abnf", name="ABNF", desc="Augmented Backus-Naur Form", order=1)
9+
FormalLanguageName.objects.get_or_create(slug="asn1", name="ASN.1", desc="Abstract Syntax Notation One", order=2)
10+
FormalLanguageName.objects.get_or_create(slug="cbor", name="CBOR", desc="Concise Binary Object Representation", order=3)
11+
FormalLanguageName.objects.get_or_create(slug="ccode", name="C Code", desc="Code in the C Programming Language", order=4)
12+
FormalLanguageName.objects.get_or_create(slug="json", name="JSON", desc="Javascript Object Notation", order=5)
13+
FormalLanguageName.objects.get_or_create(slug="xml", name="XML", desc="Extensible Markup Language", order=6)
14+
15+
def noop(apps, schema_editor):
16+
pass
17+
18+
class Migration(migrations.Migration):
19+
20+
dependencies = [
21+
('name', '0017_formallanguagename'),
22+
]
23+
24+
operations = [
25+
migrations.RunPython(insert_initial_formal_language_names, noop)
26+
]

ietf/name/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class StdLevelName(NameModel):
4646
class IntendedStdLevelName(NameModel):
4747
"""Proposed Standard, (Draft Standard), Internet Standard, Experimental,
4848
Informational, Best Current Practice, Historic, ..."""
49+
class FormalLanguageName(NameModel):
50+
"""ABNF, ASN.1, C code, CBOR, JSON, XML, ..."""
4951
class DocReminderTypeName(NameModel):
5052
"Stream state"
5153
class BallotPositionName(NameModel):

ietf/stats/backfill_data.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,24 @@
1414
from django.conf import settings
1515

1616
from ietf.doc.models import Document
17+
from ietf.name.models import FormalLanguageName
1718
from ietf.utils.draft import Draft
1819

1920
parser = argparse.ArgumentParser()
2021
parser.add_argument("--document", help="specific document name")
2122
parser.add_argument("--words", action="store_true", help="fill in word count")
23+
parser.add_argument("--formlang", action="store_true", help="fill in formal languages")
2224
args = parser.parse_args()
2325

26+
formal_language_dict = { l.pk: l for l in FormalLanguageName.objects.all() }
27+
2428

2529
docs_qs = Document.objects.filter(type="draft")
2630

2731
if args.document:
2832
docs_qs = docs_qs.filter(docalias__name=args.document)
2933

30-
for doc in docs_qs.prefetch_related("docalias_set"):
34+
for doc in docs_qs.prefetch_related("docalias_set", "formal_languages"):
3135
canonical_name = doc.name
3236
for n in doc.docalias_set.all():
3337
if n.name.startswith("rfc"):
@@ -45,14 +49,33 @@
4549
with open(path, 'r') as f:
4650
d = Draft(f.read(), path)
4751

52+
updated = False
53+
4854
updates = {}
4955

5056
if args.words:
5157
words = d.get_wordcount()
5258
if words != doc.words:
5359
updates["words"] = words
5460

61+
if args.formlang:
62+
langs = d.get_formal_languages()
63+
64+
new_formal_languages = set(formal_language_dict[l] for l in langs)
65+
old_formal_languages = set(doc.formal_languages.all())
66+
67+
if new_formal_languages != old_formal_languages:
68+
for l in new_formal_languages - old_formal_languages:
69+
doc.formal_languages.add(l)
70+
updated = True
71+
for l in old_formal_languages - new_formal_languages:
72+
doc.formal_languages.remove(l)
73+
updated = True
74+
5575
if updates:
5676
Document.objects.filter(pk=doc.pk).update(**updates)
77+
updated = True
78+
79+
if updated:
5780
print "updated", canonical_name
5881

ietf/stats/views.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ def generate_canonical_names(docalias_qs):
134134
stats_title = ""
135135
bin_size = 1
136136

137+
total_docs = docalias_qs.count()
138+
137139
if stats_type == "authors":
138140
stats_title = "Number of authors for each {}".format(doc_label)
139141

@@ -142,8 +144,6 @@ def generate_canonical_names(docalias_qs):
142144
for name, author_count in generate_canonical_names(docalias_qs.values_list("name").annotate(Count("document__authors"))):
143145
bins[author_count].append(name)
144146

145-
total_docs = sum(len(names) for author_count, names in bins.iteritems())
146-
147147
series_data = []
148148
for author_count, names in sorted(bins.iteritems(), key=lambda t: t[0]):
149149
percentage = len(names) * 100.0 / total_docs
@@ -163,8 +163,6 @@ def generate_canonical_names(docalias_qs):
163163
for name, pages in generate_canonical_names(docalias_qs.values_list("name", "document__pages")):
164164
bins[pages].append(name)
165165

166-
total_docs = sum(len(names) for pages, names in bins.iteritems())
167-
168166
series_data = []
169167
for pages, names in sorted(bins.iteritems(), key=lambda t: t[0]):
170168
percentage = len(names) * 100.0 / total_docs
@@ -187,8 +185,6 @@ def generate_canonical_names(docalias_qs):
187185
for name, words in generate_canonical_names(docalias_qs.values_list("name", "document__words")):
188186
bins[put_into_bin(words, bin_size)].append(name)
189187

190-
total_docs = sum(len(names) for words, names in bins.iteritems())
191-
192188
series_data = []
193189
for (value, words), names in sorted(bins.iteritems(), key=lambda t: t[0][0]):
194190
percentage = len(names) * 100.0 / total_docs
@@ -203,7 +199,7 @@ def generate_canonical_names(docalias_qs):
203199
})
204200

205201
elif stats_type == "format":
206-
stats_title = "Formats for each {}".format(doc_label)
202+
stats_title = "Submission formats for each {}".format(doc_label)
207203

208204
bins = defaultdict(list)
209205

@@ -244,8 +240,6 @@ def generate_canonical_names(docalias_qs):
244240
if canonical_name:
245241
bins[ext.upper()].append(canonical_name)
246242

247-
total_docs = sum(len(names) for fmt, names in bins.iteritems())
248-
249243
series_data = []
250244
for fmt, names in sorted(bins.iteritems(), key=lambda t: t[0]):
251245
percentage = len(names) * 100.0 / total_docs
@@ -258,6 +252,27 @@ def generate_canonical_names(docalias_qs):
258252
"animation": False,
259253
})
260254

255+
elif stats_type == "formlang":
256+
stats_title = "Formal languages used for each {}".format(doc_label)
257+
258+
bins = defaultdict(list)
259+
260+
for name, formal_language_name in generate_canonical_names(docalias_qs.values_list("name", "document__formal_languages__name")):
261+
bins[formal_language_name].append(name)
262+
263+
series_data = []
264+
for formal_language, names in sorted(bins.iteritems(), key=lambda t: t[0]):
265+
percentage = len(names) * 100.0 / total_docs
266+
if formal_language is not None:
267+
series_data.append((formal_language, len(names)))
268+
table_data.append((formal_language, percentage, names))
269+
270+
chart_data.append({
271+
"data": series_data,
272+
"animation": False,
273+
})
274+
275+
261276
return render(request, "stats/document_stats.html", {
262277
"chart_data": mark_safe(json.dumps(chart_data)),
263278
"table_data": table_data,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<h3>{{ stats_title }}</h3>
2+
3+
<div id="chart"></div>
4+
5+
<script>
6+
var chartConf = {
7+
chart: {
8+
type: 'column'
9+
},
10+
title: {
11+
text: '{{ stats_title|escapejs }}'
12+
},
13+
xAxis: {
14+
type: "category",
15+
title: {
16+
text: 'Formal language'
17+
}
18+
},
19+
yAxis: {
20+
title: {
21+
text: 'Number of {{ doc_label }}s'
22+
}
23+
},
24+
tooltip: {
25+
formatter: function () {
26+
console.log(this);
27+
var s = '<b>' + this.points[0].key + '</b>';
28+
29+
$.each(this.points, function () {
30+
s += '<br/>' + chartConf.yAxis.title.text + ': ' + this.y;
31+
});
32+
33+
return s;
34+
},
35+
shared: true
36+
},
37+
series: {{ chart_data }}
38+
};
39+
</script>
40+
41+
<h3>Data</h3>
42+
43+
<table class="table table-condensed stats-data">
44+
<thead>
45+
<tr>
46+
<th>Formal language</th>
47+
<th>Percentage of {{ doc_label }}s</th>
48+
<th>{{ doc_label|capfirst }}s</th>
49+
</tr>
50+
</thead>
51+
<tbody>
52+
{% for formal_language, percentage, names in table_data %}
53+
<tr>
54+
<td>{{ formal_language }}</td>
55+
<td>{{ percentage|floatformat:2 }}%</td>
56+
<td>{% include "stats/includes/docnames_cell.html" %}</td>
57+
</tr>
58+
{% endfor %}
59+
</tbody>
60+
</table>

0 commit comments

Comments
 (0)