Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions ietf/doc/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,34 @@ def test_edit_authors_permissions(self):
draft = Document.objects.get(pk=draft.pk)
self.assertEqual(draft.author_persons(), orig_authors + [new_auth_person])

def test_edit_authors_blocked_when_rfcauthors_exist(self):
"""edit_authors returns 403 for all users when RfcAuthors exist"""
rfc = WgRfcFactory()
RfcAuthorFactory(document=rfc)
url = urlreverse('ietf.doc.views_doc.edit_authors', kwargs=dict(name=rfc.name))

self.client.login(username='secretary', password='secretary+password')
r = self.client.get(url)
self.assertEqual(r.status_code, 403)
r = self.client.post(url, {})
self.assertEqual(r.status_code, 403)

def test_document_main_hides_edit_authors_when_rfcauthors_exist(self):
"""document_main does not offer edit link for authors when RfcAuthors exist"""
rfc = WgRfcFactory()
edit_authors_url = urlreverse('ietf.doc.views_doc.edit_authors', kwargs=dict(name=rfc.name))

self.client.login(username='secretary', password='secretary+password')

r = self.client.get(urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=rfc.name)))
self.assertEqual(r.status_code, 200)
self.assertContains(r, edit_authors_url)

RfcAuthorFactory(document=rfc)
r = self.client.get(urlreverse('ietf.doc.views_doc.document_main', kwargs=dict(name=rfc.name)))
self.assertEqual(r.status_code, 200)
self.assertNotContains(r, edit_authors_url)

def make_edit_authors_post_data(self, basis, authors):
"""Helper to generate edit_authors POST data for a set of authors"""
def _add_prefix(s):
Expand Down
9 changes: 6 additions & 3 deletions ietf/doc/views_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from django.core.files.base import ContentFile
from django.core.exceptions import PermissionDenied
from django.db.models import Max
from django.http import FileResponse, HttpResponse, Http404, HttpResponseBadRequest, JsonResponse
from django.http import FileResponse, HttpResponse, Http404, HttpResponseBadRequest, HttpResponseForbidden, JsonResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.template.loader import render_to_string
from django.urls import reverse as urlreverse
Expand Down Expand Up @@ -258,7 +258,7 @@ def document_main(request, name, rev=None, document_html=False):
interesting_relations_that, interesting_relations_that_doc = interesting_doc_relations(doc)

can_edit = has_role(request.user, ("Area Director", "Secretariat"))
can_edit_authors = has_role(request.user, ("Secretariat"))
can_edit_authors = has_role(request.user, ("Secretariat")) and not doc.rfcauthor_set.exists()

stream_slugs = StreamName.objects.values_list("slug", flat=True)
# For some reason, AnonymousUser has __iter__, but is not iterable,
Expand Down Expand Up @@ -1842,12 +1842,15 @@ def add_fields(self, form, index):
if fh in form.fields:
form.fields[fh].widget = forms.HiddenInput()

doc = get_object_or_404(Document, name=name)
if doc.rfcauthor_set.exists():
return HttpResponseForbidden("Contact the RFC Editor to change RFC Author information")

AuthorFormSet = forms.formset_factory(DocAuthorForm,
formset=_AuthorsBaseFormSet,
can_delete=True,
can_order=True,
extra=0)
doc = get_object_or_404(Document, name=name)

