Merged in [14468] from rjsparks@nostrum.com:
Add the ability to add a comment to a group's history. Fixes #1483.
- Legacy-Id: 14485
Note: SVN reference [14468] has been migrated to Git commit 13e8f8982c
This commit is contained in:
commit
7a0a99001c
|
@ -57,3 +57,12 @@ def email_milestones_changed(request, group, changes, states):
|
||||||
if (addrs.to or addrs.cc) and msg:
|
if (addrs.to or addrs.cc) and msg:
|
||||||
wrap_up_email(addrs, msg)
|
wrap_up_email(addrs, msg)
|
||||||
|
|
||||||
|
def email_comment(request, event):
|
||||||
|
(to, cc) = gather_address_lists('group_added_comment',group=event.group)
|
||||||
|
send_mail(request, to, None, "Comment added to %s history"%event.group.acronym,
|
||||||
|
"group/comment_added_email.txt",
|
||||||
|
dict( event = event,
|
||||||
|
group_url=settings.IDTRACKER_BASE_URL + event.group.about_url(),
|
||||||
|
),
|
||||||
|
cc = cc)
|
||||||
|
|
||||||
|
|
|
@ -738,6 +738,30 @@ class GroupEditTests(TestCase):
|
||||||
group = Group.objects.get(acronym=group.acronym)
|
group = Group.objects.get(acronym=group.acronym)
|
||||||
self.assertEqual(group.state_id, "active")
|
self.assertEqual(group.state_id, "active")
|
||||||
|
|
||||||
|
def test_add_comment(self):
|
||||||
|
make_test_data()
|
||||||
|
group = Group.objects.get(acronym="mars")
|
||||||
|
url = urlreverse('ietf.group.views.add_comment', kwargs=dict(acronym=group.acronym))
|
||||||
|
empty_outbox()
|
||||||
|
for username in ['secretary','ad','marschairman','marssecretary','marsdelegate']:
|
||||||
|
login_testing_unauthorized(self, username, url)
|
||||||
|
# get
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertContains(r, "Add comment")
|
||||||
|
self.assertContains(r, group.acronym)
|
||||||
|
q = PyQuery(r.content)
|
||||||
|
self.assertEqual(len(q('form textarea[name=comment]')), 1)
|
||||||
|
# post
|
||||||
|
r = self.client.post(url, dict(comment="Test comment %s"%username))
|
||||||
|
self.assertEqual(r.status_code, 302)
|
||||||
|
person = Person.objects.get(user__username=username)
|
||||||
|
self.assertTrue(GroupEvent.objects.filter(group=group,by=person,type='added_comment',desc='Test comment %s'%username).exists())
|
||||||
|
self.client.logout()
|
||||||
|
self.client.login(username='ameschairman',password='ameschairman+password')
|
||||||
|
r=self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code,403)
|
||||||
|
self.assertEqual(len(outbox),5)
|
||||||
|
|
||||||
class MilestoneTests(TestCase):
|
class MilestoneTests(TestCase):
|
||||||
def create_test_milestones(self):
|
def create_test_milestones(self):
|
||||||
draft = make_test_data()
|
draft = make_test_data()
|
||||||
|
|
|
@ -24,6 +24,7 @@ info_detail_urls = [
|
||||||
url(r'^about/status/edit/$', views.group_about_status_edit),
|
url(r'^about/status/edit/$', views.group_about_status_edit),
|
||||||
url(r'^about/status/meeting/(?P<num>\d+)/$', views.group_about_status_meeting),
|
url(r'^about/status/meeting/(?P<num>\d+)/$', views.group_about_status_meeting),
|
||||||
url(r'^history/$',views.history),
|
url(r'^history/$',views.history),
|
||||||
|
url(r'^history/addcomment/$',views.add_comment),
|
||||||
url(r'^email/$', views.email),
|
url(r'^email/$', views.email),
|
||||||
url(r'^deps/(?P<output_type>[\w-]+)/$', views.dependencies),
|
url(r'^deps/(?P<output_type>[\w-]+)/$', views.dependencies),
|
||||||
url(r'^meetings/$', views.meetings),
|
url(r'^meetings/$', views.meetings),
|
||||||
|
|
|
@ -41,6 +41,7 @@ import datetime
|
||||||
from tempfile import mkstemp
|
from tempfile import mkstemp
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
|
|
||||||
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db.models.aggregates import Max
|
from django.db.models.aggregates import Max
|
||||||
|
@ -65,7 +66,7 @@ from ietf.group.dot import make_dot
|
||||||
from ietf.group.forms import (GroupForm, StatusUpdateForm, ConcludeGroupForm, StreamEditForm,
|
from ietf.group.forms import (GroupForm, StatusUpdateForm, ConcludeGroupForm, StreamEditForm,
|
||||||
ManageReviewRequestForm, EmailOpenAssignmentsForm, ReviewerSettingsForm,
|
ManageReviewRequestForm, EmailOpenAssignmentsForm, ReviewerSettingsForm,
|
||||||
AddUnavailablePeriodForm, EndUnavailablePeriodForm, ReviewSecretarySettingsForm, )
|
AddUnavailablePeriodForm, EndUnavailablePeriodForm, ReviewSecretarySettingsForm, )
|
||||||
from ietf.group.mails import email_admin_re_charter, email_personnel_change
|
from ietf.group.mails import email_admin_re_charter, email_personnel_change, email_comment
|
||||||
from ietf.group.models import ( Group, Role, GroupEvent, GroupStateTransitions, GroupURL, ChangeStateGroupEvent )
|
from ietf.group.models import ( Group, Role, GroupEvent, GroupStateTransitions, GroupURL, ChangeStateGroupEvent )
|
||||||
from ietf.group.utils import (get_charter_text, can_manage_group_type,
|
from ietf.group.utils import (get_charter_text, can_manage_group_type,
|
||||||
milestone_reviewer_for_group_type, can_provide_status_update,
|
milestone_reviewer_for_group_type, can_provide_status_update,
|
||||||
|
@ -74,7 +75,7 @@ from ietf.group.utils import (get_charter_text, can_manage_group_type,
|
||||||
save_group_in_history, can_manage_group,
|
save_group_in_history, can_manage_group,
|
||||||
get_group_or_404, setup_default_community_list_for_group, )
|
get_group_or_404, setup_default_community_list_for_group, )
|
||||||
#
|
#
|
||||||
from ietf.ietfauth.utils import has_role
|
from ietf.ietfauth.utils import has_role, is_authorized_in_group
|
||||||
from ietf.mailtrigger.utils import gather_relevant_expansions
|
from ietf.mailtrigger.utils import gather_relevant_expansions
|
||||||
from ietf.meeting.helpers import get_meeting
|
from ietf.meeting.helpers import get_meeting
|
||||||
from ietf.meeting.utils import group_sessions
|
from ietf.meeting.utils import group_sessions
|
||||||
|
@ -632,10 +633,13 @@ def history(request, acronym, group_type=None):
|
||||||
group = get_group_or_404(acronym, group_type)
|
group = get_group_or_404(acronym, group_type)
|
||||||
|
|
||||||
events = group.groupevent_set.all().select_related('by').order_by('-time', '-id')
|
events = group.groupevent_set.all().select_related('by').order_by('-time', '-id')
|
||||||
|
can_add_comment = is_authorized_in_group(request.user,group)
|
||||||
|
|
||||||
return render(request, 'group/history.html',
|
return render(request, 'group/history.html',
|
||||||
construct_group_menu_context(request, group, "history", group_type, {
|
construct_group_menu_context(request, group, "history", group_type, {
|
||||||
|
"group": group,
|
||||||
"events": events,
|
"events": events,
|
||||||
|
"can_add_comment": can_add_comment,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
def materials(request, acronym, group_type=None):
|
def materials(request, acronym, group_type=None):
|
||||||
|
@ -1784,4 +1788,24 @@ def change_review_secretary_settings(request, acronym, group_type=None):
|
||||||
'back_url': back_url,
|
'back_url': back_url,
|
||||||
'settings_form': settings_form,
|
'settings_form': settings_form,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
class AddCommentForm(forms.Form):
|
||||||
|
comment = forms.CharField(required=True, widget=forms.Textarea, strip=False)
|
||||||
|
|
||||||
|
def add_comment(request, acronym, group_type=None):
|
||||||
|
group = get_group_or_404(acronym, group_type)
|
||||||
|
|
||||||
|
if not is_authorized_in_group(request.user,group):
|
||||||
|
return HttpResponseForbidden("You need to a chair, secretary, or delegate of this group to add a comment.")
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = AddCommentForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
comment = form.cleaned_data['comment']
|
||||||
|
event = GroupEvent.objects.create(group=group,desc=comment,type="added_comment",by=request.user.person)
|
||||||
|
email_comment(request,event)
|
||||||
|
return redirect('ietf.group.views.history', acronym=group.acronym)
|
||||||
|
else:
|
||||||
|
form = AddCommentForm()
|
||||||
|
|
||||||
|
return render(request, 'group/add_comment.html', { 'group':group, 'form':form, })
|
||||||
|
|
|
@ -144,6 +144,26 @@ def is_authorized_in_doc_stream(user, doc):
|
||||||
|
|
||||||
return Role.objects.filter(Q(name__in=("chair", "secr", "delegate", "auth"), person__user=user) & group_req).exists()
|
return Role.objects.filter(Q(name__in=("chair", "secr", "delegate", "auth"), person__user=user) & group_req).exists()
|
||||||
|
|
||||||
|
def is_authorized_in_group(user, group):
|
||||||
|
"""Return whether user is authorized to perform duties on
|
||||||
|
a given group."""
|
||||||
|
|
||||||
|
if not user.is_authenticated:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if has_role(user, ["Secretariat",]):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if group.parent:
|
||||||
|
if group.parent.type_id == 'area' and has_role(user, ['Area Director',]):
|
||||||
|
return True
|
||||||
|
if group.parent.acronym == 'irtf' and has_role(user, ['IRTF Chair',]):
|
||||||
|
return True
|
||||||
|
if group.parent.acronym == 'iab' and has_role(user, ['IAB','IAB Executive Director',]):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return Role.objects.filter(name__in=("chair", "secr", "delegate", "auth"), person__user=user,group=group ).exists()
|
||||||
|
|
||||||
def is_individual_draft_author(user, doc):
|
def is_individual_draft_author(user, doc):
|
||||||
|
|
||||||
if not user.is_authenticated:
|
if not user.is_authenticated:
|
||||||
|
|
33
ietf/mailtrigger/migrations/0011_group_added_comment.py
Normal file
33
ietf/mailtrigger/migrations/0011_group_added_comment.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.8 on 2017-12-28 11:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
def forward(apps, schema_editor):
|
||||||
|
MailTrigger = apps.get_model('mailtrigger','MailTrigger')
|
||||||
|
Recipient = apps.get_model('mailtrigger', 'Recipient')
|
||||||
|
group_added_comment = MailTrigger.objects.create(
|
||||||
|
slug='group_added_comment',
|
||||||
|
desc="Recipients when a comment is added to a group's history",
|
||||||
|
)
|
||||||
|
group_added_comment.to = Recipient.objects.filter(slug__in=[
|
||||||
|
'group_chairs',
|
||||||
|
'group_secretaries',
|
||||||
|
'group_responsible_directors',
|
||||||
|
])
|
||||||
|
|
||||||
|
def reverse(apps, schema_editor):
|
||||||
|
MailTrigger = apps.get_model('mailtrigger','MailTrigger')
|
||||||
|
MailTrigger.objects.filter(slug='group_added_comment').delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mailtrigger', '0010_auto_20161207_1104'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forward, reverse)
|
||||||
|
]
|
|
@ -2732,6 +2732,19 @@
|
||||||
"model": "mailtrigger.mailtrigger",
|
"model": "mailtrigger.mailtrigger",
|
||||||
"pk": "doc_telechat_details_changed"
|
"pk": "doc_telechat_details_changed"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"cc": [],
|
||||||
|
"desc": "Recipients when a comment is added to a group's history",
|
||||||
|
"to": [
|
||||||
|
"group_chairs",
|
||||||
|
"group_responsible_directors",
|
||||||
|
"group_secretaries"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"model": "mailtrigger.mailtrigger",
|
||||||
|
"pk": "group_added_comment"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"cc": [],
|
"cc": [],
|
||||||
|
@ -3091,6 +3104,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"cc": [
|
"cc": [
|
||||||
"doc_group_chairs",
|
"doc_group_chairs",
|
||||||
|
"doc_group_mail_list",
|
||||||
"doc_notify",
|
"doc_notify",
|
||||||
"doc_shepherd",
|
"doc_shepherd",
|
||||||
"iesg_secretary"
|
"iesg_secretary"
|
||||||
|
@ -8470,7 +8484,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"default_offset_days": -89,
|
"default_offset_days": -68,
|
||||||
"desc": "IETF Online Registration Opens",
|
"desc": "IETF Online Registration Opens",
|
||||||
"name": "Registration Opens",
|
"name": "Registration Opens",
|
||||||
"order": 0,
|
"order": 0,
|
||||||
|
@ -9988,7 +10002,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"command": "xym",
|
"command": "xym",
|
||||||
"switch": "--version",
|
"switch": "--version",
|
||||||
"time": "2017-09-29T00:07:40.668",
|
"time": "2017-12-31T00:07:14.314",
|
||||||
"used": true,
|
"used": true,
|
||||||
"version": "xym 0.4"
|
"version": "xym 0.4"
|
||||||
},
|
},
|
||||||
|
@ -9999,7 +10013,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"command": "pyang",
|
"command": "pyang",
|
||||||
"switch": "--version",
|
"switch": "--version",
|
||||||
"time": "2017-09-29T00:07:41.703",
|
"time": "2017-12-31T00:07:15.241",
|
||||||
"used": true,
|
"used": true,
|
||||||
"version": "pyang 1.7.3"
|
"version": "pyang 1.7.3"
|
||||||
},
|
},
|
||||||
|
@ -10010,9 +10024,9 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"command": "yanglint",
|
"command": "yanglint",
|
||||||
"switch": "--version",
|
"switch": "--version",
|
||||||
"time": "2017-09-29T00:07:41.812",
|
"time": "2017-12-31T00:07:15.325",
|
||||||
"used": true,
|
"used": true,
|
||||||
"version": "yanglint 0.13.49"
|
"version": "yanglint 0.14.53"
|
||||||
},
|
},
|
||||||
"model": "utils.versioninfo",
|
"model": "utils.versioninfo",
|
||||||
"pk": 3
|
"pk": 3
|
||||||
|
|
24
ietf/templates/group/add_comment.html
Normal file
24
ietf/templates/group/add_comment.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{# Copyright The IETF Trust 2015, All Rights Reserved #}
|
||||||
|
{% load origin %}
|
||||||
|
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
{% block title %}Add comment for {{ group }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% origin %}
|
||||||
|
<h1>Add comment<br><small>{{ group }} ({{ group.acronym }})</small></h1>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<p class="help-block">The comment will be added to the history trail for the group.</p>
|
||||||
|
|
||||||
|
{% buttons %}
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
<a class="btn btn-default pull-right" href="{% url "ietf.group.views.history" acronym=group.acronym %}">Back</a>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
10
ietf/templates/group/comment_added_email.txt
Normal file
10
ietf/templates/group/comment_added_email.txt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% autoescape off %}
|
||||||
|
Please DO NOT reply to this email.
|
||||||
|
|
||||||
|
{{event.by}} added the following comment to the history of {{event.group.acronym}}:
|
||||||
|
|
||||||
|
{{ event.desc }}
|
||||||
|
|
||||||
|
Information for the group can be found at {{ group_url }}.
|
||||||
|
|
||||||
|
{% endautoescape%}
|
|
@ -10,6 +10,10 @@
|
||||||
|
|
||||||
{% block group_content %}
|
{% block group_content %}
|
||||||
{% origin %}
|
{% origin %}
|
||||||
|
<h2>Group History</h2>
|
||||||
|
{% if can_add_comment %}
|
||||||
|
<a class="btn btn-default" href="{% url 'ietf.group.views.add_comment' acronym=group.acronym %}"><span class="fa fa-plus"></span> Add comment</a>
|
||||||
|
{% endif %}
|
||||||
<table class="table table-condensed table-striped tablesorter">
|
<table class="table table-condensed table-striped tablesorter">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -35,4 +39,4 @@
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
|
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue