From 9579525ca468df56b50b92b04ba18eddf9316d02 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Thu, 22 May 2014 15:53:20 +0000 Subject: [PATCH] Add simple materials page, tie up some of the loose ends in group generalization work - Legacy-Id: 7768 --- ietf/group/edit.py | 34 ++++++++++++++-- ietf/group/features.py | 5 ++- ietf/group/info.py | 61 +++++++++++++++++++---------- ietf/group/milestones.py | 17 +++++--- ietf/group/models.py | 7 +++- ietf/group/urls.py | 3 ++ ietf/group/urls_info.py | 3 +- ietf/group/utils.py | 12 ++++++ ietf/templates/group/materials.html | 27 +++++++++++++ 9 files changed, 133 insertions(+), 36 deletions(-) create mode 100644 ietf/templates/group/materials.html diff --git a/ietf/group/edit.py b/ietf/group/edit.py index b77eafd62..9d5477698 100644 --- a/ietf/group/edit.py +++ b/ietf/group/edit.py @@ -7,7 +7,7 @@ import shutil from django import forms from django.shortcuts import render, get_object_or_404, redirect -from django.http import HttpResponseForbidden +from django.http import HttpResponseForbidden, Http404 from django.utils.html import mark_safe from django.http import Http404, HttpResponse from django.contrib.auth.decorators import login_required @@ -18,7 +18,8 @@ from ietf.doc.models import DocAlias, DocTagName, Document, State, save_document from ietf.doc.utils import get_tags_for_stream_id 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_type +from ietf.group.utils import (save_group_in_history, can_manage_group_type, can_manage_materials, + get_group_or_404) from ietf.ietfauth.utils import has_role from ietf.person.forms import EmailsField from ietf.person.models import Person, Email @@ -166,6 +167,9 @@ def submit_initial_charter(request, group_type, acronym=None): return HttpResponseForbidden("You don't have permission to access this view") group = get_object_or_404(Group, acronym=acronym) + if not group.features.has_chartering_process: + raise Http404 + if not group.charter: group.charter = get_or_create_initial_charter(group, group_type) group.save() @@ -188,6 +192,9 @@ def edit(request, group_type=None, acronym=None, action="edit"): else: raise Http404 + if not group_type and group: + group_type = group.type_id + if request.method == 'POST': form = GroupForm(request.POST, group=group, confirmed=request.POST.get("confirmed", False), group_type=group_type) if form.is_valid(): @@ -330,7 +337,7 @@ class ConcludeForm(forms.Form): @login_required def conclude(request, group_type, acronym): """Request the closing of group, prompting for instructions.""" - group = get_object_or_404(Group, type=group_type, acronym=acronym) + group = get_group_or_404(acronym, group_type) if not can_manage_group_type(request.user, group_type): return HttpResponseForbidden("You don't have permission to access this view") @@ -357,7 +364,10 @@ def conclude(request, group_type, acronym): @login_required def customize_workflow(request, group_type, acronym): - group = get_object_or_404(Group, type=group_type, acronym=acronym) + group = get_group_or_404(acronym, group_type) + if not group.features.customize_workflow: + raise Http404 + if (not has_role(request.user, "Secretariat") and not group.role_set.filter(name="chair", person__user=request.user)): return HttpResponseForbidden("You don't have permission to access this view") @@ -445,3 +455,19 @@ def customize_workflow(request, group_type, acronym): 'states': states, 'tags': tags, }) + +@login_required +def upload_materials(request, acronym, group_type=None): + group = get_group_or_404(acronym, group_type) + if not group.features.has_materials: + raise Http404 + + if not can_manage_materials(request.user, group): + return HttpResponseForbidden("You don't have permission to access this view") + + # FIXME: fill in + + return render(request, 'group/materials.html', + construct_group_menu_context(request, group, "materials", group_type, { + "materials": materials, + })) diff --git a/ietf/group/features.py b/ietf/group/features.py index 532dfc06a..e97d06529 100644 --- a/ietf/group/features.py +++ b/ietf/group/features.py @@ -4,7 +4,8 @@ class GroupFeatures(object): has_milestones = False has_chartering_process = False - has_documents = False + has_documents = False # i.e. drafts/RFCs + has_materials = False customize_workflow = False default_tab = "group_charter" @@ -16,4 +17,4 @@ class GroupFeatures(object): self.customize_workflow = True self.default_tab = "group_docs" elif group.type_id in ("team",): - self.has_documents = True + self.has_materials = True diff --git a/ietf/group/info.py b/ietf/group/info.py index 22a17a2d6..90a9c633d 100644 --- a/ietf/group/info.py +++ b/ietf/group/info.py @@ -46,12 +46,13 @@ from django.db.models import Q from django.utils.safestring import mark_safe from ietf.doc.views_search import SearchForm, retrieve_search_results -from ietf.doc.models import State, DocAlias, RelatedDocument +from ietf.doc.models import Document, State, DocAlias, RelatedDocument from ietf.doc.utils import get_chartering_type from ietf.doc.templatetags.ietf_filters import clean_whitespace from ietf.group.models import Group, Role, ChangeStateGroupEvent from ietf.name.models import GroupTypeName from ietf.group.utils import get_charter_text, can_manage_group_type, milestone_reviewer_for_group_type +from ietf.group.utils import can_manage_materials, get_group_or_404 from ietf.utils.pipe import pipe def roles(group, role_name): @@ -256,6 +257,9 @@ def concluded_groups(request): return render(request, 'group/concluded_groups.html', dict(group_types=group_types)) +def get_group_materials(group): + return Document.objects.filter(group=group).exclude(states__slug="deleted")#.exclude(session=None).exclude(type="charter") + def construct_group_menu_context(request, group, selected, group_type, others): """Return context with info for the group menu filled in.""" kwargs = dict(acronym=group.acronym) @@ -264,11 +268,13 @@ def construct_group_menu_context(request, group, selected, group_type, others): # entries entries = [] - if group.features().has_documents: + if group.features.has_documents: entries.append(("Documents", urlreverse("ietf.group.info.group_documents", kwargs=kwargs))) entries.append(("Charter", urlreverse("ietf.group.info.group_charter", kwargs=kwargs))) + if group.features.has_materials and get_group_materials(group).exists(): + entries.append(("Materials", urlreverse("ietf.group.info.materials", kwargs=kwargs))) entries.append(("History", urlreverse("ietf.group.info.history", kwargs=kwargs))) - if group.features().has_documents: + if group.features.has_documents: entries.append(("Dependency Graph", urlreverse("ietf.group.info.dependencies_pdf", kwargs=kwargs))) if group.list_archive.startswith("http:") or group.list_archive.startswith("https:") or group.list_archive.startswith("ftp:"): @@ -283,14 +289,17 @@ def construct_group_menu_context(request, group, selected, group_type, others): is_chair = group.has_role(request.user, "chair") can_manage = can_manage_group_type(request.user, group.type_id) - if group.features().has_milestones: + if group.features.has_milestones: if group.state_id != "proposed" and (is_chair or can_manage): actions.append((u"Add or edit milestones", urlreverse("group_edit_milestones", kwargs=kwargs))) + if group.features.has_materials and can_manage_materials(request.user, group): + actions.append((u"Upload materials", urlreverse("group_upload_materials", kwargs=kwargs))) + if group.state_id != "conclude" and can_manage: actions.append((u"Edit group", urlreverse("group_edit", kwargs=kwargs))) - if group.features().customize_workflow and (is_chair or can_manage): + if group.features.customize_workflow and (is_chair or can_manage): actions.append((u"Customize workflow", urlreverse("ietf.group.edit.customize_workflow", kwargs=kwargs))) if group.state_id in ("active", "dormant") and can_manage: @@ -341,23 +350,17 @@ def search_for_group_documents(group): return docs, meta, docs_related, meta_related -def get_group_or_404(acronym, group_type): - """Helper to overcome the schism between group-type prefixed URLs and generic.""" - possible_groups = Group.objects.all() - if group_type: - possible_groups = possible_groups.filter(type=group_type) - - return get_object_or_404(possible_groups, acronym=acronym) - def group_home(request, acronym, group_type=None): group = get_group_or_404(acronym, group_type) kwargs = dict(acronym=group.acronym) if group_type: kwargs["group_type"] = group_type - return HttpResponseRedirect(urlreverse(group.features().default_tab, kwargs=kwargs)) + return HttpResponseRedirect(urlreverse(group.features.default_tab, kwargs=kwargs)) def group_documents(request, acronym, group_type=None): group = get_group_or_404(acronym, group_type) + if not group.features.has_documents: + raise Http404 docs, meta, docs_related, meta_related = search_for_group_documents(group) @@ -372,6 +375,8 @@ def group_documents(request, acronym, group_type=None): def group_documents_txt(request, acronym, group_type=None): """Return tabulator-separated rows with documents for group.""" group = get_group_or_404(acronym, group_type) + if not group.features.has_documents: + raise Http404 docs, meta, docs_related, meta_related = search_for_group_documents(group) @@ -393,7 +398,6 @@ def group_documents_txt(request, acronym, group_type=None): return HttpResponse(u"\n".join(rows), content_type='text/plain; charset=UTF-8') - def group_charter(request, acronym, group_type=None): group = get_group_or_404(acronym, group_type) @@ -410,10 +414,10 @@ def group_charter(request, acronym, group_type=None): can_manage = can_manage_group_type(request.user, group.type_id) - if group.features().has_chartering_process: + if group.features.has_chartering_process: description = group.charter_text else: - description = group.description or "No description entered yet." + description = group.description or "No description yet." return render(request, 'group/group_charter.html', construct_group_menu_context(request, group, "charter", group_type, { @@ -433,10 +437,21 @@ def history(request, acronym, group_type=None): return render(request, 'group/history.html', construct_group_menu_context(request, group, "history", group_type, { - "events": events, - })) - - + "events": events, + })) + +def materials(request, acronym, group_type=None): + group = get_group_or_404(acronym, group_type) + if not group.features.has_materials: + raise Http404 + + materials = get_group_materials(group).order_by("-time") + + return render(request, 'group/materials.html', + construct_group_menu_context(request, group, "materials", group_type, { + "materials": materials, + })) + def nodename(name): return name.replace('-','_') @@ -556,6 +571,8 @@ def make_dot(group): def dependencies_dot(request, acronym, group_type=None): group = get_group_or_404(acronym, group_type) + if not group.features.has_documents: + raise Http404 return HttpResponse(make_dot(group), content_type='text/plain; charset=UTF-8' @@ -564,6 +581,8 @@ def dependencies_dot(request, acronym, group_type=None): @cache_page ( 60 * 60 ) def dependencies_pdf(request, acronym, group_type=None): group = get_group_or_404(acronym, group_type) + if not group.features.has_documents: + raise Http404 dothandle,dotname = mkstemp() os.close(dothandle) diff --git a/ietf/group/milestones.py b/ietf/group/milestones.py index 6b4a0083e..44ac4423d 100644 --- a/ietf/group/milestones.py +++ b/ietf/group/milestones.py @@ -12,7 +12,8 @@ from django.contrib.auth.decorators import login_required from ietf.doc.models import Document, DocEvent from ietf.doc.utils import get_chartering_type from ietf.group.models import Group, GroupMilestone, MilestoneGroupEvent -from ietf.group.utils import save_milestone_in_history, can_manage_group_type, milestone_reviewer_for_group_type +from ietf.group.utils import (save_milestone_in_history, can_manage_group_type, milestone_reviewer_for_group_type, + get_group_or_404) from ietf.name.models import GroupMilestoneStateName from ietf.group.mails import email_milestones_changed @@ -108,17 +109,19 @@ class MilestoneForm(forms.Form): return r @login_required -def edit_milestones(request, group_type, acronym, milestone_set="current"): +def edit_milestones(request, acronym, group_type=None, milestone_set="current"): # milestones_set + needs_review: we have several paths into this view # management (IRTF chair/AD/...)/Secr. -> all actions on current + add new # group chair -> limited actions on current + add new for review # (re)charter -> all actions on existing in state charter + add new in state charter # # For charters we store the history on the charter document to not confuse people. - group = get_object_or_404(Group, type=group_type, acronym=acronym) + group = get_group_or_404(acronym, group_type) + if not group.features.has_milestones: + raise Http404 needs_review = False - if not can_manage_group_type(request.user, group_type): + if not can_manage_group_type(request.user, group.type_id): if group.role_set.filter(name="chair", person__user=request.user): if milestone_set == "current": needs_review = True @@ -332,8 +335,10 @@ def edit_milestones(request, group_type, acronym, milestone_set="current"): @login_required def reset_charter_milestones(request, group_type, acronym): """Reset charter milestones to the currently in-use milestones.""" - group = get_object_or_404(Group, type=group_type, acronym=acronym) - + group = get_group_or_404(acronym, group_type) + if not group.features.has_milestones: + raise Http404 + if (not can_manage_group_type(request.user, group_type) and not group.role_set.filter(name="chair", person__user=request.user)): return HttpResponseForbidden("You are not chair of this group.") diff --git a/ietf/group/models.py b/ietf/group/models.py index e66572b37..64009012e 100644 --- a/ietf/group/models.py +++ b/ietf/group/models.py @@ -80,9 +80,12 @@ class Group(GroupInfo): def bg_color(self): return bg_group_colors[self.upcase_acronym] + @property def features(self): - from ietf.group.features import GroupFeatures - return GroupFeatures(self) + if not hasattr(self, "features_cache"): + from ietf.group.features import GroupFeatures + self.features_cache = GroupFeatures(self) + return self.features_cache def json_url(self): return "/group/%s.json" % (self.acronym,) diff --git a/ietf/group/urls.py b/ietf/group/urls.py index f3cf567b3..f68272aad 100644 --- a/ietf/group/urls.py +++ b/ietf/group/urls.py @@ -7,10 +7,13 @@ urlpatterns = patterns('', (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'), + # FIXME: the remainder here is currently duplicated in urls_info.py, 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_charter', None, 'group_charter'), (r'^(?P[a-zA-Z0-9-._]+)/history/$', 'ietf.group.info.history'), + (r'^(?P[a-zA-Z0-9-._]+)/materials/$', 'ietf.group.info.materials', None, "group_materials"), + (r'^(?P[a-zA-Z0-9-._]+)/materials/upload/$', 'ietf.group.edit.upload_materials', None, "group_upload_materials"), (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'), diff --git a/ietf/group/urls_info.py b/ietf/group/urls_info.py index 0039047b5..0d838a1b3 100644 --- a/ietf/group/urls_info.py +++ b/ietf/group/urls_info.py @@ -19,7 +19,8 @@ urlpatterns = patterns('', (r'^bofs/$', info.bofs), (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_documents, None, "group_docs"), + (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_charter, None, 'group_charter'), (r'^(?P[a-zA-Z0-9-._]+)/history/$', info.history), (r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', info.dependencies_dot), diff --git a/ietf/group/utils.py b/ietf/group/utils.py index 89e8cd120..849802411 100644 --- a/ietf/group/utils.py +++ b/ietf/group/utils.py @@ -1,6 +1,7 @@ import os from django.conf import settings +from django.shortcuts import get_object_or_404 from ietf.group.models import Group, RoleHistory from ietf.person.models import Email @@ -107,3 +108,14 @@ def milestone_reviewer_for_group_type(group_type): else: return "Area Director" +def can_manage_materials(user, group): + return group.has_role(user, ("chair", "delegate", "secr")) or has_role(user, 'Secretariat') + +def get_group_or_404(acronym, group_type): + """Helper to overcome the schism between group-type prefixed URLs and generic.""" + possible_groups = Group.objects.all() + if group_type: + possible_groups = possible_groups.filter(type=group_type) + + return get_object_or_404(possible_groups, acronym=acronym) + diff --git a/ietf/templates/group/materials.html b/ietf/templates/group/materials.html new file mode 100644 index 000000000..d56b623e3 --- /dev/null +++ b/ietf/templates/group/materials.html @@ -0,0 +1,27 @@ +{% extends "group/group_base.html" %} +{% load ietf_filters %} + +{% block group_subtitle %}Materials{% endblock %} + +{% block morecss %} + {{ block.super }} + .material { margin-top: 0.5em; } +{% endblock %} + +{% block group_content %} +{% load ietf_filters %} + +

Materials

+ + {% if materials %} + {% for d in materials %} +
+ {{ d.title }} + ({{ d.time|date:"Y-m-d" }}) +
+ {% endfor %} + {% else %} +

No materials uploaded.

+ {% endif %} + +{% endblock %}