Skip to content

Commit 92d425f

Browse files
committed
Added 'Additional URLs' for documents, the same way we have them for groups.
This could be used to point to a document source repository, to extracted yang module files, document wikis, and other relevant resources. - Legacy-Id: 14166
1 parent 34c32e1 commit 92d425f

File tree

12 files changed

+10215
-9966
lines changed

12 files changed

+10215
-9966
lines changed

ietf/doc/admin.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
DocHistoryAuthor, DocHistory, DocAlias, DocReminder, DocEvent, NewRevisionDocEvent,
88
StateDocEvent, ConsensusDocEvent, BallotType, BallotDocEvent, WriteupDocEvent, LastCallDocEvent,
99
TelechatDocEvent, BallotPositionDocEvent, ReviewRequestDocEvent, InitialReviewDocEvent,
10-
AddedMessageEvent, SubmissionDocEvent, DeletedEvent, EditedAuthorsDocEvent, )
10+
AddedMessageEvent, SubmissionDocEvent, DeletedEvent, EditedAuthorsDocEvent, DocumentURL)
1111

1212

1313
from ietf.doc.utils import get_state_types
@@ -211,3 +211,7 @@ class BallotPositionDocEventAdmin(DocEventAdmin):
211211
raw_id_fields = ["doc", "by", "ad", "ballot"]
212212
admin.site.register(BallotPositionDocEvent, BallotPositionDocEventAdmin)
213213

214+
class DocumentUrlAdmin(admin.ModelAdmin):
215+
list_display = ['id', 'doc', 'tag', 'url', 'desc', ]
216+
raw_id_fields = ['doc', ]
217+
admin.site.register(DocumentURL, DocumentUrlAdmin)

ietf/doc/models.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020

2121
from ietf.group.models import Group
2222
from ietf.name.models import ( DocTypeName, DocTagName, StreamName, IntendedStdLevelName, StdLevelName,
23-
DocRelationshipName, DocReminderTypeName, BallotPositionName, ReviewRequestStateName, FormalLanguageName )
23+
DocRelationshipName, DocReminderTypeName, BallotPositionName, ReviewRequestStateName, FormalLanguageName,
24+
DocUrlTagName)
2425
from ietf.person.models import Email, Person
2526
from ietf.utils import log
2627
from ietf.utils.admin import admin_link
@@ -777,9 +778,14 @@ def fake_history_obj(self, rev):
777778
stream=self.stream, group=self.group)
778779

779780
return dh
780-
781781

782782

783+
class DocumentURL(models.Model):
784+
doc = models.ForeignKey(Document)
785+
tag = models.ForeignKey(DocUrlTagName)
786+
desc = models.CharField(max_length=255, default='', blank=True)
787+
url = models.URLField()
788+
783789
class RelatedDocHistory(models.Model):
784790
source = models.ForeignKey('DocHistory')
785791
target = models.ForeignKey('DocAlias', related_name="reversely_related_document_history_set")

ietf/doc/resources.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
TelechatDocEvent, DocReminder, LastCallDocEvent, NewRevisionDocEvent, WriteupDocEvent,
1313
InitialReviewDocEvent, DocHistoryAuthor, BallotDocEvent, RelatedDocument,
1414
RelatedDocHistory, BallotPositionDocEvent, AddedMessageEvent, SubmissionDocEvent,
15-
ReviewRequestDocEvent, EditedAuthorsDocEvent)
15+
ReviewRequestDocEvent, EditedAuthorsDocEvent, DocumentURL)
1616

1717
from ietf.name.resources import BallotPositionNameResource, DocTypeNameResource
1818
class BallotTypeResource(ModelResource):
@@ -629,3 +629,22 @@ class Meta:
629629
"docevent_ptr": ALL_WITH_RELATIONS,
630630
}
631631
api.doc.register(EditedAuthorsDocEventResource())
632+
633+
634+
from ietf.name.resources import DocUrlTagNameResource
635+
class DocumentURLResource(ModelResource):
636+
doc = ToOneField(DocumentResource, 'doc')
637+
tag = ToOneField(DocUrlTagNameResource, 'tag')
638+
class Meta:
639+
queryset = DocumentURL.objects.all()
640+
serializer = api.Serializer()
641+
cache = SimpleCache()
642+
#resource_name = 'documenturl'
643+
filtering = {
644+
"id": ALL,
645+
"desc": ALL,
646+
"url": ALL,
647+
"doc": ALL_WITH_RELATIONS,
648+
"tag": ALL_WITH_RELATIONS,
649+
}
650+
api.doc.register(DocumentURLResource())

ietf/doc/tests_draft.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,11 @@ def test_expire_last_call(self):
765765
self.assertTrue('draft-ietf-mars-test@' in outbox[-1]['To'])
766766

