Code reorganization, doing away with multiple urls_* and views_ files in ietf.group. No intentional functionality changes.
- Legacy-Id: 13709
This commit is contained in:
parent
c28b919e26
commit
ce1b655fa2
132
ietf/group/dot.py
Normal file
132
ietf/group/dot.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
# Copyright The IETF Trust 2007, All Rights Reserved
|
||||
# -*- check-flake8 -*-
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
from django.db.models import Q
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from ietf.doc.models import RelatedDocument
|
||||
|
||||
|
||||
class Edge(object):
|
||||
def __init__(self, relateddocument):
|
||||
self.relateddocument = relateddocument
|
||||
|
||||
def __hash__(self):
|
||||
return hash("|".join([str(hash(nodename(self.relateddocument.source.name))),
|
||||
str(hash(nodename(self.relateddocument.target.document.name))),
|
||||
self.relateddocument.relationship.slug]))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
def sourcename(self):
|
||||
return nodename(self.relateddocument.source.name)
|
||||
|
||||
def targetname(self):
|
||||
return nodename(self.relateddocument.target.document.name)
|
||||
|
||||
def styles(self):
|
||||
|
||||
# Note that the old style=dotted, color=red styling is never used
|
||||
|
||||
if self.relateddocument.is_downref():
|
||||
return { 'color': 'red', 'arrowhead': 'normalnormal' }
|
||||
else:
|
||||
styles = { 'refnorm' : { 'color': 'blue' },
|
||||
'refinfo' : { 'color': 'green' },
|
||||
'refold' : { 'color': 'orange' },
|
||||
'refunk' : { 'style': 'dashed' },
|
||||
'replaces': { 'color': 'pink', 'style': 'dashed', 'arrowhead': 'diamond' },
|
||||
}
|
||||
return styles[self.relateddocument.relationship.slug]
|
||||
|
||||
|
||||
def nodename(name):
|
||||
return name.replace('-', '_')
|
||||
|
||||
|
||||
def get_node_styles(node, group):
|
||||
|
||||
styles = dict()
|
||||
|
||||
# Shape and style (note that old diamond shape is never used
|
||||
|
||||
styles['style'] = 'filled'
|
||||
|
||||
if node.get_state('draft').slug == 'rfc':
|
||||
styles['shape'] = 'box'
|
||||
elif node.get_state('draft-iesg') and not node.get_state('draft-iesg').slug in ['watching', 'dead']:
|
||||
styles['shape'] = 'parallelogram'
|
||||
elif node.get_state('draft').slug == 'expired':
|
||||
styles['shape'] = 'house'
|
||||
styles['style'] = 'solid'
|
||||
styles['peripheries'] = 3
|
||||
elif node.get_state('draft').slug == 'repl':
|
||||
styles['shape'] = 'ellipse'
|
||||
styles['style'] = 'solid'
|
||||
styles['peripheries'] = 3
|
||||
else:
|
||||
pass # quieter form of styles['shape'] = 'ellipse'
|
||||
|
||||
# Color (note that the old 'Flat out red' is never used
|
||||
if node.group.acronym == 'none':
|
||||
styles['color'] = '"#FF800D"' # orangeish
|
||||
elif node.group == group:
|
||||
styles['color'] = '"#0AFE47"' # greenish
|
||||
else:
|
||||
styles['color'] = '"#9999FF"' # blueish
|
||||
|
||||
# Label
|
||||
label = node.name
|
||||
if label.startswith('draft-'):
|
||||
if label.startswith('draft-ietf-'):
|
||||
label = label[11:]
|
||||
else:
|
||||
label = label[6:]
|
||||
try:
|
||||
t = label.index('-')
|
||||
label = r"%s\n%s" % (label[:t], label[t+1:])
|
||||
except:
|
||||
pass
|
||||
if node.group.acronym != 'none' and node.group != group:
|
||||
label = "(%s) %s" % (node.group.acronym, label)
|
||||
if node.get_state('draft').slug == 'rfc':
|
||||
label = "%s\\n(%s)" % (label, node.canonical_name())
|
||||
styles['label'] = '"%s"' % label
|
||||
|
||||
return styles
|
||||
|
||||
|
||||
def make_dot(group):
|
||||
references = Q(source__group=group, source__type='draft', relationship__slug__startswith='ref')
|
||||
both_rfcs = Q(source__states__slug='rfc', target__document__states__slug='rfc')
|
||||
inactive = Q(source__states__slug__in=['expired', 'repl'])
|
||||
attractor = Q(target__name__in=['rfc5000', 'rfc5741'])
|
||||
removed = Q(source__states__slug__in=['auth-rm', 'ietf-rm'])
|
||||
relations = ( RelatedDocument.objects.filter(references).exclude(both_rfcs)
|
||||
.exclude(inactive).exclude(attractor).exclude(removed) )
|
||||
|
||||
edges = set()
|
||||
for x in relations:
|
||||
target_state = x.target.document.get_state_slug('draft')
|
||||
if target_state != 'rfc' or x.is_downref():
|
||||
edges.add(Edge(x))
|
||||
|
||||
replacements = RelatedDocument.objects.filter(relationship__slug='replaces',
|
||||
target__document__in=[x.relateddocument.target.document for x in edges])
|
||||
|
||||
for x in replacements:
|
||||
edges.add(Edge(x))
|
||||
|
||||
nodes = set([x.relateddocument.source for x in edges]).union([x.relateddocument.target.document for x in edges])
|
||||
|
||||
for node in nodes:
|
||||
node.nodename = nodename(node.name)
|
||||
node.styles = get_node_styles(node, group)
|
||||
|
||||
return render_to_string('group/dot.txt',
|
||||
dict( nodes=nodes, edges=edges )
|
||||
)
|
||||
|
||||
|
|
@ -41,5 +41,5 @@ class GroupFeatures(object):
|
|||
if group in active_review_teams():
|
||||
self.has_reviews = True
|
||||
import ietf.group.views
|
||||
self.default_tab = ietf.group.views_review.review_requests
|
||||
self.default_tab = ietf.group.views.review_requests
|
||||
|
||||
|
|
318
ietf/group/forms.py
Normal file
318
ietf/group/forms.py
Normal file
|
@ -0,0 +1,318 @@
|
|||
# Copyright The IETF Trust 2007, All Rights Reserved
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
# Stdlib imports
|
||||
import re
|
||||
|
||||
# Django imports
|
||||
from django import forms
|
||||
from django.utils.html import mark_safe
|
||||
|
||||
# IETF imports
|
||||
from ietf.group.models import Group, GroupHistory, GroupStateName
|
||||
from ietf.person.fields import SearchableEmailsField, PersonEmailChoiceField
|
||||
from ietf.person.models import Person
|
||||
from ietf.review.models import ReviewerSettings, UnavailablePeriod, ReviewSecretarySettings
|
||||
from ietf.review.utils import close_review_request_states, setup_reviewer_field
|
||||
from ietf.utils.textupload import get_cleaned_text_file_content
|
||||
from ietf.utils.text import strip_suffix
|
||||
from ietf.utils.ordereddict import insert_after_in_ordered_dict
|
||||
from ietf.utils.fields import DatepickerDateField, MultiEmailField
|
||||
|
||||
# --- Constants --------------------------------------------------------
|
||||
|
||||
MAX_GROUP_DELEGATES = 3
|
||||
|
||||
# --- Utility Functions ------------------------------------------------
|
||||
|
||||
def roles_for_group_type(group_type):
|
||||
roles = ["chair", "secr", "techadv", "delegate", ]
|
||||
if group_type == "dir":
|
||||
roles.append("reviewer")
|
||||
return roles
|
||||
|
||||
# --- Forms ------------------------------------------------------------
|
||||
|
||||
class StatusUpdateForm(forms.Form):
|
||||
content = forms.CharField(widget=forms.Textarea, label='Status update', help_text = 'Edit the status update', required=False, strip=False)
|
||||
txt = forms.FileField(label='.txt format', help_text='Or upload a .txt file', required=False)
|
||||
|
||||
def clean_content(self):
|
||||
return self.cleaned_data['content'].replace('\r','')
|
||||
|
||||
def clean_txt(self):
|
||||
return get_cleaned_text_file_content(self.cleaned_data["txt"])
|
||||
|
||||
|
||||
class ConcludeGroupForm(forms.Form):
|
||||
instructions = forms.CharField(widget=forms.Textarea(attrs={'rows': 30}), required=True, strip=False)
|
||||
|
||||
class GroupForm(forms.Form):
|
||||
name = forms.CharField(max_length=80, label="Name", required=True)
|
||||
acronym = forms.CharField(max_length=40, label="Acronym", required=True)
|
||||
state = forms.ModelChoiceField(GroupStateName.objects.all(), label="State", required=True)
|
||||
|
||||
# roles
|
||||
chair_roles = SearchableEmailsField(label="Chairs", required=False, only_users=True)
|
||||
secr_roles = SearchableEmailsField(label="Secretaries", required=False, only_users=True)
|
||||
techadv_roles = SearchableEmailsField(label="Technical Advisors", required=False, only_users=True)
|
||||
delegate_roles = SearchableEmailsField(label="Delegates", required=False, only_users=True, max_entries=MAX_GROUP_DELEGATES,
|
||||
help_text=mark_safe("Chairs can delegate the authority to update the state of group documents - at most %s persons at a given time." % MAX_GROUP_DELEGATES))
|
||||
reviewer_roles = SearchableEmailsField(label="Reviewers", required=False, only_users=True)
|
||||
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active", role__group__type='area').order_by('name'), label="Shepherding AD", empty_label="(None)", required=False)
|
||||
|
||||
parent = forms.ModelChoiceField(Group.objects.filter(state="active").order_by('name'), empty_label="(None)", required=False)
|
||||
list_email = forms.CharField(max_length=64, required=False)
|
||||
list_subscribe = forms.CharField(max_length=255, required=False)
|
||||
list_archive = forms.CharField(max_length=255, required=False)
|
||||
urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", help_text="Format: https://site/path (Optional description). Separate multiple entries with newline. Prefer HTTPS URLs where possible.", required=False)
|
||||
|
||||
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)
|
||||
|
||||
if self.group_type == "rg":
|
||||
self.fields["state"].queryset = self.fields["state"].queryset.exclude(slug__in=("bof", "bof-conc"))
|
||||
|
||||
# if previous AD is now ex-AD, append that person to the list
|
||||
ad_pk = self.initial.get('ad')
|
||||
choices = self.fields['ad'].choices
|
||||
if ad_pk and ad_pk not in [pk for pk, name in choices]:
|
||||
self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())]
|
||||
|
||||
if self.group:
|
||||
self.fields['acronym'].widget.attrs['readonly'] = ""
|
||||
|
||||
if self.group_type == "rg":
|
||||
self.fields['ad'].widget = forms.HiddenInput()
|
||||
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(acronym="irtf")
|
||||
self.fields['parent'].initial = self.fields['parent'].queryset.first()
|
||||
self.fields['parent'].widget = forms.HiddenInput()
|
||||
else:
|
||||
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(type="area")
|
||||
self.fields['parent'].label = "IETF Area"
|
||||
|
||||
role_fields_to_remove = (set(strip_suffix(attr, "_roles") for attr in self.fields if attr.endswith("_roles"))
|
||||
- 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
|
||||
# over the place, loose history, and generally muck up a lot of
|
||||
# things, so we don't permit it
|
||||
if self.group:
|
||||
return self.group.acronym # no change permitted
|
||||
|
||||
acronym = self.cleaned_data['acronym'].strip().lower()
|
||||
|
||||
if not re.match(r'^[a-z][a-z0-9]+$', acronym):
|
||||
raise forms.ValidationError("Acronym is invalid, must be at least two characters and only contain lowercase letters and numbers starting with a letter.")
|
||||
|
||||
# be careful with acronyms, requiring confirmation to take existing or override historic
|
||||
existing = Group.objects.filter(acronym__iexact=acronym)
|
||||
if existing:
|
||||
existing = existing[0]
|
||||
|
||||
confirmed = self.data.get("confirm_acronym", False)
|
||||
|
||||
def insert_confirm_field(label, initial):
|
||||
# set required to false, we don't need it since we do the
|
||||
# validation of the field in here, and otherwise the
|
||||
# browser and Django may barf
|
||||
insert_after_in_ordered_dict(self.fields, "confirm_acronym", forms.BooleanField(label=label, required=False), after="acronym")
|
||||
# we can't set initial, it's ignored since the form is bound, instead mutate the data
|
||||
self.data = self.data.copy()
|
||||
self.data["confirm_acronym"] = initial
|
||||
|
||||
if existing and existing.type_id == self.group_type:
|
||||
if existing.state_id == "bof":
|
||||
insert_confirm_field(label="Turn BoF %s into proposed %s and start chartering it" % (existing.acronym, existing.type.name), initial=True)
|
||||
if confirmed:
|
||||
return acronym
|
||||
else:
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.name)
|
||||
else:
|
||||
insert_confirm_field(label="Set state of %s %s to proposed and start chartering it" % (existing.acronym, existing.type.name), initial=False)
|
||||
if confirmed:
|
||||
return acronym
|
||||
else:
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing %s (%s, %s)." % (existing.type.name, existing.name, existing.state.name if existing.state else "unknown state"))
|
||||
|
||||
if existing:
|
||||
raise forms.ValidationError("Acronym used for an existing group (%s)." % existing.name)
|
||||
|
||||
old = GroupHistory.objects.filter(acronym__iexact=acronym, type__in=("wg", "rg"))
|
||||
if old:
|
||||
insert_confirm_field(label="Confirm reusing acronym %s" % old[0].acronym, initial=False)
|
||||
if confirmed:
|
||||
return acronym
|
||||
else:
|
||||
raise forms.ValidationError("Warning: Acronym used for a historic group.")
|
||||
|
||||
return acronym
|
||||
|
||||
def clean_urls(self):
|
||||
return [x.strip() for x in self.cleaned_data["urls"].splitlines() if x.strip()]
|
||||
|
||||
def clean_delegates(self):
|
||||
if len(self.cleaned_data["delegates"]) > MAX_GROUP_DELEGATES:
|
||||
raise forms.ValidationError("At most %s delegates can be appointed at the same time, please remove %s delegates." % (
|
||||
MAX_GROUP_DELEGATES, len(self.cleaned_data["delegates"]) - MAX_GROUP_DELEGATES))
|
||||
return self.cleaned_data["delegates"]
|
||||
|
||||
def clean_parent(self):
|
||||
p = self.cleaned_data["parent"]
|
||||
seen = set()
|
||||
if self.group:
|
||||
seen.add(self.group)
|
||||
while p != None and p not in seen:
|
||||
seen.add(p)
|
||||
p = p.parent
|
||||
if p is None:
|
||||
return self.cleaned_data["parent"]
|
||||
else:
|
||||
raise forms.ValidationError("A group cannot be its own ancestor. "
|
||||
"Found that the group '%s' would end up being the ancestor of (%s)" % (p.acronym, ', '.join([g.acronym for g in seen])))
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(GroupForm, self).clean()
|
||||
state = cleaned_data.get('state', None)
|
||||
parent = cleaned_data.get('parent', None)
|
||||
if state and (state.slug in ['bof', ] and not parent):
|
||||
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
|
||||
|
||||
|
||||
class StreamEditForm(forms.Form):
|
||||
delegates = SearchableEmailsField(required=False, only_users=True)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
class ManageReviewRequestForm(forms.Form):
|
||||
ACTIONS = [
|
||||
("assign", "Assign"),
|
||||
("close", "Close"),
|
||||
]
|
||||
|
||||
action = forms.ChoiceField(choices=ACTIONS, widget=forms.HiddenInput, required=False)
|
||||
close = forms.ModelChoiceField(queryset=close_review_request_states(), required=False)
|
||||
reviewer = PersonEmailChoiceField(empty_label="(None)", required=False, label_with="person")
|
||||
add_skip = forms.BooleanField(required=False)
|
||||
|
||||
def __init__(self, review_req, *args, **kwargs):
|
||||
if not "prefix" in kwargs:
|
||||
if review_req.pk is None:
|
||||
kwargs["prefix"] = "r{}-{}".format(review_req.type_id, review_req.doc_id)
|
||||
else:
|
||||
kwargs["prefix"] = "r{}".format(review_req.pk)
|
||||
|
||||
super(ManageReviewRequestForm, self).__init__(*args, **kwargs)
|
||||
|
||||
if review_req.pk is None:
|
||||
self.fields["close"].queryset = self.fields["close"].queryset.filter(slug__in=["no-review-version", "no-review-document"])
|
||||
|
||||
close_initial = None
|
||||
if review_req.pk is None:
|
||||
close_initial = "no-review-version"
|
||||
elif review_req.reviewer:
|
||||
close_initial = "no-response"
|
||||
else:
|
||||
close_initial = "overtaken"
|
||||
|
||||
if close_initial:
|
||||
self.fields["close"].initial = close_initial
|
||||
|
||||
self.fields["close"].widget.attrs["class"] = "form-control input-sm"
|
||||
|
||||
setup_reviewer_field(self.fields["reviewer"], review_req)
|
||||
self.fields["reviewer"].widget.attrs["class"] = "form-control input-sm"
|
||||
|
||||
if self.is_bound:
|
||||
if self.data.get("action") == "close":
|
||||
self.fields["close"].required = True
|
||||
|
||||
|
||||
class EmailOpenAssignmentsForm(forms.Form):
|
||||
frm = forms.CharField(label="From", widget=forms.EmailInput(attrs={"readonly":True}))
|
||||
to = MultiEmailField()
|
||||
cc = MultiEmailField(required=False)
|
||||
reply_to = MultiEmailField(required=False)
|
||||
subject = forms.CharField()
|
||||
body = forms.CharField(widget=forms.Textarea, strip=False)
|
||||
|
||||
|
||||
class ReviewerSettingsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ReviewerSettings
|
||||
fields = ['min_interval', 'filter_re', 'skip_next', 'remind_days_before_deadline','expertise']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
exclude_fields = kwargs.pop('exclude_fields', [])
|
||||
super(ReviewerSettingsForm, self).__init__(*args, **kwargs)
|
||||
for field_name in exclude_fields:
|
||||
self.fields.pop(field_name)
|
||||
|
||||
def clean_skip_next(self):
|
||||
skip_next = self.cleaned_data.get('skip_next')
|
||||
if skip_next < 0:
|
||||
raise forms.ValidationError("Skip next must not be negative")
|
||||
return skip_next
|
||||
|
||||
|
||||
class AddUnavailablePeriodForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = UnavailablePeriod
|
||||
fields = ['start_date', 'end_date', 'availability']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddUnavailablePeriodForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["start_date"] = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label=self.fields["start_date"].label, help_text=self.fields["start_date"].help_text, required=self.fields["start_date"].required)
|
||||
self.fields["end_date"] = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label=self.fields["end_date"].label, help_text=self.fields["end_date"].help_text, required=self.fields["end_date"].required)
|
||||
|
||||
self.fields['availability'].widget = forms.RadioSelect(choices=UnavailablePeriod.LONG_AVAILABILITY_CHOICES)
|
||||
|
||||
def clean(self):
|
||||
start = self.cleaned_data.get("start_date")
|
||||
end = self.cleaned_data.get("end_date")
|
||||
if start and end and start > end:
|
||||
self.add_error("start_date", "Start date must be before or equal to end date.")
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class EndUnavailablePeriodForm(forms.Form):
|
||||
def __init__(self, start_date, *args, **kwargs):
|
||||
super(EndUnavailablePeriodForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["end_date"] = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1", "start-date": start_date.isoformat() if start_date else "" })
|
||||
|
||||
self.start_date = start_date
|
||||
|
||||
def clean_end_date(self):
|
||||
end = self.cleaned_data["end_date"]
|
||||
if self.start_date and end < self.start_date:
|
||||
raise forms.ValidationError("End date must be equal to or come after start date.")
|
||||
return end
|
||||
|
||||
|
||||
class ReviewSecretarySettingsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ReviewSecretarySettings
|
||||
fields = ['remind_days_before_deadline']
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ else:
|
|||
class StreamTests(TestCase):
|
||||
def test_streams(self):
|
||||
make_test_data()
|
||||
r = self.client.get(urlreverse("ietf.group.views_stream.streams"))
|
||||
r = self.client.get(urlreverse("ietf.group.views.streams"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue("Independent Submission Editor" in unicontent(r))
|
||||
|
||||
|
@ -45,7 +45,7 @@ class StreamTests(TestCase):
|
|||
draft.stream_id = "iab"
|
||||
draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_stream", by=Person.objects.get(user__username="secretary"), desc="Test")])
|
||||
|
||||
r = self.client.get(urlreverse("ietf.group.views_stream.stream_documents", kwargs=dict(acronym="iab")))
|
||||
r = self.client.get(urlreverse("ietf.group.views.stream_documents", kwargs=dict(acronym="iab")))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(draft.name in unicontent(r))
|
||||
|
||||
|
@ -54,7 +54,7 @@ class StreamTests(TestCase):
|
|||
|
||||
stream_acronym = "ietf"
|
||||
|
||||
url = urlreverse("ietf.group.views_stream.stream_edit", kwargs=dict(acronym=stream_acronym))
|
||||
url = urlreverse("ietf.group.views.stream_edit", kwargs=dict(acronym=stream_acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# get
|
||||
|
|
|
@ -8,6 +8,7 @@ import StringIO
|
|||
|
||||
from pyquery import PyQuery
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -317,7 +318,7 @@ class GroupPagesTests(TestCase):
|
|||
self.assertTrue(group.acronym in unicontent(r))
|
||||
self.assertTrue(group.description in unicontent(r))
|
||||
|
||||
for url in group_urlreverse_list(group, 'ietf.group.views_edit.edit'):
|
||||
for url in group_urlreverse_list(group, 'ietf.group.views.edit'):
|
||||
|
||||
for username in can_edit[group.type_id]:
|
||||
verify_can_edit_group(url, group, username)
|
||||
|
@ -432,7 +433,7 @@ class GroupEditTests(TestCase):
|
|||
def test_create(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('ietf.group.views_edit.edit', kwargs=dict(group_type="wg", action="charter"))
|
||||
url = urlreverse('ietf.group.views.edit', kwargs=dict(group_type="wg", action="charter"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
num_wgs = len(Group.objects.filter(type="wg"))
|
||||
|
@ -491,7 +492,7 @@ class GroupEditTests(TestCase):
|
|||
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('ietf.group.views_edit.edit', kwargs=dict(group_type="rg", action="charter"))
|
||||
url = urlreverse('ietf.group.views.edit', kwargs=dict(group_type="rg", action="charter"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
irtf = Group.objects.get(acronym='irtf')
|
||||
|
@ -516,7 +517,7 @@ class GroupEditTests(TestCase):
|
|||
def test_create_based_on_existing_bof(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('ietf.group.views_edit.edit', kwargs=dict(group_type="wg", action="charter"))
|
||||
url = urlreverse('ietf.group.views.edit', kwargs=dict(group_type="wg", action="charter"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
@ -551,7 +552,7 @@ class GroupEditTests(TestCase):
|
|||
make_test_data()
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
url = urlreverse('ietf.group.views_edit.edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym, action="edit"))
|
||||
url = urlreverse('ietf.group.views.edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym, action="edit"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
|
@ -629,7 +630,7 @@ class GroupEditTests(TestCase):
|
|||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
# Edit name
|
||||
url = urlreverse('ietf.group.views_edit.edit', kwargs=dict(acronym=group.acronym, action="edit", field="name"))
|
||||
url = urlreverse('ietf.group.views.edit', kwargs=dict(acronym=group.acronym, action="edit", field="name"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
|
@ -646,7 +647,7 @@ class GroupEditTests(TestCase):
|
|||
|
||||
|
||||
# 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"))
|
||||
url = urlreverse('ietf.group.views.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)
|
||||
|
@ -666,7 +667,7 @@ class GroupEditTests(TestCase):
|
|||
review_req = make_review_data(doc)
|
||||
group = review_req.team
|
||||
|
||||
url = urlreverse('ietf.group.views_edit.edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym, action="edit"))
|
||||
url = urlreverse('ietf.group.views.edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym, action="edit"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
|
@ -700,7 +701,7 @@ class GroupEditTests(TestCase):
|
|||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
url = urlreverse('ietf.group.views_edit.conclude', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
url = urlreverse('ietf.group.views.conclude', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
|
@ -1009,7 +1010,7 @@ class CustomizeWorkflowTests(TestCase):
|
|||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
url = urlreverse('ietf.group.views_edit.customize_workflow', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
url = urlreverse('ietf.group.views.customize_workflow', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
state = State.objects.get(used=True, type="draft-stream-ietf", slug="wg-lc")
|
||||
|
@ -1138,7 +1139,7 @@ class AjaxTests(TestCase):
|
|||
def test_group_menu_data(self):
|
||||
make_test_data()
|
||||
|
||||
r = self.client.get(urlreverse('ietf.group.views_ajax.group_menu_data'))
|
||||
r = self.client.get(urlreverse('ietf.group.views.group_menu_data'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
parents = json.loads(r.content)
|
||||
|
|
|
@ -17,7 +17,7 @@ from ietf.review.utils import (
|
|||
review_requests_needing_secretary_reminder, email_secretary_reminder,
|
||||
)
|
||||
from ietf.name.models import ReviewTypeName, ReviewResultName, ReviewRequestStateName
|
||||
import ietf.group.views_review
|
||||
import ietf.group.views
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
from ietf.dbtemplate.factories import DBTemplateFactory
|
||||
from ietf.person.factories import PersonFactory
|
||||
|
@ -29,14 +29,14 @@ class ReviewTests(TestCase):
|
|||
|
||||
group = review_req.team
|
||||
|
||||
for url in [urlreverse(ietf.group.views_review.review_requests, kwargs={ 'acronym': group.acronym }),
|
||||
urlreverse(ietf.group.views_review.review_requests, kwargs={ 'acronym': group.acronym , 'group_type': group.type_id})]:
|
||||
for url in [urlreverse(ietf.group.views.review_requests, kwargs={ 'acronym': group.acronym }),
|
||||
urlreverse(ietf.group.views.review_requests, kwargs={ 'acronym': group.acronym , 'group_type': group.type_id})]:
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(review_req.doc.name in unicontent(r))
|
||||
self.assertTrue(unicode(review_req.reviewer.person) in unicontent(r))
|
||||
|
||||
url = urlreverse(ietf.group.views_review.review_requests, kwargs={ 'acronym': group.acronym })
|
||||
url = urlreverse(ietf.group.views.review_requests, kwargs={ 'acronym': group.acronym })
|
||||
|
||||
# close request, listed under closed
|
||||
review_req.state = ReviewRequestStateName.objects.get(slug="completed")
|
||||
|
@ -138,8 +138,8 @@ class ReviewTests(TestCase):
|
|||
group = review_req1.team
|
||||
|
||||
# get
|
||||
for url in [urlreverse(ietf.group.views_review.reviewer_overview, kwargs={ 'acronym': group.acronym }),
|
||||
urlreverse(ietf.group.views_review.reviewer_overview, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id })]:
|
||||
for url in [urlreverse(ietf.group.views.reviewer_overview, kwargs={ 'acronym': group.acronym }),
|
||||
urlreverse(ietf.group.views.reviewer_overview, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id })]:
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(unicode(reviewer) in unicontent(r))
|
||||
|
@ -155,12 +155,12 @@ class ReviewTests(TestCase):
|
|||
|
||||
group = review_req1.team
|
||||
|
||||
url = urlreverse(ietf.group.views_review.manage_review_requests, kwargs={ 'acronym': group.acronym, "assignment_status": "assigned" })
|
||||
url = urlreverse(ietf.group.views.manage_review_requests, kwargs={ 'acronym': group.acronym, "assignment_status": "assigned" })
|
||||
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
assigned_url = urlreverse(ietf.group.views_review.manage_review_requests, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id, "assignment_status": "assigned" })
|
||||
unassigned_url = urlreverse(ietf.group.views_review.manage_review_requests, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id, "assignment_status": "unassigned" })
|
||||
assigned_url = urlreverse(ietf.group.views.manage_review_requests, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id, "assignment_status": "assigned" })
|
||||
unassigned_url = urlreverse(ietf.group.views.manage_review_requests, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id, "assignment_status": "unassigned" })
|
||||
|
||||
review_req2 = ReviewRequest.objects.create(
|
||||
doc=review_req1.doc,
|
||||
|
@ -310,11 +310,11 @@ class ReviewTests(TestCase):
|
|||
|
||||
group = review_req1.team
|
||||
|
||||
url = urlreverse(ietf.group.views_review.email_open_review_assignments, kwargs={ 'acronym': group.acronym })
|
||||
url = urlreverse(ietf.group.views.email_open_review_assignments, kwargs={ 'acronym': group.acronym })
|
||||
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
url = urlreverse(ietf.group.views_review.email_open_review_assignments, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id })
|
||||
url = urlreverse(ietf.group.views.email_open_review_assignments, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id })
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
@ -351,14 +351,14 @@ class ReviewTests(TestCase):
|
|||
|
||||
reviewer = review_req.reviewer.person
|
||||
|
||||
url = urlreverse(ietf.group.views_review.change_reviewer_settings, kwargs={
|
||||
url = urlreverse(ietf.group.views.change_reviewer_settings, kwargs={
|
||||
"acronym": review_req.team.acronym,
|
||||
"reviewer_email": review_req.reviewer_id,
|
||||
})
|
||||
|
||||
login_testing_unauthorized(self, reviewer.user.username, url)
|
||||
|
||||
url = urlreverse(ietf.group.views_review.change_reviewer_settings, kwargs={
|
||||
url = urlreverse(ietf.group.views.change_reviewer_settings, kwargs={
|
||||
"group_type": review_req.team.type_id,
|
||||
"acronym": review_req.team.acronym,
|
||||
"reviewer_email": review_req.reviewer_id,
|
||||
|
@ -472,13 +472,13 @@ class ReviewTests(TestCase):
|
|||
|
||||
secretary = Person.objects.get(user__username="reviewsecretary")
|
||||
|
||||
url = urlreverse(ietf.group.views_review.change_review_secretary_settings, kwargs={
|
||||
url = urlreverse(ietf.group.views.change_review_secretary_settings, kwargs={
|
||||
"acronym": review_req.team.acronym,
|
||||
})
|
||||
|
||||
login_testing_unauthorized(self, secretary.user.username, url)
|
||||
|
||||
url = urlreverse(ietf.group.views_review.change_review_secretary_settings, kwargs={
|
||||
url = urlreverse(ietf.group.views.change_review_secretary_settings, kwargs={
|
||||
"group_type": review_req.team.type_id,
|
||||
"acronym": review_req.team.acronym,
|
||||
})
|
||||
|
|
|
@ -1,21 +1,90 @@
|
|||
# Copyright The IETF Trust 2007, All Rights Reserved
|
||||
|
||||
from django.conf.urls import include
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from ietf.group import views, views_ajax, views_edit
|
||||
from ietf.community import views as community_views
|
||||
from ietf.doc import views_material as material_views
|
||||
from ietf.group import views, milestones as milestone_views
|
||||
from ietf.utils.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
# These are not used externally, only in include statements further down:
|
||||
info_detail_urls = [
|
||||
url(r'^$', views.group_home),
|
||||
url(r'^documents/txt/$', views.group_documents_txt),
|
||||
url(r'^documents/$', views.group_documents),
|
||||
url(r'^documents/manage/$', community_views.manage_list),
|
||||
url(r'^documents/csv/$', community_views.export_to_csv),
|
||||
url(r'^documents/feed/$', community_views.feed),
|
||||
url(r'^documents/subscription/$', community_views.subscription),
|
||||
url(r'^charter/$', views.group_about),
|
||||
url(r'^about/$', views.group_about),
|
||||
url(r'^about/status/$', views.group_about_status),
|
||||
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'^history/$',views.history),
|
||||
url(r'^email/$', views.email),
|
||||
url(r'^deps/(?P<output_type>[\w-]+)/$', views.dependencies),
|
||||
url(r'^meetings/$', views.meetings),
|
||||
url(r'^edit/$', views.edit, {'action': "edit"}),
|
||||
url(r'^edit/(?P<field>\w+)/?$', views.edit, {'action': "edit"}),
|
||||
url(r'^conclude/$', views.conclude),
|
||||
url(r'^milestones/$', milestone_views.edit_milestones, {'milestone_set': "current"}, name='ietf.group.milestones.edit_milestones;current'),
|
||||
url(r'^milestones/charter/$', milestone_views.edit_milestones, {'milestone_set': "charter"}, name='ietf.group.milestones.edit_milestones;charter'),
|
||||
url(r'^milestones/charter/reset/$', milestone_views.reset_charter_milestones, None, 'ietf.group.milestones.reset_charter_milestones'),
|
||||
url(r'^workflow/$', views.customize_workflow),
|
||||
url(r'^materials/$', views.materials),
|
||||
url(r'^materials/new/$', material_views.choose_material_type),
|
||||
url(r'^materials/new/(?P<doc_type>[\w-]+)/$', material_views.edit_material, { 'action': "new" }, 'ietf.doc.views_material.edit_material'),
|
||||
url(r'^archives/$', views.derived_archives),
|
||||
url(r'^photos/$', views.group_photos),
|
||||
url(r'^reviews/$', views.review_requests),
|
||||
url(r'^reviews/manage/(?P<assignment_status>assigned|unassigned)/$', views.manage_review_requests),
|
||||
url(r'^reviews/email-assignments/$', views.email_open_review_assignments),
|
||||
url(r'^reviewers/$', views.reviewer_overview),
|
||||
url(r'^reviewers/(?P<reviewer_email>[\w%+-.@]+)/settings/$', views.change_reviewer_settings),
|
||||
url(r'^secretarysettings/$', views.change_review_secretary_settings),
|
||||
url(r'^email-aliases/$', RedirectView.as_view(pattern_name=views.email,permanent=False),name='ietf.group.urls_info_details.redirect.email'),
|
||||
]
|
||||
|
||||
|
||||
group_urls = [
|
||||
url(r'^$', views.active_groups),
|
||||
url(r'^groupmenu.json', views_ajax.group_menu_data, None, 'ietf.group.views_ajax.group_menu_data'),
|
||||
url(r'^%(acronym)s.json$' % settings.URL_REGEXPS, views_ajax.group_json),
|
||||
url(r'^groupmenu.json', views.group_menu_data, None, 'ietf.group.views.group_menu_data'),
|
||||
url(r'^%(acronym)s.json$' % settings.URL_REGEXPS, views.group_json),
|
||||
url(r'^chartering/$', views.chartering_groups),
|
||||
url(r'^chartering/create/(?P<group_type>(wg|rg))/$', views_edit.edit, {'action': "charter"}),
|
||||
url(r'^chartering/create/(?P<group_type>(wg|rg))/$', views.edit, {'action': "charter"}),
|
||||
url(r'^concluded/$', views.concluded_groups),
|
||||
url(r'^email-aliases/$', views.email_aliases),
|
||||
url(r'^all-status/$', views.all_status),
|
||||
|
||||
#
|
||||
url(r'^%(acronym)s/$' % settings.URL_REGEXPS, views.group_home),
|
||||
url(r'^%(acronym)s/' % settings.URL_REGEXPS, include('ietf.group.urls_info_details')),
|
||||
url(r'^%(acronym)s/' % settings.URL_REGEXPS, include(info_detail_urls)),
|
||||
]
|
||||
|
||||
|
||||
stream_urls = [
|
||||
url(r'^$', views.streams),
|
||||
url(r'^%(acronym)s/$' % settings.URL_REGEXPS, views.stream_documents, None),
|
||||
url(r'^%(acronym)s/edit/$' % settings.URL_REGEXPS, views.stream_edit),
|
||||
]
|
||||
|
||||
|
||||
grouptype_urls = [
|
||||
url(r'^$', views.active_groups),
|
||||
url(r'^summary.txt', RedirectView.as_view(url='/wg/1wg-summary.txt', permanent=True)),
|
||||
url(r'^summary-by-area.txt', RedirectView.as_view(url='/wg/1wg-summary.txt', permanent=True)),
|
||||
url(r'^summary-by-acronym.txt', RedirectView.as_view(url='/wg/1wg-summary-by-acronym.txt', permanent=True)),
|
||||
url(r'^1wg-summary.txt', views.wg_summary_area),
|
||||
url(r'^1wg-summary-by-acronym.txt', views.wg_summary_acronym),
|
||||
url(r'^1wg-charters.txt', views.wg_charters),
|
||||
url(r'^1wg-charters-by-acronym.txt', views.wg_charters_by_acronym),
|
||||
url(r'^chartering/$', RedirectView.as_view(url='/group/chartering/', permanent=True)),
|
||||
url(r'^chartering/create/$', RedirectView.as_view(url='/group/chartering/create/%(group_type)s/', permanent=True)),
|
||||
url(r'^bofs/$', views.bofs),
|
||||
url(r'^email-aliases/$', views.email_aliases),
|
||||
url(r'^bofs/create/$', views.edit, {'action': "create", }),
|
||||
url(r'^photos/$', views.chair_photos),
|
||||
url(r'^%(acronym)s/' % settings.URL_REGEXPS, include(info_detail_urls)),
|
||||
]
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
# Copyright The IETF Trust 2008, All Rights Reserved
|
||||
|
||||
from django.conf.urls import include
|
||||
from django.views.generic import RedirectView
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.group import views, views_edit
|
||||
from ietf.utils.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.active_groups),
|
||||
url(r'^summary.txt', RedirectView.as_view(url='/wg/1wg-summary.txt', permanent=True)),
|
||||
url(r'^summary-by-area.txt', RedirectView.as_view(url='/wg/1wg-summary.txt', permanent=True)),
|
||||
url(r'^summary-by-acronym.txt', RedirectView.as_view(url='/wg/1wg-summary-by-acronym.txt', permanent=True)),
|
||||
url(r'^1wg-summary.txt', views.wg_summary_area),
|
||||
url(r'^1wg-summary-by-acronym.txt', views.wg_summary_acronym),
|
||||
url(r'^1wg-charters.txt', views.wg_charters),
|
||||
url(r'^1wg-charters-by-acronym.txt', views.wg_charters_by_acronym),
|
||||
url(r'^chartering/$', RedirectView.as_view(url='/group/chartering/', permanent=True)),
|
||||
url(r'^chartering/create/$', RedirectView.as_view(url='/group/chartering/create/%(group_type)s/', permanent=True)),
|
||||
url(r'^bofs/$', views.bofs),
|
||||
url(r'^email-aliases/$', views.email_aliases),
|
||||
url(r'^bofs/create/$', views_edit.edit, {'action': "create", }),
|
||||
url(r'^photos/$', views.chair_photos),
|
||||
url(r'^%(acronym)s/' % settings.URL_REGEXPS, include('ietf.group.urls_info_details')),
|
||||
]
|
|
@ -1,44 +0,0 @@
|
|||
from django.views.generic import RedirectView
|
||||
|
||||
from ietf.community import views as community_views
|
||||
from ietf.doc import views_material as material_views
|
||||
from ietf.group import views, views_edit, views_review, milestones as milestone_views
|
||||
from ietf.utils.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.group_home),
|
||||
url(r'^documents/txt/$', views.group_documents_txt),
|
||||
url(r'^documents/$', views.group_documents),
|
||||
url(r'^documents/manage/$', community_views.manage_list),
|
||||
url(r'^documents/csv/$', community_views.export_to_csv),
|
||||
url(r'^documents/feed/$', community_views.feed),
|
||||
url(r'^documents/subscription/$', community_views.subscription),
|
||||
url(r'^charter/$', views.group_about),
|
||||
url(r'^about/$', views.group_about),
|
||||
url(r'^about/status/$', views.group_about_status),
|
||||
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'^history/$',views.history),
|
||||
url(r'^email/$', views.email),
|
||||
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"}, name='ietf.group.milestones.edit_milestones;current'),
|
||||
url(r'^milestones/charter/$', milestone_views.edit_milestones, {'milestone_set': "charter"}, name='ietf.group.milestones.edit_milestones;charter'),
|
||||
url(r'^milestones/charter/reset/$', milestone_views.reset_charter_milestones, None, 'ietf.group.milestones.reset_charter_milestones'),
|
||||
url(r'^workflow/$', views_edit.customize_workflow),
|
||||
url(r'^materials/$', views.materials),
|
||||
url(r'^materials/new/$', material_views.choose_material_type),
|
||||
url(r'^materials/new/(?P<doc_type>[\w-]+)/$', material_views.edit_material, { 'action': "new" }, 'ietf.doc.views_material.edit_material'),
|
||||
url(r'^archives/$', views.derived_archives),
|
||||
url(r'^photos/$', views.group_photos),
|
||||
url(r'^reviews/$', views_review.review_requests),
|
||||
url(r'^reviews/manage/(?P<assignment_status>assigned|unassigned)/$', views_review.manage_review_requests),
|
||||
url(r'^reviews/email-assignments/$', views_review.email_open_review_assignments),
|
||||
url(r'^reviewers/$', views_review.reviewer_overview),
|
||||
url(r'^reviewers/(?P<reviewer_email>[\w%+-.@]+)/settings/$', views_review.change_reviewer_settings),
|
||||
url(r'^secretarysettings/$', views_review.change_review_secretary_settings),
|
||||
url(r'^email-aliases/$', RedirectView.as_view(pattern_name=views.email,permanent=False),name='ietf.group.urls_info_details.redirect.email'),
|
||||
]
|
|
@ -1,12 +0,0 @@
|
|||
# Copyright The IETF Trust 2008, All Rights Reserved
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.group import views_stream
|
||||
from ietf.utils.urls import url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views_stream.streams),
|
||||
url(r'^%(acronym)s/$' % settings.URL_REGEXPS, views_stream.stream_documents, None),
|
||||
url(r'^%(acronym)s/edit/$' % settings.URL_REGEXPS, views_stream.stream_edit),
|
||||
]
|
|
@ -177,9 +177,9 @@ def construct_group_menu_context(request, group, selected, group_type, others):
|
|||
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:
|
||||
import ietf.group.views_review
|
||||
entries.append(("Review requests", urlreverse(ietf.group.views_review.review_requests, kwargs=kwargs)))
|
||||
entries.append(("Reviewers", urlreverse(ietf.group.views_review.reviewer_overview, kwargs=kwargs)))
|
||||
import ietf.group.views
|
||||
entries.append(("Review requests", urlreverse(ietf.group.views.review_requests, kwargs=kwargs)))
|
||||
entries.append(("Reviewers", urlreverse(ietf.group.views.reviewer_overview, kwargs=kwargs)))
|
||||
if group.type_id in ('rg','wg','team'):
|
||||
entries.append(("Meetings", urlreverse("ietf.group.views.meetings", kwargs=kwargs)))
|
||||
entries.append(("History", urlreverse("ietf.group.views.history", kwargs=kwargs)))
|
||||
|
@ -214,23 +214,23 @@ def construct_group_menu_context(request, group, selected, group_type, others):
|
|||
actions.append((u"Upload material", urlreverse("ietf.doc.views_material.choose_material_type", kwargs=kwargs)))
|
||||
|
||||
if group.features.has_reviews and can_manage_review_requests_for_team(request.user, group):
|
||||
import ietf.group.views_review
|
||||
actions.append((u"Manage unassigned reviews", urlreverse(ietf.group.views_review.manage_review_requests, kwargs=dict(assignment_status="unassigned", **kwargs))))
|
||||
actions.append((u"Manage assigned reviews", urlreverse(ietf.group.views_review.manage_review_requests, kwargs=dict(assignment_status="assigned", **kwargs))))
|
||||
import ietf.group.views
|
||||
actions.append((u"Manage unassigned reviews", urlreverse(ietf.group.views.manage_review_requests, kwargs=dict(assignment_status="unassigned", **kwargs))))
|
||||
actions.append((u"Manage assigned reviews", urlreverse(ietf.group.views.manage_review_requests, kwargs=dict(assignment_status="assigned", **kwargs))))
|
||||
|
||||
if Role.objects.filter(name="secr", group=group, person__user=request.user).exists():
|
||||
actions.append((u"Secretary settings", urlreverse(ietf.group.views_review.change_review_secretary_settings, kwargs=kwargs)))
|
||||
actions.append((u"Secretary settings", urlreverse(ietf.group.views.change_review_secretary_settings, kwargs=kwargs)))
|
||||
|
||||
|
||||
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"))))
|
||||
actions.append((u"Edit group", urlreverse("ietf.group.views.edit", kwargs=dict(kwargs, action="edit"))))
|
||||
|
||||
if group.features.customize_workflow and (is_admin or can_manage):
|
||||
actions.append((u"Customize workflow", urlreverse("ietf.group.views_edit.customize_workflow", kwargs=kwargs)))
|
||||
actions.append((u"Customize workflow", urlreverse("ietf.group.views.customize_workflow", kwargs=kwargs)))
|
||||
|
||||
if group.state_id in ("active", "dormant") and not group.type_id in ["sdo", "rfcedtyp", "isoc", ] and can_manage:
|
||||
actions.append((u"Request closing group", urlreverse("ietf.group.views_edit.conclude", kwargs=kwargs)))
|
||||
actions.append((u"Request closing group", urlreverse("ietf.group.views.conclude", kwargs=kwargs)))
|
||||
|
||||
d = {
|
||||
"group": group,
|
||||
|
|
1297
ietf/group/views.py
1297
ietf/group/views.py
File diff suppressed because it is too large
Load diff
|
@ -1,29 +0,0 @@
|
|||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse as urlreverse
|
||||
from django.utils.html import escape
|
||||
from django.views.decorators.cache import cache_page, cache_control
|
||||
|
||||
from ietf.group.models import Group
|
||||
|
||||
def group_json(request, acronym):
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
||||
return HttpResponse(json.dumps(group.json_dict(request.build_absolute_uri('/')),
|
||||
sort_keys=True, indent=2),
|
||||
content_type="text/json")
|
||||
|
||||
@cache_control(public=True, max_age=30*60)
|
||||
@cache_page(30 * 60)
|
||||
def group_menu_data(request):
|
||||
groups = Group.objects.filter(state="active", type__in=("wg", "rg"), parent__state="active").order_by("acronym")
|
||||
|
||||
groups_by_parent = defaultdict(list)
|
||||
for g in groups:
|
||||
url = urlreverse("ietf.group.views.group_home", kwargs={ 'group_type': g.type_id, 'acronym': g.acronym })
|
||||
groups_by_parent[g.parent_id].append({ 'acronym': g.acronym, 'name': escape(g.name), 'url': url })
|
||||
|
||||
return JsonResponse(groups_by_parent)
|
|
@ -1,558 +0,0 @@
|
|||
# edit/create view for groups
|
||||
|
||||
import re
|
||||
import datetime
|
||||
|
||||
from django import forms
|
||||
from django.shortcuts import render, redirect
|
||||
from django.http import HttpResponse, HttpResponseForbidden, Http404, HttpResponseRedirect
|
||||
from django.utils.html import mark_safe
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import DocTagName, State
|
||||
from ietf.doc.utils import get_tags_for_stream_id
|
||||
from ietf.doc.utils_charter import charter_name_for_group
|
||||
from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStateName,
|
||||
GroupStateTransitions, GroupTypeName, GroupURL, ChangeStateGroupEvent )
|
||||
from ietf.group.utils import (save_group_in_history, can_manage_group, can_manage_group_type,
|
||||
get_group_or_404, setup_default_community_list_for_group, )
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.person.fields import SearchableEmailsField
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.group.mails import ( email_admin_re_charter, email_personnel_change)
|
||||
from ietf.utils.ordereddict import insert_after_in_ordered_dict
|
||||
from ietf.utils.text import strip_suffix
|
||||
|
||||
|
||||
# This function, in addition to encapsulating a group's roles list for
|
||||
# readability, also ensures that the roles with edit buttons in forms
|
||||
# are the same which are accepted byt the GroupForm. Please adjust the
|
||||
# list here if you change the *_roles fields the GroupForm knows about.
|
||||
def roles_for_group_type(group_type):
|
||||
roles = ["chair", "secr", "techadv", "delegate", ]
|
||||
if group_type == "dir":
|
||||
roles.append("reviewer")
|
||||
return roles
|
||||
|
||||
MAX_GROUP_DELEGATES = 3
|
||||
|
||||
class GroupForm(forms.Form):
|
||||
name = forms.CharField(max_length=80, label="Name", required=True)
|
||||
acronym = forms.CharField(max_length=40, label="Acronym", required=True)
|
||||
state = forms.ModelChoiceField(GroupStateName.objects.all(), label="State", required=True)
|
||||
|
||||
# roles
|
||||
chair_roles = SearchableEmailsField(label="Chairs", required=False, only_users=True)
|
||||
secr_roles = SearchableEmailsField(label="Secretaries", required=False, only_users=True)
|
||||
techadv_roles = SearchableEmailsField(label="Technical Advisors", required=False, only_users=True)
|
||||
delegate_roles = SearchableEmailsField(label="Delegates", required=False, only_users=True, max_entries=MAX_GROUP_DELEGATES,
|
||||
help_text=mark_safe("Chairs can delegate the authority to update the state of group documents - at most %s persons at a given time." % MAX_GROUP_DELEGATES))
|
||||
reviewer_roles = SearchableEmailsField(label="Reviewers", required=False, only_users=True)
|
||||
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active", role__group__type='area').order_by('name'), label="Shepherding AD", empty_label="(None)", required=False)
|
||||
|
||||
parent = forms.ModelChoiceField(Group.objects.filter(state="active").order_by('name'), empty_label="(None)", required=False)
|
||||
list_email = forms.CharField(max_length=64, required=False)
|
||||
list_subscribe = forms.CharField(max_length=255, required=False)
|
||||
list_archive = forms.CharField(max_length=255, required=False)
|
||||
urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", help_text="Format: https://site/path (Optional description). Separate multiple entries with newline. Prefer HTTPS URLs where possible.", required=False)
|
||||
|
||||
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)
|
||||
|
||||
if self.group_type == "rg":
|
||||
self.fields["state"].queryset = self.fields["state"].queryset.exclude(slug__in=("bof", "bof-conc"))
|
||||
|
||||
# if previous AD is now ex-AD, append that person to the list
|
||||
ad_pk = self.initial.get('ad')
|
||||
choices = self.fields['ad'].choices
|
||||
if ad_pk and ad_pk not in [pk for pk, name in choices]:
|
||||
self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())]
|
||||
|
||||
if self.group:
|
||||
self.fields['acronym'].widget.attrs['readonly'] = ""
|
||||
|
||||
if self.group_type == "rg":
|
||||
self.fields['ad'].widget = forms.HiddenInput()
|
||||
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(acronym="irtf")
|
||||
self.fields['parent'].initial = self.fields['parent'].queryset.first()
|
||||
self.fields['parent'].widget = forms.HiddenInput()
|
||||
else:
|
||||
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(type="area")
|
||||
self.fields['parent'].label = "IETF Area"
|
||||
|
||||
role_fields_to_remove = (set(strip_suffix(attr, "_roles") for attr in self.fields if attr.endswith("_roles"))
|
||||
- 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
|
||||
# over the place, loose history, and generally muck up a lot of
|
||||
# things, so we don't permit it
|
||||
if self.group:
|
||||
return self.group.acronym # no change permitted
|
||||
|
||||
acronym = self.cleaned_data['acronym'].strip().lower()
|
||||
|
||||
if not re.match(r'^[a-z][a-z0-9]+$', acronym):
|
||||
raise forms.ValidationError("Acronym is invalid, must be at least two characters and only contain lowercase letters and numbers starting with a letter.")
|
||||
|
||||
# be careful with acronyms, requiring confirmation to take existing or override historic
|
||||
existing = Group.objects.filter(acronym__iexact=acronym)
|
||||
if existing:
|
||||
existing = existing[0]
|
||||
|
||||
confirmed = self.data.get("confirm_acronym", False)
|
||||
|
||||
def insert_confirm_field(label, initial):
|
||||
# set required to false, we don't need it since we do the
|
||||
# validation of the field in here, and otherwise the
|
||||
# browser and Django may barf
|
||||
insert_after_in_ordered_dict(self.fields, "confirm_acronym", forms.BooleanField(label=label, required=False), after="acronym")
|
||||
# we can't set initial, it's ignored since the form is bound, instead mutate the data
|
||||
self.data = self.data.copy()
|
||||
self.data["confirm_acronym"] = initial
|
||||
|
||||
if existing and existing.type_id == self.group_type:
|
||||
if existing.state_id == "bof":
|
||||
insert_confirm_field(label="Turn BoF %s into proposed %s and start chartering it" % (existing.acronym, existing.type.name), initial=True)
|
||||
if confirmed:
|
||||
return acronym
|
||||
else:
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.name)
|
||||
else:
|
||||
insert_confirm_field(label="Set state of %s %s to proposed and start chartering it" % (existing.acronym, existing.type.name), initial=False)
|
||||
if confirmed:
|
||||
return acronym
|
||||
else:
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing %s (%s, %s)." % (existing.type.name, existing.name, existing.state.name if existing.state else "unknown state"))
|
||||
|
||||
if existing:
|
||||
raise forms.ValidationError("Acronym used for an existing group (%s)." % existing.name)
|
||||
|
||||
old = GroupHistory.objects.filter(acronym__iexact=acronym, type__in=("wg", "rg"))
|
||||
if old:
|
||||
insert_confirm_field(label="Confirm reusing acronym %s" % old[0].acronym, initial=False)
|
||||
if confirmed:
|
||||
return acronym
|
||||
else:
|
||||
raise forms.ValidationError("Warning: Acronym used for a historic group.")
|
||||
|
||||
return acronym
|
||||
|
||||
def clean_urls(self):
|
||||
return [x.strip() for x in self.cleaned_data["urls"].splitlines() if x.strip()]
|
||||
|
||||
def clean_delegates(self):
|
||||
if len(self.cleaned_data["delegates"]) > MAX_GROUP_DELEGATES:
|
||||
raise forms.ValidationError("At most %s delegates can be appointed at the same time, please remove %s delegates." % (
|
||||
MAX_GROUP_DELEGATES, len(self.cleaned_data["delegates"]) - MAX_GROUP_DELEGATES))
|
||||
return self.cleaned_data["delegates"]
|
||||
|
||||
def clean_parent(self):
|
||||
p = self.cleaned_data["parent"]
|
||||
seen = set()
|
||||
if self.group:
|
||||
seen.add(self.group)
|
||||
while p != None and p not in seen:
|
||||
seen.add(p)
|
||||
p = p.parent
|
||||
if p is None:
|
||||
return self.cleaned_data["parent"]
|
||||
else:
|
||||
raise forms.ValidationError("A group cannot be its own ancestor. "
|
||||
"Found that the group '%s' would end up being the ancestor of (%s)" % (p.acronym, ', '.join([g.acronym for g in seen])))
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(GroupForm, self).clean()
|
||||
state = cleaned_data.get('state', None)
|
||||
parent = cleaned_data.get('parent', None)
|
||||
if state and (state.slug in ['bof', ] and not parent):
|
||||
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:
|
||||
if u.name:
|
||||
res.append(u"%s (%s)" % (u.url, u.name))
|
||||
else:
|
||||
res.append(u.url)
|
||||
return fs.join(res)
|
||||
|
||||
## XXX Remove after testing
|
||||
# def get_or_create_initial_charter(group, group_type):
|
||||
# charter_name = charter_name_for_group(group)
|
||||
#
|
||||
# try:
|
||||
# charter = Document.objects.get(docalias__name=charter_name)
|
||||
# except Document.DoesNotExist:
|
||||
# charter = Document(
|
||||
# name=charter_name,
|
||||
# type_id="charter",
|
||||
# title=group.name,
|
||||
# group=group,
|
||||
# abstract=group.name,
|
||||
# rev="00-00",
|
||||
# )
|
||||
# charter.save()
|
||||
# charter.set_state(State.objects.get(used=True, type="charter", slug="notrev"))
|
||||
#
|
||||
# # Create an alias as well
|
||||
# DocAlias.objects.create(name=charter.name, document=charter)
|
||||
#
|
||||
# return charter
|
||||
#
|
||||
# @login_required
|
||||
# def submit_initial_charter(request, group_type=None, acronym=None):
|
||||
#
|
||||
# # This needs refactoring.
|
||||
# # The signature assumed you could have groups with the same name, but with different types, which we do not allow.
|
||||
# # Consequently, this can be called with an existing group acronym and a type
|
||||
# # that doesn't match the existing group type. The code below essentially ignores the group_type argument.
|
||||
# #
|
||||
# # If possible, the use of get_or_create_initial_charter should be moved
|
||||
# # directly into charter_submit, and this function should go away.
|
||||
#
|
||||
# if acronym==None:
|
||||
# raise Http404
|
||||
#
|
||||
# group = get_object_or_404(Group, acronym=acronym)
|
||||
# if not group.features.has_chartering_process:
|
||||
# raise Http404
|
||||
#
|
||||
# # This is where we start ignoring the passed in group_type
|
||||
# group_type = group.type_id
|
||||
#
|
||||
# if not can_manage_group(request.user, group):
|
||||
# return HttpResponseForbidden("You don't have permission to access this view")
|
||||
#
|
||||
# if not group.charter:
|
||||
# group.charter = get_or_create_initial_charter(group, group_type)
|
||||
# group.save()
|
||||
#
|
||||
# return redirect('ietf.doc.views_charter.submit', name=group.charter.name, option="initcharter")
|
||||
|
||||
@login_required
|
||||
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":
|
||||
new_group = False
|
||||
elif action in ("create","charter"):
|
||||
group = None
|
||||
new_group = True
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
if not new_group:
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
if not group_type and group:
|
||||
group_type = group.type_id
|
||||
if not (can_manage_group(request.user, group)
|
||||
or group.has_role(request.user, group.features.admin_roles)):
|
||||
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, field=field)
|
||||
if form.is_valid():
|
||||
clean = form.cleaned_data
|
||||
if new_group:
|
||||
try:
|
||||
group = Group.objects.get(acronym=clean["acronym"])
|
||||
save_group_in_history(group)
|
||||
group.time = datetime.datetime.now()
|
||||
group.save()
|
||||
except Group.DoesNotExist:
|
||||
group = Group.objects.create(name=clean["name"],
|
||||
acronym=clean["acronym"],
|
||||
type=GroupTypeName.objects.get(slug=group_type),
|
||||
state=clean["state"]
|
||||
)
|
||||
|
||||
if group.features.has_documents:
|
||||
setup_default_community_list_for_group(group)
|
||||
|
||||
e = ChangeStateGroupEvent(group=group, type="changed_state")
|
||||
e.time = group.time
|
||||
e.by = request.user.person
|
||||
e.state_id = clean["state"].slug
|
||||
e.desc = "Group created in state %s" % clean["state"].name
|
||||
e.save()
|
||||
else:
|
||||
save_group_in_history(group)
|
||||
|
||||
|
||||
## XXX Remove after testing
|
||||
# if action == "charter" and not group.charter: # make sure we have a charter
|
||||
# group.charter = get_or_create_initial_charter(group, group_type)
|
||||
|
||||
changes = []
|
||||
|
||||
def desc(attr, new, old):
|
||||
entry = "%(attr)s changed to <b>%(new)s</b> from %(old)s"
|
||||
if new_group:
|
||||
entry = "%(attr)s changed to <b>%(new)s</b>"
|
||||
|
||||
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)))
|
||||
setattr(group, attr, clean[attr])
|
||||
|
||||
# update the attributes, keeping track of what we're doing
|
||||
diff('name', "Name")
|
||||
diff('acronym', "Acronym")
|
||||
diff('state', "State")
|
||||
diff('parent', "IETF Area" if group.type=="wg" else "Group parent")
|
||||
diff('list_email', "Mailing list email")
|
||||
diff('list_subscribe', "Mailing list subscribe address")
|
||||
diff('list_archive', "Mailing list archive")
|
||||
|
||||
personnel_change_text=""
|
||||
changed_personnel = set()
|
||||
# update roles
|
||||
for attr, f in form.fields.iteritems():
|
||||
if not (attr.endswith("_roles") or attr == "ad"):
|
||||
continue
|
||||
|
||||
slug = attr
|
||||
slug = strip_suffix(slug, "_roles")
|
||||
|
||||
title = f.label
|
||||
|
||||
new = clean[attr]
|
||||
if attr == 'ad':
|
||||
new = [ new.role_email('ad') ] if new else []
|
||||
old = Email.objects.filter(role__group=group, role__name=slug).select_related("person")
|
||||
if set(new) != set(old):
|
||||
changes.append((attr, new, desc(title,
|
||||
", ".join(x.get_name() for x in new),
|
||||
", ".join(x.get_name() for x in old))))
|
||||
group.role_set.filter(name=slug).delete()
|
||||
for e in new:
|
||||
Role.objects.get_or_create(name_id=slug, email=e, group=group, person=e.person)
|
||||
added = set(new) - set(old)
|
||||
deleted = set(old) - set(new)
|
||||
if added:
|
||||
change_text=title + ' added: ' + ", ".join(x.name_and_email() for x in added)
|
||||
personnel_change_text+=change_text+"\n"
|
||||
if deleted:
|
||||
change_text=title + ' deleted: ' + ", ".join(x.name_and_email() for x in deleted)
|
||||
personnel_change_text+=change_text+"\n"
|
||||
changed_personnel.update(set(old)^set(new))
|
||||
|
||||
if personnel_change_text!="":
|
||||
email_personnel_change(request, group, personnel_change_text, changed_personnel)
|
||||
|
||||
# update urls
|
||||
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()
|
||||
|
||||
if changes and not new_group:
|
||||
for attr, new, desc in changes:
|
||||
if attr == 'state':
|
||||
ChangeStateGroupEvent.objects.create(group=group, time=group.time, state=new, by=request.user.person, type="changed_state", desc=desc)
|
||||
else:
|
||||
GroupEvent.objects.create(group=group, time=group.time, by=request.user.person, type="info_changed", desc=desc)
|
||||
|
||||
group.save()
|
||||
|
||||
if action=="charter":
|
||||
return redirect('ietf.doc.views_charter.submit', name=charter_name_for_group(group), option="initcharter")
|
||||
|
||||
return HttpResponseRedirect(group.about_url())
|
||||
else: # form.is_valid()
|
||||
if not new_group:
|
||||
ad_role = group.ad_role()
|
||||
init = dict(name=group.name,
|
||||
acronym=group.acronym,
|
||||
state=group.state,
|
||||
ad=ad_role and ad_role.person and ad_role.person.id,
|
||||
parent=group.parent.id if group.parent else None,
|
||||
list_email=group.list_email if group.list_email else None,
|
||||
list_subscribe=group.list_subscribe if group.list_subscribe else None,
|
||||
list_archive=group.list_archive if group.list_archive else None,
|
||||
urls=format_urls(group.groupurl_set.all()),
|
||||
)
|
||||
|
||||
for slug in roles_for_group_type(group_type):
|
||||
init[slug + "_roles"] = Email.objects.filter(role__group=group, role__name=slug).order_by('role__person__name')
|
||||
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, 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)
|
||||
|
||||
@login_required
|
||||
def conclude(request, acronym, group_type=None):
|
||||
"""Request the closing of group, prompting for instructions."""
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
|
||||
if not can_manage_group_type(request.user, group):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ConcludeForm(request.POST)
|
||||
if form.is_valid():
|
||||
instructions = form.cleaned_data['instructions']
|
||||
|
||||
email_admin_re_charter(request, group, "Request closing of group", instructions, 'group_closure_requested')
|
||||
|
||||
e = GroupEvent(group=group, by=request.user.person)
|
||||
e.type = "requested_close"
|
||||
e.desc = "Requested closing group"
|
||||
e.save()
|
||||
|
||||
kwargs = {'acronym':group.acronym}
|
||||
if group_type:
|
||||
kwargs['group_type'] = group_type
|
||||
|
||||
return redirect(group.features.about_page, **kwargs)
|
||||
else:
|
||||
form = ConcludeForm()
|
||||
|
||||
return render(request, 'group/conclude.html', {
|
||||
'form': form,
|
||||
'group': group,
|
||||
'group_type': group_type,
|
||||
})
|
||||
|
||||
@login_required
|
||||
def customize_workflow(request, group_type=None, acronym=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
if not group_type:
|
||||
group_type = group.type_id
|
||||
if not group.features.customize_workflow:
|
||||
raise Http404
|
||||
|
||||
if not (can_manage_group(request.user, group)
|
||||
or group.has_role(request.user, group.features.admin_roles)):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
||||
if group_type == "rg":
|
||||
stream_id = "irtf"
|
||||
MANDATORY_STATES = ('candidat', 'active', 'rfc-edit', 'pub', 'dead')
|
||||
else:
|
||||
stream_id = "ietf"
|
||||
MANDATORY_STATES = ('c-adopt', 'wg-doc', 'sub-pub')
|
||||
|
||||
if request.method == 'POST':
|
||||
action = request.POST.get("action")
|
||||
if action == "setstateactive":
|
||||
active = request.POST.get("active") == "1"
|
||||
try:
|
||||
state = State.objects.exclude(slug__in=MANDATORY_STATES).get(pk=request.POST.get("state"))
|
||||
except State.DoesNotExist:
|
||||
return HttpResponse("Invalid state %s" % request.POST.get("state"))
|
||||
|
||||
if active:
|
||||
group.unused_states.remove(state)
|
||||
else:
|
||||
group.unused_states.add(state)
|
||||
|
||||
# redirect so the back button works correctly, otherwise
|
||||
# repeated POSTs fills up the history
|
||||
return redirect("ietf.group.views_edit.customize_workflow", group_type=group.type_id, acronym=group.acronym)
|
||||
|
||||
if action == "setnextstates":
|
||||
try:
|
||||
state = State.objects.get(pk=request.POST.get("state"))
|
||||
except State.DoesNotExist:
|
||||
return HttpResponse("Invalid state %s" % request.POST.get("state"))
|
||||
|
||||
next_states = State.objects.filter(used=True, type='draft-stream-%s' % stream_id, pk__in=request.POST.getlist("next_states"))
|
||||
unused = group.unused_states.all()
|
||||
if set(next_states.exclude(pk__in=unused)) == set(state.next_states.exclude(pk__in=unused)):
|
||||
# just use the default
|
||||
group.groupstatetransitions_set.filter(state=state).delete()
|
||||
else:
|
||||
transitions, _ = GroupStateTransitions.objects.get_or_create(group=group, state=state)
|
||||
transitions.next_states = next_states
|
||||
|
||||
return redirect("ietf.group.views_edit.customize_workflow", group_type=group.type_id, acronym=group.acronym)
|
||||
|
||||
if action == "settagactive":
|
||||
active = request.POST.get("active") == "1"
|
||||
try:
|
||||
tag = DocTagName.objects.get(pk=request.POST.get("tag"))
|
||||
except DocTagName.DoesNotExist:
|
||||
return HttpResponse("Invalid tag %s" % request.POST.get("tag"))
|
||||
|
||||
if active:
|
||||
group.unused_tags.remove(tag)
|
||||
else:
|
||||
group.unused_tags.add(tag)
|
||||
|
||||
return redirect("ietf.group.views_edit.customize_workflow", group_type=group.type_id, acronym=group.acronym)
|
||||
|
||||
# put some info for the template on tags and states
|
||||
unused_tags = group.unused_tags.all().values_list('slug', flat=True)
|
||||
tags = DocTagName.objects.filter(slug__in=get_tags_for_stream_id(stream_id))
|
||||
for t in tags:
|
||||
t.used = t.slug not in unused_tags
|
||||
|
||||
unused_states = group.unused_states.all().values_list('slug', flat=True)
|
||||
states = State.objects.filter(used=True, type="draft-stream-%s" % stream_id)
|
||||
transitions = dict((o.state, o) for o in group.groupstatetransitions_set.all())
|
||||
for s in states:
|
||||
s.used = s.slug not in unused_states
|
||||
s.mandatory = s.slug in MANDATORY_STATES
|
||||
|
||||
default_n = s.next_states.all()
|
||||
if s in transitions:
|
||||
n = transitions[s].next_states.all()
|
||||
else:
|
||||
n = default_n
|
||||
|
||||
s.next_states_checkboxes = [(x in n, x in default_n, x) for x in states]
|
||||
s.used_next_states = [x for x in n if x.slug not in unused_states]
|
||||
|
||||
return render(request, 'group/customize_workflow.html', {
|
||||
'group': group,
|
||||
'states': states,
|
||||
'tags': tags,
|
||||
})
|
|
@ -1,682 +0,0 @@
|
|||
import datetime, math
|
||||
from collections import defaultdict
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import Http404, HttpResponseForbidden, HttpResponseRedirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import reverse as urlreverse
|
||||
from django.db.models import Max
|
||||
from django import forms
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from ietf.review.models import ReviewRequest, ReviewerSettings, UnavailablePeriod, ReviewSecretarySettings
|
||||
from ietf.review.utils import (can_manage_review_requests_for_team,
|
||||
can_access_review_stats_for_team,
|
||||
close_review_request_states,
|
||||
extract_revision_ordered_review_requests_for_documents_and_replaced,
|
||||
assign_review_request_to_reviewer,
|
||||
close_review_request,
|
||||
setup_reviewer_field,
|
||||
suggested_review_requests_for_team,
|
||||
unavailable_periods_to_list,
|
||||
current_unavailable_periods_for_reviewers,
|
||||
email_reviewer_availability_change,
|
||||
reviewer_rotation_list,
|
||||
latest_review_requests_for_reviewers,
|
||||
augment_review_requests_with_events,
|
||||
get_default_filter_re,
|
||||
days_needed_to_fulfill_min_interval_for_reviewers,
|
||||
)
|
||||
from ietf.doc.models import LastCallDocEvent
|
||||
from ietf.group.models import Role
|
||||
from ietf.group.utils import get_group_or_404, construct_group_menu_context
|
||||
from ietf.person.fields import PersonEmailChoiceField
|
||||
from ietf.name.models import ReviewRequestStateName
|
||||
from ietf.utils.mail import send_mail_text, parse_preformatted
|
||||
from ietf.utils.fields import DatepickerDateField, MultiEmailField
|
||||
from ietf.ietfauth.utils import user_is_person
|
||||
from ietf.dbtemplate.models import DBTemplate
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.mailtrigger.models import Recipient
|
||||
|
||||
def get_open_review_requests_for_team(team, assignment_status=None):
|
||||
open_review_requests = ReviewRequest.objects.filter(
|
||||
team=team,
|
||||
state__in=("requested", "accepted")
|
||||
).prefetch_related(
|
||||
"reviewer__person", "type", "state", "doc", "doc__states",
|
||||
).order_by("-time", "-id")
|
||||
|
||||
if assignment_status == "unassigned":
|
||||
open_review_requests = suggested_review_requests_for_team(team) + list(open_review_requests.filter(reviewer=None))
|
||||
elif assignment_status == "assigned":
|
||||
open_review_requests = list(open_review_requests.exclude(reviewer=None))
|
||||
else:
|
||||
open_review_requests = suggested_review_requests_for_team(team) + list(open_review_requests)
|
||||
|
||||
today = datetime.date.today()
|
||||
unavailable_periods = current_unavailable_periods_for_reviewers(team)
|
||||
for r in open_review_requests:
|
||||
if r.reviewer:
|
||||
r.reviewer_unavailable = any(p.availability == "unavailable"
|
||||
for p in unavailable_periods.get(r.reviewer.person_id, []))
|
||||
r.due = max(0, (today - r.deadline).days)
|
||||
|
||||
return open_review_requests
|
||||
|
||||
def review_requests(request, acronym, group_type=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
if not group.features.has_reviews:
|
||||
raise Http404
|
||||
|
||||
assigned_review_requests = []
|
||||
unassigned_review_requests = []
|
||||
|
||||
for r in get_open_review_requests_for_team(group):
|
||||
if r.reviewer:
|
||||
assigned_review_requests.append(r)
|
||||
else:
|
||||
unassigned_review_requests.append(r)
|
||||
|
||||
open_review_requests = [
|
||||
("Unassigned", unassigned_review_requests),
|
||||
("Assigned", assigned_review_requests),
|
||||
]
|
||||
|
||||
closed_review_requests = ReviewRequest.objects.filter(
|
||||
team=group,
|
||||
).exclude(
|
||||
state__in=("requested", "accepted")
|
||||
).prefetch_related("reviewer__person", "type", "state", "doc", "result").order_by("-time", "-id")
|
||||
|
||||
since_choices = [
|
||||
(None, "1 month"),
|
||||
("3m", "3 months"),
|
||||
("6m", "6 months"),
|
||||
("1y", "1 year"),
|
||||
("2y", "2 years"),
|
||||
("all", "All"),
|
||||
]
|
||||
since = request.GET.get("since", None)
|
||||
if since not in [key for key, label in since_choices]:
|
||||
since = None
|
||||
|
||||
if since != "all":
|
||||
date_limit = {
|
||||
None: datetime.timedelta(days=31),
|
||||
"3m": datetime.timedelta(days=31 * 3),
|
||||
"6m": datetime.timedelta(days=180),
|
||||
"1y": datetime.timedelta(days=365),
|
||||
"2y": datetime.timedelta(days=2 * 365),
|
||||
}[since]
|
||||
|
||||
closed_review_requests = closed_review_requests.filter(time__gte=datetime.date.today() - date_limit)
|
||||
|
||||
return render(request, 'group/review_requests.html',
|
||||
construct_group_menu_context(request, group, "review requests", group_type, {
|
||||
"open_review_requests": open_review_requests,
|
||||
"closed_review_requests": closed_review_requests,
|
||||
"since_choices": since_choices,
|
||||
"since": since,
|
||||
"can_manage_review_requests": can_manage_review_requests_for_team(request.user, group),
|
||||
"can_access_stats": can_access_review_stats_for_team(request.user, group),
|
||||
}))
|
||||
|
||||
def reviewer_overview(request, acronym, group_type=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
if not group.features.has_reviews:
|
||||
raise Http404
|
||||
|
||||
can_manage = can_manage_review_requests_for_team(request.user, group)
|
||||
|
||||
reviewers = reviewer_rotation_list(group)
|
||||
|
||||
reviewer_settings = { s.person_id: s for s in ReviewerSettings.objects.filter(team=group) }
|
||||
unavailable_periods = defaultdict(list)
|
||||
for p in unavailable_periods_to_list().filter(team=group):
|
||||
unavailable_periods[p.person_id].append(p)
|
||||
reviewer_roles = { r.person_id: r for r in Role.objects.filter(group=group, name="reviewer").select_related("email") }
|
||||
|
||||
today = datetime.date.today()
|
||||
|
||||
req_data_for_reviewers = latest_review_requests_for_reviewers(group)
|
||||
review_state_by_slug = { n.slug: n for n in ReviewRequestStateName.objects.all() }
|
||||
|
||||
days_needed = days_needed_to_fulfill_min_interval_for_reviewers(group)
|
||||
|
||||
for person in reviewers:
|
||||
person.settings = reviewer_settings.get(person.pk) or ReviewerSettings(team=group, person=person)
|
||||
person.settings_url = None
|
||||
person.role = reviewer_roles.get(person.pk)
|
||||
if person.role and (can_manage or user_is_person(request.user, person)):
|
||||
kwargs = { "acronym": group.acronym, "reviewer_email": person.role.email.address }
|
||||
if group_type:
|
||||
kwargs["group_type"] = group_type
|
||||
person.settings_url = urlreverse("ietf.group.views_review.change_reviewer_settings", kwargs=kwargs)
|
||||
person.unavailable_periods = unavailable_periods.get(person.pk, [])
|
||||
person.completely_unavailable = any(p.availability == "unavailable"
|
||||
and (p.start_date is None or p.start_date <= today) and (p.end_date is None or today <= p.end_date)
|
||||
for p in person.unavailable_periods)
|
||||
person.busy = person.id in days_needed
|
||||
|
||||
|
||||
MAX_CLOSED_REQS = 10
|
||||
req_data = req_data_for_reviewers.get(person.pk, [])
|
||||
open_reqs = sum(1 for d in req_data if d.state in ["requested", "accepted"])
|
||||
latest_reqs = []
|
||||
for d in req_data:
|
||||
if d.state in ["requested", "accepted"] or len(latest_reqs) < MAX_CLOSED_REQS + open_reqs:
|
||||
latest_reqs.append((d.req_pk, d.doc, d.reviewed_rev, d.assigned_time, d.deadline,
|
||||
review_state_by_slug.get(d.state),
|
||||
int(math.ceil(d.assignment_to_closure_days)) if d.assignment_to_closure_days is not None else None))
|
||||
person.latest_reqs = latest_reqs
|
||||
|
||||
return render(request, 'group/reviewer_overview.html',
|
||||
construct_group_menu_context(request, group, "reviewers", group_type, {
|
||||
"reviewers": reviewers,
|
||||
"can_access_stats": can_access_review_stats_for_team(request.user, group)
|
||||
}))
|
||||
|
||||
class ManageReviewRequestForm(forms.Form):
|
||||
ACTIONS = [
|
||||
("assign", "Assign"),
|
||||
("close", "Close"),
|
||||
]
|
||||
|
||||
action = forms.ChoiceField(choices=ACTIONS, widget=forms.HiddenInput, required=False)
|
||||
close = forms.ModelChoiceField(queryset=close_review_request_states(), required=False)
|
||||
reviewer = PersonEmailChoiceField(empty_label="(None)", required=False, label_with="person")
|
||||
add_skip = forms.BooleanField(required=False)
|
||||
|
||||
def __init__(self, review_req, *args, **kwargs):
|
||||
if not "prefix" in kwargs:
|
||||
if review_req.pk is None:
|
||||
kwargs["prefix"] = "r{}-{}".format(review_req.type_id, review_req.doc_id)
|
||||
else:
|
||||
kwargs["prefix"] = "r{}".format(review_req.pk)
|
||||
|
||||
super(ManageReviewRequestForm, self).__init__(*args, **kwargs)
|
||||
|
||||
if review_req.pk is None:
|
||||
self.fields["close"].queryset = self.fields["close"].queryset.filter(slug__in=["no-review-version", "no-review-document"])
|
||||
|
||||
close_initial = None
|
||||
if review_req.pk is None:
|
||||
close_initial = "no-review-version"
|
||||
elif review_req.reviewer:
|
||||
close_initial = "no-response"
|
||||
else:
|
||||
close_initial = "overtaken"
|
||||
|
||||
if close_initial:
|
||||
self.fields["close"].initial = close_initial
|
||||
|
||||
self.fields["close"].widget.attrs["class"] = "form-control input-sm"
|
||||
|
||||
setup_reviewer_field(self.fields["reviewer"], review_req)
|
||||
self.fields["reviewer"].widget.attrs["class"] = "form-control input-sm"
|
||||
|
||||
if self.is_bound:
|
||||
if self.data.get("action") == "close":
|
||||
self.fields["close"].required = True
|
||||
|
||||
|
||||
@login_required
|
||||
def manage_review_requests(request, acronym, group_type=None, assignment_status=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
if not group.features.has_reviews:
|
||||
raise Http404
|
||||
|
||||
if not can_manage_review_requests_for_team(request.user, group):
|
||||
return HttpResponseForbidden("You do not have permission to perform this action")
|
||||
|
||||
review_requests = get_open_review_requests_for_team(group, assignment_status=assignment_status)
|
||||
|
||||
document_requests = extract_revision_ordered_review_requests_for_documents_and_replaced(
|
||||
ReviewRequest.objects.filter(state__in=("part-completed", "completed"), team=group).prefetch_related("result"),
|
||||
set(r.doc_id for r in review_requests),
|
||||
)
|
||||
|
||||
# we need a mutable query dict for resetting upon saving with
|
||||
# conflicts
|
||||
query_dict = request.POST.copy() if request.method == "POST" else None
|
||||
|
||||
for req in review_requests:
|
||||
req.form = ManageReviewRequestForm(req, query_dict)
|
||||
|
||||
# add previous requests
|
||||
l = []
|
||||
for r in document_requests.get(req.doc_id, []):
|
||||
# take all on the latest reviewed rev
|
||||
if l and l[0].reviewed_rev:
|
||||
if r.doc_id == l[0].doc_id and r.reviewed_rev:
|
||||
if int(r.reviewed_rev) > int(l[0].reviewed_rev):
|
||||
l = [r]
|
||||
elif int(r.reviewed_rev) == int(l[0].reviewed_rev):
|
||||
l.append(r)
|
||||
else:
|
||||
l = [r]
|
||||
|
||||
augment_review_requests_with_events(l)
|
||||
|
||||
req.latest_reqs = l
|
||||
|
||||
saving = False
|
||||
newly_closed = newly_opened = newly_assigned = 0
|
||||
|
||||
if request.method == "POST":
|
||||
form_action = request.POST.get("action", "")
|
||||
saving = form_action.startswith("save")
|
||||
|
||||
# check for conflicts
|
||||
review_requests_dict = { unicode(r.pk): r for r in review_requests }
|
||||
posted_reqs = set(request.POST.getlist("reviewrequest", []))
|
||||
current_reqs = set(review_requests_dict.iterkeys())
|
||||
|
||||
closed_reqs = posted_reqs - current_reqs
|
||||
newly_closed = len(closed_reqs)
|
||||
|
||||
opened_reqs = current_reqs - posted_reqs
|
||||
newly_opened = len(opened_reqs)
|
||||
for r in opened_reqs:
|
||||
review_requests_dict[r].form.add_error(None, "New request.")
|
||||
|
||||
for req in review_requests:
|
||||
existing_reviewer = request.POST.get(req.form.prefix + "-existing_reviewer")
|
||||
if existing_reviewer is None:
|
||||
continue
|
||||
|
||||
if existing_reviewer != unicode(req.reviewer_id or ""):
|
||||
msg = "Assignment was changed."
|
||||
a = req.form["action"].value()
|
||||
if a == "assign":
|
||||
msg += " Didn't assign reviewer."
|
||||
elif a == "close":
|
||||
msg += " Didn't close request."
|
||||
req.form.add_error(None, msg)
|
||||
req.form.data[req.form.prefix + "-action"] = "" # cancel the action
|
||||
|
||||
newly_assigned += 1
|
||||
|
||||
form_results = []
|
||||
for req in review_requests:
|
||||
form_results.append(req.form.is_valid())
|
||||
|
||||
if saving and all(form_results) and not (newly_closed > 0 or newly_opened > 0 or newly_assigned > 0):
|
||||
for review_req in review_requests:
|
||||
action = review_req.form.cleaned_data.get("action")
|
||||
if action == "assign":
|
||||
assign_review_request_to_reviewer(request, review_req, review_req.form.cleaned_data["reviewer"],review_req.form.cleaned_data["add_skip"])
|
||||
elif action == "close":
|
||||
close_review_request(request, review_req, review_req.form.cleaned_data["close"])
|
||||
|
||||
kwargs = { "acronym": group.acronym }
|
||||
if group_type:
|
||||
kwargs["group_type"] = group_type
|
||||
|
||||
if form_action == "save-continue":
|
||||
if assignment_status:
|
||||
kwargs["assignment_status"] = assignment_status
|
||||
|
||||
return redirect(manage_review_requests, **kwargs)
|
||||
else:
|
||||
import ietf.group.views_review
|
||||
return redirect(ietf.group.views_review.review_requests, **kwargs)
|
||||
|
||||
other_assignment_status = {
|
||||
"unassigned": "assigned",
|
||||
"assigned": "unassigned",
|
||||
}.get(assignment_status)
|
||||
|
||||
return render(request, 'group/manage_review_requests.html', {
|
||||
'group': group,
|
||||
'review_requests': review_requests,
|
||||
'newly_closed': newly_closed,
|
||||
'newly_opened': newly_opened,
|
||||
'newly_assigned': newly_assigned,
|
||||
'saving': saving,
|
||||
'assignment_status': assignment_status,
|
||||
'other_assignment_status': other_assignment_status,
|
||||
})
|
||||
|
||||
class EmailOpenAssignmentsForm(forms.Form):
|
||||
frm = forms.CharField(label="From", widget=forms.EmailInput(attrs={"readonly":True}))
|
||||
to = MultiEmailField()
|
||||
cc = MultiEmailField(required=False)
|
||||
reply_to = MultiEmailField(required=False)
|
||||
subject = forms.CharField()
|
||||
body = forms.CharField(widget=forms.Textarea, strip=False)
|
||||
|
||||
@login_required
|
||||
def email_open_review_assignments(request, acronym, group_type=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
if not group.features.has_reviews:
|
||||
raise Http404
|
||||
|
||||
if not can_manage_review_requests_for_team(request.user, group):
|
||||
return HttpResponseForbidden("You do not have permission to perform this action")
|
||||
|
||||
review_requests = list(ReviewRequest.objects.filter(
|
||||
team=group,
|
||||
state__in=("requested", "accepted"),
|
||||
).exclude(
|
||||
reviewer=None,
|
||||
).prefetch_related("reviewer", "type", "state", "doc").distinct().order_by("reviewer","-deadline"))
|
||||
|
||||
review_requests.sort(key=lambda r:r.reviewer.person.last_name()+r.reviewer.person.first_name())
|
||||
|
||||
for r in review_requests:
|
||||
if r.doc.telechat_date():
|
||||
r.section = 'For telechat %s' % r.doc.telechat_date().isoformat()
|
||||
r.section_order='0'+r.section
|
||||
elif r.type_id == 'early':
|
||||
r.section = 'Early review requests:'
|
||||
r.section_order='2'
|
||||
else:
|
||||
r.section = 'Last calls:'
|
||||
r.section_order='1'
|
||||
e = r.doc.latest_event(LastCallDocEvent, type="sent_last_call")
|
||||
r.lastcall_ends = e and e.expires.date().isoformat()
|
||||
r.earlier_review = ReviewRequest.objects.filter(doc=r.doc,reviewer__in=r.reviewer.person.email_set.all(),state="completed")
|
||||
if r.earlier_review:
|
||||
req_rev = r.requested_rev or r.doc.rev
|
||||
earlier_review_rev = r.earlier_review.aggregate(Max('reviewed_rev'))['reviewed_rev__max']
|
||||
if req_rev == earlier_review_rev:
|
||||
r.earlier_review_mark = '**'
|
||||
else:
|
||||
r.earlier_review_mark = '*'
|
||||
|
||||
review_requests.sort(key=lambda r: r.section_order)
|
||||
|
||||
back_url = request.GET.get("next")
|
||||
if not back_url:
|
||||
kwargs = { "acronym": group.acronym }
|
||||
if group_type:
|
||||
kwargs["group_type"] = group_type
|
||||
|
||||
import ietf.group.views_review
|
||||
back_url = urlreverse(ietf.group.views_review.review_requests, kwargs=kwargs)
|
||||
|
||||
if request.method == "POST" and request.POST.get("action") == "email":
|
||||
form = EmailOpenAssignmentsForm(request.POST)
|
||||
if form.is_valid():
|
||||
send_mail_text(request, form.cleaned_data["to"], form.cleaned_data["frm"], form.cleaned_data["subject"], form.cleaned_data["body"],cc=form.cleaned_data["cc"],extra={"Reply-to":", ".join(form.cleaned_data["reply_to"])})
|
||||
return HttpResponseRedirect(back_url)
|
||||
else:
|
||||
(to,cc) = gather_address_lists('review_assignments_summarized',group=group)
|
||||
reply_to = Recipient.objects.get(slug='group_secretaries').gather(group=group)
|
||||
frm = request.user.person.formatted_email()
|
||||
|
||||
templateqs = DBTemplate.objects.filter(path="/group/%s/email/open_assignments.txt" % group.acronym)
|
||||
if templateqs.exists():
|
||||
template = templateqs.first()
|
||||
else:
|
||||
template = DBTemplate.objects.get(path="/group/defaults/email/open_assignments.txt")
|
||||
|
||||
partial_msg = render_to_string(template.path, {
|
||||
"review_requests": review_requests,
|
||||
"rotation_list": reviewer_rotation_list(group)[:10],
|
||||
"group" : group,
|
||||
})
|
||||
|
||||
(msg,_,_) = parse_preformatted(partial_msg)
|
||||
|
||||
body = msg.get_payload()
|
||||
subject = msg['Subject']
|
||||
|
||||
form = EmailOpenAssignmentsForm(initial={
|
||||
"to": ", ".join(to),
|
||||
"cc": ", ".join(cc),
|
||||
"reply_to": ", ".join(reply_to),
|
||||
"frm": frm,
|
||||
"subject": subject,
|
||||
"body": body,
|
||||
})
|
||||
|
||||
return render(request, 'group/email_open_review_assignments.html', {
|
||||
'group': group,
|
||||
'review_requests': review_requests,
|
||||
'form': form,
|
||||
'back_url': back_url,
|
||||
})
|
||||
|
||||
|
||||
class ReviewerSettingsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ReviewerSettings
|
||||
fields = ['min_interval', 'filter_re', 'skip_next', 'remind_days_before_deadline','expertise']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
exclude_fields = kwargs.pop('exclude_fields', [])
|
||||
super(ReviewerSettingsForm, self).__init__(*args, **kwargs)
|
||||
for field_name in exclude_fields:
|
||||
self.fields.pop(field_name)
|
||||
|
||||
def clean_skip_next(self):
|
||||
skip_next = self.cleaned_data.get('skip_next')
|
||||
if skip_next < 0:
|
||||
raise forms.ValidationError("Skip next must not be negative")
|
||||
return skip_next
|
||||
|
||||
class AddUnavailablePeriodForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = UnavailablePeriod
|
||||
fields = ['start_date', 'end_date', 'availability']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddUnavailablePeriodForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["start_date"] = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label=self.fields["start_date"].label, help_text=self.fields["start_date"].help_text, required=self.fields["start_date"].required)
|
||||
self.fields["end_date"] = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label=self.fields["end_date"].label, help_text=self.fields["end_date"].help_text, required=self.fields["end_date"].required)
|
||||
|
||||
self.fields['availability'].widget = forms.RadioSelect(choices=UnavailablePeriod.LONG_AVAILABILITY_CHOICES)
|
||||
|
||||
def clean(self):
|
||||
start = self.cleaned_data.get("start_date")
|
||||
end = self.cleaned_data.get("end_date")
|
||||
if start and end and start > end:
|
||||
self.add_error("start_date", "Start date must be before or equal to end date.")
|
||||
return self.cleaned_data
|
||||
|
||||
class EndUnavailablePeriodForm(forms.Form):
|
||||
def __init__(self, start_date, *args, **kwargs):
|
||||
super(EndUnavailablePeriodForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["end_date"] = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1", "start-date": start_date.isoformat() if start_date else "" })
|
||||
|
||||
self.start_date = start_date
|
||||
|
||||
def clean_end_date(self):
|
||||
end = self.cleaned_data["end_date"]
|
||||
if self.start_date and end < self.start_date:
|
||||
raise forms.ValidationError("End date must be equal to or come after start date.")
|
||||
return end
|
||||
|
||||
|
||||
@login_required
|
||||
def change_reviewer_settings(request, acronym, reviewer_email, group_type=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
if not group.features.has_reviews:
|
||||
raise Http404
|
||||
|
||||
reviewer_role = get_object_or_404(Role, name="reviewer", group=group, email=reviewer_email)
|
||||
reviewer = reviewer_role.person
|
||||
|
||||
if not (user_is_person(request.user, reviewer)
|
||||
or can_manage_review_requests_for_team(request.user, group)):
|
||||
return HttpResponseForbidden("You do not have permission to perform this action")
|
||||
|
||||
exclude_fields = []
|
||||
if not can_manage_review_requests_for_team(request.user, group):
|
||||
exclude_fields.append('skip_next')
|
||||
|
||||
settings = ReviewerSettings.objects.filter(person=reviewer, team=group).first()
|
||||
if not settings:
|
||||
settings = ReviewerSettings(person=reviewer, team=group)
|
||||
settings.filter_re = get_default_filter_re(reviewer)
|
||||
|
||||
back_url = request.GET.get("next")
|
||||
if not back_url:
|
||||
import ietf.group.views_review
|
||||
kwargs = { "acronym": group.acronym}
|
||||
if group_type:
|
||||
kwargs["group_type"] = group_type
|
||||
back_url = urlreverse(ietf.group.views_review.reviewer_overview, kwargs=kwargs)
|
||||
|
||||
# settings
|
||||
if request.method == "POST" and request.POST.get("action") == "change_settings":
|
||||
prev_min_interval = settings.get_min_interval_display()
|
||||
prev_skip_next = settings.skip_next
|
||||
settings_form = ReviewerSettingsForm(request.POST, instance=settings, exclude_fields=exclude_fields)
|
||||
if settings_form.is_valid():
|
||||
settings = settings_form.save()
|
||||
|
||||
changes = []
|
||||
if settings.get_min_interval_display() != prev_min_interval:
|
||||
changes.append("Frequency changed to \"{}\" from \"{}\".".format(settings.get_min_interval_display() or "Not specified", prev_min_interval or "Not specified"))
|
||||
if settings.skip_next != prev_skip_next:
|
||||
changes.append("Skip next assignments changed to {} from {}.".format(settings.skip_next, prev_skip_next))
|
||||
|
||||
if changes:
|
||||
email_reviewer_availability_change(request, group, reviewer_role, "\n\n".join(changes), request.user.person)
|
||||
|
||||
return HttpResponseRedirect(back_url)
|
||||
else:
|
||||
settings_form = ReviewerSettingsForm(instance=settings,exclude_fields=exclude_fields)
|
||||
|
||||
# periods
|
||||
unavailable_periods = unavailable_periods_to_list().filter(person=reviewer, team=group)
|
||||
|
||||
if request.method == "POST" and request.POST.get("action") == "add_period":
|
||||
period_form = AddUnavailablePeriodForm(request.POST)
|
||||
if period_form.is_valid():
|
||||
period = period_form.save(commit=False)
|
||||
period.team = group
|
||||
period.person = reviewer
|
||||
period.save()
|
||||
|
||||
today = datetime.date.today()
|
||||
|
||||
in_the_past = period.end_date and period.end_date < today
|
||||
|
||||
if not in_the_past:
|
||||
msg = "Unavailable for review: {} - {} ({})".format(
|
||||
period.start_date.isoformat() if period.start_date else "indefinite",
|
||||
period.end_date.isoformat() if period.end_date else "indefinite",
|
||||
period.get_availability_display(),
|
||||
)
|
||||
|
||||
if period.availability == "unavailable":
|
||||
# the secretary might need to reassign
|
||||
# assignments, so mention the current ones
|
||||
|
||||
review_reqs = ReviewRequest.objects.filter(state__in=["requested", "accepted"], reviewer=reviewer_role.email, team=group)
|
||||
msg += "\n\n"
|
||||
|
||||
if review_reqs:
|
||||
msg += "{} is currently assigned to review:".format(reviewer_role.person)
|
||||
for r in review_reqs:
|
||||
msg += "\n\n"
|
||||
msg += "{} (deadline: {})".format(r.doc_id, r.deadline.isoformat())
|
||||
else:
|
||||
msg += "{} does not have any assignments currently.".format(reviewer_role.person)
|
||||
|
||||
email_reviewer_availability_change(request, group, reviewer_role, msg, request.user.person)
|
||||
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
else:
|
||||
period_form = AddUnavailablePeriodForm()
|
||||
|
||||
if request.method == "POST" and request.POST.get("action") == "delete_period":
|
||||
period_id = request.POST.get("period_id")
|
||||
if period_id is not None:
|
||||
for period in unavailable_periods:
|
||||
if str(period.pk) == period_id:
|
||||
period.delete()
|
||||
|
||||
today = datetime.date.today()
|
||||
|
||||
in_the_past = period.end_date and period.end_date < today
|
||||
|
||||
if not in_the_past:
|
||||
msg = "Removed unavailable period: {} - {} ({})".format(
|
||||
period.start_date.isoformat() if period.start_date else "indefinite",
|
||||
period.end_date.isoformat() if period.end_date else "indefinite",
|
||||
period.get_availability_display(),
|
||||
)
|
||||
|
||||
email_reviewer_availability_change(request, group, reviewer_role, msg, request.user.person)
|
||||
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
for p in unavailable_periods:
|
||||
if not p.end_date:
|
||||
p.end_form = EndUnavailablePeriodForm(p.start_date, request.POST if request.method == "POST" and request.POST.get("action") == "end_period" else None)
|
||||
|
||||
if request.method == "POST" and request.POST.get("action") == "end_period":
|
||||
period_id = request.POST.get("period_id")
|
||||
for period in unavailable_periods:
|
||||
if str(period.pk) == period_id:
|
||||
if not period.end_date and period.end_form.is_valid():
|
||||
period.end_date = period.end_form.cleaned_data["end_date"]
|
||||
period.save()
|
||||
|
||||
msg = "Set end date of unavailable period: {} - {} ({})".format(
|
||||
period.start_date.isoformat() if period.start_date else "indefinite",
|
||||
period.end_date.isoformat() if period.end_date else "indefinite",
|
||||
period.get_availability_display(),
|
||||
)
|
||||
|
||||
email_reviewer_availability_change(request, group, reviewer_role, msg, request.user.person)
|
||||
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
|
||||
return render(request, 'group/change_reviewer_settings.html', {
|
||||
'group': group,
|
||||
'reviewer_email': reviewer_email,
|
||||
'back_url': back_url,
|
||||
'settings_form': settings_form,
|
||||
'period_form': period_form,
|
||||
'unavailable_periods': unavailable_periods,
|
||||
})
|
||||
|
||||
|
||||
class ReviewSecretarySettingsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ReviewSecretarySettings
|
||||
fields = ['remind_days_before_deadline']
|
||||
|
||||
|
||||
@login_required
|
||||
def change_review_secretary_settings(request, acronym, group_type=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
if not group.features.has_reviews:
|
||||
raise Http404
|
||||
if not Role.objects.filter(name="secr", group=group, person__user=request.user).exists():
|
||||
raise Http404
|
||||
|
||||
person = request.user.person
|
||||
|
||||
settings = (ReviewSecretarySettings.objects.filter(person=person, team=group).first()
|
||||
or ReviewSecretarySettings(person=person, team=group))
|
||||
|
||||
import ietf.group.views_review
|
||||
back_url = urlreverse(ietf.group.views_review.review_requests, kwargs={ "acronym": acronym, "group_type": group.type_id })
|
||||
|
||||
# settings
|
||||
if request.method == "POST":
|
||||
settings_form = ReviewSecretarySettingsForm(request.POST, instance=settings)
|
||||
if settings_form.is_valid():
|
||||
settings_form.save()
|
||||
return HttpResponseRedirect(back_url)
|
||||
else:
|
||||
settings_form = ReviewSecretarySettingsForm(instance=settings)
|
||||
|
||||
return render(request, 'group/change_review_secretary_settings.html', {
|
||||
'group': group,
|
||||
'back_url': back_url,
|
||||
'settings_form': settings_form,
|
||||
})
|
|
@ -1,78 +0,0 @@
|
|||
# Copyright The IETF Trust 2008, All Rights Reserved
|
||||
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.http import Http404, HttpResponseForbidden
|
||||
from django import forms
|
||||
|
||||
from ietf.doc.models import Document
|
||||
from ietf.doc.utils_search import prepare_document_table
|
||||
from ietf.group.models import Group, GroupEvent, Role
|
||||
from ietf.group.utils import save_group_in_history
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.name.models import StreamName
|
||||
from ietf.person.fields import SearchableEmailsField
|
||||
from ietf.person.models import Email
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
def streams(request):
|
||||
streams = [ s.slug for s in StreamName.objects.all().exclude(slug__in=['ietf', 'legacy']) ]
|
||||
streams = Group.objects.filter(acronym__in=streams)
|
||||
return render(request, 'group/index.html', {'streams':streams})
|
||||
|
||||
def stream_documents(request, acronym):
|
||||
streams = [ s.slug for s in StreamName.objects.all().exclude(slug__in=['ietf', 'legacy']) ]
|
||||
if not acronym in streams:
|
||||
raise Http404("No such stream: %s" % acronym)
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
editable = has_role(request.user, "Secretariat") or group.has_role(request.user, "chair")
|
||||
stream = StreamName.objects.get(slug=acronym)
|
||||
|
||||
qs = Document.objects.filter(states__type="draft", states__slug__in=["active", "rfc"], stream=acronym)
|
||||
docs, meta = prepare_document_table(request, qs)
|
||||
return render(request, 'group/stream_documents.html', {'stream':stream, 'docs':docs, 'meta':meta, 'editable':editable } )
|
||||
|
||||
class StreamEditForm(forms.Form):
|
||||
delegates = SearchableEmailsField(required=False, only_users=True)
|
||||
|
||||
def stream_edit(request, acronym):
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
||||
if not (has_role(request.user, "Secretariat") or group.has_role(request.user, "chair")):
|
||||
return HttpResponseForbidden("You don't have permission to access this page.")
|
||||
|
||||
chairs = Email.objects.filter(role__group=group, role__name="chair").select_related("person")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = StreamEditForm(request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
save_group_in_history(group)
|
||||
|
||||
# update roles
|
||||
attr, slug, title = ('delegates', 'delegate', "Delegates")
|
||||
|
||||
new = form.cleaned_data[attr]
|
||||
old = Email.objects.filter(role__group=group, role__name=slug).select_related("person")
|
||||
if set(new) != set(old):
|
||||
desc = "%s changed to <b>%s</b> from %s" % (
|
||||
title, ", ".join(x.get_name() for x in new), ", ".join(x.get_name() for x in old))
|
||||
|
||||
GroupEvent.objects.create(group=group, by=request.user.person, type="info_changed", desc=desc)
|
||||
|
||||
group.role_set.filter(name=slug).delete()
|
||||
for e in new:
|
||||
Role.objects.get_or_create(name_id=slug, email=e, group=group, person=e.person)
|
||||
|
||||
return redirect("ietf.group.views_stream.streams")
|
||||
else:
|
||||
form = StreamEditForm(initial=dict(delegates=Email.objects.filter(role__group=group, role__name="delegate")))
|
||||
|
||||
return render(request, 'group/stream_edit.html',
|
||||
{
|
||||
'group': group,
|
||||
'chairs': chairs,
|
||||
'form': form,
|
||||
},
|
||||
)
|
||||
|
|
@ -182,7 +182,7 @@ class ApiTests(TestCase):
|
|||
make_meeting_test_data()
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
url = urlreverse("ietf.group.views_ajax.group_json", kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse("ietf.group.views.group_json", kwargs=dict(acronym=group.acronym))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
info = json.loads(r.content)
|
||||
|
|
|
@ -407,7 +407,7 @@ def email_reviewer_availability_change(request, team, reviewer_role, msg, by):
|
|||
|
||||
subject = "Reviewer availability of {} changed in {}".format(reviewer_role.person, team.acronym)
|
||||
|
||||
url = urlreverse("ietf.group.views_review.reviewer_overview", kwargs={ "group_type": team.type_id, "acronym": team.acronym })
|
||||
url = urlreverse("ietf.group.views.reviewer_overview", kwargs={ "group_type": team.type_id, "acronym": team.acronym })
|
||||
url = request.build_absolute_uri(url)
|
||||
send_mail(request, to, None, subject, "review/reviewer_availability_changed.txt", {
|
||||
"reviewer_overview_url": url,
|
||||
|
@ -910,8 +910,8 @@ def email_secretary_reminder(review_request, secretary_role):
|
|||
|
||||
subject = "Reminder: deadline for review of {} in {} is {}".format(review_request.doc_id, team.acronym, review_request.deadline.isoformat())
|
||||
|
||||
import ietf.group.views_review
|
||||
settings_url = urlreverse(ietf.group.views_review.change_review_secretary_settings, kwargs={ "acronym": team.acronym, "group_type": team.type_id })
|
||||
import ietf.group.views
|
||||
settings_url = urlreverse(ietf.group.views.change_review_secretary_settings, kwargs={ "acronym": team.acronym, "group_type": team.type_id })
|
||||
import ietf.doc.views_review
|
||||
request_url = urlreverse(ietf.doc.views_review.review_request, kwargs={ "name": review_request.doc_id, "request_id": review_request.pk })
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
<link rel="apple-touch-icon" href="{% static 'ietf/images/apple-touch-icon.png' %}">
|
||||
</head>
|
||||
|
||||
<body {% block bodyAttrs %}{%endblock%} data-group-menu-data-url="{% url 'ietf.group.views_ajax.group_menu_data' %}">
|
||||
<body {% block bodyAttrs %}{%endblock%} data-group-menu-data-url="{% url 'ietf.group.views.group_menu_data' %}">
|
||||
<nav class="navbar {% if server_mode and server_mode != "production" %}navbar-default{% else %}navbar-inverse{% endif %} {% if navbar_mode %}{{ navbar_mode }}{% else %}navbar-fixed-top{% endif %}">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
|
||||
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>Review Teams</li>
|
||||
{% for g in user|managed_review_groups %}
|
||||
<li><a href="{% url "ietf.group.views_review.review_requests" g.acronym %}">{{ g.acronym }} reviews</a></li>
|
||||
<li><a href="{% url "ietf.group.views.review_requests" g.acronym %}">{{ g.acronym }} reviews</a></li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
@ -79,9 +79,9 @@
|
|||
{% endif %}
|
||||
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
|
||||
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>RFC streams</li>
|
||||
<li><a href="{% url "ietf.group.views_stream.stream_documents" acronym="iab" %}">IAB</a></li>
|
||||
<li><a href="{% url "ietf.group.views_stream.stream_documents" acronym="irtf" %}">IRTF</a></li>
|
||||
<li><a href="{% url "ietf.group.views_stream.stream_documents" acronym="ise" %}">ISE</a></li>
|
||||
<li><a href="{% url "ietf.group.views.stream_documents" acronym="iab" %}">IAB</a></li>
|
||||
<li><a href="{% url "ietf.group.views.stream_documents" acronym="irtf" %}">IRTF</a></li>
|
||||
<li><a href="{% url "ietf.group.views.stream_documents" acronym="ise" %}">ISE</a></li>
|
||||
|
||||
{% if flavor == "top" %}</ul>{% endif %}
|
||||
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
{% if editable_streams %}
|
||||
<li class="sect">Streams</li>
|
||||
{% for stream in editable_streams %}
|
||||
<li{% if forloop.last %} style="margin-bottom: 2px;"{% endif %}><a href="{% url "ietf.group.views_stream.stream_edit" stream.slug %}">{{ stream.name }} stream</a></li>
|
||||
<li{% if forloop.last %} style="margin-bottom: 2px;"{% endif %}><a href="{% url "ietf.group.views.stream_edit" stream.slug %}">{{ stream.name }} stream</a></li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
<tr>
|
||||
<th></th>
|
||||
<th>Team</th>
|
||||
<td><a href="{% url "ietf.group.views_review.review_requests" group_type=review_req.team.type_id acronym=review_req.team.acronym %}">{{ review_req.team.acronym|upper }}</a></td>
|
||||
<td><a href="{% url "ietf.group.views.review_requests" group_type=review_req.team.type_id acronym=review_req.team.acronym %}">{{ review_req.team.acronym|upper }}</a></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<p>Groups in the <i>BOF</i> state.</p>
|
||||
|
||||
{% if user|has_role:"Area Director,Secretariat" %}
|
||||
<p><a class="btn btn-default" role="button" href="{% url 'ietf.group.views_edit.edit' group_type='wg' action='create' %}">Create new BOF</a></p>
|
||||
<p><a class="btn btn-default" role="button" href="{% url 'ietf.group.views.edit' group_type='wg' action='create' %}">Create new BOF</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% if not groups %}
|
||||
|
@ -47,4 +47,4 @@
|
|||
|
||||
{% block js %}
|
||||
<script src="{% static "jquery.tablesorter/js/jquery.tablesorter.combined.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<h2>{{ t.name }}s</h2>
|
||||
|
||||
{% if t.can_manage %}
|
||||
<p><a class="btn btn-default" role="button" href="{% url 'ietf.group.views_edit.edit' group_type=t.pk action='charter' %}">Charter new {{ t.name }}</a></p>
|
||||
<p><a class="btn btn-default" role="button" href="{% url 'ietf.group.views.edit' group_type=t.pk action='charter' %}">Charter new {{ t.name }}</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% if not t.chartering_groups %}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<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>
|
||||
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views.edit' acronym=group.acronym field='name' %}">Edit</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ group.name }}</td>
|
||||
|
@ -49,7 +49,7 @@
|
|||
<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>
|
||||
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views.edit' acronym=group.acronym field='state' %}">Edit</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
|
@ -120,7 +120,7 @@
|
|||
<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>
|
||||
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views.edit' acronym=group.acronym field='urls' %}">Edit</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
|
@ -144,7 +144,7 @@
|
|||
<th>{{ label }}</th>
|
||||
<td class="edit">
|
||||
{% if can_edit_group and slug in editable_roles %}
|
||||
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views_edit.edit' acronym=group.acronym field=slug %}">Edit</a>
|
||||
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views.edit' acronym=group.acronym field=slug %}">Edit</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
|
@ -167,7 +167,7 @@
|
|||
<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>
|
||||
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views.edit' acronym=group.acronym field='list_email' %}">Edit</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ group.list_email|urlize }}</td>
|
||||
|
@ -177,7 +177,7 @@
|
|||
<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>
|
||||
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views.edit' acronym=group.acronym field='list_subscribe' %}">Edit</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ group.list_subscribe|urlize }}</td>
|
||||
|
@ -187,7 +187,7 @@
|
|||
<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>
|
||||
<a class="btn btn-default btn-xs" href="{% url 'ietf.group.views.edit' acronym=group.acronym field='list_archive' %}">Edit</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ group.list_archive|urlize }}</td>
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
<h1>Manage {{ assignment_status }} open review requests for {{ group.acronym }}</h1>
|
||||
|
||||
<p>Other options:
|
||||
<a href="{% url "ietf.group.views_review.review_requests" group_type=group.type_id acronym=group.acronym %}">All review requests</a>
|
||||
- <a href="{% url "ietf.group.views_review.reviewer_overview" group_type=group.type_id acronym=group.acronym %}">Reviewers</a>
|
||||
- <a href="{% url "ietf.group.views_review.email_open_review_assignments" group_type=group.type_id acronym=group.acronym %}?next={{ request.get_full_path|urlencode }}">Email open assignments summary</a>
|
||||
<a href="{% url "ietf.group.views.review_requests" group_type=group.type_id acronym=group.acronym %}">All review requests</a>
|
||||
- <a href="{% url "ietf.group.views.reviewer_overview" group_type=group.type_id acronym=group.acronym %}">Reviewers</a>
|
||||
- <a href="{% url "ietf.group.views.email_open_review_assignments" group_type=group.type_id acronym=group.acronym %}?next={{ request.get_full_path|urlencode }}">Email open assignments summary</a>
|
||||
{% if other_assignment_status %}
|
||||
- <a href="{% url "ietf.group.views_review.manage_review_requests" group_type=group.type_id acronym=group.acronym assignment_status=other_assignment_status %}">Manage {{ other_assignment_status }} reviews</a>
|
||||
- <a href="{% url "ietf.group.views.manage_review_requests" group_type=group.type_id acronym=group.acronym assignment_status=other_assignment_status %}">Manage {{ other_assignment_status }} reviews</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
|
@ -169,7 +169,7 @@
|
|||
{% endfor %}
|
||||
|
||||
{% buttons %}
|
||||
<a href="{% url "ietf.group.views_review.review_requests" group_type=group.type_id acronym=group.acronym %}" class="btn btn-default pull-right">Cancel</a>
|
||||
<a href="{% url "ietf.group.views.review_requests" group_type=group.type_id acronym=group.acronym %}" class="btn btn-default pull-right">Cancel</a>
|
||||
<button class="btn btn-primary" type="submit" name="action" value="save">Save changes</button>
|
||||
<button class="btn btn-primary" type="submit" name="action" value="save-continue">Save and continue editing</button>
|
||||
<button class="btn btn-default" type="submit" name="action" value="refresh">Refresh (keeping changes)</button>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
{% if editable %}
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url "ietf.group.views_stream.stream_edit" stream.slug %}">Assign delegates</a>
|
||||
<a class="btn btn-default" href="{% url "ietf.group.views.stream_edit" stream.slug %}">Assign delegates</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<a class="btn btn-default pull-right" href="{% url "ietf.group.views_stream.streams" %}{{ group.acronym }}">Back</a>
|
||||
<a class="btn btn-default pull-right" href="{% url "ietf.group.views.streams" %}{{ group.acronym }}">Back</a>
|
||||
{% endbuttons %}
|
||||
|
||||
</form>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<td><a {% if r.pk != None %}href="{% url "ietf.doc.views_review.review_request" name=r.doc.name request_id=r.pk %}"{% endif %}>{{ r.doc.name }}</a></td>
|
||||
<td>{% if r.requested_rev %}{{ r.requested_rev }}{% else %}Current{% endif %}</td>
|
||||
<td>{{r.doc.rev}}</td>
|
||||
<td><a href="{% url 'ietf.group.views_review.review_requests' acronym=r.team.acronym %}">{{ r.team.acronym }}</a></td>
|
||||
<td><a href="{% url 'ietf.group.views.review_requests' acronym=r.team.acronym %}">{{ r.team.acronym }}</a></td>
|
||||
<td>{{ r.type.name }}</td>
|
||||
<td>
|
||||
{{ r.deadline|date:"Y-m-d" }}
|
||||
|
@ -70,7 +70,7 @@
|
|||
<tr>
|
||||
<td><a {% if r.pk != None %}href="{% url "ietf.doc.views_review.review_request" name=r.doc.name request_id=r.pk %}"{% endif %}>{{ r.doc.name }}</a></td>
|
||||
<td>{{r.reviewed_rev|default:"See review"}}{% if r.requested_rev %}{% if r.requested_rev != r.reviewed_rev %}({{ r.requested_rev }} requested){% endif %}{% endif %}</td>
|
||||
<td><a href="{% url 'ietf.group.views_review.review_requests' acronym=r.team.acronym %}">{{ r.team.acronym }}</a></td>
|
||||
<td><a href="{% url 'ietf.group.views.review_requests' acronym=r.team.acronym %}">{{ r.team.acronym }}</a></td>
|
||||
<td>{{ r.type.name }}</td>
|
||||
<td>
|
||||
{{ r.deadline|date:"Y-m-d" }}
|
||||
|
@ -158,7 +158,7 @@
|
|||
</table>
|
||||
|
||||
<div>
|
||||
<a class="btn btn-default" href="{% url "ietf.group.views_review.change_reviewer_settings" group_type=t.type_id acronym=t.acronym reviewer_email=t.role.email.address %}?next={{ request.get_full_path|urlencode }}">Change settings</a>
|
||||
<a class="btn btn-default" href="{% url "ietf.group.views.change_reviewer_settings" group_type=t.type_id acronym=t.acronym reviewer_email=t.role.email.address %}?next={{ request.get_full_path|urlencode }}">Change settings</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import debug # pyflakes:ignore
|
|||
|
||||
from ietf import api
|
||||
from ietf.doc import views_search
|
||||
from ietf.group.urls import group_urls, grouptype_urls, stream_urls
|
||||
from ietf.help import views as help_views
|
||||
from ietf.ipr.sitemaps import IPRMap
|
||||
from ietf.liaisons.sitemaps import LiaisonMap
|
||||
|
@ -46,7 +47,7 @@ urlpatterns = [
|
|||
url(r'^drafts/', include('ietf.doc.redirect_drafts_urls')),
|
||||
url(r'^mailtrigger/',include('ietf.mailtrigger.urls')),
|
||||
url(r'^feed/', include('ietf.feed_urls')),
|
||||
url(r'^group/', include('ietf.group.urls')),
|
||||
url(r'^group/', include(group_urls)),
|
||||
url(r'^help/', include('ietf.help.urls')),
|
||||
url(r'^idtracker/', include('ietf.doc.redirect_idtracker_urls')),
|
||||
url(r'^iesg/', include('ietf.iesg.urls')),
|
||||
|
@ -61,11 +62,11 @@ urlpatterns = [
|
|||
url(r'^sitemap-(?P<section>.+).xml$', sitemap_views.sitemap, {'sitemaps': sitemaps}),
|
||||
url(r'^sitemap.xml$', sitemap_views.index, { 'sitemaps': sitemaps}),
|
||||
url(r'^stats/', include('ietf.stats.urls')),
|
||||
url(r'^stream/', include('ietf.group.urls_stream')),
|
||||
url(r'^stream/', include(stream_urls)),
|
||||
url(r'^submit/', include('ietf.submit.urls')),
|
||||
url(r'^sync/', include('ietf.sync.urls')),
|
||||
url(r'^templates/', include('ietf.dbtemplate.urls')),
|
||||
url(r'^(?P<group_type>(wg|rg|ag|team|dir|area|program))/', include('ietf.group.urls_info')),
|
||||
url(r'^(?P<group_type>(wg|rg|ag|team|dir|area|program))/', include(grouptype_urls)),
|
||||
|
||||
# Redirects
|
||||
url(r'^(?P<path>public)/', include('ietf.redirects.urls')),
|
||||
|
|
Loading…
Reference in a new issue