if request.method == 'POST':
change_basis_form = DocAuthorChangeBasisForm(request.POST)
Expand Down
2 changes: 2 additions & 0 deletions ietf/person/templatetags/person_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def person_link(person, **kwargs):
title = kwargs.get("title", "")
cls = kwargs.get("class", "")
with_email = kwargs.get("with_email", True)
titlepage_name = kwargs.get("titlepage_name", None)
if person is not None:
plain_name = person.plain_name()
name = (
Expand All @@ -61,6 +62,7 @@ def person_link(person, **kwargs):
return {
"name": name,
"plain_name": plain_name,
"titlepage_name": titlepage_name,
"email": email,
"title": title,
"class": cls,
Expand Down
65 changes: 64 additions & 1 deletion ietf/person/templatetags/tests.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Copyright The IETF Trust 2022, All Rights Reserved
from django.template.loader import render_to_string

from ietf.person.factories import PersonFactory
from ietf.utils.test_utils import TestCase

Expand All @@ -8,14 +10,14 @@
class PersonLinkTests(TestCase):
# Tests of the person_link template tag. These assume it is implemented as an
# inclusion tag.
# TODO test that the template actually renders the data in the dict
def test_person_link(self):
person = PersonFactory()
self.assertEqual(
person_link(person),
{
'name': person.name,
'plain_name': person.plain_name(),
'titlepage_name': None,
'email': person.email_address(),
'title': '',
'class': '',
Expand All @@ -27,6 +29,7 @@ def test_person_link(self):
{
'name': person.name,
'plain_name': person.plain_name(),
'titlepage_name': None,
'email': person.email_address(),
'title': '',
'class': '',
Expand All @@ -38,6 +41,7 @@ def test_person_link(self):
{
'name': person.name,
'plain_name': person.plain_name(),
'titlepage_name': None,
'email': person.email_address(),
'title': 'Random Title',
'class': '',
Expand All @@ -50,12 +54,71 @@ def test_person_link(self):
{
'name': person.name,
'plain_name': person.plain_name(),
'titlepage_name': None,
'email': person.email_address(),
'title': '',
'class': 'some-class',
'with_email': True,
}
)
self.assertEqual(
person_link(person, titlepage_name='G. Surname'),
{
'name': person.name,
'plain_name': person.plain_name(),
'titlepage_name': 'G. Surname',
'email': person.email_address(),
'title': '',
'class': '',
'with_email': True,
}
)

def test_person_link_renders(self):
"""Verifies person/person_link.html renders context dict values correctly."""
person = PersonFactory()
name = person.name
email = person.email_address()
base_context = {
'name': name,
'plain_name': person.plain_name(),
'titlepage_name': None,
'email': email,
'title': '',
'class': '',
'with_email': True,
}

# Default: name is used as link text with default title attribute
html = render_to_string('person/person_link.html', base_context)
self.assertIn(f'>{name}</a>', html)
self.assertIn(f'Datatracker profile of {name}', html)
self.assertIn('bi-envelope', html)

# titlepage_name overrides name as link text
html = render_to_string('person/person_link.html', {**base_context, 'titlepage_name': 'G. Surname'})
self.assertIn('>G. Surname</a>', html)
self.assertNotIn(f'>{name}</a>', html)

# with_email=False suppresses the envelope link
html = render_to_string('person/person_link.html', {**base_context, 'with_email': False})
self.assertNotIn('bi-envelope', html)

# Custom title appears in the anchor title attribute
html = render_to_string('person/person_link.html', {**base_context, 'title': 'Special Title'})
self.assertIn('title="Special Title"', html)

# Empty context (None person) renders (None)
self.assertInHTML(
'<span class="text-body-secondary">(None)</span>',
render_to_string('person/person_link.html', {}),
)

# System email renders (System)
self.assertInHTML(
'<span class="text-body-secondary">(System)</span>',
render_to_string('person/person_link.html', {'email': 'system@datatracker.ietf.org', 'name': ''}),
)

def test_invalid_person(self):
"""Generates correct context dict when input is invalid/missing"""
Expand Down
4 changes: 1 addition & 3 deletions ietf/submit/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,4 @@ def active(dirent):
log.log(f"Error processing {item.name}: {e}")

ftp_moddir = Path(settings.FTP_DIR) / "yang" / "draftmod/"
if not moddir.endswith("/"):
moddir += "/"
subprocess.call(("/usr/bin/rsync", "-aq", "--delete", moddir, ftp_moddir))
subprocess.call(("/usr/bin/rsync", "-aq", "--delete", f"{moddir}/", str(ftp_moddir)))
2 changes: 1 addition & 1 deletion ietf/templates/doc/document_info.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
<td>
{# Implementation that uses the current primary email for each author #}
{% if doc.pk %}{% for author in doc.author_persons_or_names %}
{% if author.person %}{% person_link author.person %}{% else %}{{ author.titlepage_name }}{% endif %}{% if not forloop.last %},{% endif %}
{% if author.person %}{% person_link author.person titlepage_name=author.titlepage_name %}{% else %}{{ author.titlepage_name }}{% endif %}{% if not forloop.last %},{% endif %}
{% endfor %}{% endif %}
{% if document_html and not snapshot or document_html and doc.rev == latest_rev%}
<br>
Expand Down
2 changes: 1 addition & 1 deletion ietf/templates/person/person_link.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% if email and email == "system@datatracker.ietf.org" or name and name == "(System)" %}<span class="text-body-secondary">(System)</span>{% else %}<span {% if class %}class="{{ class }}"
{% endif %}>{% if email or name %}<a {% if class %}class="text-reset"{% endif %}
title="{% if title %}{{ title }}{% else %}Datatracker profile of {{ name }}{% endif %}"
{% if email %} href="{% url 'ietf.person.views.profile' email_or_name=email %}" {% else %} href="{% url 'ietf.person.views.profile' email_or_name=name %}" {% endif %}>{{ name }}</a>{% if email and with_email %} <a {% if class %}class="text-reset"{% endif %}
{% if email %} href="{% url 'ietf.person.views.profile' email_or_name=email %}" {% else %} href="{% url 'ietf.person.views.profile' email_or_name=name %}" {% endif %}>{% if titlepage_name %}{{ titlepage_name }}{% else %}{{ name }}{% endif %}</a>{% if email and with_email %} <a {% if class %}class="text-reset"{% endif %}
href="mailto:{{ email|urlencode }}"
aria-label="Compose email to {{ email }}"
title="Compose email to {{ email }}">
Expand Down
Loading