767767
class IndividualInfoFormsTests(TestCase):
768+
769+
def setUp(self):
770+
self.doc = make_test_data()
771+
self.docname = self.doc.name
772+
768773
def test_doc_change_stream(self):
769774
url = urlreverse('ietf.doc.views_draft.change_stream', kwargs=dict(name=self.docname))
770775
login_testing_unauthorized(self, "secretary", url)
@@ -1050,11 +1055,24 @@ def test_doc_change_shepherd_writeup(self):
10501055
q = PyQuery(r.content)
10511056
self.assertTrue(q('textarea')[0].text.strip().startswith("As required by RFC 4858"))
10521057

1053-
def setUp(self):
1054-
make_test_data()
1055-
self.docname='draft-ietf-mars-test'
1058+
def test_doc_change_document_urls(self):
1059+
url = urlreverse('ietf.doc.views_draft.edit_document_urls', kwargs=dict(name=self.docname))
1060+
1061+
# get
1062+
login_testing_unauthorized(self, "secretary", url)
1063+
1064+
r = self.client.get(url)
1065+
self.assertEqual(r.status_code,200)
1066+
q = PyQuery(r.content)
1067+
self.assertEqual(len(q('form textarea[id=id_urls]')),1)
1068+
1069+
# direct edit
1070+
r = self.client.post(url, dict(urls='wiki https://wiki.org/ Wiki\nrepository https://repository.org/ Repo\n', submit="1"))
1071+
self.assertEqual(r.status_code,302)
10561072
self.doc = Document.objects.get(name=self.docname)
1057-
1073+
self.assertTrue(self.doc.latest_event(DocEvent,type="changed_document").desc.startswith('Changed document URLs'))
1074+
self.assertIn('wiki https://wiki.org/', self.doc.latest_event(DocEvent,type="changed_document").desc)
1075+
self.assertIn('https://wiki.org/', [ u.url for u in self.doc.documenturl_set.all() ])
10581076

10591077
class SubmitToIesgTests(TestCase):
10601078
def test_verify_permissions(self):

ietf/doc/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@
118118
url(r'^%(name)s/edit/approvaltext/$' % settings.URL_REGEXPS, views_ballot.ballot_approvaltext),
119119
url(r'^%(name)s/edit/approveballot/$' % settings.URL_REGEXPS, views_ballot.approve_ballot),
120120
url(r'^%(name)s/edit/makelastcall/$' % settings.URL_REGEXPS, views_ballot.make_last_call),
121-
121+
url(r'^%(name)s/edit/urls/$' % settings.URL_REGEXPS, views_draft.edit_document_urls),
122+
122123
url(r'^help/state/(?P<type>[\w-]+)/$', views_help.state_help),
123124
url(r'^help/relationships/$', views_help.relationship_help),
124125
url(r'^help/relationships/(?P<subset>\w+)/$', views_help.relationship_help),

ietf/doc/views_draft.py

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
import datetime
44

55
from django import forms
6+
from django.conf import settings
7+
from django.contrib import messages
8+
from django.contrib.auth.decorators import login_required
9+
from django.core.exceptions import ValidationError, ObjectDoesNotExist
10+
from django.core.validators import URLValidator
611
from django.db.models import Q
712
from django.http import HttpResponseRedirect, HttpResponseForbidden, Http404
813
from django.shortcuts import render, get_object_or_404, redirect
914
from django.template.loader import render_to_string
10-
from django.conf import settings
1115
from django.forms.utils import ErrorList
12-
from django.contrib.auth.decorators import login_required
1316
from django.template.defaultfilters import pluralize
14-
from django.contrib import messages
1517

1618
import debug # pyflakes:ignore
1719

@@ -33,7 +35,7 @@
3335
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream, user_is_person
3436
from ietf.ietfauth.utils import role_required
3537
from ietf.message.models import Message
36-
from ietf.name.models import IntendedStdLevelName, DocTagName, StreamName
38+
from ietf.name.models import IntendedStdLevelName, DocTagName, StreamName, DocUrlTagName
3739
from ietf.person.fields import SearchableEmailField
3840
from ietf.person.models import Person, Email
3941
from ietf.utils.mail import send_mail, send_mail_message
@@ -1110,14 +1112,89 @@ def edit_consensus(request, name):
11101112
},
11111113
)
11121114

1113-
class PublicationForm(forms.Form):
1114-
subject = forms.CharField(max_length=200, required=True)
1115-
body = forms.CharField(widget=forms.Textarea, required=True, strip=False)
1115+
def edit_document_urls(request, name):
1116+
class DocumentUrlForm(forms.Form):
1117+
urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", required=False,
1118+
help_text=("Format: 'tag https://site/path (Optional description)'."
1119+
" Separate multiple entries with newline. Prefer HTTPS URLs where possible.") )
1120+
1121+
def clean_urls(self):
1122+
lines = [x.strip() for x in self.cleaned_data["urls"].splitlines() if x.strip()]
1123+
url_validator = URLValidator()
1124+
for l in lines:
1125+
errors = []
1126+
parts = l.split()
1127+
if len(parts) == 1:
1128+
errors.append("Too few fields: Expected at least url and tag: '%s'" % l)
1129+
elif len(parts) >= 2:
1130+
tag = parts[0]
1131+
url = parts[1]
1132+
try:
1133+
url_validator(url)
1134+
except ValidationError as e:
1135+
errors.append(e)
1136+
try:
1137+
DocUrlTagName.objects.get(slug=tag)
1138+
except ObjectDoesNotExist:
1139+
errors.append("Bad tag in '%s': Expected one of %s" % (l, ', '.join([ o.slug for o in DocUrlTagName.objects.all() ])))
1140+
if errors:
1141+
raise ValidationError(errors)
1142+
return lines
1143+
1144+
def format_urls(urls, fs="\n"):
1145+
res = []
1146+
for u in urls:
1147+
if u.desc:
1148+
res.append(u"%s %s (%s)" % (u.tag.slug, u.url, u.desc.strip('()')))
1149+
else:
1150+
res.append(u"%s %s" % (u.tag.slug, u.url))
1151+
return fs.join(res)
1152+
1153+
doc = get_object_or_404(Document, name=name)
1154+
1155+
if not (has_role(request.user, ("Secretariat", "Area Director"))
1156+
or is_authorized_in_doc_stream(request.user, doc)):
1157+
return HttpResponseForbidden("You do not have the necessary permissions to view this page")
1158+
1159+
old_urls = format_urls(doc.documenturl_set.all())
1160+
1161+
if request.method == 'POST':
1162+
form = DocumentUrlForm(request.POST)
1163+
if form.is_valid():
1164+
old_urls = sorted(old_urls.splitlines())
1165+
new_urls = sorted(form.cleaned_data['urls'])
1166+
if old_urls != new_urls:
1167+
doc.documenturl_set.all().delete()
1168+
for u in new_urls:
1169+
parts = u.split(None, 2)
1170+
tag = parts[0]
1171+
url = parts[1]
1172+
desc = ' '.join(parts[2:]).strip('()')
1173+
doc.documenturl_set.create(url=url, tag_id=tag, desc=desc)
1174+
new_urls = format_urls(doc.documenturl_set.all())
1175+
e = DocEvent(doc=doc, rev=doc.rev, by=request.user.person, type='changed_document')
1176+
e.desc = "Changed document URLs from:\n\n%s\n\nto:\n\n%s" % (old_urls, new_urls)
1177+
e.save()
1178+
doc.save_with_history([e])
1179+
messages.success(request,"Document URLs updated.")
1180+
else:
1181+
messages.info(request,"No change in Document URLs.")
1182+
return redirect('ietf.doc.views_doc.document_main', name=doc.name)
1183+
else:
1184+
form = DocumentUrlForm(initial={'urls': old_urls, })
1185+
1186+
info = "Valid tags:<br><br> %s" % ', '.join([ o.slug for o in DocUrlTagName.objects.all() ])
1187+
title = "Additional document URLs"
1188+
return render(request, 'doc/edit_field.html',dict(doc=doc, form=form, title=title, info=info) )
11161189

11171190
def request_publication(request, name):
11181191
"""Request publication by RFC Editor for a document which hasn't
11191192
been through the IESG ballot process."""
11201193

1194+
class PublicationForm(forms.Form):
1195+
subject = forms.CharField(max_length=200, required=True)
1196+
body = forms.CharField(widget=forms.Textarea, required=True, strip=False)
1197+
11211198
doc = get_object_or_404(Document, type="draft", name=name, stream__in=("iab", "ise", "irtf"))
11221199

11231200
if not is_authorized_in_doc_stream(request.user, doc):

ietf/name/admin.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
IprLicenseTypeName, LiaisonStatementEventTypeName, LiaisonStatementPurposeName,
99
LiaisonStatementState, LiaisonStatementTagName, MeetingTypeName, NomineePositionStateName,
1010
ReviewRequestStateName, ReviewResultName, ReviewTypeName, RoleName, RoomResourceName,
11-
SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName, TopicAudienceName, )
11+
SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName, TopicAudienceName,
12+
DocUrlTagName)
1213

1314
from ietf.stats.models import CountryAlias
1415

@@ -75,3 +76,4 @@ class ImportantDateNameAdmin(NameAdmin):
7576
admin.site.register(StreamName, NameAdmin)
7677
admin.site.register(TimeSlotTypeName, NameAdmin)
7778
admin.site.register(TopicAudienceName, NameAdmin)
79+
admin.site.register(DocUrlTagName, NameAdmin)

0 commit comments

Comments
 (0)