Add review tracking models, add a request review page (with test), show
review requests on doc page - Legacy-Id: 11206
This commit is contained in:
parent
54c4c5efc5
commit
64a65340a2
|
@ -703,7 +703,10 @@ EVENT_TYPES = [
|
||||||
|
|
||||||
# RFC Editor
|
# RFC Editor
|
||||||
("rfc_editor_received_announcement", "Announcement was received by RFC Editor"),
|
("rfc_editor_received_announcement", "Announcement was received by RFC Editor"),
|
||||||
("requested_publication", "Publication at RFC Editor requested")
|
("requested_publication", "Publication at RFC Editor requested"),
|
||||||
|
|
||||||
|
# review
|
||||||
|
("requested_review", "Requested review"),
|
||||||
]
|
]
|
||||||
|
|
||||||
class DocEvent(models.Model):
|
class DocEvent(models.Model):
|
||||||
|
|
82
ietf/doc/tests_review.py
Normal file
82
ietf/doc/tests_review.py
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from pyquery import PyQuery
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse as urlreverse
|
||||||
|
|
||||||
|
import debug # pyflakes:ignore
|
||||||
|
|
||||||
|
from ietf.review.models import ReviewRequest
|
||||||
|
from ietf.person.models import Person
|
||||||
|
from ietf.group.models import Group, Role
|
||||||
|
from ietf.name.models import ReviewResultName
|
||||||
|
from ietf.utils.test_utils import TestCase
|
||||||
|
from ietf.utils.test_data import make_test_data
|
||||||
|
from ietf.utils.test_utils import login_testing_unauthorized
|
||||||
|
|
||||||
|
def make_review_data():
|
||||||
|
review_team = Group.objects.create(state_id="active", acronym="reviewteam", name="Review Team", type_id="team")
|
||||||
|
review_team.reviewresultname_set.add(ReviewResultName.objects.filter(slug__in=["issues", "ready-issues", "ready", "not-ready"]))
|
||||||
|
|
||||||
|
p = Person.objects.get(user__username="plain")
|
||||||
|
Role.objects.create(name_id="reviewer", person=p, email=p.email_set.first(), group=review_team)
|
||||||
|
|
||||||
|
return review_team
|
||||||
|
|
||||||
|
class ReviewTests(TestCase):
|
||||||
|
def test_request_review(self):
|
||||||
|
doc = make_test_data()
|
||||||
|
review_team = make_review_data()
|
||||||
|
|
||||||
|
url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name })
|
||||||
|
login_testing_unauthorized(self, "secretary", url)
|
||||||
|
|
||||||
|
# get
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
deadline_date = datetime.date.today() + datetime.timedelta(days=10)
|
||||||
|
|
||||||
|
# post request
|
||||||
|
r = self.client.post(url, {
|
||||||
|
"type": "early",
|
||||||
|
"team": review_team.pk,
|
||||||
|
"deadline_date": deadline_date.isoformat(),
|
||||||
|
"requested_rev": "01"
|
||||||
|
})
|
||||||
|
self.assertEqual(r.status_code, 302)
|
||||||
|
|
||||||
|
req = ReviewRequest.objects.get(doc=doc)
|
||||||
|
self.assertEqual(req.deadline.date(), deadline_date)
|
||||||
|
self.assertEqual(req.deadline.time(), datetime.time(23, 59, 59))
|
||||||
|
self.assertEqual(req.state_id, "requested")
|
||||||
|
self.assertEqual(req.team, review_team)
|
||||||
|
self.assertEqual(req.requested_rev, "01")
|
||||||
|
self.assertEqual(doc.latest_event().type, "requested_review")
|
||||||
|
|
||||||
|
def test_request_review_by_reviewer(self):
|
||||||
|
doc = make_test_data()
|
||||||
|
review_team = make_review_data()
|
||||||
|
|
||||||
|
url = urlreverse('ietf.doc.views_review.request_review', kwargs={ "name": doc.name })
|
||||||
|
login_testing_unauthorized(self, "plain", url)
|
||||||
|
|
||||||
|
# post request
|
||||||
|
deadline_date = datetime.date.today() + datetime.timedelta(days=10)
|
||||||
|
|
||||||
|
r = self.client.post(url, {
|
||||||
|
"type": "early",
|
||||||
|
"team": review_team.pk,
|
||||||
|
"deadline_date": deadline_date.isoformat(),
|
||||||
|
"requested_rev": "01"
|
||||||
|
})
|
||||||
|
self.assertEqual(r.status_code, 302)
|
||||||
|
|
||||||
|
req = ReviewRequest.objects.get(doc=doc)
|
||||||
|
self.assertEqual(req.state_id, "requested")
|
||||||
|
self.assertEqual(req.team, review_team)
|
||||||
|
|
||||||
|
def test_doc_page(self):
|
||||||
|
pass
|
||||||
|
|
|
@ -36,6 +36,7 @@ from django.views.generic import RedirectView
|
||||||
from ietf.doc import views_search, views_draft, views_ballot
|
from ietf.doc import views_search, views_draft, views_ballot
|
||||||
from ietf.doc import views_status_change
|
from ietf.doc import views_status_change
|
||||||
from ietf.doc import views_doc
|
from ietf.doc import views_doc
|
||||||
|
from ietf.doc import views_review
|
||||||
|
|
||||||
session_patterns = [
|
session_patterns = [
|
||||||
url(r'^add$', views_doc.add_sessionpresentation),
|
url(r'^add$', views_doc.add_sessionpresentation),
|
||||||
|
@ -73,6 +74,8 @@ urlpatterns = patterns('',
|
||||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot/$', views_doc.document_ballot, name="doc_ballot"),
|
url(r'^(?P<name>[A-Za-z0-9._+-]+)/ballot/$', views_doc.document_ballot, name="doc_ballot"),
|
||||||
(r'^(?P<name>[A-Za-z0-9._+-]+)/(?:(?P<rev>[0-9-]+)/)?doc.json$', views_doc.document_json),
|
(r'^(?P<name>[A-Za-z0-9._+-]+)/(?:(?P<rev>[0-9-]+)/)?doc.json$', views_doc.document_json),
|
||||||
(r'^(?P<name>[A-Za-z0-9._+-]+)/ballotpopup/(?P<ballot_id>[0-9]+)/$', views_doc.ballot_popup),
|
(r'^(?P<name>[A-Za-z0-9._+-]+)/ballotpopup/(?P<ballot_id>[0-9]+)/$', views_doc.ballot_popup),
|
||||||
|
url(r'^(?P<name>[A-Za-z0-9._+-]+)/requestreview/$', views_review.request_review),
|
||||||
|
url(r'^(?P<name>[A-Za-z0-9._+-]+)/review/(?P<request_id>[0-9]+)/$', views_review.review),
|
||||||
|
|
||||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/email-aliases/$', RedirectView.as_view(pattern_name='doc_email', permanent=False),name='doc_specific_email_aliases'),
|
url(r'^(?P<name>[A-Za-z0-9._+-]+)/email-aliases/$', RedirectView.as_view(pattern_name='doc_email', permanent=False),name='doc_specific_email_aliases'),
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ from ietf.doc.models import DocEvent, ConsensusDocEvent, BallotDocEvent, NewRevi
|
||||||
from ietf.doc.models import save_document_in_history
|
from ietf.doc.models import save_document_in_history
|
||||||
from ietf.name.models import DocReminderTypeName, DocRelationshipName
|
from ietf.name.models import DocReminderTypeName, DocRelationshipName
|
||||||
from ietf.group.models import Role
|
from ietf.group.models import Role
|
||||||
from ietf.ietfauth.utils import has_role
|
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream
|
||||||
from ietf.utils import draft, markup_txt
|
from ietf.utils import draft, markup_txt
|
||||||
from ietf.utils.mail import send_mail
|
from ietf.utils.mail import send_mail
|
||||||
from ietf.mailtrigger.utils import gather_address_lists
|
from ietf.mailtrigger.utils import gather_address_lists
|
||||||
|
@ -89,6 +89,15 @@ def can_adopt_draft(user, doc):
|
||||||
group__state="active",
|
group__state="active",
|
||||||
person__user=user).exists())
|
person__user=user).exists())
|
||||||
|
|
||||||
|
def can_request_review_of_doc(user, doc):
|
||||||
|
if not user.is_authenticated():
|
||||||
|
return False
|
||||||
|
|
||||||
|
from ietf.review.utils import active_review_teams
|
||||||
|
if Role.objects.filter(name="reviewer", person__user=user, group__in=active_review_teams()):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return is_authorized_in_doc_stream(user, doc)
|
||||||
|
|
||||||
def two_thirds_rule( recused=0 ):
|
def two_thirds_rule( recused=0 ):
|
||||||
# For standards-track, need positions from 2/3 of the non-recused current IESG.
|
# For standards-track, need positions from 2/3 of the non-recused current IESG.
|
||||||
|
|
|
@ -48,7 +48,8 @@ from ietf.doc.models import ( Document, DocAlias, DocHistory, DocEvent, BallotDo
|
||||||
from ietf.doc.utils import ( add_links_in_new_revision_events, augment_events_with_revision,
|
from ietf.doc.utils import ( add_links_in_new_revision_events, augment_events_with_revision,
|
||||||
can_adopt_draft, get_chartering_type, get_document_content, get_tags_for_stream_id,
|
can_adopt_draft, get_chartering_type, get_document_content, get_tags_for_stream_id,
|
||||||
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
|
needed_ballot_positions, nice_consensus, prettify_std_name, update_telechat, has_same_ballot,
|
||||||
get_initial_notify, make_notify_changed_event, crawl_history, default_consensus)
|
get_initial_notify, make_notify_changed_event, crawl_history, default_consensus,
|
||||||
|
can_request_review_of_doc )
|
||||||
from ietf.community.utils import augment_docs_with_tracking_info
|
from ietf.community.utils import augment_docs_with_tracking_info
|
||||||
from ietf.group.models import Role
|
from ietf.group.models import Role
|
||||||
from ietf.group.utils import can_manage_group, can_manage_materials
|
from ietf.group.utils import can_manage_group, can_manage_materials
|
||||||
|
@ -57,10 +58,11 @@ from ietf.name.models import StreamName, BallotPositionName
|
||||||
from ietf.person.models import Email
|
from ietf.person.models import Email
|
||||||
from ietf.utils.history import find_history_active_at
|
from ietf.utils.history import find_history_active_at
|
||||||
from ietf.doc.forms import TelechatForm, NotifyForm
|
from ietf.doc.forms import TelechatForm, NotifyForm
|
||||||
from ietf.doc.mails import email_comment
|
from ietf.doc.mails import email_comment
|
||||||
from ietf.mailtrigger.utils import gather_relevant_expansions
|
from ietf.mailtrigger.utils import gather_relevant_expansions
|
||||||
from ietf.meeting.models import Session
|
from ietf.meeting.models import Session
|
||||||
from ietf.meeting.utils import group_sessions, get_upcoming_manageable_sessions, sort_sessions
|
from ietf.meeting.utils import group_sessions, get_upcoming_manageable_sessions, sort_sessions
|
||||||
|
from ietf.review.models import ReviewRequest
|
||||||
|
|
||||||
def render_document_top(request, doc, tab, name):
|
def render_document_top(request, doc, tab, name):
|
||||||
tabs = []
|
tabs = []
|
||||||
|
@ -279,8 +281,8 @@ def document_main(request, name, rev=None):
|
||||||
can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc)
|
can_edit_stream_info = is_authorized_in_doc_stream(request.user, doc)
|
||||||
can_edit_shepherd_writeup = can_edit_stream_info or user_is_person(request.user, doc.shepherd and doc.shepherd.person) or has_role(request.user, ["Area Director"])
|
can_edit_shepherd_writeup = can_edit_stream_info or user_is_person(request.user, doc.shepherd and doc.shepherd.person) or has_role(request.user, ["Area Director"])
|
||||||
can_edit_notify = can_edit_shepherd_writeup
|
can_edit_notify = can_edit_shepherd_writeup
|
||||||
can_edit_consensus = False
|
|
||||||
|
|
||||||
|
can_edit_consensus = False
|
||||||
consensus = nice_consensus(default_consensus(doc))
|
consensus = nice_consensus(default_consensus(doc))
|
||||||
if doc.stream_id == "ietf" and iesg_state:
|
if doc.stream_id == "ietf" and iesg_state:
|
||||||
show_in_states = set(IESG_BALLOT_ACTIVE_STATES)
|
show_in_states = set(IESG_BALLOT_ACTIVE_STATES)
|
||||||
|
@ -294,6 +296,8 @@ def document_main(request, name, rev=None):
|
||||||
e = doc.latest_event(ConsensusDocEvent, type="changed_consensus")
|
e = doc.latest_event(ConsensusDocEvent, type="changed_consensus")
|
||||||
consensus = nice_consensus(e and e.consensus)
|
consensus = nice_consensus(e and e.consensus)
|
||||||
|
|
||||||
|
can_request_review = can_request_review_of_doc(request.user, doc)
|
||||||
|
|
||||||
# mailing list search archive
|
# mailing list search archive
|
||||||
search_archive = "www.ietf.org/mail-archive/web/"
|
search_archive = "www.ietf.org/mail-archive/web/"
|
||||||
if doc.stream_id == "ietf" and group.type_id == "wg" and group.list_archive:
|
if doc.stream_id == "ietf" and group.type_id == "wg" and group.list_archive:
|
||||||
|
@ -353,6 +357,8 @@ def document_main(request, name, rev=None):
|
||||||
published = doc.latest_event(type="published_rfc")
|
published = doc.latest_event(type="published_rfc")
|
||||||
started_iesg_process = doc.latest_event(type="started_iesg_process")
|
started_iesg_process = doc.latest_event(type="started_iesg_process")
|
||||||
|
|
||||||
|
review_requests = ReviewRequest.objects.filter(doc=doc)
|
||||||
|
|
||||||
return render_to_response("doc/document_draft.html",
|
return render_to_response("doc/document_draft.html",
|
||||||
dict(doc=doc,
|
dict(doc=doc,
|
||||||
group=group,
|
group=group,
|
||||||
|
@ -374,6 +380,7 @@ def document_main(request, name, rev=None):
|
||||||
can_edit_consensus=can_edit_consensus,
|
can_edit_consensus=can_edit_consensus,
|
||||||
can_edit_replaces=can_edit_replaces,
|
can_edit_replaces=can_edit_replaces,
|
||||||
can_view_possibly_replaces=can_view_possibly_replaces,
|
can_view_possibly_replaces=can_view_possibly_replaces,
|
||||||
|
can_request_review=can_request_review,
|
||||||
|
|
||||||
rfc_number=rfc_number,
|
rfc_number=rfc_number,
|
||||||
draft_name=draft_name,
|
draft_name=draft_name,
|
||||||
|
@ -412,6 +419,7 @@ def document_main(request, name, rev=None):
|
||||||
search_archive=search_archive,
|
search_archive=search_archive,
|
||||||
actions=actions,
|
actions=actions,
|
||||||
presentations=presentations,
|
presentations=presentations,
|
||||||
|
review_requests=review_requests,
|
||||||
),
|
),
|
||||||
context_instance=RequestContext(request))
|
context_instance=RequestContext(request))
|
||||||
|
|
||||||
|
|
108
ietf/doc/views_review.py
Normal file
108
ietf/doc/views_review.py
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.http import HttpResponseForbidden
|
||||||
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
|
from django.core.urlresolvers import reverse as urlreverse
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
from ietf.doc.models import Document, NewRevisionDocEvent, DocEvent
|
||||||
|
from ietf.doc.utils import can_request_review_of_doc
|
||||||
|
from ietf.ietfauth.utils import is_authorized_in_doc_stream
|
||||||
|
from ietf.review.models import ReviewRequest, ReviewRequestStateName
|
||||||
|
from ietf.review.utils import active_review_teams
|
||||||
|
from ietf.utils.fields import DatepickerDateField
|
||||||
|
|
||||||
|
|
||||||
|
class RequestReviewForm(forms.ModelForm):
|
||||||
|
deadline_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={ "autoclose": "1", "start-date": "+0d" })
|
||||||
|
deadline_time = forms.TimeField(widget=forms.TextInput(attrs={ 'placeholder': "HH:MM" }), help_text="If time is not specified, end of day is assumed", required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ReviewRequest
|
||||||
|
fields = ('type', 'team', 'deadline', 'requested_rev')
|
||||||
|
|
||||||
|
def __init__(self, user, doc, *args, **kwargs):
|
||||||
|
super(RequestReviewForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.doc = doc
|
||||||
|
|
||||||
|
self.fields['type'].widget = forms.RadioSelect(choices=[t for t in self.fields['type'].choices if t[0]])
|
||||||
|
|
||||||
|
f = self.fields["team"]
|
||||||
|
f.queryset = active_review_teams()
|
||||||
|
if not is_authorized_in_doc_stream(user, doc): # user is a reviewer
|
||||||
|
f.queryset = f.queryset.filter(role__name="reviewer", role__person__user=user)
|
||||||
|
if len(f.queryset) < 6:
|
||||||
|
f.widget = forms.RadioSelect(choices=[t for t in f.choices if t[0]])
|
||||||
|
|
||||||
|
self.fields["deadline"].required = False
|
||||||
|
self.fields["requested_rev"].label = "Document revision"
|
||||||
|
|
||||||
|
def clean_deadline_date(self):
|
||||||
|
v = self.cleaned_data.get('deadline_date')
|
||||||
|
if v < datetime.date.today():
|
||||||
|
raise forms.ValidationError("Select a future date.")
|
||||||
|
return v
|
||||||
|
|
||||||
|
def clean_requested_rev(self):
|
||||||
|
rev = self.cleaned_data.get("requested_rev")
|
||||||
|
if rev:
|
||||||
|
rev = rev.rjust(2, "0")
|
||||||
|
|
||||||
|
if not NewRevisionDocEvent.objects.filter(doc=self.doc, rev=rev).exists():
|
||||||
|
raise forms.ValidationError("Could not find revision '{}' of the document.".format(rev))
|
||||||
|
|
||||||
|
return rev
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
deadline_date = self.cleaned_data.get('deadline_date')
|
||||||
|
deadline_time = self.cleaned_data.get('deadline_time', None)
|
||||||
|
|
||||||
|
if deadline_date:
|
||||||
|
if deadline_time is None:
|
||||||
|
deadline_time = datetime.time(23, 59, 59)
|
||||||
|
|
||||||
|
self.cleaned_data["deadline"] = datetime.datetime.combine(deadline_date, deadline_time)
|
||||||
|
|
||||||
|
return self.cleaned_data
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def request_review(request, name):
|
||||||
|
doc = get_object_or_404(Document, name=name)
|
||||||
|
|
||||||
|
if not can_request_review_of_doc(request.user, doc):
|
||||||
|
return HttpResponseForbidden("You do not have permission to perform this action")
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = RequestReviewForm(request.user, doc, request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
review_req = form.save(commit=False)
|
||||||
|
review_req.doc = doc
|
||||||
|
review_req.state = ReviewRequestStateName.objects.get(slug="requested", used=True)
|
||||||
|
review_req.save()
|
||||||
|
|
||||||
|
DocEvent.objects.create(
|
||||||
|
type="requested_review",
|
||||||
|
doc=doc,
|
||||||
|
by=request.user.person,
|
||||||
|
desc="{} review by {} requested".format(review_req.type.name, review_req.team.acronym.upper()),
|
||||||
|
)
|
||||||
|
|
||||||
|
# FIXME: if I'm a reviewer, auto-assign to myself?
|
||||||
|
return redirect('doc_view', name=doc.name)
|
||||||
|
|
||||||
|
else:
|
||||||
|
form = RequestReviewForm(request.user, doc)
|
||||||
|
|
||||||
|
return render(request, 'doc/review/request_review.html', {
|
||||||
|
'doc': doc,
|
||||||
|
'form': form,
|
||||||
|
})
|
||||||
|
|
||||||
|
def review(request, name, request_id):
|
||||||
|
doc = get_object_or_404(Document, name=name)
|
||||||
|
review_request = get_object_or_404(ReviewRequest, pk=request_id)
|
||||||
|
|
||||||
|
print doc, review_request
|
|
@ -3,7 +3,8 @@ from ietf.name.models import (GroupTypeName, GroupStateName, RoleName, StreamNam
|
||||||
DocRelationshipName, DocTypeName, DocTagName, StdLevelName, IntendedStdLevelName,
|
DocRelationshipName, DocTypeName, DocTagName, StdLevelName, IntendedStdLevelName,
|
||||||
DocReminderTypeName, BallotPositionName, SessionStatusName, TimeSlotTypeName,
|
DocReminderTypeName, BallotPositionName, SessionStatusName, TimeSlotTypeName,
|
||||||
ConstraintName, NomineePositionStateName, FeedbackTypeName, DBTemplateTypeName,
|
ConstraintName, NomineePositionStateName, FeedbackTypeName, DBTemplateTypeName,
|
||||||
DraftSubmissionStateName, RoomResourceName)
|
DraftSubmissionStateName, RoomResourceName,
|
||||||
|
ReviewRequestStateName, ReviewTypeName, ReviewResultName)
|
||||||
|
|
||||||
|
|
||||||
class NameAdmin(admin.ModelAdmin):
|
class NameAdmin(admin.ModelAdmin):
|
||||||
|
@ -35,3 +36,6 @@ admin.site.register(FeedbackTypeName, NameAdmin)
|
||||||
admin.site.register(DBTemplateTypeName, NameAdmin)
|
admin.site.register(DBTemplateTypeName, NameAdmin)
|
||||||
admin.site.register(DraftSubmissionStateName, NameAdmin)
|
admin.site.register(DraftSubmissionStateName, NameAdmin)
|
||||||
admin.site.register(RoomResourceName, NameAdmin)
|
admin.site.register(RoomResourceName, NameAdmin)
|
||||||
|
admin.site.register(ReviewRequestStateName, NameAdmin)
|
||||||
|
admin.site.register(ReviewTypeName, NameAdmin)
|
||||||
|
admin.site.register(ReviewResultName, NameAdmin)
|
||||||
|
|
|
@ -188,7 +188,7 @@
|
||||||
"order": 0,
|
"order": 0,
|
||||||
"revname": "Conflict reviewed by",
|
"revname": "Conflict reviewed by",
|
||||||
"used": true,
|
"used": true,
|
||||||
"name": "Conflict reviews",
|
"name": "conflict reviews",
|
||||||
"desc": ""
|
"desc": ""
|
||||||
},
|
},
|
||||||
"model": "name.docrelationshipname",
|
"model": "name.docrelationshipname",
|
||||||
|
@ -1752,6 +1752,205 @@
|
||||||
"model": "name.nomineepositionstatename",
|
"model": "name.nomineepositionstatename",
|
||||||
"pk": "declined"
|
"pk": "declined"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 1,
|
||||||
|
"used": true,
|
||||||
|
"name": "Requested",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewrequeststatename",
|
||||||
|
"pk": "requested"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 2,
|
||||||
|
"used": true,
|
||||||
|
"name": "Accepted",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewrequeststatename",
|
||||||
|
"pk": "accepted"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 3,
|
||||||
|
"used": true,
|
||||||
|
"name": "Rejected",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewrequeststatename",
|
||||||
|
"pk": "rejected"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 4,
|
||||||
|
"used": true,
|
||||||
|
"name": "Withdrawn",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewrequeststatename",
|
||||||
|
"pk": "withdrawn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 5,
|
||||||
|
"used": true,
|
||||||
|
"name": "Overtaken By Events",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewrequeststatename",
|
||||||
|
"pk": "overtaken"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 6,
|
||||||
|
"used": true,
|
||||||
|
"name": "No Response",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewrequeststatename",
|
||||||
|
"pk": "noresponse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 7,
|
||||||
|
"used": true,
|
||||||
|
"name": "Completed",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewrequeststatename",
|
||||||
|
"pk": "completed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 1,
|
||||||
|
"used": true,
|
||||||
|
"teams": [],
|
||||||
|
"name": "Almost Ready",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewresultname",
|
||||||
|
"pk": "almost-ready"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 2,
|
||||||
|
"used": true,
|
||||||
|
"teams": [],
|
||||||
|
"name": "Has Issues",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewresultname",
|
||||||
|
"pk": "issues"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 3,
|
||||||
|
"used": true,
|
||||||
|
"teams": [],
|
||||||
|
"name": "Has Nits",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewresultname",
|
||||||
|
"pk": "nits"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 4,
|
||||||
|
"used": true,
|
||||||
|
"teams": [],
|
||||||
|
"name": "Not Ready",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewresultname",
|
||||||
|
"pk": "not-ready"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 5,
|
||||||
|
"used": true,
|
||||||
|
"teams": [],
|
||||||
|
"name": "On the Right Track",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewresultname",
|
||||||
|
"pk": "right-track"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 6,
|
||||||
|
"used": true,
|
||||||
|
"teams": [],
|
||||||
|
"name": "Ready",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewresultname",
|
||||||
|
"pk": "ready"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 7,
|
||||||
|
"used": true,
|
||||||
|
"teams": [],
|
||||||
|
"name": "Ready with Issues",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewresultname",
|
||||||
|
"pk": "ready-issues"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 8,
|
||||||
|
"used": true,
|
||||||
|
"teams": [],
|
||||||
|
"name": "Ready with Nits",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewresultname",
|
||||||
|
"pk": "ready-nits"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 9,
|
||||||
|
"used": true,
|
||||||
|
"teams": [],
|
||||||
|
"name": "Serious Issues",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewresultname",
|
||||||
|
"pk": "serious-issues"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 1,
|
||||||
|
"used": true,
|
||||||
|
"name": "Early",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewtypename",
|
||||||
|
"pk": "early"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 2,
|
||||||
|
"used": true,
|
||||||
|
"name": "Last Call",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewtypename",
|
||||||
|
"pk": "lc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 3,
|
||||||
|
"used": true,
|
||||||
|
"name": "Telechat",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.reviewtypename",
|
||||||
|
"pk": "telechat"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"order": 0,
|
"order": 0,
|
||||||
|
@ -1942,6 +2141,16 @@
|
||||||
"model": "name.rolename",
|
"model": "name.rolename",
|
||||||
"pk": "matman"
|
"pk": "matman"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"order": 14,
|
||||||
|
"used": true,
|
||||||
|
"name": "Reviewer",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
"model": "name.rolename",
|
||||||
|
"pk": "reviewer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"order": 0,
|
"order": 0,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# simple script for exporting name related base data for the tests
|
||||||
|
|
||||||
# boiler plate
|
# boiler plate
|
||||||
import os, sys
|
import os, sys
|
||||||
import django
|
import django
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('group', '0008_auto_20160505_0523'),
|
||||||
|
('name', '0010_new_liaison_names'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ReviewRequestStateName',
|
||||||
|
fields=[
|
||||||
|
('slug', models.CharField(max_length=32, serialize=False, primary_key=True)),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('desc', models.TextField(blank=True)),
|
||||||
|
('used', models.BooleanField(default=True)),
|
||||||
|
('order', models.IntegerField(default=0)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['order'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ReviewResultName',
|
||||||
|
fields=[
|
||||||
|
('slug', models.CharField(max_length=32, serialize=False, primary_key=True)),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('desc', models.TextField(blank=True)),
|
||||||
|
('used', models.BooleanField(default=True)),
|
||||||
|
('order', models.IntegerField(default=0)),
|
||||||
|
('teams', models.ManyToManyField(help_text=b"Which teams this result can be set for. This also implicitly defines which teams are review teams - if there are no possible review results defined for a given team, it can't be a review team.", to='group.Group', blank=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['order'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ReviewTypeName',
|
||||||
|
fields=[
|
||||||
|
('slug', models.CharField(max_length=32, serialize=False, primary_key=True)),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('desc', models.TextField(blank=True)),
|
||||||
|
('used', models.BooleanField(default=True)),
|
||||||
|
('order', models.IntegerField(default=0)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['order'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
]
|
48
ietf/name/migrations/0012_insert_review_name_data.py
Normal file
48
ietf/name/migrations/0012_insert_review_name_data.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def insert_initial_review_data(apps, schema_editor):
|
||||||
|
ReviewRequestStateName = apps.get_model("name", "ReviewRequestStateName")
|
||||||
|
ReviewRequestStateName.objects.get_or_create(slug="requested", name="Requested", order=1)
|
||||||
|
ReviewRequestStateName.objects.get_or_create(slug="accepted", name="Accepted", order=2)
|
||||||
|
ReviewRequestStateName.objects.get_or_create(slug="rejected", name="Rejected", order=3)
|
||||||
|
ReviewRequestStateName.objects.get_or_create(slug="withdrawn", name="Withdrawn", order=4)
|
||||||
|
ReviewRequestStateName.objects.get_or_create(slug="overtaken", name="Overtaken By Events", order=5)
|
||||||
|
ReviewRequestStateName.objects.get_or_create(slug="noresponse", name="No Response", order=6)
|
||||||
|
ReviewRequestStateName.objects.get_or_create(slug="completed", name="Completed", order=7)
|
||||||
|
|
||||||
|
ReviewTypeName = apps.get_model("name", "ReviewTypeName")
|
||||||
|
ReviewTypeName.objects.get_or_create(slug="early", name="Early", order=1)
|
||||||
|
ReviewTypeName.objects.get_or_create(slug="lc", name="Last Call", order=2)
|
||||||
|
ReviewTypeName.objects.get_or_create(slug="telechat", name="Telechat", order=3)
|
||||||
|
|
||||||
|
ReviewResultName = apps.get_model("name", "ReviewResultName")
|
||||||
|
ReviewResultName.objects.get_or_create(slug="almost-ready", name="Almost Ready", order=1)
|
||||||
|
ReviewResultName.objects.get_or_create(slug="issues", name="Has Issues", order=2)
|
||||||
|
ReviewResultName.objects.get_or_create(slug="nits", name="Has Nits", order=3)
|
||||||
|
ReviewResultName.objects.get_or_create(slug="not-ready", name="Not Ready", order=4)
|
||||||
|
ReviewResultName.objects.get_or_create(slug="right-track", name="On the Right Track", order=5)
|
||||||
|
ReviewResultName.objects.get_or_create(slug="ready", name="Ready", order=6)
|
||||||
|
ReviewResultName.objects.get_or_create(slug="ready-issues", name="Ready with Issues", order=7)
|
||||||
|
ReviewResultName.objects.get_or_create(slug="ready-nits", name="Ready with Nits", order=8)
|
||||||
|
ReviewResultName.objects.get_or_create(slug="serious-issues", name="Serious Issues", order=9)
|
||||||
|
|
||||||
|
RoleName = apps.get_model("name", "RoleName")
|
||||||
|
RoleName.objects.get_or_create(slug="reviewer", name="Reviewer", order=max(r.order for r in RoleName.objects.all()) + 1)
|
||||||
|
|
||||||
|
def noop(apps, schema_editor):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('name', '0011_reviewrequeststatename_reviewresultname_reviewtypename'),
|
||||||
|
('group', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(insert_initial_review_data, noop),
|
||||||
|
]
|
|
@ -87,3 +87,14 @@ class LiaisonStatementEventTypeName(NameModel):
|
||||||
"Submitted, Modified, Approved, Posted, Killed, Resurrected, MsgIn, MsgOut, Comment"
|
"Submitted, Modified, Approved, Posted, Killed, Resurrected, MsgIn, MsgOut, Comment"
|
||||||
class LiaisonStatementTagName(NameModel):
|
class LiaisonStatementTagName(NameModel):
|
||||||
"Action Required, Action Taken"
|
"Action Required, Action Taken"
|
||||||
|
class ReviewRequestStateName(NameModel):
|
||||||
|
"""Requested, Accepted, Rejected, Withdrawn, Overtaken By Events,
|
||||||
|
No Response , Completed"""
|
||||||
|
class ReviewTypeName(NameModel):
|
||||||
|
"""Early Review, Last Call, Telechat"""
|
||||||
|
class ReviewResultName(NameModel):
|
||||||
|
"""Almost ready, Has issues, Has nits, Not Ready,
|
||||||
|
On the right track, Ready, Ready with issues,
|
||||||
|
Ready with nits, Serious Issues"""
|
||||||
|
teams = models.ManyToManyField("group.Group", help_text="Which teams this result can be set for. This also implicitly defines which teams are review teams - if there are no possible review results defined for a given team, it can't be a review team.", blank=True)
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ from ietf.name.models import (TimeSlotTypeName, GroupStateName, DocTagName, Inte
|
||||||
IprEventTypeName, GroupMilestoneStateName, SessionStatusName, DocReminderTypeName,
|
IprEventTypeName, GroupMilestoneStateName, SessionStatusName, DocReminderTypeName,
|
||||||
ConstraintName, MeetingTypeName, DocRelationshipName, RoomResourceName, IprLicenseTypeName,
|
ConstraintName, MeetingTypeName, DocRelationshipName, RoomResourceName, IprLicenseTypeName,
|
||||||
LiaisonStatementTagName, FeedbackTypeName, LiaisonStatementState, StreamName,
|
LiaisonStatementTagName, FeedbackTypeName, LiaisonStatementState, StreamName,
|
||||||
BallotPositionName, DBTemplateTypeName, NomineePositionStateName)
|
BallotPositionName, DBTemplateTypeName, NomineePositionStateName,
|
||||||
|
ReviewRequestStateName, ReviewTypeName, ReviewResultName)
|
||||||
|
|
||||||
|
|
||||||
class TimeSlotTypeNameResource(ModelResource):
|
class TimeSlotTypeNameResource(ModelResource):
|
||||||
|
@ -413,3 +414,46 @@ class NomineePositionStateNameResource(ModelResource):
|
||||||
}
|
}
|
||||||
api.name.register(NomineePositionStateNameResource())
|
api.name.register(NomineePositionStateNameResource())
|
||||||
|
|
||||||
|
class ReviewRequestStateNameResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
cache = SimpleCache()
|
||||||
|
queryset = ReviewRequestStateName.objects.all()
|
||||||
|
#resource_name = 'reviewrequeststatename'
|
||||||
|
filtering = {
|
||||||
|
"slug": ALL,
|
||||||
|
"name": ALL,
|
||||||
|
"desc": ALL,
|
||||||
|
"used": ALL,
|
||||||
|
"order": ALL,
|
||||||
|
}
|
||||||
|
api.name.register(ReviewRequestStateNameResource())
|
||||||
|
|
||||||
|
class ReviewTypeNameResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
cache = SimpleCache()
|
||||||
|
queryset = ReviewTypeName.objects.all()
|
||||||
|
#resource_name = 'reviewtypename'
|
||||||
|
filtering = {
|
||||||
|
"slug": ALL,
|
||||||
|
"name": ALL,
|
||||||
|
"desc": ALL,
|
||||||
|
"used": ALL,
|
||||||
|
"order": ALL,
|
||||||
|
}
|
||||||
|
api.name.register(ReviewTypeNameResource())
|
||||||
|
|
||||||
|
class ReviewResultNameResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
cache = SimpleCache()
|
||||||
|
queryset = ReviewResultName.objects.all()
|
||||||
|
#resource_name = 'reviewresultname'
|
||||||
|
filtering = {
|
||||||
|
"slug": ALL,
|
||||||
|
"name": ALL,
|
||||||
|
"desc": ALL,
|
||||||
|
"used": ALL,
|
||||||
|
"order": ALL,
|
||||||
|
"teams": ALL_WITH_RELATIONS,
|
||||||
|
}
|
||||||
|
api.name.register(ReviewResultNameResource())
|
||||||
|
|
||||||
|
|
0
ietf/review/__init__.py
Normal file
0
ietf/review/__init__.py
Normal file
50
ietf/review/migrations/0001_initial.py
Normal file
50
ietf/review/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('group', '0008_auto_20160505_0523'),
|
||||||
|
('name', '0012_insert_review_name_data'),
|
||||||
|
('doc', '0012_auto_20160207_0537'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Reviewer',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('frequency', models.IntegerField(help_text=b'Can review every N days')),
|
||||||
|
('available', models.DateTimeField(help_text=b'When will this reviewer be available again', null=True, blank=True)),
|
||||||
|
('filter_re', models.CharField(max_length=255, blank=True)),
|
||||||
|
('skip_next', models.IntegerField(help_text=b'Skip the next N review assignments')),
|
||||||
|
('role', models.ForeignKey(to='group.Role')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ReviewRequest',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('time', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('deadline', models.DateTimeField()),
|
||||||
|
('requested_rev', models.CharField(help_text=b'Fill in if a specific revision is to be reviewed, e.g. 02', max_length=16, verbose_name=b'requested revision', blank=True)),
|
||||||
|
('reviewed_rev', models.CharField(max_length=16, verbose_name=b'reviewed revision', blank=True)),
|
||||||
|
('doc', models.ForeignKey(related_name='review_request_set', to='doc.Document')),
|
||||||
|
('result', models.ForeignKey(blank=True, to='name.ReviewResultName', null=True)),
|
||||||
|
('review', models.OneToOneField(null=True, blank=True, to='doc.Document')),
|
||||||
|
('reviewer', models.ForeignKey(blank=True, to='review.Reviewer', null=True)),
|
||||||
|
('state', models.ForeignKey(to='name.ReviewRequestStateName')),
|
||||||
|
('team', models.ForeignKey(to='group.Group')),
|
||||||
|
('type', models.ForeignKey(to='name.ReviewTypeName')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
},
|
||||||
|
bases=(models.Model,),
|
||||||
|
),
|
||||||
|
]
|
0
ietf/review/migrations/__init__.py
Normal file
0
ietf/review/migrations/__init__.py
Normal file
40
ietf/review/models.py
Normal file
40
ietf/review/models.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from ietf.doc.models import Document
|
||||||
|
from ietf.group.models import Group, Role
|
||||||
|
from ietf.name.models import ReviewTypeName, ReviewRequestStateName, ReviewResultName
|
||||||
|
|
||||||
|
class Reviewer(models.Model):
|
||||||
|
"""
|
||||||
|
These records associate reviewers with review teams and keep track
|
||||||
|
of admin data associated with the reviewer in the particular team.
|
||||||
|
There will be one record for each combination of reviewer and team.
|
||||||
|
"""
|
||||||
|
role = models.ForeignKey(Role)
|
||||||
|
frequency = models.IntegerField(help_text="Can review every N days")
|
||||||
|
available = models.DateTimeField(blank=True, null=True, help_text="When will this reviewer be available again")
|
||||||
|
filter_re = models.CharField(max_length=255, blank=True)
|
||||||
|
skip_next = models.IntegerField(help_text="Skip the next N review assignments")
|
||||||
|
|
||||||
|
class ReviewRequest(models.Model):
|
||||||
|
"""
|
||||||
|
There should be one ReviewRequest entered for each combination of
|
||||||
|
document, rev, and reviewer.
|
||||||
|
"""
|
||||||
|
# Fields filled in on the initial record creation:
|
||||||
|
time = models.DateTimeField(auto_now_add=True)
|
||||||
|
type = models.ForeignKey(ReviewTypeName)
|
||||||
|
doc = models.ForeignKey(Document, related_name='review_request_set')
|
||||||
|
team = models.ForeignKey(Group)
|
||||||
|
deadline = models.DateTimeField()
|
||||||
|
requested_rev = models.CharField(verbose_name="requested revision", max_length=16, blank=True, help_text="Fill in if a specific revision is to be reviewed, e.g. 02")
|
||||||
|
state = models.ForeignKey(ReviewRequestStateName)
|
||||||
|
# Fields filled in as reviewer is assigned, and as the review
|
||||||
|
# is uploaded
|
||||||
|
reviewer = models.ForeignKey(Reviewer, blank=True, null=True)
|
||||||
|
review = models.OneToOneField(Document, blank=True, null=True)
|
||||||
|
reviewed_rev = models.CharField(verbose_name="reviewed revision", max_length=16, blank=True)
|
||||||
|
result = models.ForeignKey(ReviewResultName, blank=True, null=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s review on %s by %s %s" % (self.type, self.doc, self.team, self.state)
|
6
ietf/review/utils.py
Normal file
6
ietf/review/utils.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from ietf.group.models import Group
|
||||||
|
|
||||||
|
def active_review_teams():
|
||||||
|
# if there's a ReviewResultName defined, it's a review team
|
||||||
|
return Group.objects.filter(state="active").exclude(reviewresultname=None)
|
||||||
|
|
|
@ -273,6 +273,7 @@ INSTALLED_APPS = (
|
||||||
'ietf.person',
|
'ietf.person',
|
||||||
'ietf.redirects',
|
'ietf.redirects',
|
||||||
'ietf.release',
|
'ietf.release',
|
||||||
|
'ietf.review',
|
||||||
'ietf.submit',
|
'ietf.submit',
|
||||||
'ietf.sync',
|
'ietf.sync',
|
||||||
'ietf.utils',
|
'ietf.utils',
|
||||||
|
|
|
@ -192,6 +192,28 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
{% if review_requests or can_request_review %}
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Reviews</th>
|
||||||
|
<td class="edit"></td>
|
||||||
|
<td>
|
||||||
|
{% for r in review_requests %}
|
||||||
|
<div>
|
||||||
|
<a href="{% url "ietf.doc.views_review.review" doc.name r.pk %}">{{ r.team.acronym|upper }} {{ r.type.name }} Review ({{ r.state.name }})</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if can_request_review %}
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-default btn-xs" href="{% url "ietf.doc.views_review.request_review" doc.name %}"><span class="fa fa-check-circle-o"></span> Request review</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if conflict_reviews %}
|
{% if conflict_reviews %}
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
|
34
ietf/templates/doc/review/request_review.html
Normal file
34
ietf/templates/doc/review/request_review.html
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{# Copyright The IETF Trust 2016, All Rights Reserved #}
|
||||||
|
{% load origin bootstrap3 static %}
|
||||||
|
|
||||||
|
{% block pagehead %}
|
||||||
|
<link rel="stylesheet" href="{% static 'bootstrap-datepicker/css/bootstrap-datepicker3.min.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}Request review of {{ doc.name }} {% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% origin %}
|
||||||
|
<h1>Request review<br><small>{{ doc.name }}</small></h1>
|
||||||
|
|
||||||
|
<p>Submit a request to have the document reviewed.</p>
|
||||||
|
|
||||||
|
<form class="form-horizontal" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_field form.type layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.team layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.deadline_date layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.deadline_time layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.requested_rev layout="horizontal" %}
|
||||||
|
|
||||||
|
{% buttons %}
|
||||||
|
<button type="submit" class="btn btn-primary" name="save_addresses" value="Save">Request review</button>
|
||||||
|
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.canonical_name %}">Back</a>
|
||||||
|
{% endbuttons %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script src="{% static 'bootstrap-datepicker/js/bootstrap-datepicker.min.js' %}"></script>
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue