This addresses ease of editing various group attributes, and in particular is

intended to make it easier to see that you can edit things like the
external/additional URLs:

 - Added the ability to edit individual fields in a group's about page, and
   added edit buttons for editable fields on the about page, just as for
   documents (the ability to edit all editable fields already was available
   from the 'Edit group' button on the /group//about/ page).

 - Made the tab label for the group-about tab consistently say 'About',
   instead of 'Charter' for some groups.

 = Shifted the position of the about tab to the start of the tab line.

 - Removed the datatracker account requirement information at the top of the
   group edit page for users logged in to their account.

 - Tweaked the 'Show update' link on the 'Status Update' line.

 - Changed the label for the external URLs from 'More Info' to 'Additional
   URLs', which was already in use on the edit form.
 - Legacy-Id: 12904
This commit is contained in:
Henrik Levkowetz 2017-02-23 20:55:38 +00:00
parent ffa19c9847
commit 23ebe5d35d
8 changed files with 147 additions and 38 deletions

2
PLAN
View file

@ -10,8 +10,6 @@ Planned work in rough order
* Add a 'rev' field to DocEvent, migrate 'rev' information from New
Revision and Submit DocEvents, and infer 'rev' information for others
* Schema Changes: tags, external links for groups.
* Add "Waiting for implementation" and "Held by WG" states, with comment field
* Break out the htmlzation code used on tools in a library, and use that

View file

@ -620,6 +620,44 @@ class GroupEditTests(TestCase):
for prefix in ['ad1','ad2','aread','marschairman','marsdelegate']:
self.assertTrue(prefix+'@' in outbox[0]['To'])
def test_edit_field(self):
make_test_data()
group = Group.objects.get(acronym="mars")
# Edit name
url = urlreverse('ietf.group.views_edit.edit', kwargs=dict(acronym=group.acronym, action="edit", field="name"))
login_testing_unauthorized(self, "secretary", url)
# normal get
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('div#content > form input[name=name]')), 1)
self.assertEqual(len(q('form input[name=acronym]')), 0)
# edit info
r = self.client.post(url, dict(name="Mars Not Special Interest Group"))
self.assertEqual(r.status_code, 302)
#
group = Group.objects.get(acronym="mars")
self.assertEqual(group.name, "Mars Not Special Interest Group")
# Edit list email
url = urlreverse('ietf.group.views_edit.edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym, action="edit", field="list_email"))
# normal get
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('div#content > form input[name=list_email]')), 1)
self.assertEqual(len(q('div#content > form input[name=name]')), 0)
# edit info
r = self.client.post(url, dict(list_email="mars@mail"))
self.assertEqual(r.status_code, 302)
#
group = Group.objects.get(acronym="mars")
self.assertEqual(group.list_email, "mars@mail")
def test_edit_reviewers(self):
doc = make_test_data()
review_req = make_review_data(doc)

View file

@ -23,6 +23,7 @@ urlpatterns = [
url(r'^deps/(?P<output_type>[\w-]+)/$', views.dependencies),
url(r'^meetings/$', views.meetings),
url(r'^edit/$', views_edit.edit, {'action': "edit"}),
url(r'^edit/(?P<field>\w+)/?$', views_edit.edit, {'action': "edit"}),
url(r'^conclude/$', views_edit.conclude),
url(r'^milestones/$', milestone_views.edit_milestones, {'milestone_set': "current"}, "group_edit_milestones"),
url(r'^milestones/charter/$', milestone_views.edit_milestones, {'milestone_set': "charter"}, "group_edit_charter_milestones"),

View file

@ -169,12 +169,9 @@ def construct_group_menu_context(request, group, selected, group_type, others):
# menu entries
entries = []
entries.append(("About", urlreverse("ietf.group.views.group_about", kwargs=kwargs)))
if group.features.has_documents:
entries.append(("Documents", urlreverse("ietf.group.views.group_documents", kwargs=kwargs)))
if group.features.has_chartering_process:
entries.append(("Charter", urlreverse("ietf.group.views.group_about", kwargs=kwargs)))
else:
entries.append(("About", urlreverse("ietf.group.views.group_about", kwargs=kwargs)))
if group.features.has_materials and get_group_materials(group).exists():
entries.append(("Materials", urlreverse("ietf.group.views.materials", kwargs=kwargs)))
if group.features.has_reviews:
@ -199,6 +196,7 @@ def construct_group_menu_context(request, group, selected, group_type, others):
is_admin = group.has_role(request.user, group.features.admin_roles)
can_manage = can_manage_group_type(request.user, group)
can_edit_group = False # we'll set this further down
if group.features.has_milestones:
if group.state_id != "proposed" and (is_admin or can_manage):
@ -223,6 +221,7 @@ def construct_group_menu_context(request, group, selected, group_type, others):
if group.state_id != "conclude" and (is_admin or can_manage):
can_edit_group = True
actions.append((u"Edit group", urlreverse("ietf.group.views_edit.edit", kwargs=dict(kwargs, action="edit"))))
if group.features.customize_workflow and (is_admin or can_manage):
@ -237,6 +236,7 @@ def construct_group_menu_context(request, group, selected, group_type, others):
"menu_entries": entries,
"menu_actions": actions,
"group_type": group_type,
"can_edit_group": can_edit_group,
}
d.update(others)

View file

@ -435,7 +435,7 @@ def group_about(request, acronym, group_type=None):
return render(request, 'group/group_about.html',
construct_group_menu_context(request, group, "charter" if group.features.has_chartering_process else "about", group_type, {
construct_group_menu_context(request, group, "about", group_type, {
"milestones_in_review": group.groupmilestone_set.filter(state="review"),
"milestone_reviewer": milestone_reviewer_for_group_type(group_type),
"requested_close": requested_close,

View file

@ -57,6 +57,13 @@ class GroupForm(forms.Form):
def __init__(self, *args, **kwargs):
self.group = kwargs.pop('group', None)
self.group_type = kwargs.pop('group_type', False)
if "field" in kwargs:
field = kwargs["field"]
del kwargs["field"]
if field in roles_for_group_type(self.group_type):
field = field + "_roles"
else:
field = None
super(self.__class__, self).__init__(*args, **kwargs)
@ -85,6 +92,10 @@ class GroupForm(forms.Form):
- set(roles_for_group_type(self.group_type)))
for r in role_fields_to_remove:
del self.fields[r + "_roles"]
if field:
for f in self.fields:
if f != field:
del self.fields[f]
def clean_acronym(self):
# Changing the acronym of an already existing group will cause 404s all
@ -158,6 +169,7 @@ class GroupForm(forms.Form):
raise forms.ValidationError("You requested the creation of a BoF, but specified no parent area. A parent is required when creating a bof.")
return cleaned_data
def format_urls(urls, fs="\n"):
res = []
for u in urls:
@ -221,7 +233,7 @@ def format_urls(urls, fs="\n"):
# return redirect('charter_submit', name=group.charter.name, option="initcharter")
@login_required
def edit(request, group_type=None, acronym=None, action="edit"):
def edit(request, group_type=None, acronym=None, action="edit", field=None):
"""Edit or create a group, notifying parties as
necessary and logging changes as group events."""
if action == "edit":
@ -241,7 +253,7 @@ def edit(request, group_type=None, acronym=None, action="edit"):
return HttpResponseForbidden("You don't have permission to access this view")
if request.method == 'POST':
form = GroupForm(request.POST, group=group, group_type=group_type)
form = GroupForm(request.POST, group=group, group_type=group_type, field=field)
if form.is_valid():
clean = form.cleaned_data
if new_group:
@ -284,6 +296,8 @@ def edit(request, group_type=None, acronym=None, action="edit"):
return entry % dict(attr=attr, new=new, old=old)
def diff(attr, name):
if field and attr != field:
return
v = getattr(group, attr)
if clean[attr] != v:
changes.append((attr, clean[attr], desc(name, clean[attr], v)))
@ -335,20 +349,21 @@ def edit(request, group_type=None, acronym=None, action="edit"):
email_personnel_change(request, group, personnel_change_text, changed_personnel)
# update urls
new_urls = clean['urls']
old_urls = format_urls(group.groupurl_set.order_by('url'), ", ")
if ", ".join(sorted(new_urls)) != old_urls:
changes.append(('urls', new_urls, desc('Urls', ", ".join(sorted(new_urls)), old_urls)))
group.groupurl_set.all().delete()
# Add new ones
for u in new_urls:
m = re.search('(?P<url>[\w\d:#@%/;$()~_?\+-=\\\.&]+)( \((?P<name>.+)\))?', u)
if m:
if m.group('name'):
url = GroupURL(url=m.group('url'), name=m.group('name'), group=group)
else:
url = GroupURL(url=m.group('url'), name='', group=group)
url.save()
if 'urls' in clean:
new_urls = clean['urls']
old_urls = format_urls(group.groupurl_set.order_by('url'), ", ")
if ", ".join(sorted(new_urls)) != old_urls:
changes.append(('urls', new_urls, desc('Urls', ", ".join(sorted(new_urls)), old_urls)))
group.groupurl_set.all().delete()
# Add new ones
for u in new_urls:
m = re.search('(?P<url>[\w\d:#@%/;$()~_?\+-=\\\.&]+)( \((?P<name>.+)\))?', u)
if m:
if m.group('name'):
url = GroupURL(url=m.group('url'), name=m.group('name'), group=group)
else:
url = GroupURL(url=m.group('url'), name='', group=group)
url.save()
group.time = datetime.datetime.now()
@ -384,15 +399,13 @@ def edit(request, group_type=None, acronym=None, action="edit"):
else:
init = dict(ad=request.user.person.id if group_type == "wg" and has_role(request.user, "Area Director") else None,
)
form = GroupForm(initial=init, group=group, group_type=group_type)
form = GroupForm(initial=init, group=group, group_type=group_type, field=field)
return render(request, 'group/edit.html',
dict(group=group,
form=form,
action=action))
class ConcludeForm(forms.Form):
instructions = forms.CharField(widget=forms.Textarea(attrs={'rows': 30}), required=True, strip=False)

View file

@ -30,9 +30,13 @@
{% endif %}
</h1>
{% if not request.user.is_authenticated %}
<p class="alert alert-info">Note that persons with authorization to manage information, e.g.
chairs and delegates, need a datatracker account to actually do
so. New accounts can be <a href="{% url "ietf.ietfauth.views.create_account" %}">created here</a>.</p>
{% else %}
<p></p>
{% endif %}
<form class="form-horizontal" method="post">
{% csrf_token %}

View file

@ -11,17 +11,23 @@
{% endif %}
<table class="table table-condensed">
<thead><tr><th colspan="3"></th></tr></thead>
<thead><tr><th colspan="4"></th></tr></thead>
<tbody class="meta">
<tr>
<th>{{ group.type.name }}</th>
<th>Name</th>
<td class="edit">
{% if can_edit_group %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views_edit.edit' acronym=group.acronym field='name' %}">Edit</a>
{% endif %}
</td>
<td>{{ group.name }}</td>
</tr>
<tr>
<td></td>
<th>Acronym</th>
<td class="edit"></td>
<td>{{ group.acronym }}</td>
</tr>
@ -29,15 +35,23 @@
<td></td>
{% if group.parent and group.parent.type_id == "area" %}
<th>{{ group.parent.type.name }}</th>
<td class="edit"></td>
<td>{{ group.parent.name }} ({{ group.parent.acronym }})</td>
{% else %}
<th></th><td></td>
<th></th>
<td class="edit"></td>
<td></td>
{% endif %}
</tr>
<tr>
<td></td>
<th>State</th>
<td class="edit">
{% if can_edit_group %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views_edit.edit' acronym=group.acronym field='state' %}">Edit</a>
{% endif %}
</td>
<td>
{{ group.state.name }}
{% if requested_close %}
@ -50,6 +64,7 @@
<tr>
<td></td>
<th>Charter</th>
<td class="edit"></td>
<td>
{% if group.charter %}
<a href="{% url "ietf.doc.views_doc.document_main" name=group.charter.name %}">{{ group.charter.name }}-{{ group.charter.rev }}</a>
@ -68,16 +83,19 @@
<tr id='status_update'>
<td></td>
<th>Status Update</th>
<td class="edit">
{% if can_provide_status_update %}
<a class="btn btn-default btn-xs" href="{% url "ietf.group.views.group_about_status_edit" acronym=group.acronym %}">Edit</a>
{% endif %}
</td>
<td>
{% if status_update %}
<a href="{% url "ietf.group.views.group_about_status" acronym=group.acronym %}">Show update</a>
(last changed {{status_update.time|date:"Y-m-d"}})
{% else %}
(None)
{% endif %}
<a class="btn btn-default btn-xs" href="{% url "ietf.group.views.group_about_status" acronym=group.acronym %}">Show</a>
{% if can_provide_status_update %}
<a class="btn btn-default btn-xs" href="{% url "ietf.group.views.group_about_status_edit" acronym=group.acronym %}">Edit</a>
{% endif %}
</td>
</tr>
{% endif %}
@ -86,6 +104,7 @@
<tr id='dependency_graph'>
<td></td>
<th>Dependencies</th>
<td class="edit"></td>
<td>
<a href="{% url 'ietf.group.views.dependencies' group_type=group.type_id acronym=group.acronym output_type='svg' %}">
Document dependency graph (SVG)
@ -98,7 +117,12 @@
{% if urls %}
<tr>
<td></td>
<th>More info</th>
<th>Additional URLs</th>
<td class="edit">
{% if can_edit_group %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views_edit.edit' acronym=group.acronym field='urls' %}">Edit</a>
{% endif %}
</td>
<td>
{% for url in urls %}
<a href="{{ url.url }}">{% firstof url.name url.url %}</a>{% if not forloop.last %}<br>{% endif %}
@ -118,6 +142,11 @@
<td></td>
{% endif %}
<th>{{ label }}</th>
<td class="edit">
{% if can_edit_group and not slug == "ad" %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views_edit.edit' acronym=group.acronym field=slug %}">Edit</a>
{% endif %}
</td>
<td>
@ -135,10 +164,34 @@
<tbody class="meta">
<tr>
<th>Mailing list</th>
<th>Address</th><td>{{ group.list_email|urlize }}</td>
<th>Address</th>
<td class="edit">
{% if can_edit_group %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views_edit.edit' acronym=group.acronym field='list_email' %}">Edit</a>
{% endif %}
</td>
<td>{{ group.list_email|urlize }}</td>
</tr>
<tr>
<td></td>
<th>To subscribe</th>
<td class="edit">
{% if can_edit_group %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views_edit.edit' acronym=group.acronym field='list_subscribe' %}">Edit</a>
{% endif %}
</td>
<td>{{ group.list_subscribe|urlize }}</td>
</tr>
<tr>
<td></td>
<th>Archive</th>
<td class="edit">
{% if can_edit_group %}
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views_edit.edit' acronym=group.acronym field='list_archive' %}">Edit</a>
{% endif %}
</td>
<td>{{ group.list_archive|urlize }}</td>
</tr>
<tr><td></td><th>To subscribe</th><td>{{ group.list_subscribe|urlize }}</td></tr>
<tr><td></td><th>Archive</th><td>{{ group.list_archive|urlize }}</td></tr>
</tbody>
{% endif %}
@ -146,13 +199,15 @@
<tbody class="meta">
<tr>
<th>Jabber chat</th>
<th>Room address</th>
<td><a href="xmpp:{{ group.acronym }}@jabber.ietf.org?join">xmpp:{{ group.acronym }}@jabber.ietf.org?join</a></td>
<th>Room address</th>
<td class="edit"></td>
<td><a href="xmpp:{{ group.acronym }}@jabber.ietf.org?join">xmpp:{{ group.acronym }}@jabber.ietf.org?join</a></td>
</tr>
<tr>
<td></td>
<th>Logs</th>
<td class="edit"></td>
<td><a href="https://jabber.ietf.org/logs/{{ group.acronym }}/">https://jabber.ietf.org/logs/{{ group.acronym }}/</a></td>
</tr>
</tbody>