diff --git a/ietf/doc/templatetags/active_groups_menu.py b/ietf/doc/templatetags/active_groups_menu.py new file mode 100644 index 000000000..a9915343c --- /dev/null +++ b/ietf/doc/templatetags/active_groups_menu.py @@ -0,0 +1,14 @@ +from django import template +from django.template.loader import render_to_string + +from ietf.name.models import GroupTypeName + +register = template.Library() + +@register.simple_tag +def active_groups_menu(): + parents = GroupTypeName.objects.filter(slug__in=['ag','area','team','dir']) + for p in parents: + p.menu_url = '/%s/'%p.slug + return render_to_string('base/menu_active_groups.html', { 'parents': parents }) + diff --git a/ietf/group/edit.py b/ietf/group/edit.py index 390a34495..e4a40aea9 100644 --- a/ietf/group/edit.py +++ b/ietf/group/edit.py @@ -167,14 +167,29 @@ def get_or_create_initial_charter(group, group_type): return charter @login_required -def submit_initial_charter(request, group_type, acronym=None): - if not can_manage_group_type(request.user, group_type): - return HttpResponseForbidden("You don't have permission to access this view") +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_type(request.user, group_type): + 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() diff --git a/ietf/group/features.py b/ietf/group/features.py index 98b5ba2da..7e32136b8 100644 --- a/ietf/group/features.py +++ b/ietf/group/features.py @@ -20,6 +20,7 @@ class GroupFeatures(object): self.default_tab = "group_docs" elif group.type_id in ("team",): self.has_materials = True + self.default_tab = "group_about" if self.has_chartering_process: self.about_page = "group_charter" diff --git a/ietf/group/info.py b/ietf/group/info.py index 4d36e1546..22fb13000 100644 --- a/ietf/group/info.py +++ b/ietf/group/info.py @@ -189,16 +189,43 @@ def wg_charters_by_acronym(request, group_type): { 'groups': groups }, content_type='text/plain; charset=UTF-8') -def active_groups(request, group_type): - if group_type == "wg": +def active_groups(request, group_type=None): + + if not group_type: + return active_group_types(request) + elif group_type == "wg": return active_wgs(request) elif group_type == "rg": return active_rgs(request) + elif group_type == "ag": + return active_ags(request) elif group_type == "area": return active_areas(request) + elif group_type == "team": + return active_teams(request) + elif group_type == "dir": + return active_dirs(request) else: raise Http404 +def active_group_types(request): + grouptypes = GroupTypeName.objects.filter(slug__in=['wg','rg','ag','team','dir','area']) + return render(request, 'group/active_groups.html', {'grouptypes':grouptypes}) + +def active_dirs(request): + dirs = Group.objects.filter(type="dir", state="active").order_by("name") + for group in dirs: + group.chairs = sorted(roles(group, "chair"), key=extract_last_name) + group.ads = sorted(roles(group, "ad"), key=extract_last_name) + group.secretaries = sorted(roles(group, "secr"), key=extract_last_name) + return render(request, 'group/active_dirs.html', {'dirs' : dirs }) + +def active_teams(request): + teams = Group.objects.filter(type="team", state="active").order_by("name") + for group in teams: + group.chairs = sorted(roles(group, "chair"), key=extract_last_name) + return render(request, 'group/active_teams.html', {'teams' : teams }) + def active_areas(request): areas = Group.objects.filter(type="area", state="active").order_by("name") return render(request, 'group/active_areas.html', {'areas': areas }) @@ -235,6 +262,15 @@ def active_rgs(request): return render(request, 'group/active_rgs.html', { 'irtf': irtf, 'groups': groups }) +def active_ags(request): + + groups = Group.objects.filter(type="ag", state="active").order_by("acronym") + for group in groups: + group.chairs = sorted(roles(group, "chair"), key=extract_last_name) + group.ads = sorted(roles(group, "ad"), key=extract_last_name) + + return render(request, 'group/active_ags.html', { 'groups': groups }) + def bofs(request, group_type): groups = Group.objects.filter(type=group_type, state="bof") return render(request, 'group/bofs.html',dict(groups=groups)) diff --git a/ietf/group/tests.py b/ietf/group/tests.py index 9f783186d..36f9fdd48 100644 --- a/ietf/group/tests.py +++ b/ietf/group/tests.py @@ -63,21 +63,25 @@ class GroupTests(TestCase): make_test_data() for group in Group.objects.filter(Q(type="wg") | Q(type="rg")): client = Client(Accept='application/pdf') - r = client.get(urlreverse("ietf.group.info.dependencies_dot", - kwargs=dict(acronym=group.acronym))) - self.assertTrue(r.status_code == 200, "Failed to receive " - "a dot dependency graph for group: %s"%group.acronym) - self.assertGreater(len(r.content), 0, "Dot dependency graph for group " - "%s has no content"%group.acronym) + for url in [ urlreverse("ietf.group.info.dependencies_dot",kwargs=dict(acronym=group.acronym)), + urlreverse("ietf.group.info.dependencies_dot",kwargs=dict(acronym=group.acronym,group_type=group.type_id)), + ]: + r = client.get(url) + self.assertTrue(r.status_code == 200, "Failed to receive " + "a dot dependency graph for group: %s"%group.acronym) + self.assertGreater(len(r.content), 0, "Dot dependency graph for group " + "%s has no content"%group.acronym) def test_group_document_dependency_pdffile(self): make_test_data() for group in Group.objects.filter(Q(type="wg") | Q(type="rg")): client = Client(Accept='application/pdf') - r = client.get(urlreverse("ietf.group.info.dependencies_pdf", - kwargs=dict(acronym=group.acronym))) - self.assertTrue(r.status_code == 200, "Failed to receive " - "a pdf dependency graph for group: %s"%group.acronym) - self.assertGreater(len(r.content), 0, "Pdf dependency graph for group " - "%s has no content"%group.acronym) + for url in [ urlreverse("ietf.group.info.dependencies_pdf",kwargs=dict(acronym=group.acronym)), + urlreverse("ietf.group.info.dependencies_pdf",kwargs=dict(acronym=group.acronym,group_type=group.type_id)), + ]: + r = client.get(url) + self.assertTrue(r.status_code == 200, "Failed to receive " + "a pdf dependency graph for group: %s"%group.acronym) + self.assertGreater(len(r.content), 0, "Pdf dependency graph for group " + "%s has no content"%group.acronym) diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py index c75286ef1..9cb180e1e 100644 --- a/ietf/group/tests_info.py +++ b/ietf/group/tests_info.py @@ -10,11 +10,12 @@ import debug # pyflakes:ignore from django.conf import settings from django.core.urlresolvers import reverse as urlreverse +from django.core.urlresolvers import NoReverseMatch from ietf.doc.models import Document, DocAlias, DocEvent, State from ietf.group.models import Group, GroupEvent, GroupMilestone, GroupStateTransitions, MilestoneGroupEvent from ietf.group.utils import save_group_in_history -from ietf.name.models import DocTagName, GroupStateName +from ietf.name.models import DocTagName, GroupStateName, GroupTypeName from ietf.person.models import Person, Email from ietf.utils.test_utils import TestCase from ietf.utils.mail import outbox @@ -48,11 +49,37 @@ class GroupPagesTests(TestCase): url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="rg")) r = self.client.get(url) self.assertEqual(r.status_code, 200) + self.assertTrue('Active Research Groups' in r.content) url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="area")) r = self.client.get(url) self.assertEqual(r.status_code, 200) - self.assertTrue("farfut" in r.content) + self.assertTrue("Far Future (farfut)" in r.content) + + url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="ag")) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue("Active Area Groups" in r.content) + + url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="dir")) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue("Active Directorates" in r.content) + + url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="team")) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue("Active Teams" in r.content) + + url = urlreverse('ietf.group.info.active_groups', kwargs=dict()) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue("Directorate" in r.content) + self.assertTrue("AG" in r.content) + + for slug in GroupTypeName.objects.exclude(slug__in=['wg','rg','ag','area','dir','team']).values_list('slug',flat=True): + with self.assertRaises(NoReverseMatch): + url=urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type=slug)) def test_wg_summaries(self): draft = make_test_data() @@ -195,14 +222,17 @@ class GroupPagesTests(TestCase): due=datetime.date.today() + datetime.timedelta(days=100)) milestone.docs.add(draft) - url = group.about_url() - r = self.client.get(url) - self.assertEqual(r.status_code, 200) - self.assertTrue(group.name in r.content) - self.assertTrue(group.acronym in r.content) - self.assertTrue("This is a charter." in r.content) - self.assertTrue(milestone.desc in r.content) - self.assertTrue(milestone.docs.all()[0].name in r.content) + for url in [group.about_url(), + urlreverse('ietf.group.info.group_about',kwargs=dict(acronym=group.acronym)), + urlreverse('ietf.group.info.group_about',kwargs=dict(acronym=group.acronym,group_type=group.type_id)), + ]: + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue(group.name in r.content) + self.assertTrue(group.acronym in r.content) + self.assertTrue("This is a charter." in r.content) + self.assertTrue(milestone.desc in r.content) + self.assertTrue(milestone.docs.all()[0].name in r.content) def test_group_about(self): make_test_data() @@ -214,12 +244,16 @@ class GroupPagesTests(TestCase): state_id="active", ) - url = group.about_url() - r = self.client.get(url) - self.assertEqual(r.status_code, 200) - self.assertTrue(group.name in r.content) - self.assertTrue(group.acronym in r.content) - self.assertTrue(group.description in r.content) + for url in [group.about_url(), + urlreverse('ietf.group.info.group_about',kwargs=dict(acronym=group.acronym)), + urlreverse('ietf.group.info.group_about',kwargs=dict(acronym=group.acronym,group_type=group.type_id)), + ]: + url = group.about_url() + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue(group.name in r.content) + self.assertTrue(group.acronym in r.content) + self.assertTrue(group.description in r.content) def test_materials(self): make_test_data() @@ -235,11 +269,15 @@ class GroupPagesTests(TestCase): doc.set_state(State.objects.get(type="slides", slug="active")) DocAlias.objects.create(name=doc.name, document=doc) - url = urlreverse("group_materials", kwargs={ 'acronym': group.acronym }) - r = self.client.get(url) - self.assertEqual(r.status_code, 200) - self.assertTrue(doc.title in r.content) - self.assertTrue(doc.name in r.content) + for url in [ urlreverse("group_materials", kwargs={ 'acronym': group.acronym }), + urlreverse("group_materials", kwargs={ 'acronym': group.acronym , 'group_type': group.type_id}), + ]: + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue(doc.title in r.content) + self.assertTrue(doc.name in r.content) + + url = urlreverse("group_materials", kwargs={ 'acronym': group.acronym }) # try deleting the document and check it's gone doc.set_state(State.objects.get(type="slides", slug="deleted")) @@ -475,6 +513,19 @@ class GroupEditTests(TestCase): self.assertEqual(group.groupurl_set.all()[0].name, "MARS site") self.assertTrue(os.path.exists(os.path.join(self.charter_dir, "%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev)))) + def test_initial_charter(self): + make_test_data() + group = Group.objects.get(acronym="mars") + for url in [ urlreverse('ietf.group.edit.submit_initial_charter', kwargs={'acronym':group.acronym}), + urlreverse('ietf.group.edit.submit_initial_charter', kwargs={'acronym':group.acronym,'group_type':group.type_id}), + ]: + login_testing_unauthorized(self, "secretary", url) + r = self.client.get(url,follow=True) + self.assertEqual(r.status_code,200) + self.assertTrue(r.redirect_chain[0][0].endswith(urlreverse('charter_submit',kwargs={'name':group.charter.name,'option':'initcharter'}))) + self.client.logout() + + def test_conclude(self): make_test_data() @@ -998,11 +1049,13 @@ expand-ames-chairs@virtual.ietf.org mars_chair@ietf def tearDown(self): os.unlink(self.group_alias_file.name) - def testNothing(self): - url = urlreverse('ietf.group.info.email_aliases', kwargs=dict(acronym="mars")) - r = self.client.get(url) - self.assertTrue(all([x in r.content for x in ['mars-ads@','mars-chairs@']])) - self.assertFalse(any([x in r.content for x in ['ames-ads@','ames-chairs@']])) + def testEmailAliases(self): + + for testdict in [dict(acronym="mars"),dict(acronym="mars",group_type="wg")]: + url = urlreverse('ietf.group.info.email_aliases', kwargs=testdict) + r = self.client.get(url) + self.assertTrue(all([x in r.content for x in ['mars-ads@','mars-chairs@']])) + self.assertFalse(any([x in r.content for x in ['ames-ads@','ames-chairs@']])) url = urlreverse('ietf.group.info.email_aliases', kwargs=dict()) login_testing_unauthorized(self, "plain", url) diff --git a/ietf/group/urls.py b/ietf/group/urls.py index 08853bbbe..00124a3a7 100644 --- a/ietf/group/urls.py +++ b/ietf/group/urls.py @@ -1,36 +1,18 @@ # Copyright The IETF Trust 2007, All Rights Reserved -from django.conf.urls import patterns +from django.conf.urls import patterns, include urlpatterns = patterns('', + (r'^$', 'ietf.group.info.active_groups'), (r'^groupmenu.json', 'ietf.group.ajax.group_menu_data', None, "group_menu_data"), (r'^(?P[a-z0-9]+).json$', 'ietf.group.ajax.group_json'), (r'^chartering/$', 'ietf.group.info.chartering_groups'), (r'^chartering/create/(?P(wg|rg))/$', 'ietf.group.edit.edit', {'action': "charter"}, "group_create"), (r'^concluded/$', 'ietf.group.info.concluded_groups'), (r'^email-aliases/$', 'ietf.group.info.email_aliases'), - # FIXME: the things below are duplicated in urls_info.py while we - # figure out whether to serve everything from /group/, - # need to unify these at some point - (r'^(?P[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"), - (r'^(?P[a-zA-Z0-9-._]+)/documents/$', 'ietf.group.info.group_documents', None, "group_docs"), - (r'^(?P[a-zA-Z0-9-._]+)/charter/$', 'ietf.group.info.group_about', None, 'group_charter'), - (r'^(?P[a-zA-Z0-9-._]+)/history/$', 'ietf.group.info.history'), - (r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', 'ietf.group.info.dependencies_dot'), - (r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', 'ietf.group.info.dependencies_pdf'), - (r'^(?P[a-zA-Z0-9-._]+)/init-charter/', 'ietf.group.edit.submit_initial_charter'), - (r'^(?P[a-zA-Z0-9-._]+)/edit/$', 'ietf.group.edit.edit', {'action': "edit"}, "group_edit"), - (r'^(?P[a-zA-Z0-9-._]+)/conclude/$', 'ietf.group.edit.conclude'), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "current"}, "group_edit_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "charter"}, "group_edit_charter_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/reset/$', 'ietf.group.milestones.reset_charter_milestones', None, "group_reset_charter_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/workflow/$', 'ietf.group.edit.customize_workflow'), - (r'^(?P[a-zA-Z0-9-._]+)/about/(?P.)?$', 'ietf.group.info.group_about', None, 'group_about'), - (r'^(?P[a-zA-Z0-9-._]+)/materials/$', 'ietf.group.info.materials', None, "group_materials"), - (r'^(?P[a-zA-Z0-9-._]+)/materials/new/$', 'ietf.doc.views_material.choose_material_type'), - (r'^(?P[a-zA-Z0-9-._]+)/materials/new/(?P[\w-]+)/$', 'ietf.doc.views_material.edit_material', { 'action': "new" }, "group_new_material"), - (r'^(?P[A-Za-z0-9._+-]+)/email-aliases/$', 'ietf.group.info.email_aliases'), + (r'^(?P[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"), + (r'^(?P[a-zA-Z0-9-._]+)/', include('ietf.group.urls_info_details')), ) diff --git a/ietf/group/urls_info.py b/ietf/group/urls_info.py index dea946c16..581509d8d 100644 --- a/ietf/group/urls_info.py +++ b/ietf/group/urls_info.py @@ -1,12 +1,12 @@ # Copyright The IETF Trust 2008, All Rights Reserved -from django.conf.urls import patterns +from django.conf.urls import patterns, include from django.views.generic import RedirectView -from ietf.group import info, edit, milestones +from ietf.group import info, edit urlpatterns = patterns('', - (r'^$', info.active_groups), + (r'^$', info.active_groups), (r'^summary.txt', RedirectView.as_view(url='/wg/1wg-summary.txt')), (r'^summary-by-area.txt', RedirectView.as_view(url='/wg/1wg-summary.txt')), (r'^summary-by-acronym.txt', RedirectView.as_view(url='/wg/1wg-summary-by-acronym.txt')), @@ -19,19 +19,5 @@ urlpatterns = patterns('', (r'^bofs/$', info.bofs), (r'^email-aliases/$', 'ietf.group.info.email_aliases'), (r'^bofs/create/$', edit.edit, {'action': "create"}, "bof_create"), - (r'^(?P[a-zA-Z0-9-._]+)/documents/txt/$', info.group_documents_txt), - (r'^(?P[a-zA-Z0-9-._]+)/$', info.group_home, None, "group_home"), - (r'^(?P[a-zA-Z0-9-._]+)/documents/$', info.group_documents, None, "group_docs"), - (r'^(?P[a-zA-Z0-9-._]+)/charter/$', info.group_about, None, 'group_charter'), - (r'^(?P[a-zA-Z0-9-._]+)/history/$', info.history), - (r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', info.dependencies_dot), - (r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', info.dependencies_pdf), - (r'^(?P[a-zA-Z0-9-._]+)/init-charter/', edit.submit_initial_charter), - (r'^(?P[a-zA-Z0-9-._]+)/edit/$', edit.edit, {'action': "edit"}, "group_edit"), - (r'^(?P[a-zA-Z0-9-._]+)/conclude/$', edit.conclude), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "group_edit_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/$', milestones.edit_milestones, {'milestone_set': "charter"}, "group_edit_charter_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/reset/$', milestones.reset_charter_milestones, None, "group_reset_charter_milestones"), - (r'^(?P[a-zA-Z0-9-._]+)/workflow/$', edit.customize_workflow), - (r'^(?P[A-Za-z0-9._+-]+)/email-aliases/$', 'ietf.group.info.email_aliases'), + (r'^(?P[a-zA-Z0-9-._]+)/', include('ietf.group.urls_info_details')), ) diff --git a/ietf/group/urls_info_details.py b/ietf/group/urls_info_details.py new file mode 100644 index 000000000..430c09b39 --- /dev/null +++ b/ietf/group/urls_info_details.py @@ -0,0 +1,23 @@ +from django.conf.urls import patterns + +urlpatterns = patterns('', + (r'^$', 'ietf.group.info.group_home', None, "group_home"), + (r'^documents/txt/$', 'ietf.group.info.group_documents_txt'), + (r'^documents/$', 'ietf.group.info.group_documents', None, "group_docs"), + (r'^charter/$', 'ietf.group.info.group_about', None, 'group_charter'), + (r'^about/$', 'ietf.group.info.group_about', None, 'group_about'), + (r'^history/$','ietf.group.info.history'), + (r'^deps/dot/$', 'ietf.group.info.dependencies_dot'), + (r'^deps/pdf/$', 'ietf.group.info.dependencies_pdf'), + (r'^init-charter/', 'ietf.group.edit.submit_initial_charter'), + (r'^edit/$', 'ietf.group.edit.edit', {'action': "edit"}, "group_edit"), + (r'^conclude/$', 'ietf.group.edit.conclude'), + (r'^milestones/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "current"}, "group_edit_milestones"), + (r'^milestones/charter/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "charter"}, "group_edit_charter_milestones"), + (r'^milestones/charter/reset/$', 'ietf.group.milestones.reset_charter_milestones', None, "group_reset_charter_milestones"), + (r'^workflow/$', 'ietf.group.edit.customize_workflow'), + (r'^materials/$', 'ietf.group.info.materials', None, "group_materials"), + (r'^materials/new/$', 'ietf.doc.views_material.choose_material_type'), + (r'^materials/new/(?P[\w-]+)/$', 'ietf.doc.views_material.edit_material', { 'action': "new" }, "group_new_material"), + (r'^/email-aliases/$', 'ietf.group.info.email_aliases'), +) diff --git a/ietf/templates/base/menu.html b/ietf/templates/base/menu.html index 60eff279b..1a6834e83 100644 --- a/ietf/templates/base/menu.html +++ b/ietf/templates/base/menu.html @@ -1,5 +1,5 @@ {# Copyright The IETF Trust 2015, All Rights Reserved #}{% load origin %}{% origin %} -{% load ietf_filters community_tags wg_menu streams_menu %} +{% load ietf_filters community_tags wg_menu streams_menu active_groups_menu %} {% if flavor != "top" %} {% include "base/menu_user.html" %} @@ -14,9 +14,9 @@