Merge with trunk

- Legacy-Id: 3676
This commit is contained in:
Ole Laursen 2011-11-21 16:41:21 +00:00
commit 5a4c23ea92
224 changed files with 21125 additions and 863 deletions

View file

@ -110,7 +110,10 @@ def result_headers(cl):
# It is a non-field, but perhaps one that is sortable
admin_order_field = getattr(attr, "admin_order_field", None)
if not admin_order_field:
yield {"text": header}
yield {
"text": header,
"class_attrib": ""
}
continue
# So this _is_ a sortable non-field. Go to the yield

View file

@ -1,5 +1,6 @@
#coding: utf-8
from django.contrib import admin
from django.conf import settings
from ietf.announcements.models import *
class AnnouncedFromAdmin(admin.ModelAdmin):
@ -21,3 +22,22 @@ class ScheduledAnnouncementAdmin(admin.ModelAdmin):
pass
admin.site.register(ScheduledAnnouncement, ScheduledAnnouncementAdmin)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
class MessageAdmin(admin.ModelAdmin):
list_display = ["time", "by", "subject", "groups"]
search_fields = ["body"]
raw_id_fields = ["by"]
def groups(self, instance):
return ", ".join(g.acronym for g in related_groups.all())
admin.site.register(Message, MessageAdmin)
class SendQueueAdmin(admin.ModelAdmin):
list_display = ["time", "by", "message", "send_at", "sent_at"]
list_filter = ["time", "send_at", "sent_at"]
search_fields = ["message__body"]
raw_id_fields = ["by"]
admin.site.register(SendQueue, SendQueueAdmin)

View file

@ -1,6 +1,7 @@
# Copyright The IETF Trust 2007, All Rights Reserved
from django.db import models
from django.conf import settings
from ietf.idtracker.models import PersonOrOrgInfo, ChairsHistory
#from django.contrib.auth.models import Permission
@ -87,3 +88,43 @@ class ScheduledAnnouncement(models.Model):
db_table = 'scheduled_announcements'
if settings.USE_DB_REDESIGN_PROXY_CLASSES or hasattr(settings, "IMPORTING_FROM_OLD_SCHEMA"):
import datetime
from person.models import Email, Person
from group.models import Group
class Message(models.Model):
time = models.DateTimeField(default=datetime.datetime.now)
by = models.ForeignKey(Person)
subject = models.CharField(max_length=255)
frm = models.CharField(max_length=255)
to = models.CharField(max_length=1024)
cc = models.CharField(max_length=1024, blank=True)
bcc = models.CharField(max_length=255, blank=True)
reply_to = models.CharField(max_length=255, blank=True)
body = models.TextField()
content_type = models.CharField(max_length=255, blank=True)
related_groups = models.ManyToManyField(Group, blank=True)
class Meta:
ordering = ['time']
def __unicode__(self):
return "'%s' %s -> %s" % (self.subject, self.frm, self.to)
class SendQueue(models.Model):
time = models.DateTimeField(default=datetime.datetime.now)
by = models.ForeignKey(Person)
message = models.ForeignKey(Message)
send_at = models.DateTimeField(blank=True, null=True)
sent_at = models.DateTimeField(blank=True, null=True)
note = models.TextField(blank=True)
class Meta:
ordering = ['time']

View file

@ -1,5 +1,7 @@
import re, datetime, email
from django.conf import settings
from ietf.utils.mail import send_mail_text, send_mail_mime
first_dot_on_line_re = re.compile(r'^\.', re.MULTILINE)
@ -8,7 +10,6 @@ def send_scheduled_announcement(announcement):
# for some reason, the old Perl code base substituted away . on line starts
body = first_dot_on_line_re.sub("", announcement.body)
announcement.content_type
extra = {}
if announcement.replyto:
extra['Reply-To'] = announcement.replyto
@ -33,3 +34,35 @@ def send_scheduled_announcement(announcement):
announcement.actual_sent_time = str(now.time())
announcement.mail_sent = True
announcement.save()
def send_scheduled_announcementREDESIGN(send_queue):
message = send_queue.message
# for some reason, the old Perl code base substituted away . on line starts
body = first_dot_on_line_re.sub("", message.body)
extra = {}
if message.reply_to:
extra['Reply-To'] = message.reply_to
# announcement.content_type can contain a case-sensitive parts separator,
# so we need to keep it as is, not lowercased, but we want a lowercased
# version for the coming comparisons.
content_type_lowercase = message.content_type.lower()
if not content_type_lowercase or 'text/plain' in content_type_lowercase:
send_mail_text(None, message.to, message.frm, message.subject,
body, cc=message.cc, bcc=message.bcc)
elif 'multipart' in content_type_lowercase:
# make body a real message so we can parse it
body = ("MIME-Version: 1.0\r\nContent-Type: %s\r\n" % message.content_type) + body
msg = email.message_from_string(body.encode("utf-8"))
send_mail_mime(None, message.to, message.frm, message.subject,
msg, cc=message.cc, bcc=message.bcc)
send_queue.sent_at = datetime.datetime.now()
send_queue.save()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
send_scheduled_announcement = send_scheduled_announcementREDESIGN

View file

@ -1,7 +1,11 @@
import datetime
from django.conf import settings
import django.test
from ietf.utils.test_utils import SimpleUrlTestCase, canonicalize_sitemap
from ietf.utils.test_runner import mail_outbox
from ietf.utils.test_data import make_test_data
from ietf.utils.mail import outbox
from ietf.announcements.models import ScheduledAnnouncement
@ -27,13 +31,13 @@ class SendScheduledAnnouncementsTestCase(django.test.TestCase):
content_type="",
)
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
from ietf.announcements.send_scheduled import send_scheduled_announcement
send_scheduled_announcement(a)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("This is a test" in mail_outbox[-1]["Subject"])
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("This is a test" in outbox[-1]["Subject"])
self.assertTrue(ScheduledAnnouncement.objects.get(id=a.id).mail_sent)
def test_send_mime_announcement(self):
@ -48,12 +52,84 @@ class SendScheduledAnnouncementsTestCase(django.test.TestCase):
content_type='Multipart/Mixed; Boundary="NextPart"',
)
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
from ietf.announcements.send_scheduled import send_scheduled_announcement
send_scheduled_announcement(a)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("This is a test" in mail_outbox[-1]["Subject"])
self.assertTrue("--NextPart" in mail_outbox[-1].as_string())
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("This is a test" in outbox[-1]["Subject"])
self.assertTrue("--NextPart" in outbox[-1].as_string())
self.assertTrue(ScheduledAnnouncement.objects.get(id=a.id).mail_sent)
class SendScheduledAnnouncementsTestCaseREDESIGN(django.test.TestCase):
fixtures = ["names"]
def test_send_plain_announcement(self):
from ietf.announcements.models import Message, SendQueue
from redesign.person.models import Person
make_test_data()
msg = Message.objects.create(
by=Person.objects.get(name="(System)"),
subject="This is a test",
to="test@example.com",
frm="testmonkey@example.com",
cc="cc.a@example.com, cc.b@example.com",
bcc="bcc@example.com",
body="Hello World!",
content_type="",
)
q = SendQueue.objects.create(
by=Person.objects.get(name="(System)"),
message=msg,
send_at=datetime.datetime.now() + datetime.timedelta(hours=12)
)
mailbox_before = len(outbox)
from ietf.announcements.send_scheduled import send_scheduled_announcement
send_scheduled_announcement(q)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("This is a test" in outbox[-1]["Subject"])
self.assertTrue(SendQueue.objects.get(id=q.id).sent_at)
def test_send_mime_announcement(self):
from ietf.announcements.models import Message, SendQueue
from redesign.person.models import Person
make_test_data()
msg = Message.objects.create(
by=Person.objects.get(name="(System)"),
subject="This is a test",
to="test@example.com",
frm="testmonkey@example.com",
cc="cc.a@example.com, cc.b@example.com",
bcc="bcc@example.com",
body='--NextPart\r\n\r\nA New Internet-Draft is available from the on-line Internet-Drafts directories.\r\n--NextPart\r\nContent-Type: Message/External-body;\r\n\tname="draft-huang-behave-bih-01.txt";\r\n\tsite="ftp.ietf.org";\r\n\taccess-type="anon-ftp";\r\n\tdirectory="internet-drafts"\r\n\r\nContent-Type: text/plain\r\nContent-ID: <2010-07-30001541.I-D@ietf.org>\r\n\r\n--NextPart--',
content_type='Multipart/Mixed; Boundary="NextPart"',
)
q = SendQueue.objects.create(
by=Person.objects.get(name="(System)"),
message=msg,
send_at=datetime.datetime.now() + datetime.timedelta(hours=12)
)
mailbox_before = len(outbox)
from ietf.announcements.send_scheduled import send_scheduled_announcement
send_scheduled_announcement(q)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("This is a test" in outbox[-1]["Subject"])
self.assertTrue("--NextPart" in outbox[-1].as_string())
self.assertTrue(SendQueue.objects.get(id=q.id).sent_at)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
SendScheduledAnnouncementsTestCase = SendScheduledAnnouncementsTestCaseREDESIGN

View file

@ -3,12 +3,14 @@
from django.conf.urls.defaults import patterns
from ietf.announcements.models import Announcement
from django.conf import settings
nomcom_dict = {
'queryset': Announcement.objects.all().filter(nomcom=True)
}
}
urlpatterns = patterns('',
# (r'^nomcom/$', 'django.views.generic.simple.redirect_to', {'url': 'http://www.ietf.org/nomcom/index.html'} ),
(r'^nomcom/$', 'ietf.announcements.views.nomcom'),
(r'^nomcom/(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', nomcom_dict)
(r'^nomcom/(?P<object_id>\d+)/$', 'ietf.announcements.views.message_detail' if settings.USE_DB_REDESIGN_PROXY_CLASSES else 'django.views.generic.list_detail.object_detail', nomcom_dict)
)

View file

@ -1,6 +1,11 @@
# Copyright The IETF Trust 2007, All Rights Reserved
from django.views.generic.simple import direct_to_template
from django.shortcuts import get_object_or_404
from django.conf import settings
from django.db.models import Q
import re
from ietf.idtracker.models import ChairsHistory
from ietf.idtracker.models import Role
@ -29,3 +34,56 @@ def nomcom(request):
{ 'curr_chair' : curr_chair,
'regimes' : regimes })
def nomcomREDESIGN(request):
from group.models import Group
from ietf.announcements.models import Message
address_re = re.compile("<.*>")
nomcoms = list(Group.objects.filter(acronym__startswith="nomcom").exclude(name="nomcom"))
regimes = []
for n in nomcoms:
e = n.latest_event(type="started")
n.start_year = e.time.year if e else 0
if n.start_year <= 2003:
continue
e = n.latest_event(type="concluded")
n.end_year = e.time.year if e else ""
chair = n.role_set.get(name="chair").person
announcements = Message.objects.filter(related_groups=n).order_by('-time')
for a in announcements:
a.to_name = address_re.sub("", a.to)
regimes.append(dict(chair=chair,
announcements=announcements,
group=n))
regimes.sort(key=lambda x: x["group"].start_year, reverse=True)
return direct_to_template(request,
"announcements/nomcomREDESIGN.html",
{ 'curr_chair' : regimes[0]["chair"],
'regimes' : regimes })
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
nomcom = nomcomREDESIGN
def message_detail(request, object_id, queryset):
from group.models import Group
from ietf.announcements.models import Message
# restrict to nomcom announcements for the time being
nomcoms = Group.objects.filter(acronym__startswith="nomcom").exclude(acronym="nomcom")
m = get_object_or_404(Message, id=object_id,
related_groups__in=nomcoms)
return direct_to_template(request,
"announcements/message_detail.html",
dict(message=m))

View file

@ -15,6 +15,6 @@ if not in_id_expire_freeze():
for doc in get_expired_ids():
send_expire_notice_for_id(doc)
expire_id(doc)
syslog.syslog("Expired %s (id=%s)%s" % (doc.file_tag(), doc.id_document_tag, " in the ID Tracker" if doc.idinternal else ""))
syslog.syslog("Expired %s (id=%s)%s" % (doc.file_tag(), doc.pk, " in the ID Tracker" if doc.latest_event(type="started_iesg_process") else ""))
clean_up_id_files()

View file

@ -14,4 +14,4 @@ from ietf.idrfc.lastcall import *
drafts = get_expired_last_calls()
for doc in drafts:
expire_last_call(doc)
syslog.syslog("Expired last call for %s (id=%s)" % (doc.file_tag(), doc.id_document_tag))
syslog.syslog("Expired last call for %s (id=%s)" % (doc.file_tag(), doc.pk))

View file

@ -6,56 +6,11 @@ from ietf import settings
from django.core import management
management.setup_environ(settings)
from ietf.idtracker.models import InternetDraft,IDAuthor,WGChair
from ietf.utils.mail import send_mail_subj
notify_days = 14 # notify about documents that expire within the
# next 2 weeks
start_date = datetime.date.today() - datetime.timedelta(InternetDraft.DAYS_TO_EXPIRE - 1)
end_date = start_date + datetime.timedelta(notify_days - 1)
from ietf.idrfc.expire import get_soon_to_expire_ids, send_expire_warning_for_id
matches = InternetDraft.objects.filter(revision_date__gte=start_date,revision_date__lte=end_date,status__status='Active')
# notify about documents that expire within the next 2 weeks
notify_days = 14
#For development - focus on one draft
#matches = InternetDraft.objects.filter(filename__icontains='geopriv-http-location-delivery')
# Todo:
#second_cutoff = IDDates.objects.get(date_id=2)
#ietf_monday = IDDates.objects.get(date_id=3)
#freeze_delta = ietf_monday - second_cutoff
for draft in matches:
if not draft.can_expire():
# debugging
#print "%s can't expire, skipping" % draft
continue
expiration = draft.expiration()
# # The I-D expiration job doesn't run while submissions are frozen.
# if ietf_monday > expiration > second_cutoff:
# expiration += freeze_delta
authors = draft.authors.all()
to_addrs = [author.email() for author in authors if author.email()]
cc_addrs = None
if draft.group.acronym != 'none':
cc_addrs = [chair.person.email() for chair in WGChair.objects.filter(group_acronym=draft.group)]
#For development debugging
"""
print "filename: "+draft.filename
print "to: ", to_addrs
print "cc: ", cc_addrs
print "expires: ", expiration
print "status: ", draft.status.status, "/", draft.idstate()
print
continue
"""
if to_addrs or cc_addrs:
send_mail_subj(None, to_addrs, None, 'notify_expirations/subject.txt', 'notify_expirations/body.txt',
{
'draft':draft,
'expiration':expiration,
},
cc_addrs)
for doc in get_soon_to_expire_ids(notify_days):
send_expire_warning_for_id(doc)

View file

@ -14,7 +14,7 @@ from ietf.announcements.models import ScheduledAnnouncement
from ietf.announcements.send_scheduled import *
from django.db.models import Q
if not len(sys.argv) == 2 or sys.argv[1] not in ('all', 'rsync', 'specific'):
if len(sys.argv) != 2 or sys.argv[1] not in ('all', 'rsync', 'specific'):
print "USAGE: %s <all | rsync | specific>" % os.path.basename(__file__)
print "'all' means all not sent"
print "'rsync' means all not sent where to-be-sent-date is null"
@ -24,19 +24,28 @@ if not len(sys.argv) == 2 or sys.argv[1] not in ('all', 'rsync', 'specific'):
mode = sys.argv[1]
now = datetime.datetime.now()
now = datetime.datetime(2010, 8, 5)
announcements = ScheduledAnnouncement.objects.filter(mail_sent=False)
if mode == "rsync":
# include bogus 0000-00-00 entries
announcements = announcements.filter(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min))
elif mode == "specific":
# exclude null/bogus entries
announcements = announcements.exclude(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min))
announcements = announcements.filter(to_be_sent_date__lte=now.date(),
to_be_sent_time__lte=now.time())
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.announcements.models import SendQueue
announcements = SendQueue.objects.filter(sent_at=None)
if mode == "rsync":
announcements = announcements.filter(send_at=None)
elif mode == "specific":
announcements = announcements.exclude(send_at=None).filter(send_at__lte=now)
else:
announcements = ScheduledAnnouncement.objects.filter(mail_sent=False)
if mode == "rsync":
# include bogus 0000-00-00 entries
announcements = announcements.filter(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min))
elif mode == "specific":
# exclude null/bogus entries
announcements = announcements.exclude(Q(to_be_sent_date=None) | Q(to_be_sent_date__lte=datetime.date.min))
announcements = announcements.filter(to_be_sent_date__lte=now.date(),
to_be_sent_time__lte=now.time())
for announcement in announcements:
send_scheduled_announcement(announcement)
syslog.syslog('Sent scheduled announcement %s "%s"' % (announcement.id, announcement.subject))
subject = announcement.message.subject if settings.USE_DB_REDESIGN_PROXY_CLASSES else announcement.subject
syslog.syslog(u'Sent scheduled announcement %s "%s"' % (announcement.id, subject))

View file

@ -39,34 +39,34 @@ class IdIndexUrlTestCase(SimpleUrlTestCase):
def testUrls(self):
self.doTestUrls(__file__)
class IndexTestCase(unittest.TestCase, RealDatabaseTest):
def setUp(self):
self.setUpRealDatabase()
def tearDown(self):
self.tearDownRealDatabase()
# class IndexTestCase(unittest.TestCase, RealDatabaseTest):
# def setUp(self):
# self.setUpRealDatabase()
# def tearDown(self):
# self.tearDownRealDatabase()
def testAllId(self):
print " Testing all_id.txt generation"
c = Client()
response = c.get('/drafts/_test/all_id.txt')
self.assertEquals(response.status_code, 200)
content = response.content
# Test that correct version number is shown for couple of old drafts
self.assert_(content.find("draft-ietf-tls-psk-09") >= 0)
self.assert_(content.find("draft-eronen-eap-sim-aka-80211-00") >= 0)
# Since all_id.txt contains all old drafts, it should never shrink
lines = content.split("\n")
self.assert_(len(lines) > 18000)
# Test that the lines look OK and have correct number of tabs
r = re.compile(r'^(draft-\S*-\d\d)\t(\d\d\d\d-\d\d-\d\d)\t([^\t]+)\t([^\t]*)$')
for line in lines:
if ((line == "") or
(line == "Internet-Drafts Status Summary") or
(line == "Web version is available at") or
(line == "https://datatracker.ietf.org/public/idindex.cgi")):
pass
elif r.match(line):
pass
else:
self.fail("Unexpected line \""+line+"\"")
print "OK (all_id.txt)"
# def testAllId(self):
# print " Testing all_id.txt generation"
# c = Client()
# response = c.get('/drafts/_test/all_id.txt')
# self.assertEquals(response.status_code, 200)
# content = response.content
# # Test that correct version number is shown for couple of old drafts
# self.assert_(content.find("draft-ietf-tls-psk-09") >= 0)
# self.assert_(content.find("draft-eronen-eap-sim-aka-80211-00") >= 0)
# # Since all_id.txt contains all old drafts, it should never shrink
# lines = content.split("\n")
# self.assert_(len(lines) > 18000)
# # Test that the lines look OK and have correct number of tabs
# r = re.compile(r'^(draft-\S*-\d\d)\t(\d\d\d\d-\d\d-\d\d)\t([^\t]+)\t([^\t]*)$')
# for line in lines:
# if ((line == "") or
# (line == "Internet-Drafts Status Summary") or
# (line == "Web version is available at") or
# (line == "https://datatracker.ietf.org/public/idindex.cgi")):
# pass
# elif r.match(line):
# pass
# else:
# self.fail("Unexpected line \""+line+"\"")
# print "OK (all_id.txt)"

View file

@ -5,16 +5,16 @@
301 /drafts/current/
301 /drafts/all/
301 /drafts/dead/
301 /drafts/9574/related/
301 /drafts/9574/
#301 /drafts/9574/related/
#301 /drafts/9574/
301 /drafts/draft-ietf-dnsext-dnssec-protocol/related/
301 /drafts/draft-ietf-dnsext-dnssec-protocol/
404 /drafts/987654/
#404 /drafts/987654/
301 /drafts/all_id_txt.html
301 /drafts/all_id.html
301 /drafts/
200,heavy /drafts/_test/all_id.txt
#200,heavy /drafts/_test/all_id.txt
# this takes 3 minutes, so disabled by default
#200,heavy /drafts/_test/all_id2.txt
200,heavy /drafts/_test/id_index.txt
200,heavy /drafts/_test/id_abstracts.txt
#200,heavy /drafts/_test/id_index.txt
#200,heavy /drafts/_test/id_abstracts.txt

View file

@ -17,7 +17,8 @@ urlpatterns = patterns('',
(r'^all_id(?:_txt)?.html$', 'django.views.generic.simple.redirect_to', { 'url': 'http://www.ietf.org/id/all_id.txt' }),
)
if settings.SERVER_MODE != 'production':
if settings.SERVER_MODE != 'production' and not settings.USE_DB_REDESIGN_PROXY_CLASSES:
# these haven't been ported
urlpatterns += patterns('',
(r'^_test/all_id.txt$', views.test_all_id_txt),
(r'^_test/all_id2.txt$', views.test_all_id2_txt),

View file

@ -35,6 +35,8 @@
from django.http import HttpResponse, HttpResponsePermanentRedirect
from django.template import loader
from django.shortcuts import get_object_or_404
from django.conf import settings
from ietf.idtracker.models import Acronym, IETFWG, InternetDraft, IDInternal,PersonOrOrgInfo, Area
from ietf.idtracker.templatetags.ietf_filters import clean_whitespace
import re
@ -156,6 +158,9 @@ def test_id_abstracts_txt(request):
def redirect_id(request, object_id):
'''Redirect from historical document ID to preferred filename url.'''
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return HttpResponsePermanentRedirect("/doc/")
doc = get_object_or_404(InternetDraft, id_document_tag=object_id)
return HttpResponsePermanentRedirect("/doc/"+doc.filename+"/")
@ -163,6 +168,11 @@ def redirect_filename(request, filename):
return HttpResponsePermanentRedirect("/doc/"+filename+"/")
def wgdocs_redirect_id(request, id):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from group.models import Group
group = get_object_or_404(Group, id=id)
return HttpResponsePermanentRedirect("/wg/%s/" % group.acronym)
group = get_object_or_404(Acronym, acronym_id=id)
return HttpResponsePermanentRedirect("/wg/"+group.acronym+"/")

View file

@ -4,11 +4,16 @@ from django.conf import settings
from django.template.loader import render_to_string
from django.db.models import Q
import datetime, os, shutil, glob, re
import datetime, os, shutil, glob, re, itertools
from ietf.idtracker.models import InternetDraft, IDDates, IDStatus, IDState, DocumentComment
from ietf.utils.mail import send_mail
from ietf.idtracker.models import InternetDraft, IDDates, IDStatus, IDState, DocumentComment, IDAuthor,WGChair
from ietf.utils.mail import send_mail, send_mail_subj
from ietf.idrfc.utils import log_state_changed, add_document_comment
from redesign.doc.models import Document, DocEvent, save_document_in_history, State
from redesign.name.models import DocTagName
from redesign.person.models import Person, Email
INTERNET_DRAFT_DAYS_TO_EXPIRE = 185
def in_id_expire_freeze(when=None):
if when == None:
@ -23,6 +28,37 @@ def in_id_expire_freeze(when=None):
return second_cut_off <= when < ietf_monday
def document_expires(doc):
e = doc.latest_event(type__in=("completed_resurrect", "new_revision"))
if e:
return e.time + datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE)
else:
return None
def expirable_documents():
d = Document.objects.filter(states__type="draft", states__slug="active").exclude(tags="rfc-rev")
# we need to get those that either don't have a state or have a
# state >= 42 (AD watching), unfortunately that doesn't appear to
# be possible to get to work directly in Django 1.1
return itertools.chain(d.exclude(states__type="draft-iesg").distinct(), d.filter(states__type="draft-iesg", states__order__gte=42).distinct())
def get_soon_to_expire_ids(days):
start_date = datetime.date.today() - datetime.timedelta(InternetDraft.DAYS_TO_EXPIRE - 1)
end_date = start_date + datetime.timedelta(days - 1)
for d in InternetDraft.objects.filter(revision_date__gte=start_date,revision_date__lte=end_date,status__status='Active'):
if d.can_expire():
yield d
def get_soon_to_expire_idsREDESIGN(days):
start_date = datetime.date.today() - datetime.timedelta(1)
end_date = start_date + datetime.timedelta(days - 1)
for d in expirable_documents():
t = document_expires(d)
if t and start_date <= t.date() <= end_date:
yield d
def get_expired_ids():
cut_off = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE)
@ -32,6 +68,61 @@ def get_expired_ids():
review_by_rfc_editor=0).filter(
Q(idinternal=None) | Q(idinternal__cur_state__document_state_id__gte=42))
def get_expired_idsREDESIGN():
today = datetime.date.today()
for d in expirable_documents():
t = document_expires(d)
if t and t.date() <= today:
yield d
def send_expire_warning_for_id(doc):
expiration = doc.expiration()
# Todo:
#second_cutoff = IDDates.objects.get(date_id=2)
#ietf_monday = IDDates.objects.get(date_id=3)
#freeze_delta = ietf_monday - second_cutoff
# # The I-D expiration job doesn't run while submissions are frozen.
# if ietf_monday > expiration > second_cutoff:
# expiration += freeze_delta
authors = doc.authors.all()
to_addrs = [author.email() for author in authors if author.email()]
cc_addrs = None
if doc.group.acronym != 'none':
cc_addrs = [chair.person.email() for chair in WGChair.objects.filter(group_acronym=doc.group)]
if to_addrs or cc_addrs:
send_mail_subj(None, to_addrs, None, 'notify_expirations/subject.txt', 'notify_expirations/body.txt',
{
'draft':doc,
'expiration':expiration,
},
cc_addrs)
def send_expire_warning_for_idREDESIGN(doc):
expiration = document_expires(doc).date()
to = [e.formatted_email() for e in doc.authors.all() if not e.address.startswith("unknown-email")]
cc = None
if doc.group.type_id != "individ":
cc = [e.formatted_email() for e in Email.objects.filter(role__group=doc.group, role__name="chair") if not e.address.startswith("unknown-email")]
s = doc.get_state("draft-iesg")
state = s.name if s else "I-D Exists"
frm = None
request = None
if to or cc:
send_mail(request, to, frm,
u"Expiration impending: %s" % doc.file_tag(),
"idrfc/expire_warning_email.txt",
dict(doc=doc,
state=state,
expiration=expiration
),
cc=cc)
def send_expire_notice_for_id(doc):
doc.dunn_sent_date = datetime.date.today()
doc.save()
@ -45,7 +136,25 @@ def send_expire_notice_for_id(doc):
"I-D Expiring System <ietf-secretariat-reply@ietf.org>",
u"I-D was expired %s" % doc.file_tag(),
"idrfc/id_expired_email.txt",
dict(doc=doc))
dict(doc=doc,
state=doc.idstate()))
def send_expire_notice_for_idREDESIGN(doc):
if not doc.ad:
return
s = doc.get_state("draft-iesg")
state = s.name if s else "I-D Exists"
request = None
to = doc.ad.formatted_email()
send_mail(request, to,
"I-D Expiring System <ietf-secretariat-reply@ietf.org>",
u"I-D was expired %s" % doc.file_tag(),
"idrfc/id_expired_email.txt",
dict(doc=doc,
state=state,
))
def expire_id(doc):
def move_file(f):
@ -83,6 +192,52 @@ def expire_id(doc):
add_document_comment(None, doc, "Document is expired by system")
def expire_idREDESIGN(doc):
system = Person.objects.get(name="(System)")
# clean up files
def move_file(f):
src = os.path.join(settings.IDSUBMIT_REPOSITORY_PATH, f)
dst = os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, f)
if os.path.exists(src):
shutil.move(src, dst)
file_types = ['txt', 'txt.p7s', 'ps', 'pdf']
for t in file_types:
move_file("%s-%s.%s" % (doc.name, doc.rev, t))
# make tombstone
new_revision = "%02d" % (int(doc.rev) + 1)
new_file = open(os.path.join(settings.IDSUBMIT_REPOSITORY_PATH, "%s-%s.txt" % (doc.name, new_revision)), 'w')
txt = render_to_string("idrfc/expire_textREDESIGN.txt",
dict(doc=doc,
authors=[(e.get_name(), e.address) for e in doc.authors.all()],
expire_days=InternetDraft.DAYS_TO_EXPIRE))
new_file.write(txt)
new_file.close()
# now change the states
save_document_in_history(doc)
if doc.latest_event(type='started_iesg_process'):
dead_state = State.objects.get(type="draft-iesg", slug="dead")
prev = doc.get_state("draft-iesg")
if prev != dead_state:
doc.set_state(dead_state)
log_state_changed(None, doc, system, prev)
e = DocEvent(doc=doc, by=system)
e.type = "expired_document"
e.desc = "Document has expired"
e.save()
doc.rev = new_revision # FIXME: incrementing the revision like this is messed up
doc.set_state(State.objects.get(type="draft", slug="expired"))
doc.time = datetime.datetime.now()
doc.save()
def clean_up_id_files():
"""Move unidentified and old files out of the Internet Draft directory."""
cut_off = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE)
@ -142,3 +297,75 @@ def clean_up_id_files():
except InternetDraft.DoesNotExist:
move_file_to("unknown_ids")
def clean_up_id_filesREDESIGN():
"""Move unidentified and old files out of the Internet Draft directory."""
cut_off = datetime.date.today() - datetime.timedelta(days=INTERNET_DRAFT_DAYS_TO_EXPIRE)
pattern = os.path.join(settings.IDSUBMIT_REPOSITORY_PATH, "draft-*.*")
files = []
filename_re = re.compile('^(.*)-(\d\d)$')
def splitext(fn):
"""
Split the pathname path into a pair (root, ext) such that root + ext
== path, and ext is empty or begins with a period and contains all
periods in the last path component.
This differs from os.path.splitext in the number of periods in the ext
parts when the final path component containt more than one period.
"""
s = fn.rfind("/")
if s == -1:
s = 0
i = fn[s:].find(".")
if i == -1:
return fn, ''
else:
return fn[:s+i], fn[s+i:]
for path in glob.glob(pattern):
basename = os.path.basename(path)
stem, ext = splitext(basename)
match = filename_re.search(stem)
if not match:
filename, revision = ("UNKNOWN", "00")
else:
filename, revision = match.groups()
def move_file_to(subdir):
shutil.move(path,
os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, subdir, basename))
try:
doc = Document.objects.get(name=filename, rev=revision)
if doc.get_state_slug() == "rfc":
if ext != ".txt":
move_file_to("unknown_ids")
elif doc.get_state_slug() in ("expired", "repl", "auth-rm", "ietf-rm"):
e = doc.latest_event(type__in=('expired_document', 'new_revision', "completed_resurrect"))
expiration_date = e.time.date() if e and e.type == "expired_document" else None
if expiration_date and expiration_date < cut_off:
# Expired, Withdrawn by Author, Replaced, Withdrawn by IETF,
# and expired more than DAYS_TO_EXPIRE ago
if os.path.getsize(path) < 1500:
move_file_to("deleted_tombstones")
# revert version after having deleted tombstone
doc.rev = "%02d" % (int(revision) - 1) # FIXME: messed up
doc.save()
doc.tags.add(DocTagName.objects.get(slug='exp-tomb'))
else:
move_file_to("expired_without_tombstone")
except Document.DoesNotExist:
move_file_to("unknown_ids")
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
get_soon_to_expire_ids = get_soon_to_expire_idsREDESIGN
get_expired_ids = get_expired_idsREDESIGN
send_expire_warning_for_id = send_expire_warning_for_idREDESIGN
send_expire_notice_for_id = send_expire_notice_for_idREDESIGN
expire_id = expire_idREDESIGN
clean_up_id_files = clean_up_id_filesREDESIGN

View file

@ -40,6 +40,7 @@ from django.utils import simplejson as json
from django.db.models import Q
from django.db import models
from django.core.urlresolvers import reverse
from django.conf import settings
import types
BALLOT_ACTIVE_STATES = ['In Last Call',
@ -90,7 +91,7 @@ class IdWrapper:
def __init__(self, draft):
self.id = self
if isinstance(draft, IDInternal):
if isinstance(draft, IDInternal) and not settings.USE_DB_REDESIGN_PROXY_CLASSES:
self._idinternal = draft
self._draft = self._idinternal.draft
else:
@ -119,6 +120,15 @@ class IdWrapper:
self.publication_date = date(1990,1,1)
def rfc_editor_state(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
s = self._draft.get_state("draft-rfceditor")
if s:
# extract possible extra states
tags = self._draft.tags.filter(slug__in=("iana-crd", "ref", "missref"))
return " ".join([s.name] + [t.slug.replace("-crd", "").upper() for t in tags])
else:
return None
try:
qs = self._draft.rfc_editor_queue_state
return qs.state
@ -281,10 +291,16 @@ class RfcWrapper:
self.rfc = self
if not self._idinternal:
try:
self._idinternal = IDInternal.objects.get(rfc_flag=1, draft=self._rfcindex.rfc_number)
except IDInternal.DoesNotExist:
pass
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
pub = rfcindex.rfc_published_date
started = rfcindex.started_iesg_process if hasattr(rfcindex, 'started_iesg_process') else rfcindex.latest_event(type="started_iesg_process")
if pub and started and pub < started.time.date():
self._idinternal = rfcindex
else:
try:
self._idinternal = IDInternal.objects.get(rfc_flag=1, draft=self._rfcindex.rfc_number)
except IDInternal.DoesNotExist:
pass
if self._idinternal:
self.ietf_process = IetfProcessData(self._idinternal)
@ -295,7 +311,12 @@ class RfcWrapper:
self.maturity_level = self._rfcindex.current_status
if not self.maturity_level:
self.maturity_level = "Unknown"
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
if not rfcindex.name.startswith('rfc'):
self.draft_name = rfcindex.name
return # we've already done the lookup while importing so skip the rest
ids = InternetDraft.objects.filter(rfc_number=self.rfc_number)
if len(ids) >= 1:
self.draft_name = ids[0].filename
@ -307,10 +328,10 @@ class RfcWrapper:
self.draft_name = self._rfcindex.draft
def _rfc_doc_list(self, name):
if (not self._rfcindex) or (not self._rfcindex.__dict__[name]):
if (not self._rfcindex) or (not getattr(self._rfcindex, name)):
return None
else:
s = self._rfcindex.__dict__[name]
s = getattr(self._rfcindex, name)
s = s.replace(",", ", ")
s = re.sub("([A-Z])([0-9])", "\\1 \\2", s)
return s
@ -418,7 +439,7 @@ class IetfProcessData:
# don't call this unless has_[active_]iesg_ballot returns True
def iesg_ballot_needed( self ):
standardsTrack = 'Standard' in self.intended_maturity_level() or \
self.intended_maturity_level() == "BCP"
self.intended_maturity_level() in ("BCP", "Best Current Practice")
return self.iesg_ballot().ballot.needed( standardsTrack )
def ad_name(self):
@ -436,9 +457,21 @@ class IetfProcessData:
def state_date(self):
try:
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return self._idinternal.docevent_set.filter(
Q(desc__istartswith="Draft Added by ")|
Q(desc__istartswith="Draft Added in state ")|
Q(desc__istartswith="Draft added in state ")|
Q(desc__istartswith="State changed to ")|
Q(desc__istartswith="State Changes to ")|
Q(desc__istartswith="Sub state has been changed to ")|
Q(desc__istartswith="State has been changed to ")|
Q(desc__istartswith="IESG has approved and state has been changed to")).order_by('-time')[0].time.date()
return self._idinternal.comments().filter(
Q(comment_text__istartswith="Draft Added by ")|
Q(comment_text__istartswith="Draft Added in state ")|
Q(comment_text__istartswith="Draft added in state ")|
Q(comment_text__istartswith="State changed to ")|
Q(comment_text__istartswith="State Changes to ")|
Q(comment_text__istartswith="Sub state has been changed to ")|
@ -684,8 +717,63 @@ class BallotWrapper:
return []
else:
return self._ballot_set.exclude(draft=self._idinternal)
def _init(self):
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
self.old_init()
return
from redesign.person.models import Person
active_ads = Person.objects.filter(role__name="ad", role__group__state="active").distinct()
positions = []
seen = {}
from doc.models import BallotPositionDocEvent
for pos in BallotPositionDocEvent.objects.filter(doc=self.ballot, type="changed_ballot_position", time__gte=self.ballot.process_start, time__lte=self.ballot.process_end).select_related('ad').order_by("-time", '-id'):
if pos.ad not in seen:
p = dict(ad_name=pos.ad.name,
ad_username=pos.ad.pk, # ought to rename this in doc_ballot_list
position=pos.pos.name,
is_old_ad=pos.ad not in active_ads,
old_positions=[])
if pos.pos.slug == "discuss":
p["has_text"] = True
p["discuss_text"] = pos.discuss
p["discuss_date"] = pos.discuss_time
p["discuss_revision"] = pos.doc.rev # FIXME: wrong
if pos.comment:
p["has_text"] = True
p["comment_text"] = pos.comment
p["comment_date"] = pos.comment_time
p["comment_revision"] = pos.doc.rev # FIXME: wrong
positions.append(p)
seen[pos.ad] = p
else:
latest = seen[pos.ad]
if latest["old_positions"]:
prev = latest["old_positions"][-1]
else:
prev = latest["position"]
if prev != pos.pos.name:
seen[pos.ad]["old_positions"].append(pos.pos.name)
# add any missing ADs as No Record
if self.ballot_active:
for ad in active_ads:
if ad not in seen:
d = dict(ad_name=ad.name,
ad_username=ad.pk,
position="No Record",
)
positions.append(d)
self._positions = positions
def old_init(self):
try:
ads = set()
except NameError:
@ -784,7 +872,7 @@ def position_to_string(position):
return "No Record"
p = None
for k,v in positions.iteritems():
if position.__dict__[k] > 0:
if getattr(position, k) > 0:
p = v
if not p:
p = "No Record"

View file

@ -2,10 +2,15 @@
import datetime
from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin
from django.conf import settings
from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo
from ietf.idrfc.mails import *
from ietf.idrfc.utils import *
from doc.models import Document, DocEvent, LastCallDocEvent, WriteupDocEvent, save_document_in_history, State
from person.models import Person
def request_last_call(request, doc):
try:
ballot = doc.idinternal.ballot
@ -15,10 +20,37 @@ def request_last_call(request, doc):
send_last_call_request(request, doc, ballot)
add_document_comment(request, doc, "Last Call was requested")
def request_last_callREDESIGN(request, doc):
if not doc.latest_event(type="changed_ballot_writeup_text"):
generate_ballot_writeup(request, doc)
if not doc.latest_event(type="changed_ballot_approval_text"):
generate_approval_mail(request, doc)
if not doc.latest_event(type="changed_last_call_text"):
generate_last_call_announcement(request, doc)
send_last_call_request(request, doc)
e = DocEvent()
e.type = "requested_last_call"
e.by = request.user.get_profile()
e.doc = doc
e.desc = "Last call was requested by %s" % e.by.name
e.save()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
request_last_call = request_last_callREDESIGN
def get_expired_last_calls():
return InternetDraft.objects.filter(lc_expiration_date__lte=datetime.date.today(),
idinternal__cur_state__document_state_id=IDState.IN_LAST_CALL)
def get_expired_last_callsREDESIGN():
today = datetime.date.today()
for d in Document.objects.filter(states__type="draft-iesg", states__slug="lc"):
e = d.latest_event(LastCallDocEvent, type="sent_last_call")
if e and e.expires.date() <= today:
yield d
def expire_last_call(doc):
state = IDState.WAITING_FOR_WRITEUP
@ -36,3 +68,28 @@ def expire_last_call(doc):
log_state_changed(None, doc, by="system", email_watch_list=False)
email_last_call_expired(doc)
def expire_last_callREDESIGN(doc):
state = State.objects.get(type="draft-iesg", slug="writeupw")
e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
if e and "What does this protocol do and why" not in e.text:
# if boiler-plate text has been removed, we assume the
# write-up has been written
state = State.objects.get(type="draft-iesg", slug="goaheadw")
save_document_in_history(doc)
prev = doc.get_state("draft-iesg")
doc.set_state(state)
e = log_state_changed(None, doc, Person.objects.get(name="(System)"), prev)
doc.time = e.time
doc.save()
email_last_call_expired(doc)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
get_expired_last_calls = get_expired_last_callsREDESIGN
expire_last_call = expire_last_callREDESIGN

View file

@ -13,6 +13,8 @@ from ietf.idtracker.models import *
from ietf.ipr.search import iprs_from_docs
from ietf.ietfworkflows.streams import (get_stream_from_draft)
from ietf.ietfworkflows.models import (Stream)
from redesign.doc.models import WriteupDocEvent, BallotPositionDocEvent, LastCallDocEvent, DocAlias
from redesign.person.models import Person
def email_state_changed(request, doc, text):
to = [x.strip() for x in doc.idinternal.state_change_notice_to.replace(';', ',').split(',')]
@ -23,6 +25,21 @@ def email_state_changed(request, doc, text):
dict(text=text,
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
def email_state_changedREDESIGN(request, doc, text):
to = [x.strip() for x in doc.notify.replace(';', ',').split(',')]
if not to:
return
text = strip_tags(text)
send_mail(request, to, None,
"ID Tracker State Update Notice: %s" % doc.file_tag(),
"idrfc/state_changed_email.txt",
dict(text=text,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
email_state_changed = email_state_changedREDESIGN
def html_to_text(html):
return strip_tags(html.replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&").replace("<br>", "\n"))
@ -39,6 +56,23 @@ def email_owner(request, doc, owner, changed_by, text, subject=None):
doc=doc,
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
def email_ownerREDESIGN(request, doc, owner, changed_by, text, subject=None):
if not owner or not changed_by or owner == changed_by:
return
to = owner.formatted_email()
send_mail(request, to,
"DraftTracker Mail System <iesg-secretary@ietf.org>",
"%s updated by %s" % (doc.file_tag(), changed_by.name),
"idrfc/change_notice.txt",
dict(text=html_to_text(text),
doc=doc,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
email_owner = email_ownerREDESIGN
def full_intended_status(intended_status):
s = str(intended_status)
# FIXME: this should perhaps be defined in the db
@ -59,6 +93,17 @@ def full_intended_status(intended_status):
else:
return "a %s" % s
def generate_ballot_writeup(request, doc):
e = WriteupDocEvent()
e.type = "changed_ballot_writeup_text"
e.by = request.user.get_profile()
e.doc = doc
e.desc = u"Ballot writeup was generated"
e.text = unicode(render_to_string("idrfc/ballot_writeup.txt"))
e.save()
return e
def generate_last_call_announcement(request, doc):
status = full_intended_status(doc.intended_status).replace("a ", "").replace("an ", "")
@ -99,6 +144,57 @@ def generate_last_call_announcement(request, doc):
)
)
def generate_last_call_announcementREDESIGN(request, doc):
doc.full_status = full_intended_status(doc.intended_std_level)
status = doc.full_status.replace("a ", "").replace("an ", "")
expiration_date = date.today() + timedelta(days=14)
cc = []
if doc.group.type_id == "individ":
group = "an individual submitter"
expiration_date += timedelta(days=14)
else:
group = "the %s WG (%s)" % (doc.group.name, doc.group.acronym)
if doc.group.list_email:
cc.append(doc.group.list_email)
doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3)
iprs, _ = iprs_from_docs([ DocAlias.objects.get(name=doc.canonical_name()) ])
if iprs:
ipr_links = [ urlreverse("ietf.ipr.views.show", kwargs=dict(ipr_id=i.ipr_id)) for i in iprs]
ipr_links = [ settings.IDTRACKER_BASE_URL+url if not url.startswith("http") else url for url in ipr_links ]
else:
ipr_links = None
mail = render_to_string("idrfc/last_call_announcement.txt",
dict(doc=doc,
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url() + "ballot/",
expiration_date=expiration_date.strftime("%Y-%m-%d"), #.strftime("%B %-d, %Y"),
cc=", ".join("<%s>" % e for e in cc),
group=group,
docs=[ doc ],
urls=[ settings.IDTRACKER_BASE_URL + doc.get_absolute_url() ],
status=status,
impl_report="Draft" in status or "Full" in status,
ipr_links=ipr_links,
)
)
e = WriteupDocEvent()
e.type = "changed_last_call_text"
e.by = request.user.get_profile()
e.doc = doc
e.desc = u"Last call announcement was generated"
e.text = unicode(mail)
e.save()
return e
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
generate_last_call_announcement = generate_last_call_announcementREDESIGN
def generate_approval_mail(request, doc):
if doc.idinternal.cur_state_id in IDState.DO_NOT_PUBLISH_STATES or doc.idinternal.via_rfc_editor:
return generate_approval_mail_rfc_editor(request, doc)
@ -127,7 +223,7 @@ def generate_approval_mail(request, doc):
if len(docs) > 1:
made_by = "These documents have been reviewed in the IETF but are not the products of an IETF Working Group."
else:
made_by = "This document has been reviewed in the IETF but is not the product of an IETF Working Group.";
made_by = "This document has been reviewed in the IETF but is not the product of an IETF Working Group."
else:
if len(docs) > 1:
made_by = "These documents are products of the %s." % doc.group.name_with_wg
@ -179,6 +275,115 @@ def generate_approval_mail_rfc_editor(request, doc):
)
)
DO_NOT_PUBLISH_IESG_STATES = ("nopubadw", "nopubanw")
def generate_approval_mailREDESIGN(request, doc):
if doc.get_state_slug("draft-iesg") in DO_NOT_PUBLISH_IESG_STATES or doc.tags.filter(slug='via-rfc'):
mail = generate_approval_mail_rfc_editor(request, doc)
else:
mail = generate_approval_mail_approved(request, doc)
e = WriteupDocEvent()
e.type = "changed_ballot_approval_text"
e.by = request.user.get_profile()
e.doc = doc
e.desc = u"Ballot approval text was generated"
e.text = unicode(mail)
e.save()
return e
def generate_approval_mail_approved(request, doc):
doc.full_status = full_intended_status(doc.intended_std_level.name)
status = doc.full_status.replace("a ", "").replace("an ", "")
if "an " in status:
action_type = "Document"
else:
action_type = "Protocol"
cc = settings.DOC_APPROVAL_EMAIL_CC
# the second check catches some area working groups (like
# Transport Area Working Group)
if doc.group.type_id != "area" and not doc.group.name.endswith("Working Group"):
doc.group.name_with_wg = doc.group.name + " Working Group"
if doc.group.list_email:
cc.append("%s mailing list <%s>" % (doc.group.acronym, doc.group.list_email))
cc.append("%s chair <%s-chairs@tools.ietf.org>" % (doc.group.acronym, doc.group.acronym))
else:
doc.group.name_with_wg = doc.group.name
doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3)
if doc.group.type_id == "individ":
made_by = "This document has been reviewed in the IETF but is not the product of an IETF Working Group."
else:
made_by = "This document is the product of the %s." % doc.group.name_with_wg
director = doc.ad
other_director = Person.objects.filter(role__group__role__person=director, role__group__role__name="ad").exclude(pk=director.pk)
if doc.group.type_id != "individ" and other_director:
contacts = "The IESG contact persons are %s and %s." % (director.name, other_director[0].name)
else:
contacts = "The IESG contact person is %s." % director.name
doc_type = "RFC" if doc.get_state_slug() == "rfc" else "Internet Draft"
return render_to_string("idrfc/approval_mail.txt",
dict(doc=doc,
docs=[doc],
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
cc=",\n ".join(cc),
doc_type=doc_type,
made_by=made_by,
contacts=contacts,
status=status,
action_type=action_type,
)
)
def generate_approval_mail_rfc_editorREDESIGN(request, doc):
full_status = full_intended_status(doc.intended_std_level.name)
status = full_status.replace("a ", "").replace("an ", "")
disapproved = doc.get_state_slug("draft-iesg") in DO_NOT_PUBLISH_IESG_STATES
doc_type = "RFC" if doc.get_state_slug() == "rfc" else "Internet Draft"
to = []
if doc.group:
for r in doc.group.roles_set.filter(name="chair").select_related():
to.append(r.formatted_email())
if doc.stream_id == "ise":
# include ISE chair
g = Group.objects.get(type='individ')
for r in g.roles_set.filter(name="chair").select_related():
to.append(r.formatted_email())
elif doc.stream_id == "irtf":
# include IRTF chair
g = Group.objects.get(type='irtf')
for r in g.roles_set.filter(name="chair").select_related():
to.append(r.formatted_email())
# and IRSG
to.append('"Internet Research Steering Group" <irsg@irtf.org>')
return render_to_string("idrfc/approval_mail_rfc_editor.txt",
dict(doc=doc,
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
doc_type=doc_type,
status=status,
full_status=full_status,
disapproved=disapproved,
to=", ".join(to),
)
)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
generate_approval_mail = generate_approval_mailREDESIGN
generate_approval_mail_rfc_editor = generate_approval_mail_rfc_editorREDESIGN
def send_last_call_request(request, doc, ballot):
to = "iesg-secretary@ietf.org"
frm = '"DraftTracker Mail System" <iesg-secretary@ietf.org>'
@ -190,6 +395,19 @@ def send_last_call_request(request, doc, ballot):
dict(docs=docs,
doc_url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
def send_last_call_requestREDESIGN(request, doc):
to = "iesg-secretary@ietf.org"
frm = '"DraftTracker Mail System" <iesg-secretary@ietf.org>'
send_mail(request, to, frm,
"Last Call: %s" % doc.file_tag(),
"idrfc/last_call_request.txt",
dict(docs=[doc],
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
send_last_call_request = send_last_call_requestREDESIGN
def email_resurrect_requested(request, doc, by):
to = "I-D Administrator <internet-drafts@ietf.org>"
frm = u"%s <%s>" % by.person.email()
@ -200,6 +418,19 @@ def email_resurrect_requested(request, doc, by):
by=frm,
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
def email_resurrect_requestedREDESIGN(request, doc, by):
to = "I-D Administrator <internet-drafts@ietf.org>"
frm = by.formatted_email()
send_mail(request, to, frm,
"I-D Resurrection Request",
"idrfc/resurrect_request_email.txt",
dict(doc=doc,
by=frm,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
email_resurrect_requested = email_resurrect_requestedREDESIGN
def email_resurrection_completed(request, doc):
to = u"%s <%s>" % doc.idinternal.resurrect_requested_by.person.email()
frm = "I-D Administrator <internet-drafts-reply@ietf.org>"
@ -210,6 +441,19 @@ def email_resurrection_completed(request, doc):
by=frm,
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()))
def email_resurrection_completedREDESIGN(request, doc, requester):
to = requester.formatted_email()
frm = "I-D Administrator <internet-drafts-reply@ietf.org>"
send_mail(request, to, frm,
"I-D Resurrection Completed - %s" % doc.file_tag(),
"idrfc/resurrect_completed_email.txt",
dict(doc=doc,
by=frm,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
email_resurrection_completed = email_resurrection_completedREDESIGN
def email_ballot_deferred(request, doc, by, telechat_date):
to = "iesg@ietf.org"
frm = "DraftTracker Mail System <iesg-secretary@ietf.org>"
@ -276,7 +520,83 @@ def generate_issue_ballot_mail(request, doc):
ad_feedback=ad_feedback
)
)
def generate_issue_ballot_mailREDESIGN(request, doc):
full_status = full_intended_status(doc.intended_std_level.name)
status = full_status.replace("a ", "").replace("an ", "")
active_ads = Person.objects.filter(role__name="ad", role__group__state="active").distinct()
e = doc.latest_event(type="started_iesg_process")
positions = BallotPositionDocEvent.objects.filter(doc=doc, type="changed_ballot_position", time__gte=e.time).order_by("-time", '-id').select_related('ad')
# format positions and setup discusses and comments
ad_feedback = []
seen = set()
active_ad_positions = []
inactive_ad_positions = []
for p in positions:
if p.ad in seen:
continue
seen.add(p.ad)
def formatted(val):
if val:
return "[ X ]"
else:
return "[ ]"
fmt = u"%-21s%-10s%-11s%-9s%-10s" % (
p.ad.name[:21],
formatted(p.pos_id == "yes"),
formatted(p.pos_id == "noobj"),
formatted(p.pos_id == "discuss"),
"[ R ]" if p.pos_id == "recuse" else formatted(p.pos_id == "abstain"),
)
if p.ad in active_ads:
active_ad_positions.append(fmt)
if not p.pos_id == "discuss":
p.discuss = ""
if p.comment or p.discuss:
ad_feedback.append(p)
else:
inactive_ad_positions.append(fmt)
active_ad_positions.sort()
inactive_ad_positions.sort()
ad_feedback.sort(key=lambda p: p.ad.name)
e = doc.latest_event(LastCallDocEvent, type="sent_last_call")
last_call_expires = e.expires if e else None
e = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text")
approval_text = e.text if e else ""
e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
ballot_writeup = e.text if e else ""
# NOTE: according to Michelle Cotton <michelle.cotton@icann.org>
# (as per 2011-10-24) IANA is scraping these messages for
# information so would like to know beforehand if the format
# changes (perhaps RFC 6359 will change that)
return render_to_string("idrfc/issue_ballot_mailREDESIGN.txt",
dict(doc=doc,
doc_url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url(),
status=status,
active_ad_positions=active_ad_positions,
inactive_ad_positions=inactive_ad_positions,
ad_feedback=ad_feedback,
last_call_expires=last_call_expires,
approval_text=approval_text,
ballot_writeup=ballot_writeup,
)
)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
generate_issue_ballot_mail = generate_issue_ballot_mailREDESIGN
def email_iana(request, doc, to, msg):
# fix up message and send message to IANA for each in ballot set
import email
@ -294,6 +614,25 @@ def email_iana(request, doc, to, msg):
extra=extra,
bcc="fenner@research.att.com")
def email_ianaREDESIGN(request, doc, to, msg):
# fix up message and send it with extra info on doc in headers
import email
parsed_msg = email.message_from_string(msg.encode("utf-8"))
extra = {}
extra["Reply-To"] = "noreply@ietf.org"
extra["X-IETF-Draft-string"] = doc.name
extra["X-IETF-Draft-revision"] = doc.rev
send_mail_text(request, "IANA <%s>" % to,
parsed_msg["From"], parsed_msg["Subject"],
parsed_msg.get_payload(),
extra=extra,
bcc="fenner@research.att.com")
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
email_iana = email_ianaREDESIGN
def email_last_call_expired(doc):
text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.idinternal.cur_state.state
@ -307,3 +646,19 @@ def email_last_call_expired(doc):
url=settings.IDTRACKER_BASE_URL + doc.idinternal.get_absolute_url()),
cc="iesg-secretary@ietf.org")
def email_last_call_expiredREDESIGN(doc):
text = "IETF Last Call has ended, and the state has been changed to\n%s." % doc.get_state("draft-iesg").name
send_mail(None,
"iesg@ietf.org",
"DraftTracker Mail System <iesg-secretary@ietf.org>",
"Last Call Expired: %s" % doc.file_tag(),
"idrfc/change_notice.txt",
dict(text=text,
doc=doc,
url=settings.IDTRACKER_BASE_URL + doc.get_absolute_url()),
cc="iesg-secretary@ietf.org")
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
email_last_call_expired = email_last_call_expiredREDESIGN

View file

@ -69,8 +69,7 @@ def parse(response):
events.expandNode(node)
node.normalize()
draft_name = getChildText(node, "draft").strip()
if re.search("-\d\d\.txt$", draft_name):
draft_name = draft_name[0:-7]
draft_name = re.sub("(-\d\d)?(.txt){1,2}$", "", draft_name)
date_received = getChildText(node, "date-received")
states = []
@ -169,6 +168,9 @@ def parse_all(response):
refs.extend(indirect_refs)
del(indirect_refs)
if settings.USE_DB_REDESIGN_PROXY_CLASSES: # note: return before id lookup
return (drafts, refs)
# convert filenames to id_document_tags
log("connecting to database...")
cursor = db.connection.cursor()
@ -190,7 +192,76 @@ def insert_into_database(drafts, refs):
cursor.close()
db.connection._commit()
db.connection.close()
import django.db.transaction
def get_rfc_tag_mapping():
"""Return dict with RFC Editor state name -> DocTagName"""
from redesign.name.models import DocTagName
from redesign.name.utils import name
return {
'IANA': name(DocTagName, 'iana-crd', 'IANA coordination', "RFC-Editor/IANA Registration Coordination"),
'REF': name(DocTagName, 'ref', 'Holding for references', "Holding for normative reference"),
'MISSREF': name(DocTagName, 'missref', 'Missing references', "Awaiting missing normative reference"),
}
def get_rfc_state_mapping():
"""Return dict with RFC Editor state name -> State"""
from redesign.doc.models import State, StateType
t = StateType.objects.get(slug="draft-rfceditor")
return {
'AUTH': State.objects.get_or_create(type=t, slug='auth', name='AUTH', desc="Awaiting author action")[0],
'AUTH48': State.objects.get_or_create(type=t, slug='auth48', name="AUTH48", desc="Awaiting final author approval")[0],
'EDIT': State.objects.get_or_create(type=t, slug='edit', name='EDIT', desc="Approved by the stream manager (e.g., IESG, IAB, IRSG, ISE), awaiting processing and publishing")[0],
'IANA': State.objects.get_or_create(type=t, slug='iana-crd', name='IANA', desc="RFC-Editor/IANA Registration Coordination")[0],
'IESG': State.objects.get_or_create(type=t, slug='iesg', name='IESG', desc="Holding for IESG action")[0],
'ISR': State.objects.get_or_create(type=t, slug='isr', name='ISR', desc="Independent Submission Review by the ISE ")[0],
'ISR-AUTH': State.objects.get_or_create(type=t, slug='isr-auth', name='ISR-AUTH', desc="Independent Submission awaiting author update, or in discussion between author and ISE")[0],
'REF': State.objects.get_or_create(type=t, slug='ref', name='REF', desc="Holding for normative reference")[0],
'RFC-EDITOR': State.objects.get_or_create(type=t, slug='rfc-edit', name='RFC-EDITOR', desc="Awaiting final RFC Editor review before AUTH48")[0],
'TO': State.objects.get_or_create(type=t, slug='timeout', name='TO', desc="Time-out period during which the IESG reviews document for conflict/concurrence with other IETF working group work")[0],
'MISSREF': State.objects.get_or_create(type=t, slug='missref', name='MISSREF', desc="Awaiting missing normative reference")[0],
}
@django.db.transaction.commit_on_success
def insert_into_databaseREDESIGN(drafts, refs):
from doc.models import Document
from name.models import DocTagName
tags = get_rfc_tag_mapping()
states = get_rfc_state_mapping()
rfc_editor_tags = tags.values()
log("removing old data...")
for d in Document.objects.filter(states__type="draft-rfceditor").distinct():
d.tags.remove(*rfc_editor_tags)
d.unset_state("draft-rfceditor")
log("inserting new data...")
for name, date_received, state, stream_id in drafts:
try:
d = Document.objects.get(name=name)
except Document.DoesNotExist:
log("unknown document %s" % name)
continue
s = state.split(" ")
if s:
# first is state
d.set_state(states[s[0]])
# remainding are tags
for x in s[1:]:
d.tags.add(tags[x])
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
insert_into_database = insert_into_databaseREDESIGN
if __name__ == '__main__':
try:
log("output from mirror_rfc_editor_queue.py:\n")

View file

@ -38,7 +38,7 @@ from django import db
from xml.dom import pulldom, Node
import re
import urllib2
from datetime import datetime
from datetime import datetime, date, timedelta
import socket
import sys
@ -147,6 +147,174 @@ def insert_to_database(data):
db.connection._commit()
db.connection.close()
def get_std_level_mapping():
from name.models import StdLevelName
from name.utils import name
return {
"Standard": name(StdLevelName, "std", "Standard"),
"Draft Standard": name(StdLevelName, "ds", "Draft Standard"),
"Proposed Standard": name(StdLevelName, "ps", "Proposed Standard"),
"Informational": name(StdLevelName, "inf", "Informational"),
"Experimental": name(StdLevelName, "exp", "Experimental"),
"Best Current Practice": name(StdLevelName, "bcp", "Best Current Practice"),
"Historic": name(StdLevelName, "hist", "Historic"),
"Unknown": name(StdLevelName, "unkn", "Unknown"),
}
def get_stream_mapping():
from name.models import DocStreamName
from name.utils import name
return {
"Legacy": name(DocStreamName, "legacy", "Legacy"),
"IETF": name(DocStreamName, "ietf", "IETF"),
"INDEPENDENT": name(DocStreamName, "ise", "ISE", desc="Independent submission editor stream"),
"IAB": name(DocStreamName, "iab", "IAB"),
"IRTF": name(DocStreamName, "irtf", "IRTF"),
}
import django.db.transaction
@django.db.transaction.commit_on_success
def insert_to_databaseREDESIGN(data):
from redesign.person.models import Person
from redesign.doc.models import Document, DocAlias, DocEvent, RelatedDocument, State
from redesign.group.models import Group
from redesign.name.models import DocTagName, DocRelationshipName
from redesign.name.utils import name
system = Person.objects.get(name="(System)")
std_level_mapping = get_std_level_mapping()
stream_mapping = get_stream_mapping()
tag_has_errata = name(DocTagName, 'errata', "Has errata")
relationship_obsoletes = name(DocRelationshipName, "obs", "Obsoletes")
relationship_updates = name(DocRelationshipName, "updates", "Updates")
skip_older_than_date = (date.today() - timedelta(days=365)).strftime("%Y-%m-%d")
log("updating data...")
for d in data:
rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats = d
if rfc_published_date < skip_older_than_date:
# speed up the process by skipping old entries
continue
# we assume two things can happen: we get a new RFC, or an
# attribute has been updated at the RFC Editor (RFC Editor
# attributes currently take precedence over our local
# attributes)
# make sure we got the document and alias
created = False
doc = None
name = "rfc%s" % rfc_number
a = DocAlias.objects.filter(name=name)
if a:
doc = a[0].document
else:
if draft:
try:
doc = Document.objects.get(name=draft)
except Document.DoesNotExist:
pass
if not doc:
created = True
log("created document %s" % name)
doc = Document.objects.create(name=name)
# add alias
DocAlias.objects.create(name=name, document=doc)
if not created:
created = True
log("created alias %s to %s" % (name, doc.name))
# check attributes
changed = False
if title != doc.title:
doc.title = title
changed = True
if std_level_mapping[current_status] != doc.std_level:
doc.std_level = std_level_mapping[current_status]
changed = True
if doc.get_state_slug() != "rfc":
doc.set_state(State.objects.filter(type="draft", slug="rfc"))
changed = True
if doc.stream != stream_mapping[stream]:
doc.stream = stream_mapping[stream]
changed = True
if not doc.group and wg:
doc.group = Group.objects.get(acronym=wg)
changed = True
pubdate = datetime.strptime(rfc_published_date, "%Y-%m-%d")
if not doc.latest_event(type="published_rfc", time=pubdate):
e = DocEvent(doc=doc, type="published_rfc")
e.time = pubdate
e.by = system
e.desc = "RFC published"
e.save()
changed = True
def parse_relation_list(s):
if not s:
return []
res = []
for x in s.split(","):
if x[:3] in ("NIC", "IEN", "STD", "RTR"):
# try translating this to RFCs that we can handle
# sensibly; otherwise we'll have to ignore them
l = DocAlias.objects.filter(name__startswith="rfc", document__docalias__name=x.lower())
else:
l = DocAlias.objects.filter(name=x.lower())
for a in l:
if a not in res:
res.append(a)
return res
for x in parse_relation_list(obsoletes):
if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_obsoletes):
RelatedDocument.objects.create(source=doc, target=x, relationship=relationship_obsoletes)
changed = True
for x in parse_relation_list(updates):
if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_updates):
RelatedDocument.objects.create(source=doc, target=x, relationship=relationship_updates)
changed = True
if also:
for a in also.lower().split(","):
if not DocAlias.objects.filter(name=a):
DocAlias.objects.create(name=a, document=doc)
changed = True
if has_errata:
if not doc.tags.filter(pk=tag_has_errata.pk):
doc.tags.add(tag_has_errata)
changed = True
else:
if doc.tags.filter(pk=tag_has_errata.pk):
doc.tags.remove(tag_has_errata)
changed = True
if changed:
if not created:
log("%s changed" % name)
doc.time = datetime.now()
doc.save()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
insert_to_database = insert_to_databaseREDESIGN
if __name__ == '__main__':
try:
log("output from mirror_rfc_index.py:\n")

View file

@ -96,4 +96,9 @@ class DraftVersions(models.Model):
return "DraftVersions"+self.filename+self.revision+str(self.revision_date)
class Meta:
db_table = "draft_versions_mirror"
from django.conf import settings
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
RfcIndexOld = RfcIndex
from redesign.doc.proxy import RfcIndex

View file

@ -32,6 +32,7 @@
from django import template
from django.core.urlresolvers import reverse as urlreverse
from django.conf import settings
from ietf.idtracker.models import IDInternal, BallotInfo
from ietf.idrfc.idrfc_wrapper import position_to_string, BALLOT_ACTIVE_STATES
from ietf.idtracker.templatetags.ietf_filters import in_group, timesince_days
@ -40,12 +41,21 @@ register = template.Library()
def get_user_adid(context):
if 'user' in context and in_group(context['user'], "Area_Director"):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return context['user'].get_profile().id
return context['user'].get_profile().iesg_login_id()
else:
return None
def get_user_name(context):
if 'user' in context and context['user'].is_authenticated():
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from person.models import Person
try:
return context['user'].get_profile().name
except Person.DoesNotExist:
return None
person = context['user'].get_profile().person()
if person:
return str(person)
@ -61,7 +71,7 @@ def render_ballot_icon(context, doc):
return ""
if str(doc.cur_state) not in BALLOT_ACTIVE_STATES:
return ""
if doc.rfc_flag:
if doc.rfc_flag and not settings.USE_DB_REDESIGN_PROXY_CLASSES:
name = doc.document().filename()
else:
name = doc.document().filename

View file

@ -44,7 +44,7 @@ from pyquery import PyQuery
from ietf.idrfc.models import *
from ietf.idtracker.models import *
from ietf.utils.test_utils import SimpleUrlTestCase, RealDatabaseTest, login_testing_unauthorized
from ietf.utils.test_runner import mail_outbox
from ietf.utils.mail import outbox
from ietf.ietfworkflows.models import Stream
from django.contrib.auth.models import User
@ -88,7 +88,7 @@ class ChangeStateTestCase(django.test.TestCase):
# change state
comments_before = draft.idinternal.comments().count()
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
r = self.client.post(url,
dict(state="12", substate=""))
@ -101,9 +101,9 @@ class ChangeStateTestCase(django.test.TestCase):
self.assertEquals(draft.idinternal.cur_sub_state, None)
self.assertEquals(draft.idinternal.comments().count(), comments_before + 1)
self.assertTrue("State changed" in draft.idinternal.comments()[0].comment_text)
self.assertEquals(len(mail_outbox), mailbox_before + 2)
self.assertTrue("State Update Notice" in mail_outbox[-2]['Subject'])
self.assertTrue(draft.filename in mail_outbox[-1]['Subject'])
self.assertEquals(len(outbox), mailbox_before + 2)
self.assertTrue("State Update Notice" in outbox[-2]['Subject'])
self.assertTrue(draft.filename in outbox[-1]['Subject'])
def test_request_last_call(self):
@ -112,7 +112,7 @@ class ChangeStateTestCase(django.test.TestCase):
self.client.login(remote_user="klm")
url = urlreverse('doc_change_state', kwargs=dict(name=draft.filename))
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
self.assertRaises(BallotInfo.DoesNotExist, lambda: draft.idinternal.ballot)
r = self.client.post(url,
@ -134,8 +134,8 @@ class ChangeStateTestCase(django.test.TestCase):
self.assertTrue("Technical Summary" in draft.idinternal.ballot.ballot_writeup)
# mail notice
self.assertTrue(len(mail_outbox) > mailbox_before)
self.assertTrue("Last Call:" in mail_outbox[-1]['Subject'])
self.assertTrue(len(outbox) > mailbox_before)
self.assertTrue("Last Call:" in outbox[-1]['Subject'])
# comment
self.assertTrue("Last Call was requested" in draft.idinternal.comments()[0].comment_text)
@ -167,7 +167,7 @@ class EditInfoTestCase(django.test.TestCase):
# edit info
comments_before = draft.idinternal.comments().count()
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
draft.group = Acronym.objects.get(acronym_id=Acronym.INDIVIDUAL_SUBMITTER)
draft.save()
new_job_owner = IESGLogin.objects.exclude(id__in=[IESGLogin.objects.get(login_name="klm").id, draft.idinternal.job_owner_id])[0]
@ -193,8 +193,8 @@ class EditInfoTestCase(django.test.TestCase):
self.assertEquals(draft.idinternal.note, "")
self.assertTrue(not draft.idinternal.agenda)
self.assertEquals(draft.idinternal.comments().count(), comments_before + 3)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue(draft.filename in mail_outbox[-1]['Subject'])
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue(draft.filename in outbox[-1]['Subject'])
def test_edit_telechat_date(self):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
@ -256,7 +256,7 @@ class EditInfoTestCase(django.test.TestCase):
self.assertTrue('@' in q('form input[name=state_change_notice_to]')[0].get('value'))
# add
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
job_owner = IESGLogin.objects.filter(user_level=1)[0]
area = Area.active_areas()[0]
@ -283,7 +283,7 @@ class EditInfoTestCase(django.test.TestCase):
self.assertEquals(draft.idinternal.comments().count(), 2)
self.assertTrue("Draft added" in draft.idinternal.comments()[0].comment_text)
self.assertTrue("This is a note" in draft.idinternal.comments()[1].comment_text)
self.assertEquals(len(mail_outbox), mailbox_before)
self.assertEquals(len(outbox), mailbox_before)
class ResurrectTestCase(django.test.TestCase):
@ -308,7 +308,7 @@ class ResurrectTestCase(django.test.TestCase):
# request resurrect
comments_before = draft.idinternal.comments().count()
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
r = self.client.post(url, dict())
self.assertEquals(r.status_code, 302)
@ -317,8 +317,8 @@ class ResurrectTestCase(django.test.TestCase):
self.assertEquals(draft.idinternal.resurrect_requested_by, IESGLogin.objects.get(login_name=login_as))
self.assertEquals(draft.idinternal.comments().count(), comments_before + 1)
self.assertTrue("Resurrection" in draft.idinternal.comments()[0].comment_text)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("Resurrection" in mail_outbox[-1]['Subject'])
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("Resurrection" in outbox[-1]['Subject'])
def test_resurrect(self):
draft = InternetDraft.objects.get(filename="draft-ietf-mip6-cn-ipsec")
@ -338,7 +338,7 @@ class ResurrectTestCase(django.test.TestCase):
# request resurrect
comments_before = draft.idinternal.comments().count()
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
r = self.client.post(url, dict())
self.assertEquals(r.status_code, 302)
@ -348,7 +348,7 @@ class ResurrectTestCase(django.test.TestCase):
self.assertEquals(draft.idinternal.comments().count(), comments_before + 1)
self.assertTrue("completed" in draft.idinternal.comments()[0].comment_text)
self.assertEquals(draft.status.status, "Active")
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertEquals(len(outbox), mailbox_before + 1)
class AddCommentTestCase(django.test.TestCase):
fixtures = ['base', 'draft']
@ -366,16 +366,16 @@ class AddCommentTestCase(django.test.TestCase):
# request resurrect
comments_before = draft.idinternal.comments().count()
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
r = self.client.post(url, dict(comment="This is a test."))
self.assertEquals(r.status_code, 302)
self.assertEquals(draft.idinternal.comments().count(), comments_before + 1)
self.assertTrue("This is a test." in draft.idinternal.comments()[0].comment_text)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("updated" in mail_outbox[-1]['Subject'])
self.assertTrue(draft.filename in mail_outbox[-1]['Subject'])
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("updated" in outbox[-1]['Subject'])
self.assertTrue(draft.filename in outbox[-1]['Subject'])
class EditPositionTestCase(django.test.TestCase):
fixtures = ['base', 'draft', 'ballot']
@ -468,7 +468,7 @@ class EditPositionTestCase(django.test.TestCase):
self.assertTrue(len(q('form input[name="cc"]')) > 0)
# send
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
IESGComment.objects.create(ballot=draft.idinternal.ballot,
ad=IESGLogin.objects.get(login_name=login_as),
text="Test!", date=date.today(),
@ -477,8 +477,8 @@ class EditPositionTestCase(django.test.TestCase):
r = self.client.post(url, dict(cc="test@example.com", cc_state_change="1"))
self.assertEquals(r.status_code, 302)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("COMMENT" in mail_outbox[-1]['Subject'])
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("COMMENT" in outbox[-1]['Subject'])
class DeferBallotTestCase(django.test.TestCase):
@ -495,7 +495,7 @@ class DeferBallotTestCase(django.test.TestCase):
# defer
self.assertTrue(not draft.idinternal.ballot.defer)
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
r = self.client.post(url, dict())
self.assertEquals(r.status_code, 302)
@ -504,9 +504,9 @@ class DeferBallotTestCase(django.test.TestCase):
self.assertTrue(draft.idinternal.ballot.defer)
self.assertTrue(draft.idinternal.cur_state_id == IDState.IESG_EVALUATION_DEFER)
self.assertEquals(len(mail_outbox), mailbox_before + 2)
self.assertTrue("Deferred" in mail_outbox[-2]['Subject'])
self.assertTrue(draft.file_tag() in mail_outbox[-2]['Subject'])
self.assertEquals(len(outbox), mailbox_before + 2)
self.assertTrue("Deferred" in outbox[-2]['Subject'])
self.assertTrue(draft.file_tag() in outbox[-2]['Subject'])
def test_undefer_ballot(self):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
@ -575,7 +575,7 @@ class BallotWriteupsTestCase(django.test.TestCase):
url = urlreverse('doc_ballot_lastcall', kwargs=dict(name=draft.filename))
login_testing_unauthorized(self, "klm", url)
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
r = self.client.post(url, dict(
last_call_text=draft.idinternal.ballot.last_call_text,
@ -583,9 +583,9 @@ class BallotWriteupsTestCase(django.test.TestCase):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
self.assertEquals(draft.idinternal.cur_state_id, IDState.LAST_CALL_REQUESTED)
self.assertEquals(len(mail_outbox), mailbox_before + 3)
self.assertEquals(len(outbox), mailbox_before + 3)
self.assertTrue("Last Call" in mail_outbox[-1]['Subject'])
self.assertTrue("Last Call" in outbox[-1]['Subject'])
def test_edit_ballot_writeup(self):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
@ -628,7 +628,7 @@ class BallotWriteupsTestCase(django.test.TestCase):
IESGDiscuss.objects.create(ad=active[3], active=True, date=datetime.date.today(), text="test " * 20, ballot=draft.idinternal.ballot)
IESGComment.objects.create(ad=active[3], active=True, date=datetime.date.today(), text="test " * 20, ballot=draft.idinternal.ballot)
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
r = self.client.post(url, dict(
ballot_writeup=draft.idinternal.ballot.ballot_writeup,
@ -638,8 +638,8 @@ class BallotWriteupsTestCase(django.test.TestCase):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
self.assertTrue(draft.idinternal.ballot.ballot_issued)
self.assertEquals(len(mail_outbox), mailbox_before + 2)
self.assertTrue("Evaluation:" in mail_outbox[-2]['Subject'])
self.assertEquals(len(outbox), mailbox_before + 2)
self.assertTrue("Evaluation:" in outbox[-2]['Subject'])
def test_edit_approval_text(self):
@ -687,7 +687,7 @@ class ApproveBallotTestCase(django.test.TestCase):
self.assertEquals(len(q('pre')), 1)
# approve
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
r = self.client.post(url, dict())
self.assertEquals(r.status_code, 302)
@ -695,11 +695,11 @@ class ApproveBallotTestCase(django.test.TestCase):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
self.assertEquals(draft.idinternal.cur_state_id, IDState.APPROVED_ANNOUNCEMENT_SENT)
self.assertEquals(len(mail_outbox), mailbox_before + 4)
self.assertEquals(len(outbox), mailbox_before + 4)
self.assertTrue("Protocol Action" in mail_outbox[-2]['Subject'])
self.assertTrue("Protocol Action" in outbox[-2]['Subject'])
# the IANA copy
self.assertTrue("Protocol Action" in mail_outbox[-1]['Subject'])
self.assertTrue("Protocol Action" in outbox[-1]['Subject'])
class MakeLastCallTestCase(django.test.TestCase):
fixtures = ['base', 'draft', 'ballot']
@ -721,7 +721,7 @@ class MakeLastCallTestCase(django.test.TestCase):
self.assertEquals(len(q('input[name=last_call_sent_date]')), 1)
# make last call
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
expire_date = q('input[name=last_call_expiration_date]')[0].get("value")
@ -734,11 +734,11 @@ class MakeLastCallTestCase(django.test.TestCase):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
self.assertEquals(draft.idinternal.cur_state_id, IDState.IN_LAST_CALL)
self.assertEquals(draft.lc_expiration_date.strftime("%Y-%m-%d"), expire_date)
self.assertEquals(len(mail_outbox), mailbox_before + 4)
self.assertEquals(len(outbox), mailbox_before + 4)
self.assertTrue("Last Call" in mail_outbox[-4]['Subject'])
self.assertTrue("Last Call" in outbox[-4]['Subject'])
# the IANA copy
self.assertTrue("Last Call" in mail_outbox[-3]['Subject'])
self.assertTrue("Last Call" in outbox[-3]['Subject'])
class ExpireIDsTestCase(django.test.TestCase):
fixtures = ['base', 'draft']
@ -772,6 +772,34 @@ class ExpireIDsTestCase(django.test.TestCase):
self.assertTrue(in_id_expire_freeze(datetime.datetime(2010, 7, 12, 10, 0)))
self.assertTrue(in_id_expire_freeze(datetime.datetime(2010, 7, 25, 0, 0)))
self.assertTrue(not in_id_expire_freeze(datetime.datetime(2010, 7, 26, 0, 0)))
def test_warn_expirable_ids(self):
from ietf.idrfc.expire import get_soon_to_expire_ids, send_expire_warning_for_id
# hack into almost expirable state
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
draft.status = IDStatus.objects.get(status="Active")
draft.review_by_rfc_editor = 0
draft.revision_date = datetime.date.today() - datetime.timedelta(days=InternetDraft.DAYS_TO_EXPIRE - 7)
draft.idinternal.cur_state_id = IDState.AD_WATCHING
draft.idinternal.save()
draft.save()
author = PersonOrOrgInfo.objects.all()[0]
IDAuthor.objects.create(document=draft, person=author, author_order=1)
EmailAddress.objects.create(person_or_org=author, type="I-D", priority=draft.pk, address="author@example.com")
# test query
documents = list(get_soon_to_expire_ids(14))
self.assertEquals(len(documents), 1)
# test send warning
mailbox_before = len(outbox)
send_expire_warning_for_id(documents[0])
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("author@example.com" in str(outbox[-1]))
def test_expire_ids(self):
from ietf.idrfc.expire import get_expired_ids, send_expire_notice_for_id, expire_id
@ -798,14 +826,14 @@ class ExpireIDsTestCase(django.test.TestCase):
for d in documents:
# test notice
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
send_expire_notice_for_id(d)
self.assertEquals(InternetDraft.objects.get(filename=d.filename).dunn_sent_date, datetime.date.today())
if d.idinternal:
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("expired" in mail_outbox[-1]["Subject"])
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("expired" in outbox[-1]["Subject"])
# test expiry
txt = "%s-%s.txt" % (d.filename, d.revision_display())
@ -930,7 +958,7 @@ class ExpireLastCallTestCase(django.test.TestCase):
drafts = get_expired_last_calls()
self.assertEquals(len(drafts), 1)
mailbox_before = len(mail_outbox)
mailbox_before = len(outbox)
comments_before = draft.idinternal.comments().count()
expire_last_call(drafts[0])
@ -938,8 +966,8 @@ class ExpireLastCallTestCase(django.test.TestCase):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
self.assertEquals(draft.idinternal.cur_state.document_state_id, IDState.WAITING_FOR_WRITEUP)
self.assertEquals(draft.idinternal.comments().count(), comments_before + 1)
self.assertEquals(len(mail_outbox), mailbox_before + 1)
self.assertTrue("Last Call Expired" in mail_outbox[-1]["Subject"])
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("Last Call Expired" in outbox[-1]["Subject"])
@ -1275,3 +1303,5 @@ class MirrorScriptTestCases(unittest.TestCase,RealDatabaseTest):
self.assertEquals(len(refs), 3)
print "OK"
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from testsREDESIGN import *

1394
ietf/idrfc/testsREDESIGN.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,88 @@
200 /
200 /doc/
200,heavy /doc/all/
200,heavy /doc/active/
# draft that's now RFC
200 /doc/draft-ietf-avt-rtp-atrac-family/
200 /doc/draft-ietf-avt-rtp-atrac-family/doc.json
200 /doc/draft-ietf-avt-rtp-atrac-family/ballot.json
200 /doc/draft-ietf-avt-rtp-atrac-family/_ballot.data
# replaced draft, never went to IESG
200 /doc/draft-eronen-mobike-mopo/
404 /doc/draft-eronen-mobike-mopo/ballot.json
404 /doc/draft-eronen-mobike-mopo/_ballot.data
# expired draft
200 /doc/draft-eronen-eap-sim-aka-80211/
# Normal RFC
200 /doc/rfc4739/
200 /doc/rfc4739/doc.json
200 /doc/rfc4739/ballot.json # has ballot from I-D
200 /doc/rfc4739/_ballot.data
# RFC that's evaluated in IESG
200 /doc/rfc3852/
200 /doc/rfc3852/doc.json
200 /doc/rfc3852/ballot.json
200 /doc/rfc3852/_ballot.data
# old RFC
200 /doc/rfc822/
200 /doc/rfc822/doc.json
# ballot sets
200 /doc/rfc3550/ballot.json
200 /doc/rfc3550/_ballot.data
200 /doc/rfc3551/ballot.json
200 /doc/rfc3551/_ballot.data
200 /doc/draft-irtf-dtnrg-ltp/ballot.json
200 /doc/draft-irtf-dtnrg-ltp/_ballot.data
# file formats
200 /doc/rfc9/ # PDF only
200 /doc/rfc2490/ # TXT+PDF+PS
200 /doc/rfc500/ # not online
404 /doc/draft-no-such-draft/
404 /doc/rfc4637/
200 /doc/rfc2444/doc.json # foreignkey problem with Django 1.x
# current AD -- needs to be updated at some point
200 /doc/ad/robert.sparks/
# former AD
200 /doc/ad/sam.hartman/
404 /doc/ad/no.body/
# ballot exists, but it's not issued
404 /doc/draft-ietf-aaa-diameter-api/ballot.json
404 /doc/draft-ietf-aaa-diameter-api/_ballot.data
# ballot does not exist
404 /doc/draft-zeilenga-cldap/ballot.json
404 /doc/draft-zeilenga-cldap/_ballot.data
# comment with created_by=999
200 /doc/draft-ietf-l3vpn-2547bis-mcast-bgp/
# comment with created_by=0 (and no idinternal entry)
200 /doc/draft-ietf-proto-wgdocument-states/
200 /doc/search/
200 /doc/search/?rfcs=on&name=snmp
200 /doc/search/?rfcs=on&name=nfs&by=ad&ad=104942
200 /doc/search/?activeDrafts=on&name=sipping
200 /doc/search/?oldDrafts=on&name=tls
200 /doc/search/?activeDrafts=on&oldDrafts=on&ad=104942&by=ad
200 /doc/search/?activeDrafts=on&state=iesg-eva&by=state
200 /doc/search/?activeDrafts=on&oldDrafts=on&subState=need-rev&by=state
200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&ad=104942&name=nfs&by=ad
200 /doc/search/?rfcs=on&group=tls&by=group
200 /doc/search/?activeDrafts=on&group=tls&by=group
200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&author=eronen&by=author
200 /doc/search/?activeDrafts=on&oldDrafts=on&rfcs=on&area=934&name=ldap&by=area
200 /doc/search/?activeDrafts=on&name=asdfsadfsdfasdf
200 /doc/search/?activeDrafts=on&name=%EF%BD%8C #non-ASCII
# Test case for missing publication date
200 /doc/search/?oldDrafts=on&name=ppvpn

View file

@ -1,3 +1,5 @@
from django.conf import settings
from ietf.idtracker.models import InternetDraft, DocumentComment, BallotInfo, IESGLogin
from ietf.idrfc.mails import *
@ -60,6 +62,33 @@ def log_state_changed(request, doc, by, email_watch_list=True, note=''):
return change
def log_state_changedREDESIGN(request, doc, by, prev_iesg_state, note=''):
from doc.models import DocEvent
state = doc.get_state("draft-iesg")
e = DocEvent(doc=doc, by=by)
e.type = "changed_document"
e.desc = u"State changed to <b>%s</b> from %s" % (
state.name,
prev_iesg_state.name if prev_iesg_state else "I-D Exists")
if note:
e.desc += "<br>%s" % note
if state.slug == "lc":
writeup = doc.latest_event(WriteupDocEvent, type="changed_last_call_text")
if writeup and writeup.text:
e.desc += "<br><br><b>The following Last Call Announcement was sent out:</b><br><br>"
e.desc += writeup.text.replace("\n", "<br><br>")
e.save()
return e
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
log_state_changed = log_state_changedREDESIGN
def update_telechat(request, idinternal, new_telechat_date, new_returning_item=None):
on_agenda = bool(new_telechat_date)
@ -95,3 +124,58 @@ def update_telechat(request, idinternal, new_telechat_date, new_returning_item=N
(new_telechat_date,
idinternal.telechat_date))
idinternal.telechat_date = new_telechat_date
def update_telechatREDESIGN(request, doc, by, new_telechat_date, new_returning_item=None):
from doc.models import TelechatDocEvent
on_agenda = bool(new_telechat_date)
prev = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
prev_returning = bool(prev and prev.returning_item)
prev_telechat = prev.telechat_date if prev else None
prev_agenda = bool(prev_telechat)
returning_item_changed = bool(new_returning_item != None and new_returning_item != prev_returning)
if new_returning_item == None:
returning = prev_returning
else:
returning = new_returning_item
if returning == prev_returning and new_telechat_date == prev_telechat:
# fully updated, nothing to do
return
# auto-update returning item
if (not returning_item_changed and on_agenda and prev_agenda
and new_telechat_date != prev_telechat):
returning = True
e = TelechatDocEvent()
e.type = "scheduled_for_telechat"
e.by = by
e.doc = doc
e.returning_item = returning
e.telechat_date = new_telechat_date
if on_agenda != prev_agenda:
if on_agenda:
e.desc = "Placed on agenda for telechat - %s by %s" % (
new_telechat_date, by.name)
else:
e.desc = "Removed from agenda for telechat by %s" % by.name
elif on_agenda and new_telechat_date != prev_telechat:
e.desc = "Telechat date has been changed to <b>%s</b> from <b>%s</b> by %s" % (
new_telechat_date, prev_telechat, by.name)
else:
# we didn't reschedule but flipped returning item bit - let's
# just explain that
if returning:
e.desc = "Added as returning item on telechat by %s" % by.name
else:
e.desc = "Removed as returning item on telechat by %s" % by.name
e.save()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
update_telechat = update_telechatREDESIGN

View file

@ -10,11 +10,12 @@ from django.template.loader import render_to_string
from django.template import RequestContext
from django import forms
from django.utils.html import strip_tags
from django.conf import settings
from ietf import settings
from ietf.utils.mail import send_mail_text, send_mail_preformatted
from ietf.ietfauth.decorators import group_required
from ietf.idtracker.templatetags.ietf_filters import in_group
from ietf.ietfauth.decorators import has_role
from ietf.idtracker.models import *
from ietf.iesg.models import *
from ietf.ipr.models import IprDetail
@ -24,6 +25,9 @@ from ietf.idrfc.utils import *
from ietf.idrfc.lastcall import request_last_call
from ietf.idrfc.idrfc_wrapper import BallotWrapper
from redesign.doc.models import *
from redesign.name.models import BallotPositionName
BALLOT_CHOICES = (("yes", "Yes"),
("noobj", "No Objection"),
@ -88,14 +92,13 @@ def edit_position(request, name):
if not ad_username:
raise Http404()
ad = get_object_or_404(IESGLogin, login_name=ad_username)
pos, discuss, comment = get_ballot_info(doc.idinternal.ballot, ad)
if request.method == 'POST':
form = EditPositionForm(request.POST)
if form.is_valid():
# save the vote
# save the vote
clean = form.cleaned_data
if clean['return_to_url']:
@ -206,6 +209,152 @@ def edit_position(request, name):
),
context_instance=RequestContext(request))
class EditPositionFormREDESIGN(forms.Form):
position = forms.ModelChoiceField(queryset=BallotPositionName.objects.all(), widget=forms.RadioSelect, initial="norecord", required=True)
discuss = forms.CharField(required=False, widget=forms.Textarea)
comment = forms.CharField(required=False, widget=forms.Textarea)
return_to_url = forms.CharField(required=False, widget=forms.HiddenInput)
def clean_discuss(self):
entered_discuss = self.cleaned_data["discuss"]
entered_pos = self.cleaned_data["position"]
if entered_pos.slug == "discuss" and not entered_discuss:
raise forms.ValidationError("You must enter a non-empty discuss")
return entered_discuss
@group_required('Area_Director','Secretariat')
def edit_positionREDESIGN(request, name):
"""Vote and edit discuss and comment on Internet Draft as Area Director."""
doc = get_object_or_404(Document, docalias__name=name)
started_process = doc.latest_event(type="started_iesg_process")
if not doc.get_state("draft-iesg") or not started_process:
raise Http404()
ad = login = request.user.get_profile()
if 'HTTP_REFERER' in request.META:
return_to_url = request.META['HTTP_REFERER']
else:
return_to_url = doc.get_absolute_url()
# if we're in the Secretariat, we can select an AD to act as stand-in for
if not has_role(request.user, "Area Director"):
ad_id = request.GET.get('ad')
if not ad_id:
raise Http404()
from person.models import Person
ad = get_object_or_404(Person, pk=ad_id)
old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time)
if request.method == 'POST':
form = EditPositionForm(request.POST)
if form.is_valid():
# save the vote
clean = form.cleaned_data
if clean['return_to_url']:
return_to_url = clean['return_to_url']
pos = BallotPositionDocEvent(doc=doc, by=login)
pos.type = "changed_ballot_position"
pos.ad = ad
pos.pos = clean["position"]
pos.comment = clean["comment"].strip()
pos.comment_time = old_pos.comment_time if old_pos else None
pos.discuss = clean["discuss"].strip()
pos.discuss_time = old_pos.discuss_time if old_pos else None
changes = []
added_events = []
# possibly add discuss/comment comments to history trail
# so it's easy to see
old_comment = old_pos.comment if old_pos else ""
if pos.comment != old_comment:
pos.comment_time = pos.time
changes.append("comment")
if pos.comment:
e = DocEvent(doc=doc)
e.by = ad # otherwise we can't see who's saying it
e.type = "added_comment"
e.desc = "[Ballot comment]\n" + pos.comment
added_events.append(e)
old_discuss = old_pos.discuss if old_pos else ""
if pos.discuss != old_discuss:
pos.discuss_time = pos.time
changes.append("discuss")
if pos.discuss:
e = DocEvent(doc=doc, by=login)
e.by = ad # otherwise we can't see who's saying it
e.type = "added_comment"
e.desc = "[Ballot discuss]\n" + pos.discuss
added_events.append(e)
# figure out a description
if not old_pos and pos.pos.slug != "norecord":
pos.desc = u"[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.name)
elif old_pos and pos.pos != old_pos.pos:
pos.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.ad.name, pos.pos.name, old_pos.pos.name)
if not pos.desc and changes:
pos.desc = u"Ballot %s text updated for %s" % (u" and ".join(changes), ad.name)
# only add new event if we actually got a change
if pos.desc:
if login != ad:
pos.desc += u" by %s" % login.name
pos.save()
for e in added_events:
e.save() # save them after the position is saved to get later id
if request.POST.get("send_mail"):
qstr = "?return_to_url=%s" % return_to_url
if request.GET.get('ad'):
qstr += "&ad=%s" % request.GET.get('ad')
return HttpResponseRedirect(urlreverse("doc_send_ballot_comment", kwargs=dict(name=doc.name)) + qstr)
elif request.POST.get("Defer"):
return HttpResponseRedirect(urlreverse("doc_defer_ballot", kwargs=dict(name=doc)))
elif request.POST.get("Undefer"):
return HttpResponseRedirect(urlreverse("doc_undefer_ballot", kwargs=dict(name=doc)))
else:
return HttpResponseRedirect(return_to_url)
else:
initial = {}
if old_pos:
initial['position'] = old_pos.pos.slug
initial['discuss'] = old_pos.discuss
initial['comment'] = old_pos.comment
if return_to_url:
initial['return_to_url'] = return_to_url
form = EditPositionForm(initial=initial)
ballot_deferred = None
if doc.get_state_slug("draft-iesg") == "defer":
ballot_deferred = doc.latest_event(type="changed_document", desc__startswith="State changed to <b>IESG Evaluation - Defer</b>")
return render_to_response('idrfc/edit_positionREDESIGN.html',
dict(doc=doc,
form=form,
ad=ad,
return_to_url=return_to_url,
old_pos=old_pos,
ballot_deferred=ballot_deferred,
),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
edit_position = edit_positionREDESIGN
EditPositionForm = EditPositionFormREDESIGN
@group_required('Area_Director','Secretariat')
def send_ballot_comment(request, name):
"""Email Internet Draft ballot discuss/comment for area director."""
@ -278,6 +427,83 @@ def send_ballot_comment(request, name):
),
context_instance=RequestContext(request))
@group_required('Area_Director','Secretariat')
def send_ballot_commentREDESIGN(request, name):
"""Email Internet Draft ballot discuss/comment for area director."""
doc = get_object_or_404(Document, docalias__name=name)
started_process = doc.latest_event(type="started_iesg_process")
if not started_process:
raise Http404()
ad = login = request.user.get_profile()
return_to_url = request.GET.get('return_to_url')
if not return_to_url:
return_to_url = doc.get_absolute_url()
if 'HTTP_REFERER' in request.META:
back_url = request.META['HTTP_REFERER']
else:
back_url = doc.get_absolute_url()
# if we're in the Secretariat, we can select an AD to act as stand-in for
if not has_role(request.user, "Area Director"):
ad_id = request.GET.get('ad')
if not ad_id:
raise Http404()
from person.models import Person
ad = get_object_or_404(Person, pk=ad_id)
pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time)
if not pos:
raise Http404()
subj = []
d = ""
if pos.pos == "discuss" and pos.discuss:
d = pos.discuss
subj.append("DISCUSS")
c = ""
if pos.comment:
c = pos.comment
subj.append("COMMENT")
ad_name_genitive = ad.name + "'" if ad.name.endswith('s') else ad.name + "'s"
subject = "%s %s on %s" % (ad_name_genitive, pos.pos.name if pos.pos else "No Position", doc.name + "-" + doc.rev)
if subj:
subject += ": (with %s)" % " and ".join(subj)
doc.filename = doc.name # compatibility attributes
doc.revision_display = doc.rev
body = render_to_string("idrfc/ballot_comment_mail.txt",
dict(discuss=d, comment=c, ad=ad.name, doc=doc, pos=pos.pos))
frm = ad.formatted_email()
to = "The IESG <iesg@ietf.org>"
if request.method == 'POST':
cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()]
if request.POST.get("cc_state_change") and doc.notify:
cc.extend(doc.notify.split(','))
send_mail_text(request, to, frm, subject, body, cc=", ".join(cc))
return HttpResponseRedirect(return_to_url)
return render_to_response('idrfc/send_ballot_commentREDESIGN.html',
dict(doc=doc,
subject=subject,
body=body,
frm=frm,
to=to,
ad=ad,
can_send=d or c,
back_url=back_url,
),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
send_ballot_comment = send_ballot_commentREDESIGN
@group_required('Area_Director','Secretariat')
def defer_ballot(request, name):
@ -309,9 +535,46 @@ def defer_ballot(request, name):
return render_to_response('idrfc/defer_ballot.html',
dict(doc=doc,
telechat_date=telechat_date),
telechat_date=telechat_date,
back_url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
@group_required('Area_Director','Secretariat')
def defer_ballotREDESIGN(request, name):
"""Signal post-pone of Internet Draft ballot, notifying relevant parties."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
telechat_date = TelechatDates.objects.all()[0].date2
if request.method == 'POST':
save_document_in_history(doc)
prev = doc.get_state("draft-iesg")
doc.set_state(State.objects.get(type="draft-iesg", slug='defer'))
e = log_state_changed(request, doc, login, prev)
doc.time = e.time
doc.save()
email_state_changed(request, doc, e.desc)
update_telechat(request, doc, login, telechat_date)
email_ballot_deferred(request, doc, login.name, telechat_date)
return HttpResponseRedirect(doc.get_absolute_url())
return render_to_response('idrfc/defer_ballot.html',
dict(doc=doc,
telechat_date=telechat_date,
back_url=doc.get_absolute_url()),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
defer_ballot = defer_ballotREDESIGN
@group_required('Area_Director','Secretariat')
def undefer_ballot(request, name):
"""Delete deferral of Internet Draft ballot."""
@ -336,9 +599,45 @@ def undefer_ballot(request, name):
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
return render_to_response('idrfc/undefer_ballot.html',
dict(doc=doc,telechat_date=telechat_date),
dict(doc=doc,
telechat_date=telechat_date,
back_url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
@group_required('Area_Director','Secretariat')
def undefer_ballotREDESIGN(request, name):
"""Delete deferral of Internet Draft ballot."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
telechat_date = TelechatDates.objects.all()[0].date1
if request.method == 'POST':
save_document_in_history(doc)
prev = doc.get_state("draft-iesg")
doc.set_state(State.objects.get(type="draft-iesg", slug='iesg-eva'))
e = log_state_changed(request, doc, login, prev)
doc.time = e.time
doc.save()
email_state_changed(request, doc, e.desc)
return HttpResponseRedirect(doc.get_absolute_url())
return render_to_response('idrfc/undefer_ballot.html',
dict(doc=doc,
telechat_date=telechat_date,
back_url=doc.get_absolute_url()),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
undefer_ballot = undefer_ballotREDESIGN
class LastCallTextForm(forms.ModelForm):
def clean_last_call_text(self):
lines = self.cleaned_data["last_call_text"].split("\r\n")
@ -424,6 +723,7 @@ def lastcalltext(request, name):
return render_to_response('idrfc/ballot_lastcalltext.html',
dict(doc=doc,
back_url=doc.idinternal.get_absolute_url(),
ballot=ballot,
last_call_form=last_call_form,
can_request_last_call=can_request_last_call,
@ -432,6 +732,96 @@ def lastcalltext(request, name):
),
context_instance=RequestContext(request))
class LastCallTextFormREDESIGN(forms.Form):
last_call_text = forms.CharField(widget=forms.Textarea, required=True)
def clean_last_call_text(self):
lines = self.cleaned_data["last_call_text"].split("\r\n")
for l, next in zip(lines, lines[1:]):
if l.startswith('Subject:') and next.strip():
raise forms.ValidationError("Subject line appears to have a line break, please make sure there is no line breaks in the subject line and that it is followed by an empty line.")
return self.cleaned_data["last_call_text"].replace("\r", "")
@group_required('Area_Director','Secretariat')
def lastcalltextREDESIGN(request, name):
"""Editing of the last call text"""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
existing = doc.latest_event(WriteupDocEvent, type="changed_last_call_text")
if not existing:
existing = generate_last_call_announcement(request, doc)
form = LastCallTextForm(initial=dict(last_call_text=existing.text))
if request.method == 'POST':
if "save_last_call_text" in request.POST or "send_last_call_request" in request.POST:
form = LastCallTextForm(request.POST)
if form.is_valid():
t = form.cleaned_data['last_call_text']
if t != existing.text:
e = WriteupDocEvent(doc=doc, by=login)
e.by = login
e.type = "changed_last_call_text"
e.desc = "Last call announcement was changed"
e.text = t
e.save()
if "send_last_call_request" in request.POST:
save_document_in_history(doc)
prev = doc.get_state("draft-iesg")
doc.set_state(State.objects.get(type="draft-iesg", slug='lc-req'))
e = log_state_changed(request, doc, login, prev)
doc.time = e.time
doc.save()
email_state_changed(request, doc, e.desc)
email_owner(request, doc, doc.ad, login, e.desc)
request_last_call(request, doc)
return render_to_response('idrfc/last_call_requested.html',
dict(doc=doc),
context_instance=RequestContext(request))
if "regenerate_last_call_text" in request.POST:
e = generate_last_call_announcement(request, doc)
# make sure form has the updated text
form = LastCallTextForm(initial=dict(last_call_text=e.text))
s = doc.get_state("draft-iesg")
can_request_last_call = s.order < 27
can_make_last_call = s.order < 20
can_announce = s.order > 19
need_intended_status = ""
if not doc.intended_std_level:
need_intended_status = doc.file_tag()
return render_to_response('idrfc/ballot_lastcalltext.html',
dict(doc=doc,
back_url=doc.get_absolute_url(),
last_call_form=form,
can_request_last_call=can_request_last_call,
can_make_last_call=can_make_last_call,
need_intended_status=need_intended_status,
),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
LastCallTextForm = LastCallTextFormREDESIGN
lastcalltext = lastcalltextREDESIGN
@group_required('Area_Director','Secretariat')
def ballot_writeupnotes(request, name):
"""Editing of ballot write-up and notes"""
@ -489,7 +879,8 @@ def ballot_writeupnotes(request, name):
doc.idinternal.save()
return render_to_response('idrfc/ballot_issued.html',
dict(doc=doc),
dict(doc=doc,
back_url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
@ -507,6 +898,88 @@ def ballot_writeupnotes(request, name):
),
context_instance=RequestContext(request))
class BallotWriteupFormREDESIGN(forms.Form):
ballot_writeup = forms.CharField(widget=forms.Textarea, required=True)
def clean_ballot_writeup(self):
return self.cleaned_data["ballot_writeup"].replace("\r", "")
@group_required('Area_Director','Secretariat')
def ballot_writeupnotesREDESIGN(request, name):
"""Editing of ballot write-up and notes"""
doc = get_object_or_404(Document, docalias__name=name)
started_process = doc.latest_event(type="started_iesg_process")
if not started_process:
raise Http404()
login = request.user.get_profile()
approval = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text")
existing = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
if not existing:
existing = generate_ballot_writeup(request, doc)
form = BallotWriteupForm(initial=dict(ballot_writeup=existing.text))
if request.method == 'POST' and "save_ballot_writeup" in request.POST or "issue_ballot" in request.POST:
form = BallotWriteupForm(request.POST)
if form.is_valid():
t = form.cleaned_data["ballot_writeup"]
if t != existing.text:
e = WriteupDocEvent(doc=doc, by=login)
e.by = login
e.type = "changed_ballot_writeup_text"
e.desc = "Ballot writeup was changed"
e.text = t
e.save()
if "issue_ballot" in request.POST and approval:
if has_role(request.user, "Area Director") and not doc.latest_event(BallotPositionDocEvent, ad=login, time__gte=started_process.time):
# sending the ballot counts as a yes
pos = BallotPositionDocEvent(doc=doc, by=login)
pos.type = "changed_ballot_position"
pos.ad = login
pos.pos_id = "yes"
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.name)
pos.save()
msg = generate_issue_ballot_mail(request, doc)
send_mail_preformatted(request, msg)
email_iana(request, doc, 'drafts-eval@icann.org', msg)
e = DocEvent(doc=doc, by=login)
e.by = login
e.type = "sent_ballot_announcement"
e.desc = "Ballot has been issued by %s" % login.name
e.save()
return render_to_response('idrfc/ballot_issued.html',
dict(doc=doc,
back_url=doc.get_absolute_url()),
context_instance=RequestContext(request))
need_intended_status = ""
if not doc.intended_std_level:
need_intended_status = doc.file_tag()
return render_to_response('idrfc/ballot_writeupnotesREDESIGN.html',
dict(doc=doc,
back_url=doc.get_absolute_url(),
ballot_issued=bool(doc.latest_event(type="sent_ballot_announcement")),
ballot_writeup_form=form,
need_intended_status=need_intended_status,
approval=approval,
),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
BallotWriteupForm = BallotWriteupFormREDESIGN
ballot_writeupnotes = ballot_writeupnotesREDESIGN
@group_required('Area_Director','Secretariat')
def ballot_approvaltext(request, name):
"""Editing of approval text"""
@ -549,6 +1022,7 @@ def ballot_approvaltext(request, name):
return render_to_response('idrfc/ballot_approvaltext.html',
dict(doc=doc,
back_url=doc.idinternal.get_absolute_url(),
ballot=ballot,
approval_text_form=approval_text_form,
can_announce=can_announce,
@ -556,6 +1030,63 @@ def ballot_approvaltext(request, name):
),
context_instance=RequestContext(request))
class ApprovalTextFormREDESIGN(forms.Form):
approval_text = forms.CharField(widget=forms.Textarea, required=True)
def clean_approval_text(self):
return self.cleaned_data["approval_text"].replace("\r", "")
@group_required('Area_Director','Secretariat')
def ballot_approvaltextREDESIGN(request, name):
"""Editing of approval text"""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
existing = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text")
if not existing:
existing = generate_approval_mail(request, doc)
form = ApprovalTextForm(initial=dict(approval_text=existing.text))
if request.method == 'POST':
if "save_approval_text" in request.POST:
form = ApprovalTextForm(request.POST)
if form.is_valid():
t = form.cleaned_data['approval_text']
if t != existing.text:
e = WriteupDocEvent(doc=doc, by=login)
e.by = login
e.type = "changed_ballot_approval_text"
e.desc = "Ballot approval text was changed"
e.text = t
e.save()
if "regenerate_approval_text" in request.POST:
e = generate_approval_mail(request, doc)
# make sure form has the updated text
form = ApprovalTextForm(initial=dict(approval_text=existing.text))
can_announce = doc.get_state("draft-iesg").order > 19
need_intended_status = ""
if not doc.intended_std_level:
need_intended_status = doc.file_tag()
return render_to_response('idrfc/ballot_approvaltext.html',
dict(doc=doc,
back_url=doc.get_absolute_url(),
approval_text_form=form,
can_announce=can_announce,
need_intended_status=need_intended_status,
),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
ApprovalTextForm = ApprovalTextFormREDESIGN
ballot_approvaltext = ballot_approvaltextREDESIGN
@group_required('Secretariat')
def approve_ballot(request, name):
@ -628,6 +1159,83 @@ def approve_ballot(request, name):
announcement=announcement),
context_instance=RequestContext(request))
@group_required('Secretariat')
def approve_ballotREDESIGN(request, name):
"""Approve ballot, sending out announcement, changing state."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
e = doc.latest_event(WriteupDocEvent, type="changed_ballot_approval_text")
if not e:
e = generate_approval_mail(request, doc)
approval_text = e.text
e = doc.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
if not e:
e = generate_ballot_writeup(request, doc)
ballot_writeup = e.text
if "NOT be published" in approval_text:
action = "do_not_publish"
elif "To: RFC Editor" in approval_text:
action = "to_rfc_editor"
else:
action = "to_announcement_list"
announcement = approval_text + "\n\n" + ballot_writeup
if request.method == 'POST':
if action == "do_not_publish":
new_state = State.objects.get(type="draft-iesg", slug="dead")
else:
new_state = State.objects.get(type="draft-iesg", slug="ann")
# fixup document
save_document_in_history(doc)
prev = doc.get_state("draft-iesg")
doc.set_state(new_state)
e = DocEvent(doc=doc, by=login)
if action == "do_not_publish":
e.type = "iesg_disapproved"
e.desc = "Do Not Publish note has been sent to RFC Editor"
else:
e.type = "iesg_approved"
e.desc = "IESG has approved the document"
e.save()
change_description = e.desc + " and state has been changed to %s" % doc.get_state("draft-iesg").name
e = log_state_changed(request, doc, login, prev)
doc.time = e.time
doc.save()
email_state_changed(request, doc, change_description)
email_owner(request, doc, doc.ad, login, change_description)
# send announcement
send_mail_preformatted(request, announcement)
if action == "to_announcement_list":
email_iana(request, doc, "drafts-approval@icann.org", announcement)
return HttpResponseRedirect(doc.get_absolute_url())
return render_to_response('idrfc/approve_ballot.html',
dict(doc=doc,
action=action,
announcement=announcement),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
approve_ballot = approve_ballotREDESIGN
class MakeLastCallForm(forms.Form):
last_call_sent_date = forms.DateField(required=True)
@ -684,3 +1292,65 @@ def make_last_call(request, name):
form=form),
context_instance=RequestContext(request))
@group_required('Secretariat')
def make_last_callREDESIGN(request, name):
"""Make last call for Internet Draft, sending out announcement."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
e = doc.latest_event(WriteupDocEvent, type="changed_last_call_text")
if not e:
e = generate_last_call_announcement(request, doc)
announcement = e.text
if request.method == 'POST':
form = MakeLastCallForm(request.POST)
if form.is_valid():
send_mail_preformatted(request, announcement)
email_iana(request, doc, "drafts-lastcall@icann.org", announcement)
save_document_in_history(doc)
prev = doc.get_state("draft-iesg")
doc.set_state(State.objects.get(type="draft-iesg", slug='lc'))
e = log_state_changed(request, doc, login, prev)
doc.time = e.time
doc.save()
change_description = "Last call has been made for %s and state has been changed to %s" % (doc.name, doc.get_state("draft-iesg").name)
email_state_changed(request, doc, change_description)
email_owner(request, doc, doc.ad, login, change_description)
e = LastCallDocEvent(doc=doc, by=login)
e.type = "sent_last_call"
e.desc = "Last call sent by %s" % login.name
if form.cleaned_data['last_call_sent_date'] != e.time.date():
e.time = datetime.datetime.combine(form.cleaned_data['last_call_sent_date'], e.time.time())
e.expires = form.cleaned_data['last_call_expiration_date']
e.save()
return HttpResponseRedirect(doc.get_absolute_url())
else:
initial = {}
initial["last_call_sent_date"] = date.today()
expire_days = 14
if doc.group.type_id == "individ":
expire_days = 28
initial["last_call_expiration_date"] = date.today() + timedelta(days=expire_days)
form = MakeLastCallForm(initial=initial)
return render_to_response('idrfc/make_last_callREDESIGN.html',
dict(doc=doc,
form=form),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
make_last_call = make_last_callREDESIGN

View file

@ -41,9 +41,9 @@ from django.template.defaultfilters import truncatewords_html
from django.utils import simplejson as json
from django.utils.decorators import decorator_from_middleware
from django.middleware.gzip import GZipMiddleware
from django.core.urlresolvers import reverse, NoReverseMatch
from django.core.urlresolvers import reverse as urlreverse, NoReverseMatch
from django.conf import settings
from ietf import settings
from ietf.idtracker.models import InternetDraft, IDInternal, BallotInfo, DocumentComment
from ietf.idtracker.templatetags.ietf_filters import format_textarea, fill
from ietf.idrfc import markup_txt
@ -85,6 +85,7 @@ def include_text(request):
def document_main_rfc(request, rfc_number, tab):
rfci = get_object_or_404(RfcIndex, rfc_number=rfc_number)
rfci.viewing_as_rfc = True
doc = RfcWrapper(rfci)
info = {}
@ -122,7 +123,7 @@ def document_main(request, name, tab):
return document_main_rfc(request, int(m.group(1)), tab)
id = get_object_or_404(InternetDraft, filename=name)
doc = IdWrapper(id)
info = {}
info['has_pdf'] = (".pdf" in doc.file_types())
info['is_rfc'] = False
@ -148,21 +149,59 @@ def document_main(request, name, tab):
# doc is either IdWrapper or RfcWrapper
def _get_history(doc, versions):
results = []
if doc.is_id_wrapper:
comments = DocumentComment.objects.filter(document=doc.tracker_id).exclude(rfc_flag=1)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
versions = [] # clear versions
event_holder = doc._draft if hasattr(doc, "_draft") else doc._rfcindex
for e in event_holder.docevent_set.all().select_related('by').order_by('-time', 'id'):
info = {}
if e.type == "new_revision":
filename = u"%s-%s" % (e.doc.name, e.newrevisiondocevent.rev)
e.desc = 'New version available: <a href="http://tools.ietf.org/id/%s.txt">%s</a>' % (filename, filename)
if int(e.newrevisiondocevent.rev) != 0:
e.desc += ' (<a href="http://tools.ietf.org/rfcdiff?url2=%s">diff from -%02d</a>)' % (filename, int(e.newrevisiondocevent.rev) - 1)
info["dontmolest"] = True
multiset_ballot_text = "This was part of a ballot set with: "
if e.desc.startswith(multiset_ballot_text):
names = e.desc[len(multiset_ballot_text):].split(", ")
e.desc = multiset_ballot_text + ", ".join(u'<a href="%s">%s</a>' % (urlreverse("doc_view", kwargs={'name': n }), n) for n in names)
info["dontmolest"] = True
info['text'] = e.desc
info['by'] = e.by.name
info['textSnippet'] = truncatewords_html(format_textarea(fill(info['text'], 80)), 25)
info['snipped'] = info['textSnippet'][-3:] == "..." and e.type != "new_revision"
results.append({'comment':e, 'info':info, 'date':e.time, 'is_com':True})
prev_rev = "00"
# actually, we're already sorted and this ruins the sort from
# the ids which is sometimes needed, so the function should be
# rewritten to not rely on a resort
results.sort(key=lambda x: x['date'])
for o in results:
e = o["comment"]
if e.type == "new_revision":
e.version = e.newrevisiondocevent.rev
else:
e.version = prev_rev
prev_rev = e.version
else:
comments = DocumentComment.objects.filter(document=doc.rfc_number,rfc_flag=1)
if len(comments) > 0:
# also include rfc_flag=NULL, but only if at least one
# comment with rfc_flag=1 exists (usually NULL means same as 0)
comments = DocumentComment.objects.filter(document=doc.rfc_number).exclude(rfc_flag=0)
for comment in comments.order_by('-date','-time','-id').filter(public_flag=1).select_related('created_by'):
info = {}
info['text'] = comment.comment_text
info['by'] = comment.get_fullname()
info['textSnippet'] = truncatewords_html(format_textarea(fill(info['text'], 80)), 25)
info['snipped'] = info['textSnippet'][-3:] == "..."
results.append({'comment':comment, 'info':info, 'date':comment.datetime(), 'is_com':True})
if doc.is_id_wrapper:
comments = DocumentComment.objects.filter(document=doc.tracker_id).exclude(rfc_flag=1)
else:
comments = DocumentComment.objects.filter(document=doc.rfc_number,rfc_flag=1)
if len(comments) > 0:
# also include rfc_flag=NULL, but only if at least one
# comment with rfc_flag=1 exists (usually NULL means same as 0)
comments = DocumentComment.objects.filter(document=doc.rfc_number).exclude(rfc_flag=0)
for comment in comments.order_by('-date','-time','-id').filter(public_flag=1).select_related('created_by'):
info = {}
info['text'] = comment.comment_text
info['by'] = comment.get_fullname()
info['textSnippet'] = truncatewords_html(format_textarea(fill(info['text'], 80)), 25)
info['snipped'] = info['textSnippet'][-3:] == "..."
results.append({'comment':comment, 'info':info, 'date':comment.datetime(), 'is_com':True})
if doc.is_id_wrapper and versions:
for v in versions:
if v['draft_name'] == doc.draft_name:
@ -171,11 +210,11 @@ def _get_history(doc, versions):
results.insert(0, v)
if doc.is_id_wrapper and doc.draft_status == "Expired" and doc._draft.expiration_date:
results.append({'is_text':True, 'date':doc._draft.expiration_date, 'text':'Draft expired'})
if doc.is_rfc_wrapper:
if not settings.USE_DB_REDESIGN_PROXY_CLASSES and doc.is_rfc_wrapper:
text = 'RFC Published'
if doc.draft_name:
try:
text = 'RFC Published (see <a href="%s">%s</a> for earlier history)' % (reverse('doc_view', args=[doc.draft_name]),doc.draft_name)
text = 'RFC Published (see <a href="%s">%s</a> for earlier history)' % (urlreverse('doc_view', args=[doc.draft_name]),doc.draft_name)
except NoReverseMatch:
pass
results.append({'is_text':True, 'date':doc.publication_date, 'text':text})
@ -210,6 +249,19 @@ def _get_versions(draft, include_replaced=True):
def get_ballot(name):
r = re.compile("^rfc([1-9][0-9]*)$")
m = r.match(name)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from doc.models import DocAlias
alias = get_object_or_404(DocAlias, name=name)
d = get_object_or_404(InternetDraft, name=alias.document.name)
try:
if not d.ballot.ballot_issued:
raise Http404
except BallotInfo.DoesNotExist:
raise Http404
return (BallotWrapper(d), RfcWrapper(d) if m else IdWrapper(d))
if m:
rfc_number = int(m.group(1))
rfci = get_object_or_404(RfcIndex, rfc_number=rfc_number)

View file

@ -11,11 +11,12 @@ from django.template import RequestContext
from django import forms
from django.utils.html import strip_tags
from django.db.models import Max
from django.conf import settings
from ietf import settings
from ietf.utils.mail import send_mail_text
from ietf.ietfauth.decorators import group_required
from ietf.idtracker.templatetags.ietf_filters import in_group
from ietf.ietfauth.decorators import has_role
from ietf.idtracker.models import *
from ietf.iesg.models import *
from ietf.idrfc.mails import *
@ -26,7 +27,10 @@ from ietf.ietfworkflows.models import Stream
from ietf.ietfworkflows.utils import update_stream
from ietf.ietfworkflows.streams import get_stream_from_draft
from redesign.doc.models import *
from redesign.name.models import IntendedStdLevelName, DocTagName, DocStreamName
from redesign.person.models import Person, Email
class ChangeStateForm(forms.Form):
state = forms.ModelChoiceField(IDState.objects.all(), empty_label=None, required=True)
substate = forms.ModelChoiceField(IDSubState.objects.all(), required=False)
@ -62,7 +66,8 @@ def change_state(request, name):
request_last_call(request, doc)
return render_to_response('idrfc/last_call_requested.html',
dict(doc=doc),
dict(doc=doc,
url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
return HttpResponseRedirect(internal.get_absolute_url())
@ -98,6 +103,82 @@ def change_state(request, name):
to_iesg_eval=to_iesg_eval),
context_instance=RequestContext(request))
class ChangeStateFormREDESIGN(forms.Form):
state = forms.ModelChoiceField(State.objects.filter(type="draft-iesg"), empty_label=None, required=True)
# FIXME: no tags yet
#substate = forms.ModelChoiceField(IDSubState.objects.all(), required=False)
comment = forms.CharField(widget=forms.Textarea, required=False)
@group_required('Area_Director','Secretariat')
def change_stateREDESIGN(request, name):
"""Change state of Internet Draft, notifying parties as necessary
and logging the change as a comment."""
doc = get_object_or_404(Document, docalias__name=name)
if (not doc.latest_event(type="started_iesg_process")) or doc.get_state_slug() == "expired":
raise Http404()
login = request.user.get_profile()
if request.method == 'POST':
form = ChangeStateForm(request.POST)
if form.is_valid():
state = form.cleaned_data['state']
comment = form.cleaned_data['comment']
prev = doc.get_state("draft-iesg")
if state != prev:
save_document_in_history(doc)
doc.set_state(state)
e = log_state_changed(request, doc, login, prev, comment)
doc.time = e.time
doc.save()
email_state_changed(request, doc, e.desc)
email_owner(request, doc, doc.ad, login, e.desc)
if state.slug == "lc-req":
request_last_call(request, doc)
return render_to_response('idrfc/last_call_requested.html',
dict(doc=doc,
url=doc.get_absolute_url()),
context_instance=RequestContext(request))
return HttpResponseRedirect(doc.get_absolute_url())
else:
state = doc.get_state("draft-iesg")
form = ChangeStateForm(initial=dict(state=state.pk if state else None))
state = doc.get_state("draft-iesg")
next_states = state.next_states.all() if state else None
prev_state = None
hists = doc.history_set.exclude(states=doc.get_state("draft-iesg")).order_by('-time')[:1]
if hists:
prev_state = hists[0].get_state("draft-iesg")
to_iesg_eval = None
if not self.latest_event(type="sent_ballot_announcement"):
if next_states and next_states.filter(slug="iesg-eva"):
to_iesg_eval = State.objects.get(type="draft-iesg", slug="iesg-eva")
next_states = next_states.exclude(slug="iesg-eva")
return render_to_response('idrfc/change_stateREDESIGN.html',
dict(form=form,
doc=doc,
prev_state=prev_state,
next_states=next_states,
to_iesg_eval=to_iesg_eval),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
change_state = change_stateREDESIGN
ChangeStateForm = ChangeStateFormREDESIGN
def dehtmlify_textarea_text(s):
return s.replace("<br>", "\n").replace("<b>", "").replace("</b>", "").replace(" ", " ")
@ -333,6 +414,200 @@ def edit_info(request, name):
ballot_issued=ballot_issued),
context_instance=RequestContext(request))
class EditInfoFormREDESIGN(forms.Form):
intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.all(), empty_label=None, required=True)
via_rfc_editor = forms.BooleanField(required=False, label="Via IRTF or RFC Editor")
stream = forms.ModelChoiceField(DocStreamName.objects.all(), empty_label=None, required=True)
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), label="Responsible AD", empty_label=None, required=True)
create_in_state = forms.ModelChoiceField(State.objects.filter(type="draft-iesg", slug__in=("pub-req", "watching")), empty_label=None, required=False)
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False)
note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False)
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
returning_item = forms.BooleanField(required=False)
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
# 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).name)]
# telechat choices
dates = TelechatDates.objects.all()[0].dates()
init = kwargs['initial']['telechat_date']
if init and init not in dates:
dates.insert(0, init)
choices = [("", "(not on agenda)")]
for d in dates:
choices.append((d, d.strftime("%Y-%m-%d")))
self.fields['telechat_date'].choices = choices
# returning item is rendered non-standard
self.standard_fields = [x for x in self.visible_fields() if x.name not in ('returning_item',)]
def clean_note(self):
# note is stored munged in the database
return self.cleaned_data['note'].replace('\n', '<br>').replace('\r', '').replace(' ', '&nbsp; ')
def get_initial_notify(doc):
# set change state notice to something sensible
receivers = []
if doc.group.type_id == "individ":
for a in doc.authors.all():
receivers.append(e.address)
else:
receivers.append("%s-chairs@%s" % (doc.group.acronym, settings.TOOLS_SERVER))
for editor in Email.objects.filter(role__name="editor", role__group=doc.group):
receivers.append(e.address)
receivers.append("%s@%s" % (doc.name, settings.TOOLS_SERVER))
return ", ".join(receivers)
@group_required('Area_Director','Secretariat')
def edit_infoREDESIGN(request, name):
"""Edit various Internet Draft attributes, notifying parties as
necessary and logging changes as document events."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.get_state_slug() == "expired":
raise Http404()
login = request.user.get_profile()
new_document = False
if not doc.get_state("draft-iesg"): # FIXME: should probably receive "new document" as argument to view instead of this
new_document = True
doc.notify = get_initial_notify(doc)
e = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
initial_telechat_date = e.telechat_date if e else None
initial_returning_item = bool(e and e.returning_item)
if request.method == 'POST':
form = EditInfoForm(request.POST,
initial=dict(ad=doc.ad_id,
telechat_date=initial_telechat_date))
if form.is_valid():
save_document_in_history(doc)
r = form.cleaned_data
if new_document:
doc.set_state(r['create_in_state'])
# fix so Django doesn't barf in the diff below because these
# fields can't be NULL
doc.ad = r['ad']
replaces = Document.objects.filter(docalias__relateddocument__source=doc, docalias__relateddocument__relationship="replaces")
if replaces:
# this should perhaps be somewhere else, e.g. the
# place where the replace relationship is established?
e = DocEvent()
e.type = "added_comment"
e.by = Person.objects.get(name="(System)")
e.doc = doc
e.desc = "Earlier history may be found in the Comment Log for <a href=\"%s\">%s</a>" % (replaces[0], replaces[0].get_absolute_url())
e.save()
e = DocEvent()
e.type = "started_iesg_process"
e.by = login
e.doc = doc
e.desc = "IESG process started in state <b>%s</b>" % doc.get_state("draft-iesg").name
e.save()
orig_ad = doc.ad
changes = []
def desc(attr, new, old):
entry = "%(attr)s has been changed to <b>%(new)s</b> from <b>%(old)s</b>"
if new_document:
entry = "%(attr)s has been changed to <b>%(new)s</b>"
return entry % dict(attr=attr, new=new, old=old)
def diff(attr, name):
v = getattr(doc, attr)
if r[attr] != v:
changes.append(desc(name, r[attr], v))
setattr(doc, attr, r[attr])
# update the attributes, keeping track of what we're doing
diff('intended_std_level', "Intended Status")
diff('ad', "Responsible AD")
diff('stream', "Stream")
diff('notify', "State Change Notice email list")
if r['note'] != doc.note:
if not r['note']:
if doc.note:
changes.append("Note field has been cleared")
else:
if doc.note:
changes.append("Note changed to '%s'" % r['note'])
else:
changes.append("Note added '%s'" % r['note'])
doc.note = r['note']
for c in changes:
e = DocEvent(doc=doc, by=login)
e.desc = c
e.type = "changed_document"
e.save()
update_telechat(request, doc, login,
r['telechat_date'], r['returning_item'])
if has_role(request.user, 'Secretariat'):
via_rfc = DocTagName.objects.get(slug="via-rfc")
if r['via_rfc_editor']:
doc.tags.add(via_rfc)
else:
doc.tags.remove(via_rfc)
doc.time = datetime.datetime.now()
if changes and not new_document:
email_owner(request, doc, orig_ad, login, "\n".join(changes))
doc.save()
return HttpResponseRedirect(doc.get_absolute_url())
else:
init = dict(intended_std_level=doc.intended_std_level_id,
ad=doc.ad_id,
stream=doc.stream_id,
notify=doc.notify,
note=dehtmlify_textarea_text(doc.note),
telechat_date=initial_telechat_date,
returning_item=initial_returning_item,
)
form = EditInfoForm(initial=init)
# optionally filter out some fields
if not new_document:
form.standard_fields = [x for x in form.standard_fields if x.name != "create_in_state"]
if not has_role(request.user, 'Secretariat'):
form.standard_fields = [x for x in form.standard_fields if x.name != "via_rfc_editor"]
return render_to_response('idrfc/edit_infoREDESIGN.html',
dict(doc=doc,
form=form,
user=request.user,
login=login,
ballot_issued=doc.latest_event(type="sent_ballot_announcement")),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
EditInfoForm = EditInfoFormREDESIGN
edit_info = edit_infoREDESIGN
@group_required('Area_Director','Secretariat')
def request_resurrect(request, name):
@ -354,9 +629,37 @@ def request_resurrect(request, name):
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
return render_to_response('idrfc/request_resurrect.html',
dict(doc=doc),
dict(doc=doc,
back_url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
@group_required('Area_Director','Secretariat')
def request_resurrectREDESIGN(request, name):
"""Request resurrect of expired Internet Draft."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.get_state_slug() != "expired":
raise Http404()
login = request.user.get_profile()
if request.method == 'POST':
email_resurrect_requested(request, doc, login)
e = DocEvent(doc=doc, by=login)
e.type = "requested_resurrect"
e.desc = "Resurrection was requested"
e.save()
return HttpResponseRedirect(doc.get_absolute_url())
return render_to_response('idrfc/request_resurrect.html',
dict(doc=doc,
back_url=doc.get_absolute_url()),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
request_resurrect = request_resurrectREDESIGN
@group_required('Secretariat')
def resurrect(request, name):
"""Resurrect expired Internet Draft."""
@ -381,9 +684,45 @@ def resurrect(request, name):
return HttpResponseRedirect(doc.idinternal.get_absolute_url())
return render_to_response('idrfc/resurrect.html',
dict(doc=doc),
dict(doc=doc,
back_url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
@group_required('Secretariat')
def resurrectREDESIGN(request, name):
"""Resurrect expired Internet Draft."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.get_state_slug() != "expired":
raise Http404()
login = request.user.get_profile()
if request.method == 'POST':
save_document_in_history(doc)
e = doc.latest_event(type__in=('requested_resurrect', "completed_resurrect"))
if e and e.type == 'requested_resurrect':
email_resurrection_completed(request, doc, requester=e.by)
e = DocEvent(doc=doc, by=login)
e.type = "completed_resurrect"
e.desc = "Resurrection was completed"
e.save()
doc.set_state(State.objects.get(type="draft", slug="active"))
doc.time = datetime.datetime.now()
doc.save()
return HttpResponseRedirect(doc.get_absolute_url())
return render_to_response('idrfc/resurrect.html',
dict(doc=doc,
back_url=doc.get_absolute_url()),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
resurrect = resurrectREDESIGN
class AddCommentForm(forms.Form):
comment = forms.CharField(required=True, widget=forms.Textarea)
@ -409,5 +748,40 @@ def add_comment(request, name):
return render_to_response('idrfc/add_comment.html',
dict(doc=doc,
form=form),
form=form,
back_url=doc.idinternal.get_absolute_url()),
context_instance=RequestContext(request))
@group_required('Area_Director', 'Secretariat', 'IANA')
def add_commentREDESIGN(request, name):
"""Add comment to Internet Draft."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
login = request.user.get_profile()
if request.method == 'POST':
form = AddCommentForm(request.POST)
if form.is_valid():
c = form.cleaned_data['comment']
e = DocEvent(doc=doc, by=login)
e.type = "added_comment"
e.desc = c
e.save()
email_owner(request, doc, doc.ad, login,
"A new comment added by %s" % login.name)
return HttpResponseRedirect(doc.get_absolute_url())
else:
form = AddCommentForm()
return render_to_response('idrfc/add_comment.html',
dict(doc=doc,
form=form,
back_url=doc.get_absolute_url()),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
add_comment = add_commentREDESIGN

View file

@ -30,7 +30,7 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import re
import re, datetime
from django import forms
from django.shortcuts import render_to_response
from django.db.models import Q
@ -38,9 +38,10 @@ from django.template import RequestContext
from django.views.decorators.cache import cache_page
from ietf.idtracker.models import IDState, IESGLogin, IDSubState, Area, InternetDraft, Rfc, IDInternal, IETFWG
from ietf.idrfc.models import RfcIndex
from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponsePermanentRedirect
from ietf.idrfc.idrfc_wrapper import IdWrapper,RfcWrapper,IdRfcWrapper
from ietf.utils import normalize_draftname
from django.conf import settings
def addInputEvents(widget):
widget.attrs["oninput"] = 'inputEvent()'
@ -267,7 +268,243 @@ def search_query(query_original, sort_by=None):
meta['advanced'] = True
return (results,meta)
def genParamURL(request, ignore_list):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from doc.models import *
from person.models import *
from group.models import *
class SearchForm(forms.Form):
name = forms.CharField(required=False)
addInputEvents(name.widget) # consider moving this to jQuery client-side instead
rfcs = forms.BooleanField(required=False,initial=True)
activeDrafts = forms.BooleanField(required=False,initial=True)
oldDrafts = forms.BooleanField(required=False,initial=False)
lucky = forms.BooleanField(required=False,initial=False)
by = forms.ChoiceField(choices=[(x,x) for x in ('author','group','area','ad','state')], required=False, initial='wg', label='Foobar')
author = forms.CharField(required=False)
addInputEvents(author.widget)
group = forms.CharField(required=False)
addInputEvents(group.widget)
area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active").order_by('name'), empty_label="any area", required=False)
addInputEvents(area.widget)
ad = forms.ChoiceField(choices=(), required=False)
addInputEvents(ad.widget)
state = forms.ModelChoiceField(State.objects.filter(type="draft-iesg"), empty_label="any state", required=False)
addInputEvents(state.widget)
subState = forms.ChoiceField(choices=(), required=False)
addInputEvents(subState.widget)
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
responsible = Document.objects.values_list('ad', flat=True).distinct()
active_ads = list(Person.objects.filter(role__name="ad",
role__group__type="area",
role__group__state="active").distinct())
inactive_ads = list(Person.objects.filter(pk__in=responsible)
.exclude(pk__in=[x.pk for x in active_ads]))
extract_last_name = lambda x: x.name_parts()[3]
active_ads.sort(key=extract_last_name)
inactive_ads.sort(key=extract_last_name)
self.fields['ad'].choices = c = [('', 'any AD')] + [(ad.pk, ad.name) for ad in active_ads] + [('', '------------------')] + [(ad.pk, ad.name) for ad in inactive_ads]
self.fields['subState'].choices = [('', 'any substate'), ('0', 'no substate')] + [(n.slug, n.name) for n in DocTagName.objects.filter(slug__in=('point', 'ad-f-up', 'need-rev', 'extpty'))]
def clean_name(self):
value = self.cleaned_data.get('name','')
return normalize_draftname(value)
def clean(self):
q = self.cleaned_data
# Reset query['by'] if needed
for k in ('author','group','area','ad'):
if (q['by'] == k) and (k not in q or not q[k]):
q['by'] = None
if (q['by'] == 'state') and (not 'state' in q or not 'subState' in q or not (q['state'] or q['subState'])):
q['by'] = None
# Reset other fields
for k in ('author','group','area','ad'):
if q['by'] != k:
self.data[k] = ""
q[k] = ""
if q['by'] != 'state':
self.data['state'] = ""
self.data['subState'] = ""
q['state'] = ""
q['subState'] = ""
return q
def search_query(query_original, sort_by=None):
query = dict(query_original.items())
drafts = query['activeDrafts'] or query['oldDrafts']
if (not drafts) and (not query['rfcs']):
return ([], {})
# Non-ASCII strings don't match anything; this check
# is currently needed to avoid complaints from MySQL.
# FIXME: this should be fixed with MySQL if it's still a problem?
for k in ['name','author','group']:
try:
tmp = str(query.get(k, ''))
except:
query[k] = '*NOSUCH*'
# Start by search InternetDrafts
idresults = []
rfcresults = []
MAX = 500
docs = InternetDraft.objects.all()
# name
if query["name"]:
docs = docs.filter(Q(docalias__name__icontains=query["name"]) |
Q(title__icontains=query["name"])).distinct()
# rfc/active/old check buttons
allowed_states = []
if query["rfcs"]:
allowed_states.append("rfc")
if query["activeDrafts"]:
allowed_states.append("active")
if query["oldDrafts"]:
allowed_states.extend(['repl', 'expired', 'auth-rm', 'ietf-rm'])
docs = docs.filter(states__type="draft", states__slug__in=allowed_states)
# radio choices
by = query["by"]
if by == "author":
# FIXME: this is full name, not last name as hinted in the HTML
docs = docs.filter(authors__person__name__icontains=query["author"])
elif by == "group":
docs = docs.filter(group__acronym=query["group"])
elif by == "area":
docs = docs.filter(Q(group__parent=query["area"]) |
Q(ad__role__name="ad",
ad__role__group=query["area"]))
elif by == "ad":
docs = docs.filter(ad=query["ad"])
elif by == "state":
if query["state"]:
docs = docs.filter(states=query["state"])
if query["subState"]:
docs = docs.filter(tags=query["subState"])
# evaluate and fill in values with aggregate queries to avoid
# too many individual queries
results = list(docs.select_related("states", "ad", "ad__person", "std_level", "intended_std_level", "group", "stream")[:MAX])
rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=[r.pk for r in results]).values_list("document_id", "name"))
# canonical name
for r in results:
if r.pk in rfc_aliases:
# lambda weirdness works around lambda binding in local for loop scope
r.canonical_name = (lambda x: lambda: x)(rfc_aliases[r.pk])
else:
r.canonical_name = (lambda x: lambda: x)(r.name)
result_map = dict((r.pk, r) for r in results)
# events
event_types = ("published_rfc",
"changed_ballot_position",
"started_iesg_process",
"new_revision")
for d in rfc_aliases.keys():
for e in event_types:
setattr(result_map[d], e, None)
for e in DocEvent.objects.filter(doc__in=rfc_aliases.keys(), type__in=event_types).order_by('-time'):
r = result_map[e.doc_id]
if not getattr(r, e.type):
# sets e.g. r.published_date = e for use in proxy wrapper
setattr(r, e.type, e)
# obsoleted/updated by
for d in rfc_aliases:
r = result_map[d]
r.obsoleted_by_list = []
r.updated_by_list = []
xed_by = RelatedDocument.objects.filter(target__name__in=rfc_aliases.values(), relationship__in=("obs", "updates")).select_related('target__document_id')
rel_rfc_aliases = dict(DocAlias.objects.filter(name__startswith="rfc", document__in=[rel.source_id for rel in xed_by]).values_list('document_id', 'name'))
for rel in xed_by:
r = result_map[rel.target.document_id]
if rel.relationship_id == "obs":
attr = "obsoleted_by_list"
else:
attr = "updated_by_list"
getattr(r, attr).append(int(rel_rfc_aliases[rel.source_id][3:]))
# sort
def sort_key(d):
res = []
canonical = d.canonical_name()
if canonical.startswith('rfc'):
rfc_num = int(canonical[3:])
else:
rfc_num = None
if rfc_num != None:
res.append(2)
elif d.get_state_slug() == "active":
res.append(1)
else:
res.append(3)
if sort_by == "title":
res.append(d.title)
elif sort_by == "date":
res.append(str(d.revision_date or datetime.date(1990, 1, 1)))
elif sort_by == "status":
if rfc_num != None:
res.append(rfc_num)
else:
res.append(d.get_state().order)
elif sort_by == "ipr":
res.append(d.name)
elif sort_by == "ad":
if rfc_num != None:
res.append(rfc_num)
elif d.get_state_slug() == "active":
if d.get_state("draft-iesg"):
res.append(get_state("draft-iesg").order)
else:
res.append(0)
else:
if rfc_num != None:
res.append(rfc_num)
else:
res.append(canonical)
return res
results.sort(key=sort_key)
meta = {}
if len(docs) == MAX:
meta['max'] = MAX
if query['by']:
meta['advanced'] = True
# finally wrap in old wrappers
wrapped_results = []
for r in results:
draft = None
rfc = None
if not r.name.startswith('rfc'):
draft = IdWrapper(r)
if r.name.startswith('rfc') or r.pk in rfc_aliases:
rfc = RfcWrapper(r)
wrapped_results.append(IdRfcWrapper(draft, rfc))
return (wrapped_results, meta)
def generate_query_string(request, ignore_list):
"""Recreates the parameter string from the given request, and
returns it as a string.
Any parameter names present in ignore_list shall not be put
@ -284,7 +521,7 @@ def search_results(request):
return search_main(request)
form = SearchForm(dict(request.REQUEST.items()))
if not form.is_valid():
return HttpResponse("form not valid?", mimetype="text/plain")
return HttpResponseBadRequest("form not valid?", mimetype="text/plain")
sort_by = None
if "sortBy" in request.GET:
@ -294,7 +531,7 @@ def search_results(request):
meta['searching'] = True
meta['by'] = form.cleaned_data['by']
meta['rqps'] = genParamURL(request, ['sortBy'])
meta['rqps'] = generate_query_string(request, ['sortBy'])
# With a later Django we can do this from the template (incude with tag)
# Pass the headers and their sort key names
meta['hdrs'] = [{'htitle': 'Document', 'htype':'doc'},
@ -329,12 +566,23 @@ def search_main(request):
def by_ad(request, name):
ad_id = None
ad_name = None
for i in IESGLogin.objects.filter(user_level__in=[1,2]):
iname = str(i).lower().replace(' ','.')
if name == iname:
ad_id = i.id
ad_name = str(i)
break
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
responsible = Document.objects.values_list('ad', flat=True).distinct()
for p in Person.objects.filter(Q(role__name="ad",
role__group__type="area",
role__group__state="active")
| Q(pk__in=responsible)):
if name == p.name.lower().replace(" ", "."):
ad_id = p.id
ad_name = p.name
break
else:
for i in IESGLogin.objects.filter(user_level__in=[1,2]):
iname = str(i).lower().replace(' ','.')
if name == iname:
ad_id = i.id
ad_name = str(i)
break
if not ad_id:
raise Http404
form = SearchForm({'by':'ad','ad':ad_id,
@ -347,11 +595,17 @@ def by_ad(request, name):
@cache_page(15*60) # 15 minutes
def all(request):
active = InternetDraft.objects.all().filter(status=1).order_by("filename").values('filename')
rfc1 = InternetDraft.objects.all().filter(status=3).order_by("filename").values('filename','rfc_number')
rfc_numbers1 = InternetDraft.objects.all().filter(status=3).values_list('rfc_number', flat=True)
rfc2 = RfcIndex.objects.all().exclude(rfc_number__in=rfc_numbers1).order_by('rfc_number').values('rfc_number','draft')
dead = InternetDraft.objects.all().exclude(status__in=[1,3]).order_by("filename").select_related('status__status')
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
active = (dict(filename=n) for n in InternetDraft.objects.filter(states__type="draft", states__slug="active").order_by("name").values_list('name', flat=True))
rfc1 = (dict(filename=d, rfc_number=int(n[3:])) for d, n in DocAlias.objects.filter(document__state="rfc", name__startswith="rfc").exclude(document__name__startswith="rfc").order_by("document__name").values_list('document__name','name').distinct())
rfc2 = (dict(rfc_number=r, draft=None) for r in sorted(int(n[3:]) for n in Document.objects.filter(type="draft", name__startswith="rfc").values_list('name', flat=True)))
dead = InternetDraft.objects.exclude(state__in=("active", "rfc")).select_related("state").order_by("name")
else:
active = InternetDraft.objects.all().filter(status=1).order_by("filename").values('filename')
rfc1 = InternetDraft.objects.all().filter(status=3).order_by("filename").values('filename','rfc_number')
rfc_numbers1 = InternetDraft.objects.all().filter(status=3).values_list('rfc_number', flat=True)
rfc2 = RfcIndex.objects.all().exclude(rfc_number__in=rfc_numbers1).order_by('rfc_number').values('rfc_number','draft')
dead = InternetDraft.objects.all().exclude(status__in=[1,3]).order_by("filename").select_related('status__status')
return render_to_response('idrfc/all.html', {'active':active, 'rfc1':rfc1, 'rfc2':rfc2, 'dead':dead}, context_instance=RequestContext(request))
@cache_page(15*60) # 15 minutes
@ -366,7 +620,7 @@ def in_last_call(request):
for p in InternetDraft.objects.all().filter(idinternal__primary_flag=1).filter(idinternal__cur_state__state='In Last Call'):
if (p.idinternal.rfc_flag):
lcdocs.append(IdRfcWrapper(None,RfCWrapper(p)))
lcdocs.append(IdRfcWrapper(None,RfcWrapper(p)))
else:
lcdocs.append(IdRfcWrapper(IdWrapper(p),None))

View file

@ -1,14 +1,16 @@
#coding: utf-8
from django.contrib import admin
from django.conf import settings
from ietf.idtracker.models import *
class AcronymAdmin(admin.ModelAdmin):
list_display=('acronym', 'name')
admin.site.register(Acronym, AcronymAdmin)
class AreaAdmin(admin.ModelAdmin):
list_display=('area_acronym', 'status')
admin.site.register(Area, AreaAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
class AcronymAdmin(admin.ModelAdmin):
list_display=('acronym', 'name')
admin.site.register(Acronym, AcronymAdmin)
class AreaAdmin(admin.ModelAdmin):
list_display=('area_acronym', 'status')
admin.site.register(Area, AreaAdmin)
class AreaDirectorAdmin(admin.ModelAdmin):
raw_id_fields=['person']
@ -22,9 +24,10 @@ class AreaWGURLAdmin(admin.ModelAdmin):
pass
admin.site.register(AreaWGURL, AreaWGURLAdmin)
class BallotInfoAdmin(admin.ModelAdmin):
pass
admin.site.register(BallotInfo, BallotInfoAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
class BallotInfoAdmin(admin.ModelAdmin):
pass
admin.site.register(BallotInfo, BallotInfoAdmin)
class ChairsHistoryAdmin(admin.ModelAdmin):
list_display=('person', 'chair_type', 'start_year', 'end_year')
@ -52,12 +55,13 @@ class IDIntendedStatusAdmin(admin.ModelAdmin):
pass
admin.site.register(IDIntendedStatus, IDIntendedStatusAdmin)
class IDInternalAdmin(admin.ModelAdmin):
ordering=['draft']
list_display=['pk', 'rfc_flag', 'token_email', 'note', 'tracker_link', 'draft_link']
search_fields=['draft__filename']
raw_id_fields=['draft','ballot']
admin.site.register(IDInternal, IDInternalAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
class IDInternalAdmin(admin.ModelAdmin):
ordering=['draft']
list_display=['pk', 'rfc_flag', 'token_email', 'note', 'tracker_link', 'draft_link']
search_fields=['draft__filename']
raw_id_fields=['draft','ballot']
admin.site.register(IDInternal, IDInternalAdmin)
class IDNextStateAdmin(admin.ModelAdmin):
pass
@ -87,13 +91,15 @@ class IESGLoginAdmin(admin.ModelAdmin):
ordering=['user_level', 'last_name']
list_display=('login_name', 'first_name', 'last_name', 'user_level')
raw_id_fields=['person']
admin.site.register(IESGLogin, IESGLoginAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(IESGLogin, IESGLoginAdmin)
class IETFWGAdmin(admin.ModelAdmin):
list_display=('group_acronym', 'group_type', 'status', 'area_acronym', 'start_date', 'concluded_date', 'chairs_link')
search_fields=['group_acronym__acronym', 'group_acronym__name']
list_filter=['status', 'group_type']
admin.site.register(IETFWG, IETFWGAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(IETFWG, IETFWGAdmin)
class WGChairAdmin(admin.ModelAdmin):
list_display = ('person_link', 'group_link')
@ -103,12 +109,13 @@ class IRTFAdmin(admin.ModelAdmin):
pass
admin.site.register(IRTF, IRTFAdmin)
class InternetDraftAdmin(admin.ModelAdmin):
list_display=('filename', 'revision', 'title', 'status')
search_fields=['filename', 'title']
list_filter=['status']
raw_id_fields=['replaced_by']
admin.site.register(InternetDraft, InternetDraftAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
class InternetDraftAdmin(admin.ModelAdmin):
list_display=('filename', 'revision', 'title', 'status')
search_fields=['filename', 'title']
list_filter=['status']
raw_id_fields=['replaced_by']
admin.site.register(InternetDraft, InternetDraftAdmin)
class PersonOrOrgInfoAdmin(admin.ModelAdmin):
list_display = ['person_or_org_tag', 'last_name', 'first_name', ]
@ -124,7 +131,8 @@ class RfcAdmin(admin.ModelAdmin):
fieldsets=((None, {'fields': ('rfc_number', 'title', 'group_acronym', 'area_acronym', 'status', 'comments', 'last_modified_date')}), ('Metadata', {'fields': (('online_version', 'txt_page_count'), ('fyi_number', 'std_number')), 'classes': 'collapse'}), ('Standards Track Dates', {'fields': ('rfc_published_date', ('proposed_date', 'draft_date'), ('standard_date', 'historic_date')), 'classes': 'collapse'}), ('Last Call / Ballot Info', {'fields': ('intended_status', ('lc_sent_date', 'lc_expiration_date'), ('b_sent_date', 'b_approve_date')), 'classes': 'collapse'}))
list_display=['rfc_number', 'title']
search_fields=['title']
admin.site.register(Rfc, RfcAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(Rfc, RfcAdmin)
class RfcIntendedStatusAdmin(admin.ModelAdmin):
pass

View file

@ -1,5 +1,6 @@
# Copyright The IETF Trust 2007, All Rights Reserved
from django.conf import settings
from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
from django.utils.feedgenerator import Atom1Feed
from ietf.idtracker.models import IDInternal
@ -12,6 +13,9 @@ class DocumentComments(Feed):
if len(bits) != 1:
raise IDInternal.DoesNotExist
rfc = re.match('rfc(\d+)', bits[0])
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return IDInternal.objects.get(docalias__name=bits[0])
if rfc:
return IDInternal.objects.get(draft=int(rfc.group(1)), rfc_flag=1)
else:
@ -21,6 +25,9 @@ class DocumentComments(Feed):
# filename is a function for RFCs and an attribute for I-Ds.
# This works transparently for templates but is not transparent
# for python.
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return "I-D Tracker comments for %s" % obj.filename
if obj.rfc_flag:
filename = obj.document().filename()
else:
@ -39,8 +46,7 @@ class DocumentComments(Feed):
return obj.public_comments().order_by("-date","-id")
def item_pubdate(self, item):
time = datetime.time(*[(t and int(t) or 0) for t in item.time.split(":")])
return datetime.datetime.combine(item.date, time)
return item.datetime()
def item_author_name(self, item):
return item.get_author()
@ -52,7 +58,10 @@ class InLastCall(Feed):
link = "/idtracker/status/last-call/"
def items(self):
ret = list(IDInternal.objects.filter(primary_flag=1).filter(cur_state__state='In Last Call'))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
ret = list(IDInternal.objects.filter(states__type="draft-iesg", states__slug="lc"))
else:
ret = list(IDInternal.objects.filter(primary_flag=1).filter(cur_state__state='In Last Call'))
ret.sort(key=lambda item: (item.document().lc_expiration_date or datetime.date.today()))
return ret

View file

@ -183,6 +183,9 @@ class InternetDraft(models.Model):
return "<%s>" % (self.filename_with_rev())
def filename_with_rev(self):
return "%s-%s.txt" % (self.filename, self.revision_display())
def name(self):
# small hack to make model forward-compatible with new schema
return self.filename
def group_acronym(self):
return self.group.acronym
def group_ml_archive(self):
@ -255,11 +258,11 @@ class PersonOrOrgInfo(models.Model):
date_created = models.DateField(auto_now_add=True, null=True)
created_by = models.CharField(blank=True, null=True, max_length=8)
address_type = models.CharField(blank=True, null=True, max_length=4)
def save(self):
def save(self, **kwargs):
self.first_name_key = self.first_name.upper()
self.middle_initial_key = self.middle_initial.upper()
self.last_name_key = self.last_name.upper()
super(PersonOrOrgInfo, self).save()
super(PersonOrOrgInfo, self).save(**kwargs)
def __str__(self):
# For django.VERSION 0.96
if self.first_name == '' and self.last_name == '':
@ -273,16 +276,12 @@ class PersonOrOrgInfo(models.Model):
def email(self, priority=1, type=None):
name = unicode(self)
email = ''
types = type and [ type ] or [ "INET", "Prim", None ]
for type in types:
try:
if type:
email = self.emailaddress_set.get(priority=priority, type=type).address
else:
email = self.emailaddress_set.get(priority=priority).address
break
except (EmailAddress.DoesNotExist, AssertionError):
pass
addresses = self.emailaddress_set.filter(address__contains="@").order_by('priority')
if addresses:
email = addresses[0].address
for a in addresses:
if a.priority == priority:
email = a.address
return (name, email)
# Added by Sunny Lee to display person's affiliation - 5/26/2007
def affiliation(self, priority=1):
@ -410,6 +409,9 @@ class Rfc(models.Model):
return "%s.txt" % ( self.filename() )
def filename(self):
return "rfc%d" % ( self.rfc_number )
def name(self):
# small hack to make model forward-compatible with new schema
return self.filename()
def revision(self):
return "RFC"
def revision_display(self):
@ -1060,6 +1062,7 @@ class Role(models.Model):
IAB_EXCUTIVE_DIRECTOR = 4
IRTF_CHAIR = 5
IAD_CHAIR = 6
RSOC_CHAIR = 7
# This __str__ makes it odd to use as a ForeignKey.
def __str__(self):
@ -1139,6 +1142,23 @@ class DocumentWrapper(object):
def __init__(self, document):
self.document = document
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
InternetDraftOld = InternetDraft
IDInternalOld = IDInternal
RfcOld = Rfc
BallotInfoOld = BallotInfo
IDStateOld = IDState
IDSubStateOld = IDSubState
AreaOld = Area
AcronymOld = Acronym
IESGLoginOld = IESGLogin
IETFWGOld = IETFWG
IRTFOld = IRTF
from redesign.doc.proxy import InternetDraft, IDInternal, BallotInfo, Rfc, IDState
from redesign.name.proxy import IDSubState
from redesign.group.proxy import Area, Acronym, IETFWG, IRTF
from redesign.person.proxy import IESGLogin
# changes done by convert-096.py:changed maxlength to max_length
# removed core

View file

@ -1,12 +1,16 @@
# Copyright The IETF Trust 2007, All Rights Reserved
#
from django.contrib.sitemaps import Sitemap
from django.conf import settings
from ietf.idtracker.models import IDInternal, InternetDraft
class IDTrackerMap(Sitemap):
changefreq = "always"
def items(self):
return IDInternal.objects.exclude(draft=999999)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return IDInternal.objects.all()
else:
return IDInternal.objects.exclude(draft=999999)
class DraftMap(Sitemap):
changefreq = "always"

View file

@ -2,6 +2,7 @@
import textwrap
from django import template
from django.conf import settings
from django.utils.html import escape, fix_ampersands
from django.template.defaultfilters import linebreaksbr, wordwrap, stringfilter
from django.template import resolve_variable
@ -407,8 +408,18 @@ def startswith(x, y):
# based on http://www.djangosnippets.org/snippets/847/ by 'whiteinge'
@register.filter
def in_group(user, groups):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return has_role(user, groups.replace("Area_Director", "Area Director"))
return user and user.is_authenticated() and bool(user.groups.filter(name__in=groups.split(',')).values('name'))
@register.filter
def has_role(user, role_names):
from ietf.ietfauth.decorators import has_role
if not user:
return False
return has_role(user, role_names.split(','))
@register.filter
def stable_dictsort(value, arg):
"""

View file

@ -25,18 +25,18 @@ class IdTrackerUrlTestCase(SimpleUrlTestCase):
else:
return content
class WGRoleTest(django.test.TestCase):
fixtures = ['wgtest']
def setUp(self):
from ietf.idtracker.models import IETFWG
self.xmas = IETFWG.objects.get(group_acronym__acronym='xmas')
self.snow = IETFWG.objects.get(group_acronym__acronym='snow')
def test_roles(self):
print " Testing WG roles"
self.assertEquals(self.xmas.wgchair_set.all()[0].role(), 'xmas WG Chair')
self.assertEquals(self.snow.wgchair_set.all()[0].role(), 'snow BOF Chair')
self.assertEquals(self.xmas.wgsecretary_set.all()[0].role(), 'xmas WG Secretary')
self.assertEquals(self.xmas.wgtechadvisor_set.all()[0].role(), 'xmas Technical Advisor')
print "OK"
# class WGRoleTest(django.test.TestCase):
# fixtures = ['wgtest']
#
# def setUp(self):
# from ietf.idtracker.models import IETFWG
# self.xmas = IETFWG.objects.get(group_acronym__acronym='xmas')
# self.snow = IETFWG.objects.get(group_acronym__acronym='snow')
#
# def test_roles(self):
# print " Testing WG roles"
# self.assertEquals(self.xmas.wgchair_set.all()[0].role(), 'xmas WG Chair')
# self.assertEquals(self.snow.wgchair_set.all()[0].role(), 'snow BOF Chair')
# self.assertEquals(self.xmas.wgsecretary_set.all()[0].role(), 'xmas WG Secretary')
# self.assertEquals(self.xmas.wgtechadvisor_set.all()[0].role(), 'xmas Technical Advisor')
# print "OK"

View file

@ -6,13 +6,8 @@
200 /idtracker/status/last-call/
301 /idtracker/rfc3847/
301 /idtracker/12689/
301 /idtracker/draft-ietf-isis-link-attr/
301 /idtracker/draft-ietf-isis-link-attr/comment/65232/
301 /idtracker/draft-eronen-tls-psk/ # no IESG information
301 /idtracker/comment/65232/
301 /idtracker/ballot/1760/
404 /idtracker/ballot/1723/ # dangling ballot, does not link to any doc
301 /idtracker/
200 /feed/comments/draft-ietf-isis-link-attr/
200 /feed/comments/rfc3373/
@ -27,5 +22,4 @@
# Test case for missing comment time (bug fixed in changeset 1733)
200 /feed/comments/draft-ietf-msec-newtype-keyid/
200,heavy /sitemap-drafts.xml
200,heavy /sitemap-idtracker.xml

View file

@ -2,11 +2,12 @@
# Create your views here.
from django.http import HttpResponsePermanentRedirect, Http404
from django.conf import settings
from django.template import RequestContext
from django.shortcuts import get_object_or_404, render_to_response
from django.views.generic.list_detail import object_detail, object_list
from ietf.idtracker.models import InternetDraft, IDInternal, IDState, IDSubState, BallotInfo, DocumentComment
import re
import re, datetime
def state_desc(request, state, is_substate=0):
if int(state) == 100:
@ -27,15 +28,30 @@ IESG to do anything with the document.
context_instance=RequestContext(request))
def status(request):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
drafts = list(IDInternal.objects.filter(states__type="draft-iesg").exclude(states__type="draft-iesg", states__slug__in=('pub', 'dead', 'watching', 'rfcqueue')).distinct().order_by('states__order'))
drafts.sort(key=lambda d: (d.cur_state_id, d.status_date or datetime.date.min, d.b_sent_date or datetime.date.min))
# sadly we can't use the generic view because it only works with a queryset...
return render_to_response('idtracker/status_of_items.html', dict(object_list=drafts, title="IESG Status of Items"), context_instance=RequestContext(request))
queryset = IDInternal.objects.filter(primary_flag=1).exclude(cur_state__state__in=('RFC Ed Queue', 'RFC Published', 'AD is watching', 'Dead')).order_by('cur_state', 'status_date', 'ballot')
return object_list(request, template_name="idtracker/status_of_items.html", queryset=queryset, extra_context={'title': 'IESG Status of Items'})
def last_call(request):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
drafts = list(IDInternal.objects.filter(states__type="draft-iesg", states__slug__in=('lc', 'writeupw', 'goaheadw')).distinct().order_by('states__order'))
drafts.sort(key=lambda d: (d.cur_state_id, d.status_date or datetime.date.min, d.b_sent_date or datetime.date.min))
# sadly we can't use the generic view because it only works with a queryset...
return render_to_response('idtracker/status_of_items.html', dict(object_list=drafts, title="Documents in Last Call", lastcall=1), context_instance=RequestContext(request))
queryset = IDInternal.objects.filter(primary_flag=1).filter(cur_state__state__in=('In Last Call', 'Waiting for Writeup', 'Waiting for AD Go-Ahead')).order_by('cur_state', 'status_date', 'ballot')
return object_list(request, template_name="idtracker/status_of_items.html", queryset=queryset, extra_context={'title': 'Documents in Last Call', 'lastcall': 1})
def redirect_id(request, object_id):
'''Redirect from historical document ID to preferred filename url.'''
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise Http404() # we don't store the numbers anymore
doc = get_object_or_404(InternetDraft, id_document_tag=object_id)
return HttpResponsePermanentRedirect("/doc/"+doc.filename+"/")
@ -46,6 +62,9 @@ def redirect_filename(request, filename):
return HttpResponsePermanentRedirect("/doc/"+filename+"/")
def redirect_ballot(request, object_id):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise Http404() # we don't store the numbers anymore
ballot = get_object_or_404(BallotInfo, pk=object_id)
ids = ballot.drafts.filter(primary_flag=1)
if len(ids) == 0:
@ -57,6 +76,9 @@ def redirect_ballot(request, object_id):
return HttpResponsePermanentRedirect("/doc/"+id.draft.filename+"/#ballot")
def redirect_comment(request, object_id):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise Http404() # we don't store the numbers anymore
comment = get_object_or_404(DocumentComment, pk=object_id)
id = comment.document
if id.rfc_flag:

View file

@ -1,5 +1,6 @@
# Copyright The IETF Trust 2007, 2008, All Rights Reserved
from django.conf import settings
from django.contrib.syndication.feeds import Feed
from django.utils.feedgenerator import Atom1Feed
from ietf.idtracker.models import IDInternal
@ -11,12 +12,24 @@ class IESGAgenda(Feed):
feed_type = Atom1Feed
def items(self):
return IDInternal.objects.filter(agenda=1).order_by('telechat_date')
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from doc.models import TelechatDocEvent
drafts = IDInternal.objects.filter(docevent__telechatdocevent__telechat_date__gte=datetime.date.min).distinct()
for d in drafts:
d.latest_telechat_event = d.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
drafts = [d for d in drafts if d.latest_telechat_event.telechat_date]
drafts.sort(key=lambda d: d.latest_telechat_event.telechat_date)
return drafts
return IDInternal.objects.filter(agenda=1).order_by('telechat_date')
def item_categories(self, item):
return [ str(item.telechat_date) ]
def item_pubdate(self, item):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return item.latest_telechat_event.time
f = item.comments().filter(comment_text__startswith='Placed on agenda for telechat')
try:
comment = f[0]
@ -28,4 +41,7 @@ class IESGAgenda(Feed):
def item_author_name(self, item):
return str( item.job_owner )
def item_author_email(self, item):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return item.ad.email_address()
return item.job_owner.person.email()[1]

View file

@ -107,6 +107,7 @@ class WGAction(models.Model):
(22, "WG Rechartering::Under evaluation for IETF Review"),
(23, "WG Rechartering::Proposed for Approval")
)
# note that with the new schema, Acronym is monkey-patched and is really Group
group_acronym = models.ForeignKey(Acronym, db_column='group_acronym_id', primary_key=True, unique=True)
note = models.TextField(blank=True,null=True)
status_date = models.DateField()

View file

@ -55,59 +55,63 @@ class RescheduleOnAgendaTestCase(django.test.TestCase):
self.assertEquals(draft.idinternal.comments().count(), comments_before + 1)
self.assertTrue("Telechat" in draft.idinternal.comments()[0].comment_text)
class RescheduleInEditTestCase(django.test.TestCase):
fixtures = ['base', 'draft']
class RescheduleOnAgendaTestCaseREDESIGN(django.test.TestCase):
fixtures = ['names']
def test_reschedule(self):
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
draft.idinternal.telechat_date = TelechatDates.objects.all()[0].dates()[0]
draft.idinternal.agenda = True
draft.idinternal.returning_item = True
draft.idinternal.save()
form_id = draft.idinternal.draft_id
telechat_date_before = draft.idinternal.telechat_date
from ietf.utils.test_data import make_test_data
from redesign.person.models import Person
from doc.models import TelechatDocEvent
url = urlreverse('ietf.idrfc.views_edit.edit_info', kwargs={"name":"draft-ietf-mipshop-pfmipv6",})
self.client.login(remote_user="klm")
draft = make_test_data()
# add to schedule
e = TelechatDocEvent(type="scheduled_for_telechat")
e.doc = draft
e.by = Person.objects.get(name="Aread Irector")
e.telechat_date = TelechatDates.objects.all()[0].date1
e.returning_item = True
e.save()
form_id = draft.pk
telechat_date_before = e.telechat_date
url = urlreverse('ietf.iesg.views.agenda_documents')
self.client.login(remote_user="secretary")
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form select[name=telechat_date]')), 1)
self.assertEquals(len(q('form input[name=returning_item]')), 1)
self.assertEquals(len(q('form select[name=%s-telechat_date]' % form_id)), 1)
self.assertEquals(len(q('form input[name=%s-clear_returning_item]' % form_id)), 1)
# reschedule
comments_before = draft.idinternal.comments().count()
events_before = draft.docevent_set.count()
d = TelechatDates.objects.all()[0].dates()[2]
r = self.client.post(url, { 'telechat_date': d.strftime("%Y-%m-%d"),
'returning_item': "0",
'job_owner': "49",
'area_acronym': draft.idinternal.area_acronym_id,
'note': draft.idinternal.note,
'state_change_notice_to': draft.idinternal.state_change_notice_to,
'intended_status': "6",
'stream': Stream.objects.get(name=u'IETF').id,
})
self.assertEquals(r.status_code, 302)
r = self.client.post(url, { '%s-telechat_date' % form_id: d.strftime("%Y-%m-%d"),
'%s-clear_returning_item' % form_id: "1" })
self.assertEquals(r.status_code, 200)
# check that it moved below the right header in the DOM on the
# agenda docs page
url = urlreverse('ietf.iesg.views.agenda_documents')
r = self.client.get(url)
d_header_pos = r.content.find("IESG telechat %s" % d.strftime("%Y-%m-%d"))
draft_pos = r.content.find(draft.filename)
draft_pos = r.content.find(draft.name)
self.assertTrue(d_header_pos < draft_pos)
draft = InternetDraft.objects.get(filename="draft-ietf-mipshop-pfmipv6")
self.assertEquals(draft.idinternal.telechat_date, d)
self.assertTrue(not draft.idinternal.returning_item)
self.assertEquals(draft.idinternal.comments().count(), comments_before + 1)
self.assertTrue("Telechat" in draft.idinternal.comments()[0].comment_text)
self.assertTrue(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
self.assertEquals(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, d)
self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").returning_item)
self.assertEquals(draft.docevent_set.count(), events_before + 1)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
RescheduleOnAgendaTestCase = RescheduleOnAgendaTestCaseREDESIGN
class ManageTelechatDatesTestCase(django.test.TestCase):
fixtures = ['base', 'draft']
@ -149,6 +153,56 @@ class ManageTelechatDatesTestCase(django.test.TestCase):
self.assertTrue(dates.date4 == new_date)
self.assertTrue(dates.date1 == old_date2)
class ManageTelechatDatesTestCaseREDESIGN(django.test.TestCase):
fixtures = ['names']
def test_set_dates(self):
from ietf.utils.test_data import make_test_data
make_test_data()
dates = TelechatDates.objects.all()[0]
url = urlreverse('ietf.iesg.views.telechat_dates')
login_testing_unauthorized(self, "secretary", url)
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form input[name=date1]')), 1)
# post
new_date = dates.date1 + timedelta(days=7)
r = self.client.post(url, dict(date1=new_date.isoformat(),
date2=new_date.isoformat(),
date3=new_date.isoformat(),
date4=new_date.isoformat(),
))
self.assertEquals(r.status_code, 200)
dates = TelechatDates.objects.all()[0]
self.assertTrue(dates.date1 == new_date)
def test_rollup_dates(self):
from ietf.utils.test_data import make_test_data
make_test_data()
dates = TelechatDates.objects.all()[0]
url = urlreverse('ietf.iesg.views.telechat_dates')
login_testing_unauthorized(self, "secretary", url)
old_date2 = dates.date2
new_date = dates.date4 + timedelta(days=14)
r = self.client.post(url, dict(rollup_dates="1"))
self.assertEquals(r.status_code, 200)
dates = TelechatDates.objects.all()[0]
self.assertTrue(dates.date4 == new_date)
self.assertTrue(dates.date1 == old_date2)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
ManageTelechatDatesTestCase = ManageTelechatDatesTestCaseREDESIGN
class WorkingGroupActionsTestCase(django.test.TestCase):
fixtures = ['base', 'wgactions']
@ -258,7 +312,152 @@ class WorkingGroupActionsTestCase(django.test.TestCase):
self.assertEquals(r.status_code, 200)
self.assertTrue('(sieve)' not in r.content)
class WorkingGroupActionsTestCaseREDESIGN(django.test.TestCase):
fixtures = ['names']
def setUp(self):
super(self.__class__, self).setUp()
curdir = os.path.dirname(os.path.abspath(__file__))
self.evaldir = os.path.join(curdir, "tmp-testdir")
os.mkdir(self.evaldir)
src = os.path.join(curdir, "fixtures", "sieve-charter.txt")
shutil.copy(src, self.evaldir)
settings.IESG_WG_EVALUATION_DIR = self.evaldir
def tearDown(self):
super(self.__class__, self).tearDown()
shutil.rmtree(self.evaldir)
def test_working_group_actions(self):
from ietf.utils.test_data import make_test_data
make_test_data()
url = urlreverse('iesg_working_group_actions')
login_testing_unauthorized(self, "secretary", url)
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
for wga in WGAction.objects.all():
self.assertTrue(wga.group_acronym.name in r.content)
self.assertTrue('(sieve)' in r.content)
def test_delete_wgaction(self):
from ietf.utils.test_data import make_test_data
make_test_data()
wga = WGAction.objects.all()[0]
url = urlreverse('iesg_edit_working_group_action', kwargs=dict(wga_id=wga.pk))
login_testing_unauthorized(self, "secretary", url)
r = self.client.post(url, dict(delete="1"))
self.assertEquals(r.status_code, 302)
self.assertTrue(not WGAction.objects.filter(pk=wga.pk))
def test_edit_wgaction(self):
from ietf.utils.test_data import make_test_data
from redesign.person.models import Person
make_test_data()
wga = WGAction.objects.all()[0]
url = urlreverse('iesg_edit_working_group_action', kwargs=dict(wga_id=wga.pk))
login_testing_unauthorized(self, "secretary", url)
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form select[name=token_name]')), 1)
self.assertEquals(len(q('form select[name=telechat_date]')), 1)
# change
dates = TelechatDates.objects.all()[0]
token_name = Person.objects.get(name="Ad No1").name_parts()[1]
old = wga.pk
r = self.client.post(url, dict(status_date=dates.date1.isoformat(),
token_name=token_name,
category="23",
note="Testing.",
telechat_date=dates.date4.isoformat()))
self.assertEquals(r.status_code, 302)
wga = WGAction.objects.get(pk=old)
self.assertEquals(wga.status_date, dates.date1)
self.assertEquals(wga.token_name, token_name)
self.assertEquals(wga.category, 23)
self.assertEquals(wga.note, "Testing.")
self.assertEquals(wga.telechat_date, dates.date4)
def test_add_possible_wg(self):
from ietf.utils.test_data import make_test_data
from redesign.person.models import Person
from redesign.group.models import Group
make_test_data()
url = urlreverse('iesg_working_group_actions')
login_testing_unauthorized(self, "secretary", url)
r = self.client.post(url, dict(add="1",
filename='sieve-charter.txt'))
self.assertEquals(r.status_code, 302)
# now we got back a URL we can use for adding, but first make
# sure we got a proposed group with the acronym
group = Group.objects.create(
name="Sieve test test",
acronym="sieve",
state_id="proposed",
type_id="wg",
parent=None
)
add_url = r['Location']
r = self.client.get(add_url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue('(sieve)' in r.content)
self.assertEquals(len(q('form select[name=token_name]')), 1)
self.assertEquals(q('form input[name=status_date]')[0].get("value"), "2010-05-07")
self.assertEquals(len(q('form select[name=telechat_date]')), 1)
wgas_before = WGAction.objects.all().count()
dates = TelechatDates.objects.all()[0]
token_name = Person.objects.get(name="Ad No1").name_parts()[1]
r = self.client.post(add_url,
dict(status_date=dates.date1.isoformat(),
token_name=token_name,
category="23",
note="Testing.",
telechat_date=dates.date4.isoformat()))
self.assertEquals(r.status_code, 302)
self.assertEquals(wgas_before + 1, WGAction.objects.all().count())
def test_delete_possible_wg(self):
from ietf.utils.test_data import make_test_data
make_test_data()
url = urlreverse('iesg_working_group_actions')
login_testing_unauthorized(self, "secretary", url)
r = self.client.post(url, dict(delete="1",
filename='sieve-charter.txt'))
self.assertEquals(r.status_code, 200)
self.assertTrue('(sieve)' not in r.content)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
WorkingGroupActionsTestCase = WorkingGroupActionsTestCaseREDESIGN
class IesgUrlTestCase(SimpleUrlTestCase):

View file

@ -20,8 +20,5 @@
200 /iesg/ann/new/
# This takes ~ 300s:
#200 /iesg/ann/prev/
200 /iesg/ann/2422/
200 /iesg/ann/1563/
404 /iesg/ann/567/
200 /feed/iesg-agenda/

View file

@ -60,6 +60,18 @@ def date_threshold():
return ret
def inddocs(request):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
queryset_list_ind = [d for d in InternetDraft.objects.filter(tags__slug="via-rfc", docevent__type="iesg_approved").distinct() if d.latest_event(type__in=("iesg_disapproved", "iesg_approved")).type == "iesg_approved"]
queryset_list_ind.sort(key=lambda d: d.b_approve_date, reverse=True)
queryset_list_ind_dnp = [d for d in IDInternal.objects.filter(tags__slug="via-rfc", docevent__type="iesg_disapproved").distinct() if d.latest_event(type__in=("iesg_disapproved", "iesg_approved")).type == "iesg_disapproved"]
queryset_list_ind_dnp.sort(key=lambda d: d.dnp_date, reverse=True)
return render_to_response('iesg/independent_doc.html',
dict(object_list=queryset_list_ind,
object_list_dnp=queryset_list_ind_dnp),
context_instance=RequestContext(request))
queryset_list_ind = InternetDraft.objects.filter(idinternal__via_rfc_editor=1, idinternal__rfc_flag=0, idinternal__noproblem=1, idinternal__dnp=0).order_by('-b_approve_date')
queryset_list_ind_dnp = IDInternal.objects.filter(via_rfc_editor = 1,rfc_flag=0,dnp=1).order_by('-dnp_date')
return object_list(request, queryset=queryset_list_ind, template_name='iesg/independent_doc.html', allow_empty=True, extra_context={'object_list_dnp':queryset_list_ind_dnp })
@ -95,6 +107,61 @@ def wgdocs(request,cat):
queryset_list_doc.append(sub_item2)
return render_to_response( 'iesg/ietf_doc.html', {'object_list': queryset_list, 'object_list_doc':queryset_list_doc, 'is_recent':is_recent}, context_instance=RequestContext(request) )
def wgdocsREDESIGN(request,cat):
is_recent = 0
proto_actions = []
doc_actions = []
threshold = date_threshold()
proto_levels = ["bcp", "ds", "ps", "std"]
doc_levels = ["exp", "inf"]
if cat == 'new':
is_recent = 1
drafts = InternetDraft.objects.filter(docevent__type="iesg_approved", docevent__time__gte=threshold, intended_std_level__in=proto_levels + doc_levels).exclude(tags__slug="via-rfc").distinct()
for d in drafts:
if d.b_approve_date and d.b_approve_date >= threshold:
if d.intended_std_level_id in proto_levels:
proto_actions.append(d)
elif d.intended_std_level_id in doc_levels:
doc_actions.append(d)
elif cat == 'prev':
# proto
start_date = datetime.date(1997, 12, 1)
drafts = InternetDraft.objects.filter(docevent__type="iesg_approved", docevent__time__lt=threshold, docevent__time__gte=start_date, intended_std_level__in=proto_levels).exclude(tags__slug="via-rfc").distinct()
for d in drafts:
if d.b_approve_date and start_date <= d.b_approve_date < threshold:
proto_actions.append(d)
# doc
start_date = datetime.date(1998, 10, 15)
drafts = InternetDraft.objects.filter(docevent__type="iesg_approved", docevent__time__lt=threshold, docevent__time__gte=start_date, intended_std_level__in=doc_levels).exclude(tags__slug="via-rfc").distinct()
for d in drafts:
if d.b_approve_date and start_date <= d.b_approve_date < threshold:
doc_actions.append(d)
else:
raise Http404
proto_actions.sort(key=lambda d: d.b_approve_date, reverse=True)
doc_actions.sort(key=lambda d: d.b_approve_date, reverse=True)
return render_to_response('iesg/ietf_doc.html',
dict(object_list=proto_actions,
object_list_doc=doc_actions,
is_recent=is_recent,
title_prefix="Recent" if is_recent else "Previous"),
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
wgdocs = wgdocsREDESIGN
def get_doc_section(id):
states = [16,17,18,19,20,21]
if id.document().intended_status.intended_status_id in [1,2,6,7]:
@ -119,14 +186,63 @@ def get_doc_section(id):
s = s + "1"
return s
def agenda_docs(date, next_agenda):
if next_agenda:
matches = IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1)
def get_doc_sectionREDESIGN(id):
states = [16,17,18,19,20,21]
if id.intended_std_level_id in ["bcp", "ds", "ps", "std"]:
s = "2"
else:
matches = IDInternal.objects.filter(telechat_date=date, primary_flag=1)
idmatches = matches.filter(rfc_flag=0).order_by('ballot')
rfcmatches = matches.filter(rfc_flag=1).order_by('ballot')
res = {}
s = "3"
g = id.document().group_acronym()
if g and str(g) != 'none':
s = s + "1"
elif (s == "3") and id.via_rfc_editor:
s = s + "3"
else:
s = s + "2"
if not id.rfc_flag and id.cur_state.document_state_id not in states:
s = s + "3"
elif id.returning_item:
s = s + "2"
else:
s = s + "1"
return s
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
get_doc_section = get_doc_sectionREDESIGN
def agenda_docs(date, next_agenda):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from doc.models import TelechatDocEvent
matches = IDInternal.objects.filter(docevent__telechatdocevent__telechat_date=date)
idmatches = []
rfcmatches = []
for m in matches:
if m.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date != date:
continue
if next_agenda and not m.agenda:
continue
if m.docalias_set.filter(name__startswith="rfc"):
rfcmatches.append(m)
else:
idmatches.append(m)
idmatches.sort(key=lambda d: d.start_date or datetime.date.min)
rfcmatches.sort(key=lambda d: d.start_date or datetime.date.min)
else:
if next_agenda:
matches = IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1)
else:
matches = IDInternal.objects.filter(telechat_date=date, primary_flag=1)
idmatches = matches.filter(rfc_flag=0).order_by('ballot')
rfcmatches = matches.filter(rfc_flag=1).order_by('ballot')
res = dict(("s%s%s%s" % (i, j, k), []) for i in range(2, 5) for j in range (1, 4) for k in range(1, 4))
for id in list(idmatches)+list(rfcmatches):
section_key = "s"+get_doc_section(id)
if section_key not in res:
@ -189,11 +305,12 @@ def agenda_txt(request):
def agenda_scribe_template(request):
date = TelechatDates.objects.all()[0].date1
docs = agenda_docs(date, True)
return render_to_response('iesg/scribe_template.html', {'date':str(date), 'docs':docs}, context_instance=RequestContext(request) )
return render_to_response('iesg/scribe_template.html', {'date':str(date), 'docs':docs, 'USE_DB_REDESIGN_PROXY_CLASSES': settings.USE_DB_REDESIGN_PROXY_CLASSES}, context_instance=RequestContext(request) )
def _agenda_moderator_package(request):
data = _agenda_data(request)
data['ad_names'] = [str(x) for x in IESGLogin.active_iesg()]
data['ad_names'].sort(key=lambda x: x.split(' ')[-1])
return render_to_response("iesg/moderator_package.html", data, context_instance=RequestContext(request))
@group_required('Area_Director','Secretariat')
@ -224,7 +341,13 @@ def agenda_documents_txt(request):
dates = TelechatDates.objects.all()[0].dates()
docs = []
for date in dates:
docs.extend(IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from doc.models import TelechatDocEvent
for d in IDInternal.objects.filter(docevent__telechatdocevent__telechat_date=date):
if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date == date:
docs.append(d)
else:
docs.extend(IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1))
t = loader.get_template('iesg/agenda_documents.txt')
c = Context({'docs':docs})
return HttpResponse(t.render(c), mimetype='text/plain')
@ -259,12 +382,20 @@ def handle_reschedule_form(request, idinternal, dates):
if request.method == 'POST':
form = RescheduleForm(request.POST, **formargs)
if form.is_valid():
update_telechat(request, idinternal,
form.cleaned_data['telechat_date'])
if form.cleaned_data['clear_returning_item']:
idinternal.returning_item = False
idinternal.event_date = datetime.date.today()
idinternal.save()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
login = request.user.get_profile()
update_telechat(request, idinternal, login,
form.cleaned_data['telechat_date'],
False if form.cleaned_data['clear_returning_item'] else None)
idinternal.time = datetime.datetime.now()
idinternal.save()
else:
update_telechat(request, idinternal,
form.cleaned_data['telechat_date'])
if form.cleaned_data['clear_returning_item']:
idinternal.returning_item = False
idinternal.event_date = datetime.date.today()
idinternal.save()
else:
form = RescheduleForm(**formargs)
@ -273,7 +404,16 @@ def handle_reschedule_form(request, idinternal, dates):
def agenda_documents(request):
dates = TelechatDates.objects.all()[0].dates()
idinternals = list(IDInternal.objects.filter(telechat_date__in=dates,primary_flag=1,agenda=1).order_by('rfc_flag', 'ballot'))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from doc.models import TelechatDocEvent
idinternals = []
for d in IDInternal.objects.filter(docevent__telechatdocevent__telechat_date__in=dates):
if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date in dates:
idinternals.append(d)
idinternals.sort(key=lambda d: (d.rfc_flag, d.start_date))
else:
idinternals = list(IDInternal.objects.filter(telechat_date__in=dates,primary_flag=1,agenda=1).order_by('rfc_flag', 'ballot'))
for i in idinternals:
i.reschedule_form = handle_reschedule_form(request, i, dates)
@ -294,7 +434,10 @@ def agenda_documents(request):
w.iprUrl = "/ipr/search?option=document_search&id_document_tag=" + str(w.id.tracker_id)
iprs = IprDraft.objects.filter(document=w.id.tracker_id)
else:
ri = RfcIndex.objects.get(rfc_number=i.draft_id)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
ri = i
else:
ri = RfcIndex.objects.get(rfc_number=i.draft_id)
w = RfcWrapper(ri)
w.iprUrl = "/ipr/search?option=rfc_search&rfc_search=" + str(w.rfc.rfc_number)
iprs = IprRfc.objects.filter(document=w.rfc.rfc_number)
@ -307,7 +450,14 @@ def agenda_documents(request):
def telechat_docs_tarfile(request,year,month,day):
from tempfile import mkstemp
date=datetime.date(int(year),int(month),int(day))
docs= IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from doc.models import TelechatDocEvent
docs = []
for d in IDInternal.objects.filter(docevent__telechatdocevent__telechat_date=date):
if d.latest_event(TelechatDocEvent, type="scheduled_for_telechat").telechat_date == date:
docs.append(d)
else:
docs= IDInternal.objects.filter(telechat_date=date, primary_flag=1, agenda=1)
response = HttpResponse(mimetype='application/octet-stream')
response['Content-Disposition'] = 'attachment; filename=telechat-%s-%s-%s-docs.tgz'%(year, month, day)
tarstream = tarfile.open('','w:gz',response)
@ -330,6 +480,29 @@ def telechat_docs_tarfile(request,year,month,day):
return response
def discusses(request):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
res = []
for d in IDInternal.objects.filter(states__type="draft-iesg", states__slug__in=("pub-req", "ad-eval", "review-e", "lc-req", "lc", "writeupw", "goaheadw", "iesg-eva", "defer", "watching"), docevent__ballotpositiondocevent__pos="discuss").distinct():
found = False
for p in d.positions.all():
if p.discuss:
found = True
break
if not found:
continue
if d.rfc_flag:
doc = RfcWrapper(d)
else:
doc = IdWrapper(draft=d)
if doc.in_ietf_process() and doc.ietf_process.has_active_iesg_ballot():
res.append(doc)
return direct_to_template(request, 'iesg/discusses.html', {'docs':res})
positions = Position.objects.filter(discuss=1)
res = []
try:

View file

@ -31,6 +31,8 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from django.utils.http import urlquote
from django.conf import settings
from django.db.models import Q
from django.contrib.auth.decorators import _CheckLogin
from django.http import HttpResponseRedirect, HttpResponseForbidden
@ -62,3 +64,50 @@ def group_required(*group_names):
def decorate(view_func):
return _CheckLogin403(view_func, lambda u: bool(u.groups.filter(name__in=group_names)), "Restricted to group%s %s" % ("s" if len(group_names) != 1 else "", ",".join(group_names)))
return decorate
def has_role(user, role_names):
"""Determines whether user has any of the given standard roles
given. Role names must be a list or, in case of a single value, a
string."""
if isinstance(role_names, str) or isinstance(role_names, unicode):
role_names = [ role_names ]
if not user or not user.is_authenticated():
return False
from redesign.person.models import Person
try:
person = user.get_profile()
except Person.DoesNotExist:
return False
role_qs = {
"Area Director": Q(person=person, name="ad", group__type="area", group__state="active"),
"Secretariat": Q(person=person, name="secr", group__acronym="secretariat"),
"IANA": Q(person=person, name="delegate", group__acronym="iana"), # FIXME
}
filter_expr = Q()
for r in role_names:
filter_expr |= role_qs[r]
from redesign.group.models import Role
return bool(Role.objects.filter(filter_expr)[:1])
def role_required(*role_names):
"""View decorator for checking that the user is logged in and
belongs to (at least) one of the listed roles. Users who are not
logged in are redirected to the login page; users who don't have
one of the roles get a "403" page.
"""
def decorate(view_func):
return _CheckLogin403(view_func,
lambda u: has_role(u, role_names),
"Restricted to role%s %s" % ("s" if len(role_names) != 1 else "", ",".join(role_names)))
return decorate
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
# overwrite group_required
group_required = lambda *group_names: role_required(*[n.replace("Area_Director", "Area Director") for n in group_names])

View file

@ -70,6 +70,20 @@ class IetfUserProfile(models.Model):
except:
return None
def email(self):
# quick hack to bind new and old schema together for the time being
try:
l = IESGLogin.objects.get(login_name=self.user.username)
if l.person:
person = l.person
else:
person = PersonOrOrgInfo.objects.get(first_name=l.first_name,
last_name=l.last_name)
except IESGLogin.DoesNotExist, PersonOrOrgInfo.DoesNotExist:
person = None
from person.models import Email
return Email.objects.get(address=person.email()[1])
def __str__(self):
return "IetfUserProfile(%s)" % (self.user,)

View file

@ -31,6 +31,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
from django.conf import settings
from django.contrib.auth.models import User
from django.test.client import Client
from ietf.utils.test_utils import SimpleUrlTestCase, RealDatabaseTest
@ -41,6 +42,8 @@ class IetfAuthUrlTestCase(SimpleUrlTestCase):
def testUrls(self):
self.doTestUrls(__file__)
# this test case should really work on a test database instead of the
# real one
class IetfAuthTestCase(unittest.TestCase,RealDatabaseTest):
def setUp(self):
self.setUpRealDatabase()
@ -61,7 +64,7 @@ class IetfAuthTestCase(unittest.TestCase,RealDatabaseTest):
response = c.get(nexturl[2], {}, False, REMOTE_USER=username)
self.assertEquals(response.status_code, 200)
self.assert_("Roles/Groups:" in response.content)
self.assert_("User name" in response.content)
return response
def testLogin(self):
@ -95,4 +98,7 @@ class IetfAuthTestCase(unittest.TestCase,RealDatabaseTest):
self.assert_("IETF_Chair" in groups)
print "OK"
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
# this test doesn't make any sense anymore
IetfAuthTestCase.testGroups = lambda x: None

View file

@ -82,6 +82,23 @@ def ietf_loggedin(request):
@login_required
def profile(request):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from person.models import Person
from group.models import Role
roles = []
person = None
try:
person = request.user.get_profile()
roles = Role.objects.filter(person=person)
except Person.DoesNotExist:
pass
return render_to_response('registration/profileREDESIGN.html',
dict(roles=roles,
person=person),
context_instance=RequestContext(request))
return render_to_response('registration/profile.html', context_instance=RequestContext(request))

View file

@ -1,4 +1,7 @@
from django.conf import settings
from ietf.ietfworkflows.streams import get_streamed_draft
from redesign.group.models import Role
def get_person_for_user(user):
@ -17,10 +20,15 @@ def is_secretariat(user):
def is_wgchair(person):
return bool(person.wgchair_set.all())
def is_wgchairREDESIGN(person):
return bool(Role.objects.filter(name="chair", group__type="wg", group__state="active", person=person))
def is_wgdelegate(person):
return bool(person.wgdelegate_set.all())
def is_wgdelegateREDESIGN(person):
return bool(Role.objects.filter(name="delegate", group__type="wg", group__state="active", person=person))
def is_delegate_of_stream(user, stream):
if is_secretariat(user):
@ -28,6 +36,11 @@ def is_delegate_of_stream(user, stream):
person = get_person_for_user(user)
return stream.check_delegate(person)
def is_delegate_of_streamREDESIGN(user, stream):
if is_secretariat(user):
return True
return bool(Role.objects.filter(group__acronym=stream.slug, name="delegate", person__user=user))
def is_chair_of_stream(user, stream):
if is_secretariat(user):
@ -35,6 +48,11 @@ def is_chair_of_stream(user, stream):
person = get_person_for_user(user)
return stream.check_chair(person)
def is_chair_of_streamREDESIGN(user, stream):
if is_secretariat(user):
return True
return bool(Role.objects.filter(group__acronym=stream.slug, name="chair", person__user=user))
def is_authorized_in_draft_stream(user, draft):
if is_secretariat(user):
@ -59,10 +77,30 @@ def is_authorized_in_draft_stream(user, draft):
delegates = streamed.stream.get_delegates_for_document(draft)
return bool(person in delegates)
def is_authorized_in_draft_streamREDESIGN(user, draft):
if is_secretariat(user):
return True
# must be a chair or delegate of the stream group (or draft group)
group_req = Q(group__acronym=stream.slug)
if draft.group and stream.slug == "ietf":
group_req |= Q(group=draft.group)
return bool(Role.objects.filter(name__in=("chair", "delegate"), person__user=user).filter(group_req))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.wgchairs.accounts import is_secretariat, get_person_for_user
is_wgdelegate = is_wgdelegateREDESIGN
is_wgchair = is_wgchairREDESIGN
is_chair_of_stream = is_chair_of_streamREDESIGN
is_delegate_of_stream = is_delegate_of_streamREDESIGN
is_authorized_in_draft_stream = is_authorized_in_draft_streamREDESIGN
def can_edit_state(user, draft):
streamed = get_streamed_draft(draft)
if not streamed or not streamed.stream:
if not settings.USE_DB_REDESIGN_PROXY_CLASSES and (not streamed or not streamed.stream):
person = get_person_for_user(user)
if not person:
return False
@ -75,3 +113,13 @@ def can_edit_state(user, draft):
def can_edit_stream(user, draft):
return is_secretariat(user)
def can_adopt(user, draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES and draft.stream_id == "ise":
person = get_person_for_user(user)
if not person:
return False
return is_wgchair(person) or is_wgdelegate(person)
else:
return is_secretariat(user)

View file

@ -1,4 +1,5 @@
from django.contrib import admin
from django.conf import settings
from ietf.ietfworkflows.models import (AnnotationTag, WGWorkflow,
Stream, StreamedID)
@ -22,4 +23,5 @@ admin.site.register(StreamedID, StreamedIdAdmin)
class StreamAdmin(admin.ModelAdmin):
list_display = ['name', 'document_group_attribute', 'group_chair_attribute', 'workflow_link', ]
admin.site.register(Stream, StreamAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(Stream, StreamAdmin)

View file

@ -1,12 +1,12 @@
import datetime
from django.conf import settings
from django import forms
from django.template.loader import render_to_string
from workflows.models import State
from workflows.utils import set_workflow_for_object
from ietf.idtracker.models import PersonOrOrgInfo, IETFWG
from ietf.idtracker.models import PersonOrOrgInfo, IETFWG, InternetDraft
from ietf.wgchairs.accounts import get_person_for_user
from ietf.ietfworkflows.models import Stream, StreamDelegate
from ietf.ietfworkflows.utils import (get_workflow_for_draft, get_workflow_for_wg,
@ -18,7 +18,10 @@ from ietf.ietfworkflows.accounts import is_secretariat
from ietf.ietfworkflows.streams import (get_stream_from_draft, get_streamed_draft,
get_stream_by_name, set_stream_for_draft)
from ietf.ietfworkflows.constants import CALL_FOR_ADOPTION, IETF_STREAM
from redesign.doc.utils import get_tags_for_stream_id
from redesign.doc.models import save_document_in_history, DocEvent, Document
from redesign.name.models import DocTagName, DocStreamName
from redesign.group.models import Group, GroupStateTransitions
class StreamDraftForm(forms.Form):
@ -55,45 +58,91 @@ class NoWorkflowStateForm(StreamDraftForm):
def __init__(self, *args, **kwargs):
super(NoWorkflowStateForm, self).__init__(*args, **kwargs)
self.wgs = None
self.onlywg = None
if is_secretariat(self.user):
wgs = IETFWG.objects.all()
wgs = IETFWG.objects.all().order_by('group_acronym__acronym')
else:
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
wgs = IETFWG.objects.filter(type="wg", state="active", role__name__in=("chair", "delegate"), role__person__user=self.user).order_by('acronym').distinct()
else:
wgs = set([i.group_acronym for i in self.person.wgchair_set.all()]).union(set([i.wg for i in self.person.wgdelegate_set.all()]))
wgs = list(wgs)
wgs.sort(lambda x, y: cmp(x.group_acronym.acronym, y.group_acronym.acronym))
self.wgs = wgs
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
self.fields['wg'].choices = [(i.pk, '%s - %s' % (i.acronym, i.name)) for i in self.wgs]
else:
wgs = set([i.group_acronym for i in self.person.wgchair_set.all()]).union(set([i.wg for i in self.person.wgdelegate_set.all()]))
if len(wgs) > 1:
self.wgs = list(wgs)
self.wgs.sort(lambda x, y: cmp(x.group_acronym.acronym, y.group_acronym.acronym))
self.fields['wg'].choices = [(i.pk, '%s - %s' % (i.group_acronym.acronym, i.group_acronym.name)) for i in self.wgs]
else:
self.onlywg = list(wgs)[0].group_acronym
def save(self):
comment = self.cleaned_data.get('comment')
comment = self.cleaned_data.get('comment').strip()
weeks = self.cleaned_data.get('weeks')
if self.onlywg:
wg = self.onlywg
else:
wg = IETFWG.objects.get(pk=self.cleaned_data.get('wg'))
wg = IETFWG.objects.get(pk=self.cleaned_data.get('wg'))
estimated_date = None
if weeks:
now = datetime.date.today()
estimated_date = now + datetime.timedelta(weeks=weeks)
workflow = get_workflow_for_wg(wg)
set_workflow_for_object(self.draft, workflow)
stream = get_stream_by_name(IETF_STREAM)
streamed = get_streamed_draft(self.draft)
if not streamed:
set_stream_for_draft(self.draft, stream)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
# do changes on real Document object instead of proxy to avoid trouble
doc = Document.objects.get(pk=self.draft.pk)
save_document_in_history(doc)
doc.time = datetime.datetime.now()
new_stream = DocStreamName.objects.get(slug="ietf")
if doc.stream != new_stream:
e = DocEvent(type="changed_stream")
e.time = doc.time
e.by = self.user.get_profile()
e.doc = doc
e.desc = u"Stream changed to <b>%s</b> from %s" % (new_stream.name, doc.stream.name)
e.save()
doc.stream = new_stream
if doc.group.pk != wg.pk:
e = DocEvent(type="changed_group")
e.time = doc.time
e.by = self.user.get_profile()
e.doc = doc
e.desc = u"Changed group to <b>%s (%s)</b>" % (wg.name, wg.acronym.upper())
if doc.group.type_id != "individ":
e.desc += " from %s (%s)" % (doc.group.name, doc.group.acronym)
e.save()
doc.group_id = wg.pk
doc.save()
self.draft = InternetDraft.objects.get(pk=doc.pk) # make sure proxy object is updated
else:
workflow = get_workflow_for_wg(wg)
set_workflow_for_object(self.draft, workflow)
stream = get_stream_by_name(IETF_STREAM)
streamed = get_streamed_draft(self.draft)
streamed.stream = stream
streamed.group = wg
streamed.save()
update_state(obj=self.draft,
if not streamed:
set_stream_for_draft(self.draft, stream)
streamed = get_streamed_draft(self.draft)
streamed.stream = stream
streamed.group = wg
streamed.save()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import State
to_state = State.objects.get(slug="c-adopt", type="draft-stream-%s" % self.draft.stream_id)
else:
to_state = get_state_by_name(CALL_FOR_ADOPTION)
update_state(self.draft,
comment=comment,
person=self.person,
to_state=get_state_by_name(CALL_FOR_ADOPTION),
to_state=to_state,
estimated_date=estimated_date)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
if comment:
e = DocEvent(type="added_comment")
e.time = self.draft.time
e.by = self.person
e.doc_id = self.draft.pk
e.desc = comment
e.save()
class DraftTagsStateForm(StreamDraftForm):
@ -115,8 +164,20 @@ class DraftTagsStateForm(StreamDraftForm):
if new_state:
self.data = self.data.copy()
self.data.update({'new_state': new_state.id})
self.available_tags = self.workflow.get_tags()
self.tags = [i.annotation_tag for i in get_annotation_tags_for_draft(self.draft)]
if key.startswith('new_state_'): # hack to get value from submit buttons
self.data = self.data.copy()
self.data['new_state'] = key.replace('new_state_', '')
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
possible_tags = get_tags_for_stream_id(self.draft.stream_id)
if self.draft.stream_id == "ietf" and self.draft.group:
unused_tags = self.draft.group.unused_tags.values_list("slug", flat=True)
possible_tags = [t for t in possible_tags if t not in unused_tags]
self.available_tags = DocTagName.objects.filter(slug__in=possible_tags)
self.tags = self.draft.tags.filter(slug__in=possible_tags)
else:
self.available_tags = self.workflow.get_tags()
self.tags = [i.annotation_tag for i in get_annotation_tags_for_draft(self.draft)]
self.fields['tags'].choices = [(i.pk, i.name) for i in self.available_tags]
self.fields['tags'].initial = [i.pk for i in self.tags]
@ -128,9 +189,46 @@ class DraftTagsStateForm(StreamDraftForm):
return None
def get_transitions(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return []
return self.state.transitions.filter(workflow=self.workflow)
def get_next_states(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import State
state_type = "draft-stream-%s" % self.draft.stream_id
s = self.draft.get_state(state_type)
next_states = []
if s:
next_states = s.next_states.all()
if self.draft.stream_id == "ietf" and self.draft.group:
transitions = self.draft.group.groupstatetransitions_set.filter(state=s)
if transitions:
next_states = transitions[0].next_states.all()
else:
# return the initial state
states = State.objects.filter(type=state_type).order_by('order')
if states:
next_states = states[:1]
unused = []
if self.draft.group:
unused = self.draft.group.unused_states.values_list("pk", flat=True)
return [n for n in next_states if n.pk not in unused]
return []
def get_states(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import State
states = State.objects.filter(type="draft-stream-%s" % self.draft.stream_id)
if self.draft.stream_id == "ietf" and self.draft.group:
unused_states = self.draft.group.unused_states.values_list("pk", flat=True)
states = [s for s in states if s.pk not in unused_states]
return [(i.pk, i.name) for i in states]
return [(i.pk, i.name) for i in self.workflow.get_states()]
def save_tags(self):
@ -145,7 +243,10 @@ class DraftTagsStateForm(StreamDraftForm):
try:
shepherd = self.draft.shepherd
if shepherd:
extra_notify = ['%s <%s>' % shepherd.email()]
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
extra_notify = [shepherd.formatted_email()]
else:
extra_notify = ['%s <%s>' % shepherd.email()]
except PersonOrOrgInfo.DoesNotExist:
pass
if not set_tags and not reset_tags:
@ -159,13 +260,16 @@ class DraftTagsStateForm(StreamDraftForm):
def save_state(self):
comment = self.cleaned_data.get('comment')
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import State
state = State.objects.get(pk=self.cleaned_data.get('new_state'))
weeks = self.cleaned_data.get('weeks')
estimated_date = None
if weeks:
now = datetime.date.today()
estimated_date = now + datetime.timedelta(weeks=weeks)
update_state(obj=self.draft,
update_state(self.draft,
comment=comment,
person=self.person,
to_state=state,
@ -173,15 +277,27 @@ class DraftTagsStateForm(StreamDraftForm):
def save(self):
self.save_tags()
if 'only_tags' in self.data.keys():
return
self.save_state()
if 'only_tags' not in self.data.keys():
self.save_state()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
comment = self.cleaned_data.get('comment').strip()
if comment:
e = DocEvent(type="added_comment")
e.time = self.draft.time
e.by = self.person
e.doc_id = self.draft.pk
e.desc = comment
e.save()
class DraftStreamForm(StreamDraftForm):
comment = forms.CharField(widget=forms.Textarea)
stream = forms.ModelChoiceField(Stream.objects.all())
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
stream = forms.ModelChoiceField(DocStreamName.objects.exclude(slug="legacy"))
else:
stream = forms.ModelChoiceField(Stream.objects.all())
template = 'ietfworkflows/stream_form.html'
@ -193,7 +309,7 @@ class DraftStreamForm(StreamDraftForm):
self.fields['stream'].initial = self.stream.pk
def save(self):
comment = self.cleaned_data.get('comment')
comment = self.cleaned_data.get('comment').strip()
to_stream = self.cleaned_data.get('stream')
update_stream(self.draft,
@ -201,6 +317,13 @@ class DraftStreamForm(StreamDraftForm):
person=self.person,
to_stream=to_stream)
if comment:
e = DocEvent(type="added_comment")
e.time = self.draft.time
e.by = self.person
e.doc_id = self.draft.pk
e.desc = comment
e.save()
class StreamDelegatesForm(forms.Form):
email = forms.EmailField()

View file

@ -2,6 +2,7 @@ from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from ietf.idtracker.models import PersonOrOrgInfo, InternetDraft, Role, IRTF
from ietf.utils.admin import admin_link
@ -262,3 +263,6 @@ class StreamedID(models.Model):
class StreamDelegate(models.Model):
stream = models.ForeignKey(Stream)
person = models.ForeignKey(PersonOrOrgInfo)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.name.proxy import StreamProxy as Stream

View file

@ -1,10 +1,21 @@
from django.db import models
from django.conf import settings
from ietf.idtracker.models import InternetDraft
from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
from ietf.ietfworkflows.models import StreamedID, Stream
def get_streamed_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
class Dummy: pass
o = Dummy()
o.draft = draft
o.stream = super(InternetDraft, draft).stream
o.group = draft.group
o.get_group = lambda x: draft.group
return o
if not draft:
return None
try:
@ -14,6 +25,11 @@ def get_streamed_draft(draft):
def get_stream_from_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
s = super(InternetDraft, draft).stream
s.with_groups = s.slug in ["ietf", "irtf"]
return s
streamedid = get_streamed_draft(draft)
if streamedid:
return streamedid.stream
@ -21,6 +37,9 @@ def get_stream_from_draft(draft):
def get_stream_by_name(stream_name):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise NotImplementedError
try:
return Stream.objects.get(name=stream_name)
except Stream.DoesNotExist:
@ -28,6 +47,9 @@ def get_stream_by_name(stream_name):
def get_stream_from_id(stream_id):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise NotImplementedError
try:
return Stream.objects.get(id=stream_id)
except Stream.DoesNotExist:
@ -35,6 +57,8 @@ def get_stream_from_id(stream_id):
def _set_stream_automatically(draft, stream):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise NotImplementedError
(streamed, created) = StreamedID.objects.get_or_create(draft=draft)
if created:
streamed.stream = stream
@ -51,6 +75,9 @@ def get_stream_from_wrapper(idrfc_wrapper):
if not idwrapper:
return None
draft = idwrapper._draft
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return super(InternetDraft, draft).stream
stream = get_stream_from_draft(draft)
if stream == False:
stream_id = idwrapper.stream_id()
@ -63,6 +90,9 @@ def get_stream_from_wrapper(idrfc_wrapper):
def set_stream_for_draft(draft, stream):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
raise NotImplementedError
(streamed, created) = StreamedID.objects.get_or_create(draft=draft)
if streamed.stream != stream:
streamed.stream = stream

View file

@ -1,4 +1,6 @@
from django import template
from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
from ietf.ietfworkflows.utils import (get_workflow_for_draft,
@ -8,8 +10,7 @@ from ietf.wgchairs.accounts import (can_manage_shepherd_of_a_document,
from ietf.ietfworkflows.streams import get_stream_from_wrapper
from ietf.ietfworkflows.models import Stream
from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream,
is_chair_of_stream)
is_chair_of_stream, can_adopt)
register = template.Library()
@ -64,15 +65,15 @@ def edit_actions(context, wrapper):
idwrapper = wrapper
if not idwrapper:
return None
draft = idwrapper._draft
return {
'can_edit_state': can_edit_state(user, draft),
'can_edit_stream': can_edit_stream(user, draft),
'can_writeup': can_manage_writeup_of_a_document(user, draft),
'can_shepherd': can_manage_shepherd_of_a_document(user, draft),
'draft': draft,
'doc': wrapper,
}
doc = wrapper
possible_actions = [
("Adopt in WG", can_adopt(user, draft), urlreverse('edit_adopt', kwargs=dict(name=doc.draft_name))) if settings.USE_DB_REDESIGN_PROXY_CLASSES else ("", False, ""),
("Change stream state", can_edit_state(user, draft), urlreverse('edit_state', kwargs=dict(name=doc.draft_name))),
("Change stream", can_edit_stream(user, draft), urlreverse('edit_stream', kwargs=dict(name=doc.draft_name))),
("Change shepherd", can_manage_shepherd_of_a_document(user, draft), urlreverse('doc_managing_shepherd', kwargs=dict(acronym=draft.group.acronym, name=draft.filename))),
("Change stream writeup", can_manage_writeup_of_a_document(user, draft), urlreverse('doc_managing_writeup', kwargs=dict(acronym=draft.group.acronym, name=draft.filename))),
]
return dict(actions=[(url, action_name) for action_name, active, url, in possible_actions if active])
class StreamListNode(template.Node):
@ -85,6 +86,8 @@ class StreamListNode(template.Node):
user = self.user.resolve(context)
streams = []
for i in Stream.objects.all():
if "Legacy" in i.name:
continue
if is_chair_of_stream(user, i):
streams.append(i)
context.update({self.var_name: streams})

173
ietf/ietfworkflows/tests.py Normal file
View file

@ -0,0 +1,173 @@
import datetime, os, shutil
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse as urlreverse
import django.test
from StringIO import StringIO
from pyquery import PyQuery
from ietf.utils.test_utils import login_testing_unauthorized
from ietf.utils.test_data import make_test_data
from ietf.utils.mail import outbox
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.person.models import Person, Email
from redesign.group.models import Group, Role
from redesign.doc.models import Document, State
from redesign.doc.utils import *
from redesign.name.models import DocTagName
class EditStreamInfoTestCase(django.test.TestCase):
fixtures = ['names']
def test_adopt_document(self):
draft = make_test_data()
draft.stream_id = "ise"
draft.group = Group.objects.get(type="individ")
draft.unset_state("draft-stream-ietf")
draft.save()
url = urlreverse('edit_adopt', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "marschairman", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form input[type=submit][value*=adopt]')), 1)
self.assertEquals(len(q('form select[name="wg"] option')), 1) # we can only select "mars"
# adopt in mars WG
mailbox_before = len(outbox)
events_before = draft.docevent_set.count()
r = self.client.post(url,
dict(comment="some comment",
wg=Group.objects.get(acronym="mars").pk,
weeks="10"))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEquals(draft.group.acronym, "mars")
self.assertEquals(draft.stream_id, "ietf")
self.assertEquals(draft.docevent_set.count() - events_before, 4)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("state changed" in outbox[-1]["Subject"].lower())
self.assertTrue("wgchairman@ietf.org" in unicode(outbox[-1]))
self.assertTrue("wgdelegate@ietf.org" in unicode(outbox[-1]))
def test_set_tags(self):
draft = make_test_data()
draft.tags = DocTagName.objects.filter(slug="w-expert")
draft.group.unused_tags.add("w-refdoc")
url = urlreverse('edit_state', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "marschairman", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
# make sure the unused tags are hidden
unused = draft.group.unused_tags.values_list("slug", flat=True)
for t in q("input[name=tags]"):
self.assertTrue(t.attrib["value"] not in unused)
self.assertEquals(len(q('form input[type=submit][name=only_tags]')), 1)
# set tags
mailbox_before = len(outbox)
events_before = draft.docevent_set.count()
r = self.client.post(url,
dict(comment="some comment",
weeks="10",
tags=["need-aut", "sheph-u"],
only_tags="1",
# unused but needed for validation
new_state=draft.get_state("draft-stream-%s" % draft.stream_id).pk,
))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEquals(draft.tags.count(), 2)
self.assertEquals(draft.tags.filter(slug="w-expert").count(), 0)
self.assertEquals(draft.tags.filter(slug="need-aut").count(), 1)
self.assertEquals(draft.tags.filter(slug="sheph-u").count(), 1)
self.assertEquals(draft.docevent_set.count() - events_before, 2)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("tags changed" in outbox[-1]["Subject"].lower())
self.assertTrue("wgchairman@ietf.org" in unicode(outbox[-1]))
self.assertTrue("wgdelegate@ietf.org" in unicode(outbox[-1]))
self.assertTrue("plain@example.com" in unicode(outbox[-1]))
def test_set_state(self):
draft = make_test_data()
url = urlreverse('edit_state', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "marschairman", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
# make sure the unused states are hidden
unused = draft.group.unused_states.values_list("pk", flat=True)
for t in q("select[name=new_state]").find("option[name=tags]"):
self.assertTrue(t.attrib["value"] not in unused)
self.assertEquals(len(q('select[name=new_state]')), 1)
# set state
new_state = State.objects.get(type="draft-stream-%s" % draft.stream_id, slug="parked")
mailbox_before = len(outbox)
events_before = draft.docevent_set.count()
r = self.client.post(url,
dict(comment="some comment",
weeks="10",
tags=[x.pk for x in draft.tags.filter(slug__in=get_tags_for_stream_id(draft.stream_id))],
new_state=new_state.pk,
))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEquals(draft.get_state("draft-stream-%s" % draft.stream_id), new_state)
self.assertEquals(draft.docevent_set.count() - events_before, 2)
reminder = DocReminder.objects.filter(event__doc=draft, type="stream-s")
self.assertEquals(len(reminder), 1)
due = datetime.datetime.now() + datetime.timedelta(weeks=10)
self.assertTrue(due - datetime.timedelta(days=1) <= reminder[0].due <= due + datetime.timedelta(days=1))
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("state changed" in outbox[-1]["Subject"].lower())
self.assertTrue("wgchairman@ietf.org" in unicode(outbox[-1]))
self.assertTrue("wgdelegate@ietf.org" in unicode(outbox[-1]))
def test_set_stream(self):
draft = make_test_data()
url = urlreverse('edit_stream', kwargs=dict(name=draft.name))
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('select[name=stream]')), 1)
# set state
mailbox_before = len(outbox)
events_before = draft.docevent_set.count()
r = self.client.post(url,
dict(comment="some comment",
stream="irtf",
))
self.assertEquals(r.status_code, 302)
draft = Document.objects.get(pk=draft.pk)
self.assertEquals(draft.stream_id, "irtf")
self.assertEquals(draft.docevent_set.count() - events_before, 2)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("stream changed" in outbox[-1]["Subject"].lower())
self.assertTrue("wgchairman@ietf.org" in unicode(outbox[-1]))
self.assertTrue("wgdelegate@ietf.org" in unicode(outbox[-1]))
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
# the above tests only work with the new schema
del EditStreamInfoTestCase

View file

@ -4,6 +4,7 @@ from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('ietf.ietfworkflows.views',
url(r'^(?P<name>[^/]+)/history/$', 'stream_history', name='stream_history'),
url(r'^(?P<name>[^/]+)/edit/adopt/$', 'edit_adopt', name='edit_adopt'),
url(r'^(?P<name>[^/]+)/edit/state/$', 'edit_state', name='edit_state'),
url(r'^(?P<name>[^/]+)/edit/stream/$', 'edit_stream', name='edit_stream'),
url(r'^delegates/(?P<stream_name>[^/]+)/$', 'stream_delegates', name='stream_delegates'),

View file

@ -5,6 +5,7 @@ from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.mail import EmailMessage
from django.template.loader import render_to_string
from django.template.defaultfilters import pluralize
from workflows.models import State, StateObjectRelation
from workflows.utils import (get_workflow_for_object, set_workflow_for_object,
@ -16,7 +17,10 @@ from ietf.ietfworkflows.models import (WGWorkflow, AnnotationTagObjectRelation,
AnnotationTag, ObjectAnnotationTagHistoryEntry,
ObjectHistoryEntry, StateObjectRelationMetadata,
ObjectWorkflowHistoryEntry, ObjectStreamHistoryEntry)
from ietf.idtracker.models import InternetDraft
from ietf.utils.mail import send_mail
from redesign.doc.models import Document, DocEvent, save_document_in_history, DocReminder, DocReminderTypeName
from redesign.group.models import Role
WAITING_WRITEUP = 'WG Consensus: Waiting for Write-Up'
FOLLOWUP_TAG = 'Doc Shepherd Follow-up Underway'
@ -77,6 +81,9 @@ def get_workflow_for_wg(wg, default=None):
def get_workflow_for_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return True if get_streamed_draft(draft) else None
workflow = get_workflow_for_object(draft)
try:
workflow = workflow and workflow.wgworkflow
@ -100,6 +107,10 @@ def get_workflow_for_draft(draft):
def get_workflow_history_for_draft(draft, entry_type=None):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.proxy import ObjectHistoryEntryProxy
return ObjectHistoryEntryProxy.objects.filter(doc=draft).order_by('-time', '-id').select_related('by')
ctype = ContentType.objects.get_for_model(draft)
filter_param = {'content_type': ctype,
'content_id': draft.pk}
@ -112,12 +123,18 @@ def get_workflow_history_for_draft(draft, entry_type=None):
def get_annotation_tags_for_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.name.proxy import AnnotationTagObjectRelationProxy
return AnnotationTagObjectRelationProxy.objects.filter(document=draft.pk)
ctype = ContentType.objects.get_for_model(draft)
tags = AnnotationTagObjectRelation.objects.filter(content_type=ctype, content_id=draft.pk)
return tags
def get_state_for_draft(draft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return draft.get_state("draft-stream-%s" % draft.stream_id)
return get_state(draft)
@ -222,8 +239,55 @@ def notify_state_entry(entry, extra_notify=[]):
def notify_stream_entry(entry, extra_notify=[]):
return notify_entry(entry, 'ietfworkflows/stream_updated_mail.txt', extra_notify)
def get_notification_receivers(doc, extra_notify):
persons = set()
res = []
for r in Role.objects.filter(group=doc.group, name__in=("chair", "delegate")):
res.append(u'"%s" <%s>' % (r.person.name, r.email.address))
persons.add(r.person)
for email in doc.authors.all():
if email.person not in persons:
res.append(email.formatted_email())
persons.add(email.person)
for x in extra_notify:
if not x in res:
res.append(x)
return res
def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[]):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
doc = Document.objects.get(pk=obj.pk)
save_document_in_history(doc)
obj.tags.remove(*reset_tags)
obj.tags.add(*set_tags)
doc.time = datetime.datetime.now()
e = DocEvent(type="changed_document", time=doc.time, by=person, doc=doc)
l = []
if set_tags:
l.append(u"Annotation tag%s %s set." % (pluralize(set_tags), ", ".join(x.name for x in set_tags)))
if reset_tags:
l.append(u"Annotation tag%s %s cleared." % (pluralize(reset_tags), ", ".join(x.name for x in reset_tags)))
e.desc = " ".join(l)
e.save()
receivers = get_notification_receivers(doc, extra_notify)
send_mail(None, receivers, settings.DEFAULT_FROM_EMAIL,
u"Annotations tags changed for draft %s" % doc.name,
'ietfworkflows/annotation_tags_updated_mail.txt',
dict(doc=doc,
entry=dict(setted=", ".join(x.name for x in set_tags),
unsetted=", ".join(x.name for x in reset_tags),
change_date=doc.time,
person=person,
comment=comment)))
return
ctype = ContentType.objects.get_for_model(obj)
setted = []
resetted = []
@ -252,15 +316,60 @@ def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[
notify_tag_entry(entry, extra_notify)
def update_state(obj, comment, person, to_state, estimated_date=None, extra_notify=[]):
ctype = ContentType.objects.get_for_model(obj)
from_state = get_state_for_draft(obj)
to_state = set_state_for_draft(obj, to_state, estimated_date)
def update_state(doc, comment, person, to_state, estimated_date=None, extra_notify=[]):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
doc = Document.objects.get(pk=doc.pk)
save_document_in_history(doc)
doc.time = datetime.datetime.now()
from_state = doc.get_state("draft-stream-%s" % doc.stream_id)
doc.set_state(to_state)
e = DocEvent(type="changed_document", time=doc.time, by=person, doc=doc)
e.desc = u"%s changed to <b>%s</b> from %s" % (to_state.type.label, to_state, from_state)
e.save()
# reminder
reminder_type = DocReminderTypeName.objects.get(slug="stream-s")
try:
reminder = DocReminder.objects.get(event__doc=doc, type=reminder_type,
active=True)
except DocReminder.DoesNotExist:
reminder = None
if estimated_date:
if not reminder:
reminder = DocReminder(type=reminder_type)
reminder.event = e
reminder.due = estimated_date
reminder.active = True
reminder.save()
elif reminder:
reminder.active = False
reminder.save()
receivers = get_notification_receivers(doc, extra_notify)
send_mail(None, receivers, settings.DEFAULT_FROM_EMAIL,
u"State changed for draft %s" % doc.name,
'ietfworkflows/state_updated_mail.txt',
dict(doc=doc,
entry=dict(from_state=from_state,
to_state=to_state,
transition_date=doc.time,
person=person,
comment=comment)))
return
ctype = ContentType.objects.get_for_model(doc)
from_state = get_state_for_draft(doc)
to_state = set_state_for_draft(doc, to_state, estimated_date)
if not to_state:
return False
entry = ObjectWorkflowHistoryEntry.objects.create(
content_type=ctype,
content_id=obj.pk,
content_id=doc.pk,
from_state=from_state and from_state.name or '',
to_state=to_state and to_state.name or '',
date=datetime.datetime.now(),
@ -269,13 +378,38 @@ def update_state(obj, comment, person, to_state, estimated_date=None, extra_noti
notify_state_entry(entry, extra_notify)
def update_stream(obj, comment, person, to_stream, extra_notify=[]):
ctype = ContentType.objects.get_for_model(obj)
from_stream = get_stream_from_draft(obj)
to_stream = set_stream_for_draft(obj, to_stream)
def update_stream(doc, comment, person, to_stream, extra_notify=[]):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
doc = Document.objects.get(pk=doc.pk)
save_document_in_history(doc)
doc.time = datetime.datetime.now()
from_stream = doc.stream
doc.stream = to_stream
doc.save()
e = DocEvent(type="changed_stream", time=doc.time, by=person, doc=doc)
e.desc = u"Stream changed to <b>%s</b> from %s" % (to_stream.name, from_stream.name)
e.save()
receivers = get_notification_receivers(doc, extra_notify)
send_mail(None, receivers, settings.DEFAULT_FROM_EMAIL,
u"Stream changed for draft %s" % doc.name,
'ietfworkflows/stream_updated_mail.txt',
dict(doc=doc,
entry=dict(from_stream=from_stream,
to_stream=to_stream,
transition_date=doc.time,
person=person,
comment=comment)))
return
ctype = ContentType.objects.get_for_model(doc)
from_stream = get_stream_from_draft(doc)
to_stream = set_stream_for_draft(doc, to_stream)
entry = ObjectStreamHistoryEntry.objects.create(
content_type=ctype,
content_id=obj.pk,
content_id=doc.pk,
from_stream=from_stream and from_stream.name or '',
to_stream=to_stream and to_stream.name or '',
date=datetime.datetime.now(),

View file

@ -1,6 +1,7 @@
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from django.conf import settings
from ietf.idtracker.models import InternetDraft
from ietf.ietfworkflows.models import Stream, StreamDelegate
@ -15,7 +16,9 @@ from ietf.ietfworkflows.utils import (get_workflow_history_for_draft,
get_annotation_tags_for_draft,
get_state_for_draft)
from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream,
is_chair_of_stream)
is_chair_of_stream, can_adopt)
from redesign.doc.utils import get_tags_for_stream_id
from redesign.name.models import DocTagName
REDUCED_HISTORY_LEN = 20
@ -27,16 +30,23 @@ def stream_history(request, name):
stream = get_stream_from_draft(draft)
workflow = get_workflow_for_draft(draft)
tags = []
if workflow:
tags_setted = [i.annotation_tag.pk for i in get_annotation_tags_for_draft(draft)]
for tag in workflow.get_tags():
tag.setted = tag.pk in tags_setted
tags.append(tag)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
used = list(draft.tags.all())
tags = DocTagName.objects.filter(slug__in=get_tags_for_stream_id(draft.stream_id))
for t in tags:
t.setted = t.slug in used
else:
if workflow:
tags_setted = [i.annotation_tag.pk for i in get_annotation_tags_for_draft(draft)]
for tag in workflow.get_tags():
tag.setted = tag.pk in tags_setted
tags.append(tag)
state = get_state_for_draft(draft)
history = get_workflow_history_for_draft(draft)
show_more = False
if history.count > REDUCED_HISTORY_LEN:
if len(history) > REDUCED_HISTORY_LEN:
show_more = True
return render_to_response('ietfworkflows/stream_history.html',
{'stream': stream,
'streamed': streamed,
@ -77,18 +87,29 @@ def _edit_draft_stream(request, draft, form_class=DraftTagsStateForm):
},
context_instance=RequestContext(request))
# these three views are reusing the same view really, which apart from
# being somewhat obscure means that there are subtle bugs (like the
# title being wrong) - would probably be better to switch to a model
# where each part is edited on its own, we come from an overview page
# anyway, so there's not a big win in putting in a common
# overview/edit page here
def edit_adopt(request, name):
draft = get_object_or_404(InternetDraft, filename=name)
if not can_adopt(request.user, draft):
return HttpResponseForbidden("You don't have permission to access this view")
return _edit_draft_stream(request, draft, NoWorkflowStateForm)
def edit_state(request, name):
draft = get_object_or_404(InternetDraft, filename=name)
if not can_edit_state(request.user, draft):
return HttpResponseForbidden('You have no permission to access this view')
return HttpResponseForbidden("You don't have permission to access this view")
return _edit_draft_stream(request, draft, DraftTagsStateForm)
def edit_stream(request, name):
draft = get_object_or_404(InternetDraft, filename=name)
if not can_edit_stream(request.user, draft):
return HttpResponseForbidden('You have no permission to access this view')
return HttpResponseForbidden("You don't have permission to access this view")
return _edit_draft_stream(request, draft, DraftStreamForm)

View file

@ -1,5 +1,6 @@
#coding: utf-8
from django.contrib import admin
from django.conf import settings
from ietf.ipr.models import *
class IprContactAdmin(admin.ModelAdmin):
@ -14,7 +15,8 @@ admin.site.register(IprDetail, IprDetailAdmin)
class IprDraftAdmin(admin.ModelAdmin):
pass
admin.site.register(IprDraft, IprDraftAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(IprDraft, IprDraftAdmin)
class IprLicensingAdmin(admin.ModelAdmin):
pass
@ -26,7 +28,8 @@ admin.site.register(IprNotification, IprNotificationAdmin)
class IprRfcAdmin(admin.ModelAdmin):
pass
admin.site.register(IprRfc, IprRfcAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(IprRfc, IprRfcAdmin)
class IprSelecttypeAdmin(admin.ModelAdmin):
pass

View file

@ -1,6 +1,7 @@
# Copyright The IETF Trust 2007, All Rights Reserved
from django.db import models
from django.conf import settings
#from django import newforms as forms
from ietf.idtracker.views import InternetDraft
from ietf.idtracker.models import Rfc
@ -117,6 +118,8 @@ class IprDetail(models.Model):
def __unicode__(self):
return self.title.decode("latin-1", 'replace')
def docs(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return list(IprDraftProxy.objects.filter(ipr=self))
return list(self.drafts.all()) + list(self.rfcs.all())
@models.permalink
def get_absolute_url(self):
@ -155,8 +158,8 @@ class IprContact(models.Model):
class IprDraft(models.Model):
ipr = models.ForeignKey(IprDetail, related_name='drafts')
document = models.ForeignKey(InternetDraft, db_column='id_document_tag', related_name="ipr")
ipr = models.ForeignKey(IprDetail, related_name='drafts_old' if settings.USE_DB_REDESIGN_PROXY_CLASSES else 'drafts')
document = models.ForeignKey(InternetDraft, db_column='id_document_tag', related_name="ipr_draft_old" if settings.USE_DB_REDESIGN_PROXY_CLASSES else "ipr")
revision = models.CharField(max_length=2)
def __str__(self):
return "%s which applies to %s-%s" % ( self.ipr, self.document, self.revision )
@ -174,8 +177,8 @@ class IprNotification(models.Model):
db_table = 'ipr_notifications'
class IprRfc(models.Model):
ipr = models.ForeignKey(IprDetail, related_name='rfcs')
document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr")
ipr = models.ForeignKey(IprDetail, related_name='rfcs_old' if settings.USE_DB_REDESIGN_PROXY_CLASSES else 'rfcs')
document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr_rfc_old" if settings.USE_DB_REDESIGN_PROXY_CLASSES else "ipr")
def __str__(self):
return "%s applies to RFC%04d" % ( self.ipr, self.document_id )
class Meta:
@ -190,6 +193,68 @@ class IprUpdate(models.Model):
class Meta:
db_table = 'ipr_updates'
if settings.USE_DB_REDESIGN_PROXY_CLASSES or hasattr(settings, "IMPORTING_IPR"):
from doc.models import DocAlias
class IprDocAlias(models.Model):
ipr = models.ForeignKey(IprDetail, related_name='documents')
doc_alias = models.ForeignKey(DocAlias)
rev = models.CharField(max_length=2, blank=True)
def __unicode__(self):
if self.rev:
return u"%s which applies to %s-%s" % (self.ipr, self.doc_alias.name, self.rev)
else:
return u"%s which applies to %s" % (self.ipr, self.doc_alias.name)
# proxy stuff
IprDraftOld = IprDraft
IprRfcOld = IprRfc
from redesign.proxy_utils import TranslatingManager
class IprDraftProxy(IprDocAlias):
objects = TranslatingManager(dict(document="doc_alias__name"))
# document = models.ForeignKey(InternetDraft, db_column='id_document_tag', "ipr")
# document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr")
@property
def document(self):
from redesign.doc.proxy import DraftLikeDocAlias
return DraftLikeDocAlias.objects.get(pk=self.doc_alias_id)
#revision = models.CharField(max_length=2)
@property
def revision(self):
return self.rev
class Meta:
proxy = True
IprDraft = IprDraftProxy
class IprRfcProxy(IprDocAlias):
objects = TranslatingManager(dict(document=lambda v: ("doc_alias__name", "rfc%s" % v)))
# document = models.ForeignKey(InternetDraft, db_column='id_document_tag', "ipr")
# document = models.ForeignKey(Rfc, db_column='rfc_number', related_name="ipr")
@property
def document(self):
from redesign.doc.proxy import DraftLikeDocAlias
return DraftLikeDocAlias.objects.get(pk=self.doc_alias_id)
#revision = models.CharField(max_length=2)
@property
def revision(self):
return self.rev
class Meta:
proxy = True
IprRfc = IprRfcProxy
# changes done by convert-096.py:changed maxlength to max_length
# removed core
# removed edit_inline

View file

@ -102,11 +102,19 @@ def new(request, type, update=None, submitter=None):
setattr(self, contact, ContactForm(prefix=contact[:4], initial=contact_initial.get(contact, {}), *args, **kwnoinit))
rfclist_initial = ""
if update:
rfclist_initial = " ".join(["RFC%d" % rfc.document_id for rfc in update.rfcs.all()])
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.ipr.models import IprDocAlias
rfclist_initial = " ".join(a.doc_alias.name.upper() for a in IprDocAlias.objects.filter(doc_alias__name__startswith="rfc", ipr=update))
else:
rfclist_initial = " ".join(["RFC%d" % rfc.document_id for rfc in update.rfcs.all()])
self.base_fields["rfclist"] = forms.CharField(required=False, initial=rfclist_initial)
draftlist_initial = ""
if update:
draftlist_initial = " ".join([draft.document.filename + (draft.revision and "-%s" % draft.revision or "") for draft in update.drafts.all()])
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.ipr.models import IprDocAlias
draftlist_initial = " ".join(a.doc_alias.name + ("-%s" % a.rev if a.rev else "") for a in IprDocAlias.objects.filter(ipr=update).exclude(doc_alias__name__startswith="rfc"))
else:
draftlist_initial = " ".join([draft.document.filename + (draft.revision and "-%s" % draft.revision or "") for draft in update.drafts.all()])
self.base_fields["draftlist"] = forms.CharField(required=False, initial=draftlist_initial)
if section_list.get("holder_contact", False):
self.base_fields["hold_contact_is_submitter"] = forms.BooleanField(required=False)
@ -134,7 +142,11 @@ def new(request, type, update=None, submitter=None):
rfclist = rfclist.strip().split()
for rfc in rfclist:
try:
Rfc.objects.get(rfc_number=int(rfc))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import DocAlias
DocAlias.objects.get(name="rfc%s" % int(rfc))
else:
Rfc.objects.get(rfc_number=int(rfc))
except:
raise forms.ValidationError("Unknown RFC number: %s - please correct this." % rfc)
rfclist = " ".join(rfclist)
@ -155,7 +167,13 @@ def new(request, type, update=None, submitter=None):
filename = draft
rev = None
try:
id = InternetDraft.objects.get(filename=filename)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import DocAlias
id = DocAlias.objects.get(name=filename)
# proxy attribute for code below
id.revision = id.document.rev
else:
id = InternetDraft.objects.get(filename=filename)
except Exception, e:
log("Exception: %s" % e)
raise forms.ValidationError("Unknown Internet-Draft: %s - please correct this." % filename)
@ -263,15 +281,32 @@ def new(request, type, update=None, submitter=None):
# Save IprDraft(s)
for draft in form.cleaned_data["draftlist"].split():
id = InternetDraft.objects.get(filename=draft[:-3])
iprdraft = models.IprDraft(document=id, ipr=instance, revision=draft[-2:])
iprdraft.save()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
name = draft[:-3]
rev = draft[-2:]
from redesign.doc.models import DocAlias
models.IprDocAlias.objects.create(
doc_alias=DocAlias.objects.get(name=name),
ipr=instance,
rev=rev)
else:
id = InternetDraft.objects.get(filename=draft[:-3])
iprdraft = models.IprDraft(document=id, ipr=instance, revision=draft[-2:])
iprdraft.save()
# Save IprRfc(s)
for rfcnum in form.cleaned_data["rfclist"].split():
rfc = Rfc.objects.get(rfc_number=int(rfcnum))
iprrfc = models.IprRfc(document=rfc, ipr=instance)
iprrfc.save()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.models import DocAlias
models.IprDocAlias.objects.create(
doc_alias=DocAlias.objects.get(name="rfc%s" % int(rfcnum)),
ipr=instance,
rev="")
else:
rfc = Rfc.objects.get(rfc_number=int(rfcnum))
iprrfc = models.IprRfc(document=rfc, ipr=instance)
iprrfc.save()
send_mail(request, settings.IPR_EMAIL_TO, ('IPR Submitter App', 'ietf-ipr@ietf.org'), 'New IPR Submission Notification', "ipr/new_update_email.txt", {"ipr": instance, "update": update})
return render("ipr/submitted.html", {"update": update}, context_instance=RequestContext(request))

View file

@ -1,5 +1,7 @@
# Copyright The IETF Trust 2007, All Rights Reserved
from django.conf import settings
from django.db.models import Q
from ietf.idtracker.models import InternetDraft, Rfc
inverse = {
@ -77,3 +79,59 @@ def related_docs(doc, found = []):
set_relation(doc, 'is_draft_of', item)
found = related_docs(item, found)
return found
def related_docsREDESIGN(alias, _):
"""Get related document aliases to given alias through depth-first search."""
from redesign.doc.models import RelatedDocument
from redesign.doc.proxy import DraftLikeDocAlias
mapping = dict(
updates='that updated',
obs='that obsoleted',
replaces='that replaced',
)
inverse_mapping = dict(
updates='that was updated by',
obs='that was obsoleted by',
replaces='that was replaced by',
)
res = [ alias ]
remaining = [ alias ]
while remaining:
a = remaining.pop()
related = RelatedDocument.objects.filter(relationship__in=mapping.keys()).filter(Q(source=a.document) | Q(target=a))
for r in related:
if r.source == a.document:
found = DraftLikeDocAlias.objects.filter(pk=r.target_id)
inverse = True
else:
found = DraftLikeDocAlias.objects.filter(document=r.source)
inverse = False
for x in found:
if not x in res:
x.related = a
x.relation = (inverse_mapping if inverse else mapping)[r.relationship_id]
res.append(x)
remaining.append(x)
# there's one more source of relatedness, a draft can have been published
aliases = DraftLikeDocAlias.objects.filter(document=a.document).exclude(pk__in=[x.pk for x in res])
for oa in aliases:
rel = None
if a.name.startswith("rfc") and oa.name.startswith("draft"):
rel = "that was published as"
elif a.name.startswith("draft") and oa.name.startswith("rfc"):
rel = "which came from"
if rel:
oa.related = a
oa.relation = rel
res.append(oa)
remaining.append(oa)
return res
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
related_docs = related_docsREDESIGN

View file

@ -24,7 +24,11 @@ def mark_last_doc(iprs):
def iprs_from_docs(docs):
iprs = []
for doc in docs:
if isinstance(doc, InternetDraft):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.ipr.models import IprDocAlias
disclosures = [ x.ipr for x in IprDocAlias.objects.filter(doc_alias=doc, ipr__status__in=[1,3]) ]
elif isinstance(doc, InternetDraft):
disclosures = [ item.ipr for item in IprDraft.objects.filter(document=doc, ipr__status__in=[1,3]) ]
elif isinstance(doc, Rfc):
disclosures = [ item.ipr for item in IprRfc.objects.filter(document=doc, ipr__status__in=[1,3]) ]
@ -50,7 +54,11 @@ def patent_file_search(url, q):
return False
def search(request, type="", q="", id=""):
wgs = IETFWG.objects.filter(group_type__group_type_id=1).exclude(group_acronym__acronym='2000').select_related().order_by('acronym.acronym')
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from group.models import Group
wgs = Group.objects.filter(type="wg").exclude(acronym="2000").select_related().order_by("acronym")
else:
wgs = IETFWG.objects.filter(group_type__group_type_id=1).exclude(group_acronym__acronym='2000').select_related().order_by('acronym.acronym')
args = request.REQUEST.items()
if args:
for key, value in args:
@ -70,20 +78,32 @@ def search(request, type="", q="", id=""):
if type == "document_search":
if q:
q = normalize_draftname(q)
start = InternetDraft.objects.filter(filename__contains=q)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.proxy import DraftLikeDocAlias
start = DraftLikeDocAlias.objects.filter(name__contains=q, name__startswith="draft")
else:
start = InternetDraft.objects.filter(filename__contains=q)
if id:
try:
id = int(id,10)
except:
id = -1
start = InternetDraft.objects.filter(id_document_tag=id)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.proxy import DraftLikeDocAlias
start = DraftLikeDocAlias.objects.filter(name=id)
else:
try:
id = int(id,10)
except:
id = -1
start = InternetDraft.objects.filter(id_document_tag=id)
if type == "rfc_search":
if q:
try:
q = int(q, 10)
except:
q = -1
start = Rfc.objects.filter(rfc_number=q)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.proxy import DraftLikeDocAlias
start = DraftLikeDocAlias.objects.filter(name__contains=q, name__startswith="rfc")
else:
start = Rfc.objects.filter(rfc_number=q)
if start.count() == 1:
first = start[0]
doc = str(first)
@ -142,12 +162,20 @@ def search(request, type="", q="", id=""):
# Search by wg acronym
# Document list with IPRs
elif type == "wg_search":
try:
docs = list(InternetDraft.objects.filter(group__acronym=q))
except:
docs = []
docs += [ draft.replaced_by for draft in docs if draft.replaced_by_id ]
docs += list(Rfc.objects.filter(group_acronym=q))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.proxy import DraftLikeDocAlias
try:
docs = list(DraftLikeDocAlias.objects.filter(document__group__acronym=q))
docs += list(DraftLikeDocAlias.objects.filter(document__relateddocument__target__in=docs, document__relateddocument__relationship="replaces"))
except:
docs = []
else:
try:
docs = list(InternetDraft.objects.filter(group__acronym=q))
except:
docs = []
docs += [ draft.replaced_by for draft in docs if draft.replaced_by_id ]
docs += list(Rfc.objects.filter(group_acronym=q))
docs = [ doc for doc in docs if doc.ipr.count() ]
iprs, docs = iprs_from_docs(docs)
@ -158,11 +186,18 @@ def search(request, type="", q="", id=""):
# Search by rfc and id title
# Document list with IPRs
elif type == "title_search":
try:
docs = list(InternetDraft.objects.filter(title__icontains=q))
except:
docs = []
docs += list(Rfc.objects.filter(title__icontains=q))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.doc.proxy import DraftLikeDocAlias
try:
docs = list(DraftLikeDocAlias.objects.filter(document__title__icontains=q))
except:
docs = []
else:
try:
docs = list(InternetDraft.objects.filter(title__icontains=q))
except:
docs = []
docs += list(Rfc.objects.filter(title__icontains=q))
docs = [ doc for doc in docs if doc.ipr.count() ]
iprs, docs = iprs_from_docs(docs)

View file

@ -35,7 +35,7 @@ import unittest
from django.test.client import Client
from django.conf import settings
from ietf.utils.test_utils import SimpleUrlTestCase, RealDatabaseTest, canonicalize_feed, canonicalize_sitemap
import ietf.utils.test_runner as test_runner
from ietf.utils.mail import outbox, empty_outbox
class IprUrlTestCase(SimpleUrlTestCase):
def testUrls(self):
@ -48,6 +48,8 @@ class IprUrlTestCase(SimpleUrlTestCase):
else:
return content
# this test should be ported to run on a test database instead of the
# real database, and possibly expanded
class NewIprTestCase(unittest.TestCase,RealDatabaseTest):
SPECIFIC_DISCLOSURE = {
'legal_name':'Testing Only Please Ignore',
@ -58,6 +60,7 @@ class NewIprTestCase(unittest.TestCase,RealDatabaseTest):
'ietf_telephone':'555-555-0101',
'ietf_email':'test.participant@example.com',
'rfclist':'1149',
'draftlist':'draft-burdis-http-sasl-00',
'patents':'none',
'date_applied':'never',
'country':'nowhere',
@ -74,12 +77,12 @@ class NewIprTestCase(unittest.TestCase,RealDatabaseTest):
def testNewSpecific(self):
print " Testing IPR disclosure submission"
test_runner.mail_outbox = []
empty_outbox
c = Client()
response = c.post('/ipr/new-specific/', self.SPECIFIC_DISCLOSURE)
self.assertEquals(response.status_code, 200)
self.assert_("Your IPR disclosure has been submitted" in response.content)
self.assertEquals(len(test_runner.mail_outbox), 1)
self.assertEquals(len(outbox), 1)
print "OK (1 email found in test outbox)"

View file

@ -48,6 +48,34 @@ def list_drafts(request):
context_instance=RequestContext(request)),
mimetype="text/plain")
def list_draftsREDESIGN(request):
from ipr.models import IprDocAlias
docipr = {}
for o in IprDocAlias.objects.filter(ipr__status=1).select_related("doc_alias"):
name = o.doc_alias.name
if name.startswith("rfc"):
name = name.upper()
if not name in docipr:
docipr[name] = []
docipr[name].append(o.ipr_id)
docs = [ dict(name=name, iprs=sorted(iprs)) for name, iprs in docipr.iteritems() ]
# drafts.html is not an HTML file
return HttpResponse(render_to_string("ipr/drafts.html",
dict(docs=docs),
context_instance=RequestContext(request)),
mimetype="text/plain")
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
list_drafts = list_draftsREDESIGN
# Details views
def show(request, ipr_id=None, removed=None):
@ -93,6 +121,12 @@ def show(request, ipr_id=None, removed=None):
except:
# if file does not exist, iframe is used instead
pass
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.ipr.models import IprDraft, IprRfc
ipr.drafts = IprDraft.objects.filter(ipr=ipr).exclude(doc_alias__name__startswith="rfc").order_by("id")
ipr.rfcs = IprRfc.objects.filter(ipr=ipr).filter(doc_alias__name__startswith="rfc").order_by("id")
return render("ipr/details.html", {"ipr": ipr, "section_list": section_list},
context_instance=RequestContext(request))

View file

@ -1,3 +1,5 @@
from django.conf import settings
from ietf.idtracker.models import Role, PersonOrOrgInfo
@ -141,3 +143,6 @@ def can_edit_liaison(user, liaison):
return (is_sdo_manager_for_outgoing_liaison(person, liaison) or
is_sdo_manager_for_incoming_liaison(person, liaison))
return False
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from accountsREDESIGN import *

View file

@ -0,0 +1,127 @@
from redesign.person.models import Person
from redesign.group.models import Role
from redesign.proxy_utils import proxy_personify_role
LIAISON_EDIT_GROUPS = ['Secretariat'] # this is not working anymore, refers to old auth model
def get_ietf_chair():
try:
return proxy_personify_role(Role.objects.get(name="chair", group__acronym="ietf"))
except Role.DoesNotExist:
return None
def get_iesg_chair():
return get_ietf_chair()
def get_iab_chair():
try:
return proxy_personify_role(Role.objects.get(name="chair", group__acronym="iab"))
except Role.DoesNotExist:
return None
def get_iab_executive_director():
try:
return proxy_personify_role(Role.objects.get(name="execdir", group__acronym="iab"))
except Person.DoesNotExist:
return None
def get_person_for_user(user):
if not user.is_authenticated():
return None
try:
p = user.get_profile()
p.email = lambda: (p.name, p.email_address().address)
return p
except Person.DoesNotExist:
return None
def is_areadirector(person):
return bool(Role.objects.filter(person=person, name="ad", group__state="active", group__type="area"))
def is_wgchair(person):
return bool(Role.objects.filter(person=person, name="chair", group__state="active", group__type="wg"))
def is_wgsecretary(person):
return bool(Role.objects.filter(person=person, name="sec", group__state="active", group__type="wg"))
def is_ietfchair(person):
return bool(Role.objects.filter(person=person, name="chair", group__acronym="ietf"))
def is_iabchair(person):
return bool(Role.objects.filter(person=person, name="chair", group__acronym="iab"))
def is_iab_executive_director(person):
return bool(Role.objects.filter(person=person, name="execdir", group__acronym="iab"))
def can_add_outgoing_liaison(user):
person = get_person_for_user(user)
if not person:
return False
if (is_areadirector(person) or is_wgchair(person) or
is_wgsecretary(person) or is_ietfchair(person) or
is_iabchair(person) or is_iab_executive_director(person) or
is_sdo_liaison_manager(person) or is_secretariat(user)):
return True
return False
def is_sdo_liaison_manager(person):
return bool(Role.objects.filter(person=person, name="liaiman", group__type="sdo"))
def is_sdo_authorized_individual(person):
return bool(Role.objects.filter(person=person, name="auth", group__type="sdo"))
def is_secretariat(user):
return user.is_authenticated() and bool(Role.objects.filter(person__user=user, name="secr", group__acronym="secretariat"))
def can_add_incoming_liaison(user):
person = get_person_for_user(user)
if not person:
return False
if (is_sdo_liaison_manager(person) or
is_sdo_authorized_individual(person) or
is_secretariat(user)):
return True
return False
def can_add_liaison(user):
return can_add_incoming_liaison(user) or can_add_outgoing_liaison(user)
def is_sdo_manager_for_outgoing_liaison(person, liaison):
if liaison.from_group and liaison.from_group.type_id == "sdo":
return bool(liaison.from_group.role_set.filter(name="liaiman", person=person))
return False
def is_sdo_manager_for_incoming_liaison(person, liaison):
if liaison.to_group and liaison.to_group.type_id == "sdo":
return bool(liaison.to_group.role_set.filter(name="liaiman", person=person))
return False
def can_edit_liaison(user, liaison):
if is_secretariat(user):
return True
person = get_person_for_user(user)
if is_sdo_liaison_manager(person):
return (is_sdo_manager_for_outgoing_liaison(person, liaison) or
is_sdo_manager_for_incoming_liaison(person, liaison))
return False

View file

@ -1,5 +1,6 @@
#coding: utf-8
from django import template
from django.conf import settings
from django.contrib import admin
from django.contrib.admin.util import unquote
from django.core.exceptions import PermissionDenied
@ -18,7 +19,8 @@ from ietf.ietfauth.models import LegacyWgPassword, LegacyLiaisonUser
class FromBodiesAdmin(admin.ModelAdmin):
list_display = ['body_name', 'contact_link', 'other_sdo']
admin.site.register(FromBodies, FromBodiesAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(FromBodies, FromBodiesAdmin)
class LiaisonDetailAdmin(admin.ModelAdmin):
@ -28,12 +30,13 @@ class LiaisonDetailAdmin(admin.ModelAdmin):
# 'response_contact', 'technical_contact', 'purpose', 'purpose_text', 'deadline_date', 'action_taken',
# 'related_to')
raw_id_fields=['person', 'related_to']
admin.site.register(LiaisonDetail, LiaisonDetailAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(LiaisonDetail, LiaisonDetailAdmin)
class LiaisonPurposeAdmin(admin.ModelAdmin):
ordering = ('purpose_text', )
admin.site.register(LiaisonPurpose, LiaisonPurposeAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(LiaisonPurpose, LiaisonPurposeAdmin)
class LiaisonManagersAdmin(admin.ModelAdmin):
@ -134,3 +137,13 @@ class LegacyLiaisonUserAdmin(admin.ModelAdmin):
list_display = ['pk', 'person_link', 'login_name', 'user_level', 'comment', ]
raw_id_fields = [ 'person', ]
admin.site.register(LegacyLiaisonUser, LegacyLiaisonUserAdmin)
class LiaisonStatementAdmin(admin.ModelAdmin):
list_display = ['id', 'title', 'from_name', 'to_name', 'submitted', 'purpose', 'related_to']
list_display_links = ['id', 'title']
ordering = ('title', )
raw_id_fields = ('from_contact', 'related_to', 'from_group', 'to_group', 'attachments')
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.liaisons.models import LiaisonStatement
admin.site.register(LiaisonStatement, LiaisonStatementAdmin)

View file

@ -1,5 +1,6 @@
# Copyright The IETF Trust 2007, All Rights Reserved
from django.conf import settings
from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
from django.utils.feedgenerator import Atom1Feed
from django.db.models import Q
@ -8,6 +9,11 @@ from ietf.idtracker.models import Acronym
from datetime import datetime, time
import re
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from redesign.group.models import Group
from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail
from ietf.liaisons.models import LiaisonStatement
# A slightly funny feed class, the 'object' is really
# just a dict with some parameters that items() uses
# to construct a queryset.
@ -24,6 +30,24 @@ class Liaisons(Feed):
if bits[0] == 'from':
if len(bits) != 2:
raise FeedDoesNotExist
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
try:
group = Group.objects.get(acronym=bits[1])
obj['filter'] = { 'from_group': group }
obj['title'] = u'Liaison Statements from %s' % group.name
return obj
except Group.DoesNotExist:
# turn all-nonword characters into one-character
# wildcards to make it easier to construct the URL
search_string = re.sub(r"[^a-zA-Z1-9]", ".", bits[1])
statements = LiaisonStatement.objects.filter(from_name__iregex=search_string)
if statements:
name = statements[0].from_name
obj['filter'] = { 'from_name': name }
obj['title'] = u'Liaison Statements from %s' % name
return obj
else:
raise FeedDoesNotExist
try:
acronym = Acronym.objects.get(acronym=bits[1])
obj['filter'] = {'from_id': acronym.acronym_id}
@ -49,16 +73,22 @@ class Liaisons(Feed):
if bits[0] == 'to':
if len(bits) != 2:
raise FeedDoesNotExist
# The schema uses two different fields for the same
# basic purpose, depending on whether it's a Secretariat-submitted
# or Liaison-tool-submitted document.
obj['q'] = [ (Q(by_secretariat=0) & Q(to_body__icontains=bits[1])) | (Q(by_secretariat=1) & Q(submitter_name__icontains=bits[1])) ]
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
obj['filter'] = dict(to_name__icontains=bits[1])
else:
# The schema uses two different fields for the same
# basic purpose, depending on whether it's a Secretariat-submitted
# or Liaison-tool-submitted document.
obj['q'] = [ (Q(by_secretariat=0) & Q(to_body__icontains=bits[1])) | (Q(by_secretariat=1) & Q(submitter_name__icontains=bits[1])) ]
obj['title'] = 'Liaison Statements where to matches %s' % bits[1]
return obj
if bits[0] == 'subject':
if len(bits) != 2:
raise FeedDoesNotExist
obj['q'] = [ Q(title__icontains=bits[1]) | Q(uploads__file_title__icontains=bits[1]) ]
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
obj['q'] = [ Q(title__icontains=bits[1]) | Q(attachments__title__icontains=bits[1]) ]
else:
obj['q'] = [ Q(title__icontains=bits[1]) | Q(uploads__file_title__icontains=bits[1]) ]
obj['title'] = 'Liaison Statements where subject matches %s' % bits[1]
return obj
raise FeedDoesNotExist

View file

@ -425,3 +425,6 @@ def liaison_form_factory(request, **kwargs):
elif can_add_incoming_liaison(user):
return IncomingLiaisonForm(user, **kwargs)
return None
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.liaisons.formsREDESIGN import *

View file

@ -0,0 +1,454 @@
import datetime, os
from email.utils import parseaddr
from django import forms
from django.conf import settings
from django.db.models import Q
from django.forms.util import ErrorList
from django.forms.fields import email_re
from django.template.loader import render_to_string
from ietf.liaisons.accounts import (can_add_outgoing_liaison, can_add_incoming_liaison,
get_person_for_user, is_secretariat, is_sdo_liaison_manager)
from ietf.liaisons.utils import IETFHM
from ietf.liaisons.widgets import (FromWidget, ReadOnlyWidget, ButtonWidget,
ShowAttachmentsWidget, RelatedLiaisonWidget)
from ietf.liaisons.models import LiaisonStatement, LiaisonStatementPurposeName
from ietf.liaisons.proxy import LiaisonDetailProxy
from redesign.group.models import Group
from redesign.person.models import Person
from redesign.doc.models import Document
class LiaisonForm(forms.Form):
person = forms.ModelChoiceField(Person.objects.all())
from_field = forms.ChoiceField(widget=FromWidget, label=u'From')
replyto = forms.CharField(label=u'Reply to')
organization = forms.ChoiceField()
to_poc = forms.CharField(widget=ReadOnlyWidget, label="POC", required=False)
response_contact = forms.CharField(required=False, max_length=255)
technical_contact = forms.CharField(required=False, max_length=255)
cc1 = forms.CharField(widget=forms.Textarea, label="CC", required=False, help_text='Please insert one email address per line')
purpose = forms.ChoiceField()
purpose_text = forms.CharField(widget=forms.Textarea, label='Other purpose')
deadline_date = forms.DateField(label='Deadline')
submitted_date = forms.DateField(label='Submission date', initial=datetime.date.today())
title = forms.CharField(label=u'Title')
body = forms.CharField(widget=forms.Textarea, required=False)
attachments = forms.CharField(label='Attachments', widget=ShowAttachmentsWidget, required=False)
attach_title = forms.CharField(label='Title', required=False)
attach_file = forms.FileField(label='File', required=False)
attach_button = forms.CharField(label='',
widget=ButtonWidget(label='Attach', show_on='id_attachments',
require=['id_attach_title', 'id_attach_file'],
required_label='title and file'),
required=False)
related_to = forms.ModelChoiceField(LiaisonStatement.objects.all(), label=u'Related Liaison', widget=RelatedLiaisonWidget, required=False)
fieldsets = [('From', ('from_field', 'replyto')),
('To', ('organization', 'to_poc')),
('Other email addresses', ('response_contact', 'technical_contact', 'cc1')),
('Purpose', ('purpose', 'purpose_text', 'deadline_date')),
('References', ('related_to', )),
('Liaison Statement', ('title', 'submitted_date', 'body', 'attachments')),
('Add attachment', ('attach_title', 'attach_file', 'attach_button')),
]
class Media:
js = ("/js/jquery-1.5.1.min.js",
"/js/jquery-ui-1.8.11.custom.min.js",
"/js/liaisons.js", )
css = {'all': ("/css/liaisons.css",
"/css/jquery-ui-themes/jquery-ui-1.8.11.custom.css")}
def __init__(self, user, *args, **kwargs):
self.user = user
self.fake_person = None
self.person = get_person_for_user(user)
if kwargs.get('data', None):
if is_secretariat(self.user) and 'from_fake_user' in kwargs['data'].keys():
self.fake_person = Person.objects.get(pk=kwargs['data']['from_fake_user'])
kwargs['data'].update({'person': self.fake_person.pk})
else:
kwargs['data'].update({'person': self.person.pk})
self.instance = kwargs.pop("instance", None)
super(LiaisonForm, self).__init__(*args, **kwargs)
# now copy in values from instance, like a ModelForm
if self.instance:
for name, field in self.fields.iteritems():
try:
x = getattr(self.instance, name)
if name == "purpose": # proxy has a name-clash on purpose so help it
x = x.order
try:
x = x.pk # foreign keys need the .pk, not the actual object
except AttributeError:
pass
self.initial[name] = x
except AttributeError:
# we have some fields on the form that aren't in the model
pass
self.fields["purpose"].choices = [("", "---------")] + [(str(l.order), l.name) for l in LiaisonStatementPurposeName.objects.all()]
self.hm = IETFHM
self.set_from_field()
self.set_replyto_field()
self.set_organization_field()
def __unicode__(self):
return self.as_div()
def get_post_only(self):
return False
def set_required_fields(self):
purpose = self.data.get('purpose', None)
if purpose == '5':
self.fields['purpose_text'].required=True
else:
self.fields['purpose_text'].required=False
if purpose in ['1', '2']:
self.fields['deadline_date'].required=True
else:
self.fields['deadline_date'].required=False
def reset_required_fields(self):
self.fields['purpose_text'].required=True
self.fields['deadline_date'].required=True
def set_from_field(self):
assert NotImplemented
def set_replyto_field(self):
self.fields['replyto'].initial = self.person.email()[1]
def set_organization_field(self):
assert NotImplemented
def as_div(self):
return render_to_string('liaisons/liaisonform.html', {'form': self})
def get_fieldsets(self):
if not self.fieldsets:
yield dict(name=None, fields=self)
else:
for fieldset, fields in self.fieldsets:
fieldset_dict = dict(name=fieldset, fields=[])
for field_name in fields:
if field_name in self.fields.keyOrder:
fieldset_dict['fields'].append(self[field_name])
if not fieldset_dict['fields']:
# if there is no fields in this fieldset, we continue to next fieldset
continue
yield fieldset_dict
def full_clean(self):
self.set_required_fields()
super(LiaisonForm, self).full_clean()
self.reset_required_fields()
def has_attachments(self):
for key in self.files.keys():
if key.startswith('attach_file_') and key.replace('file', 'title') in self.data.keys():
return True
return False
def check_email(self, value):
if not value:
return
emails = value.split(',')
for email in emails:
name, addr = parseaddr(email)
if not email_re.search(addr):
raise forms.ValidationError('Invalid email address: %s' % addr)
def clean_response_contact(self):
value = self.cleaned_data.get('response_contact', None)
self.check_email(value)
return value
def clean_technical_contact(self):
value = self.cleaned_data.get('technical_contact', None)
self.check_email(value)
return value
def clean_reply_to(self):
value = self.cleaned_data.get('reply_to', None)
self.check_email(value)
return value
def clean(self):
if not self.cleaned_data.get('body', None) and not self.has_attachments():
self._errors['body'] = ErrorList([u'You must provide a body or attachment files'])
self._errors['attachments'] = ErrorList([u'You must provide a body or attachment files'])
return self.cleaned_data
def get_from_entity(self):
organization_key = self.cleaned_data.get('from_field')
return self.hm.get_entity_by_key(organization_key)
def get_to_entity(self):
organization_key = self.cleaned_data.get('organization')
return self.hm.get_entity_by_key(organization_key)
def get_poc(self, organization):
return ', '.join(u"%s <%s>" % i.email() for i in organization.get_poc())
def clean_cc1(self):
value = self.cleaned_data.get('cc1', '')
result = []
errors = []
for address in value.split('\n'):
address = address.strip();
if not address:
continue
try:
self.check_email(address)
except forms.ValidationError:
errors.append(address)
result.append(address)
if errors:
raise forms.ValidationError('Invalid email addresses: %s' % ', '.join(errors))
return ','.join(result)
def get_cc(self, from_entity, to_entity):
#Old automatic Cc code, now we retrive it from cleaned_data
#persons = to_entity.get_cc(self.person)
#persons += from_entity.get_from_cc(self.person)
#return ', '.join(['%s <%s>' % i.email() for i in persons])
cc = self.cleaned_data.get('cc1', '')
return cc
def save(self, *args, **kwargs):
l = self.instance
if not l:
l = LiaisonDetailProxy()
l.title = self.cleaned_data["title"]
l.purpose = LiaisonStatementPurposeName.objects.get(order=self.cleaned_data["purpose"])
l.body = self.cleaned_data["body"].strip()
l.deadline = self.cleaned_data["deadline_date"]
l.related_to = self.cleaned_data["related_to"]
l.reply_to = self.cleaned_data["replyto"]
l.response_contact = self.cleaned_data["response_contact"]
l.technical_contact = self.cleaned_data["technical_contact"]
now = datetime.datetime.now()
l.modified = now
l.submitted = datetime.datetime.combine(self.cleaned_data["submitted_date"], now.time())
if not l.approved:
l.approved = now
self.save_extra_fields(l)
l.save() # we have to save here to make sure we get an id for the attachments
self.save_attachments(l)
return l
def save_extra_fields(self, liaison):
from_entity = self.get_from_entity()
liaison.from_name = from_entity.name
liaison.from_group = from_entity.obj
liaison.from_contact = self.cleaned_data["person"].email_address()
organization = self.get_to_entity()
liaison.to_name = organization.name
liaison.to_group = organization.obj
liaison.to_contact = self.get_poc(organization)
liaison.cc = self.get_cc(from_entity, organization)
def save_attachments(self, instance):
written = instance.attachments.all().count()
for key in self.files.keys():
title_key = key.replace('file', 'title')
if not key.startswith('attach_file_') or not title_key in self.data.keys():
continue
attached_file = self.files.get(key)
extension=attached_file.name.rsplit('.', 1)
if len(extension) > 1:
extension = '.' + extension[1]
else:
extension = ''
written += 1
name = instance.name() + ("-attachment-%s" % written)
attach = Document.objects.create(
title = self.data.get(title_key),
type_id = "liaison",
name = name,
external_url = name + extension, # strictly speaking not necessary, but just for the time being ...
)
instance.attachments.add(attach)
attach_file = open(os.path.join(settings.LIAISON_ATTACH_PATH, attach.name + extension), 'w')
attach_file.write(attached_file.read())
attach_file.close()
def clean_title(self):
title = self.cleaned_data.get('title', None)
if self.instance and self.instance.pk:
exclude_filter = {'pk': self.instance.pk}
else:
exclude_filter = {}
exists = bool(LiaisonStatement.objects.exclude(**exclude_filter).filter(title__iexact=title).count())
if exists:
raise forms.ValidationError('A liaison statement with the same title has previously been submitted.')
return title
class IncomingLiaisonForm(LiaisonForm):
def set_from_field(self):
if is_secretariat(self.user):
sdos = Group.objects.filter(type="sdo", state="active")
else:
sdos = Group.objects.filter(type="sdo", state="active", role__person=self.person, role__name__in=("liaiman", "auth")).distinct()
self.fields['from_field'].choices = [('sdo_%s' % i.pk, i.name) for i in sdos.order_by("name")]
self.fields['from_field'].widget.submitter = unicode(self.person)
def set_organization_field(self):
self.fields['organization'].choices = self.hm.get_all_incoming_entities()
def get_post_only(self):
from_entity = self.get_from_entity()
if is_secretariat(self.user) or Role.objects.filter(person=self.person, group=from_entity.obj, name="auth"):
return False
return True
def clean(self):
if 'send' in self.data.keys() and self.get_post_only():
self._errors['from_field'] = ErrorList([u'As an IETF Liaison Manager you can not send an incoming liaison statements, you only can post them'])
return super(IncomingLiaisonForm, self).clean()
def liaison_manager_sdos(person):
return Group.objects.filter(type="sdo", state="active", role__person=person, role__name="liaiman").distinct()
class OutgoingLiaisonForm(LiaisonForm):
to_poc = forms.CharField(label="POC", required=True)
approved = forms.BooleanField(label="Obtained prior approval", required=False)
other_organization = forms.CharField(label="Other SDO", required=True)
def get_to_entity(self):
organization_key = self.cleaned_data.get('organization')
organization = self.hm.get_entity_by_key(organization_key)
if organization_key == 'othersdo' and self.cleaned_data.get('other_organization', None):
organization.name=self.cleaned_data['other_organization']
return organization
def set_from_field(self):
if is_secretariat(self.user):
self.fields['from_field'].choices = self.hm.get_all_incoming_entities()
elif is_sdo_liaison_manager(self.person):
self.fields['from_field'].choices = self.hm.get_all_incoming_entities()
all_entities = []
for i in self.hm.get_entities_for_person(self.person):
all_entities += i[1]
if all_entities:
self.fields['from_field'].widget.full_power_on = [i[0] for i in all_entities]
self.fields['from_field'].widget.reduced_to_set = ['sdo_%s' % i.pk for i in liaison_manager_sdos(self.person)]
else:
self.fields['from_field'].choices = self.hm.get_entities_for_person(self.person)
self.fields['from_field'].widget.submitter = unicode(self.person)
self.fieldsets[0] = ('From', ('from_field', 'replyto', 'approved'))
def set_organization_field(self):
# If the user is a liaison manager and is nothing more, reduce the To field to his SDOs
if not self.hm.get_entities_for_person(self.person) and is_sdo_liaison_manager(self.person):
self.fields['organization'].choices = [('sdo_%s' % i.pk, i.name) for i in liaison_manager_sdos()]
else:
self.fields['organization'].choices = self.hm.get_all_outgoing_entities()
self.fieldsets[1] = ('To', ('organization', 'other_organization', 'to_poc'))
def set_required_fields(self):
super(OutgoingLiaisonForm, self).set_required_fields()
organization = self.data.get('organization', None)
if organization == 'othersdo':
self.fields['other_organization'].required=True
else:
self.fields['other_organization'].required=False
def reset_required_fields(self):
super(OutgoingLiaisonForm, self).reset_required_fields()
self.fields['other_organization'].required=True
def get_poc(self, organization):
return self.cleaned_data['to_poc']
def save_extra_fields(self, liaison):
super(OutgoingLiaisonForm, self).save_extra_fields(liaison)
from_entity = self.get_from_entity()
needs_approval = from_entity.needs_approval(self.person)
if not needs_approval or self.cleaned_data.get('approved', False):
liaison.approved = datetime.datetime.now()
else:
liaison.approved = None
def clean_to_poc(self):
value = self.cleaned_data.get('to_poc', None)
self.check_email(value)
return value
def clean_organization(self):
to_code = self.cleaned_data.get('organization', None)
from_code = self.cleaned_data.get('from_field', None)
if not to_code or not from_code:
return to_code
all_entities = []
person = self.fake_person or self.person
for i in self.hm.get_entities_for_person(person):
all_entities += i[1]
# If the from entity is one in wich the user has full privileges the to entity could be anyone
if from_code in [i[0] for i in all_entities]:
return to_code
sdo_codes = ['sdo_%s' % i.pk for i in liaison_manager_sdos(self.person)]
if to_code in sdo_codes:
return to_code
entity = self.get_to_entity()
entity_name = entity and entity.name or to_code
if self.fake_person:
raise forms.ValidationError('%s is not allowed to send a liaison to: %s' % (self.fake_person, entity_name))
else:
raise forms.ValidationError('You are not allowed to send a liaison to: %s' % entity_name)
class EditLiaisonForm(LiaisonForm):
from_field = forms.CharField(widget=forms.TextInput, label=u'From')
replyto = forms.CharField(label=u'Reply to', widget=forms.TextInput)
organization = forms.CharField(widget=forms.TextInput)
to_poc = forms.CharField(widget=forms.TextInput, label="POC", required=False)
cc1 = forms.CharField(widget=forms.TextInput, label="CC", required=False)
class Meta:
fields = ('from_raw_body', 'to_body', 'to_poc', 'cc1', 'last_modified_date', 'title',
'response_contact', 'technical_contact', 'purpose_text', 'body',
'deadline_date', 'purpose', 'replyto', 'related_to')
def __init__(self, *args, **kwargs):
super(EditLiaisonForm, self).__init__(*args, **kwargs)
self.edit = True
self.initial.update({'attachments': self.instance.uploads_set.all()})
self.fields['submitted_date'].initial = self.instance.submitted_date
def set_from_field(self):
self.fields['from_field'].initial = self.instance.from_body
def set_replyto_field(self):
self.fields['replyto'].initial = self.instance.replyto
def set_organization_field(self):
self.fields['organization'].initial = self.instance.to_body
def save_extra_fields(self, liaison):
liaison.from_name = self.cleaned_data.get('from_field')
liaison.to_name = self.cleaned_data.get('organization')
liaison.to_contact = self.cleaned_data['to_poc']
liaison.cc = self.cleaned_data['cc1']

128
ietf/liaisons/mails.py Normal file
View file

@ -0,0 +1,128 @@
import datetime
from django.conf import settings
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse as urlreverse
from ietf.utils.mail import send_mail_text
from ietf.liaisons.utils import role_persons_with_fixed_email
from redesign.group.models import Role
def send_liaison_by_email(request, liaison, fake=False):
if liaison.is_pending(): # this conditional should definitely be at the caller, not here
return notify_pending_by_email(request, liaison, fake)
subject = u'New Liaison Statement, "%s"' % (liaison.title)
from_email = settings.LIAISON_UNIVERSAL_FROM
to_email = liaison.to_poc.split(',')
cc = liaison.cc1.split(',')
if liaison.technical_contact:
cc += liaison.technical_contact.split(',')
if liaison.response_contact:
cc += liaison.response_contact.split(',')
bcc = ['statements@ietf.org']
body = render_to_string('liaisons/liaison_mail.txt', dict(
liaison=liaison,
url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.pk)),
referenced_url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.related_to.pk)) if liaison.related_to else None,
))
if fake:
# rather than this fake stuff, it's probably better to start a
# debug SMTP server as explained in the Django docs
from ietf.liaisons.mail import IETFEmailMessage
mail = IETFEmailMessage(subject=subject,
to=to_email,
from_email=from_email,
cc = cc,
bcc = bcc,
body = body)
return mail
send_mail_text(request, to_email, from_email, subject, body, cc=", ".join(cc), bcc=", ".join(bcc))
def notify_pending_by_email(request, liaison, fake):
from ietf.liaisons.utils import IETFHM
from_entity = IETFHM.get_entity_by_key(liaison.from_raw_code)
if not from_entity:
return None
to_email = []
for person in from_entity.can_approve():
to_email.append('%s <%s>' % person.email())
subject = u'New Liaison Statement, "%s" needs your approval' % (liaison.title)
from_email = settings.LIAISON_UNIVERSAL_FROM
body = render_to_string('liaisons/pending_liaison_mail.txt', dict(
liaison=liaison,
url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=liaison.pk)),
referenced_url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.related_to.pk)) if liaison.related_to else None,
))
if fake:
mail = IETFEmailMessage(subject=subject,
to=to_email,
from_email=from_email,
body = body)
return mail
send_mail_text(request, to_email, from_email, subject, body)
def send_sdo_reminder(sdo):
roles = Role.objects.filter(name="liaiman", group=sdo)
if not roles: # no manager to contact
return None
manager_role = roles[0]
subject = 'Request for update of list of authorized individuals'
to_email = manager_role.email.address
name = manager_role.person.name
authorized_list = role_persons_with_fixed_email(sdo, "auth")
body = render_to_string('liaisons/sdo_reminder.txt', dict(
manager_name=name,
sdo_name=sdo.name,
individuals=authorized_list,
))
send_mail_text(None, to_email, settings.LIAISON_UNIVERSAL_FROM, subject, body)
return body
def possibly_send_deadline_reminder(liaison):
PREVIOUS_DAYS = {
14: 'in two weeks',
7: 'in one week',
4: 'in four days',
3: 'in three days',
2: 'in two days',
1: 'tomorrow',
0: 'today'
}
days_to_go = (liaison.deadline - datetime.date.today()).days
if not (days_to_go < 0 or days_to_go in PREVIOUS_DAYS.keys()):
return None # no reminder
if days_to_go < 0:
subject = '[Liaison OUT OF DATE] %s' % liaison.title
days_msg = 'is out of date for %s days' % (-days_to_go)
else:
subject = '[Liaison deadline %s] %s' % (PREVIOUS_DAYS[days_to_go], liaison.title)
days_msg = 'expires %s' % PREVIOUS_DAYS[days_to_go]
from_email = settings.LIAISON_UNIVERSAL_FROM
to_email = liaison.to_contact.split(',')
cc = liaison.cc.split(',')
if liaison.technical_contact:
cc += liaison.technical_contact.split(',')
if liaison.response_contact:
cc += liaison.response_contact.split(',')
bcc = 'statements@ietf.org'
body = render_to_string('liaisons/liaison_deadline_mail.txt',
dict(liaison=liaison,
days_msg=days_msg,
url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=liaison.pk)),
referenced_url=settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.related_to.pk)) if liaison.related_to else None,
))
send_mail_text(None, to_email, from_email, subject, body, cc=cc, bcc=bcc)
return body

View file

@ -3,6 +3,7 @@ import datetime
from django.conf import settings
from django.core.management.base import BaseCommand
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse as urlreverse
from ietf.liaisons.models import LiaisonDetail
from ietf.liaisons.mail import IETFEmailMessage
@ -19,7 +20,7 @@ PREVIOUS_DAYS = {
class Command(BaseCommand):
help = (u"Check liaison deadlines and send a reminder if we are close to its deadline")
help = (u"Check liaison deadlines and send a reminder if we are close to a deadline")
def send_reminder(self, liaison, days_to_go):
if days_to_go < 0:
@ -40,6 +41,8 @@ class Command(BaseCommand):
body = render_to_string('liaisons/liaison_deadline_mail.txt',
{'liaison': liaison,
'days_msg': days_msg,
'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=liaison.pk)),
'referenced_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.related_to.pk)) if liaison.related_to else None,
})
mail = IETFEmailMessage(subject=subject,
to=to_email,
@ -55,6 +58,19 @@ class Command(BaseCommand):
def handle(self, *args, **options):
today = datetime.date.today()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.liaisons.mails import possibly_send_deadline_reminder
from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail
cutoff = today - datetime.timedelta(14)
for l in LiaisonDetail.objects.filter(action_taken=False, deadline__gte=cutoff).exclude(deadline=None):
r = possibly_send_deadline_reminder(l)
if r:
print 'Liaison %05s#: Deadline reminder sent!' % liaison.pk
return
query = LiaisonDetail.objects.filter(deadline_date__isnull=False, action_taken=False, deadline_date__gte=today - datetime.timedelta(14))
for liaison in query:
delta = liaison.deadline_date - today

View file

@ -38,9 +38,14 @@ class Command(BaseCommand):
return msg
def handle(self, *args, **options):
query = SDOs.objects.all().order_by('pk')
sdo_pk = options.get('sdo_pk', None)
return_output = options.get('return_output', False)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
msg_list = send_reminders_to_sdos(sdo_pk=sdo_pk)
return msg_list if return_output else None
query = SDOs.objects.all().order_by('pk')
if sdo_pk:
query = query.filter(pk=sdo_pk)
@ -55,3 +60,31 @@ class Command(BaseCommand):
msg_list.append(msg)
if return_output:
return msg_list
def send_reminders_to_sdos(sdo_pk=None):
from redesign.group.models import Group
from ietf.liaisons.mails import send_sdo_reminder
sdos = Group.objects.filter(type="sdo").order_by('pk')
if sdo_pk:
sdos = sdos.filter(pk=sdo_pk)
if not sdos:
print "No SDOs found!"
msgs = []
for sdo in sdos:
body = send_sdo_reminder(sdo)
if not body:
msg = u'%05s#: %s has no liaison manager' % (sdo.pk, sdo.name)
else:
msg = u'%05s#: %s mail sent!' % (sdo.pk, sdo.name)
print msg
msgs.append(msg)
return msgs

View file

@ -0,0 +1,369 @@
from south.db import db
from django.db import models
from ietf.liaisons.models import *
class Migration:
def forwards(self, orm):
# Adding model 'LiaisonStatement'
db.create_table('liaisons_liaisonstatement', (
('id', orm['liaisons.liaisonstatement:id']),
('title', orm['liaisons.liaisonstatement:title']),
('purpose', orm['liaisons.liaisonstatement:purpose']),
('body', orm['liaisons.liaisonstatement:body']),
('deadline', orm['liaisons.liaisonstatement:deadline']),
('related_to', orm['liaisons.liaisonstatement:related_to']),
('from_group', orm['liaisons.liaisonstatement:from_group']),
('from_name', orm['liaisons.liaisonstatement:from_name']),
('from_contact', orm['liaisons.liaisonstatement:from_contact']),
('to_group', orm['liaisons.liaisonstatement:to_group']),
('to_name', orm['liaisons.liaisonstatement:to_name']),
('to_contact', orm['liaisons.liaisonstatement:to_contact']),
('reply_to', orm['liaisons.liaisonstatement:reply_to']),
('response_contact', orm['liaisons.liaisonstatement:response_contact']),
('technical_contact', orm['liaisons.liaisonstatement:technical_contact']),
('cc', orm['liaisons.liaisonstatement:cc']),
('submitted', orm['liaisons.liaisonstatement:submitted']),
('modified', orm['liaisons.liaisonstatement:modified']),
('approved', orm['liaisons.liaisonstatement:approved']),
('action_taken', orm['liaisons.liaisonstatement:action_taken']),
))
db.send_create_signal('liaisons', ['LiaisonStatement'])
# Adding ManyToManyField 'LiaisonStatement.attachments'
db.create_table('liaisons_liaisonstatement_attachments', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('liaisonstatement', models.ForeignKey(orm.LiaisonStatement, null=False)),
('document', models.ForeignKey(orm['doc.Document'], null=False))
))
def backwards(self, orm):
# Deleting model 'LiaisonStatement'
db.delete_table('liaisons_liaisonstatement')
# Dropping ManyToManyField 'LiaisonStatement.attachments'
db.delete_table('liaisons_liaisonstatement_attachments')
models = {
'auth.group': {
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})
},
'auth.permission': {
'Meta': {'unique_together': "(('content_type', 'codename'),)"},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'doc.docalias': {
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['doc.Document']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
},
'doc.document': {
'abstract': ('django.db.models.fields.TextField', [], {}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['person.Email']", 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'iana_state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IanaDocStateName']", 'null': 'True', 'blank': 'True'}),
'iesg_state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IesgDocStateName']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'related': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.DocAlias']", 'blank': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'rfc_state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.RfcDocStateName']", 'null': 'True', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': "orm['person.Person']"}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocStateName']", 'null': 'True', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocStreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['name.DocInfoTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}),
'wg_state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.WgDocStateName']", 'null': 'True', 'blank': 'True'})
},
'group.group': {
'acronym': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '16', 'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': "orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'iesg_state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.IesgGroupStateName']", 'null': 'True', 'blank': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.GroupTypeName']", 'null': 'True'})
},
'idtracker.personororginfo': {
'Meta': {'db_table': "'person_or_org_info'"},
'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
},
'liaisons.frombodies': {
'Meta': {'db_table': "'from_bodies'"},
'body_name': ('django.db.models.fields.CharField', [], {'max_length': '35', 'blank': 'True'}),
'email_priority': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'from_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_liaison_manager': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'other_sdo': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'poc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'db_column': "'poc'"})
},
'liaisons.liaisondetail': {
'Meta': {'db_table': "'liaison_detail'"},
'action_taken': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_column': "'taken_care'", 'blank': 'True'}),
'approval': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.OutgoingLiaisonApproval']", 'null': 'True', 'blank': 'True'}),
'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'by_secretariat': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'cc1': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'cc2': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
'deadline_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'detail_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'from_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'from_raw_body': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'from_raw_code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'last_modified_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'db_column': "'person_or_org_tag'"}),
'purpose': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.LiaisonPurpose']", 'null': 'True'}),
'purpose_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_column': "'purpose'", 'blank': 'True'}),
'related_to': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.LiaisonDetail']", 'null': 'True', 'blank': 'True'}),
'replyto': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'response_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'submitted_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'submitter_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'submitter_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'technical_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'to_body': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'to_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'to_poc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'to_raw_code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'})
},
'liaisons.liaisonmanagers': {
'Meta': {'db_table': "'liaison_managers'"},
'email_priority': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
'sdo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.SDOs']"})
},
'liaisons.liaisonpurpose': {
'Meta': {'db_table': "'liaison_purpose'"},
'purpose_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'purpose_text': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'})
},
'liaisons.liaisonstatement': {
'action_taken': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
'approved': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['doc.Document']", 'blank': 'True'}),
'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'cc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'deadline': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Email']", 'null': 'True', 'blank': 'True'}),
'from_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'liaisonstatement_from_set'", 'null': 'True', 'to': "orm['group.Group']"}),
'from_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'purpose': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['name.LiaisonStatementPurposeName']"}),
'related_to': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.LiaisonStatement']", 'null': 'True', 'blank': 'True'}),
'reply_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'response_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'submitted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'technical_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'to_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'to_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'liaisonstatement_to_set'", 'null': 'True', 'to': "orm['group.Group']"}),
'to_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'liaisons.outgoingliaisonapproval': {
'approval_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'liaisons.sdoauthorizedindividual': {
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}),
'sdo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.SDOs']"})
},
'liaisons.sdos': {
'Meta': {'db_table': "'sdos'"},
'sdo_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'sdo_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
},
'liaisons.uploads': {
'Meta': {'db_table': "'uploads'"},
'detail': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.LiaisonDetail']"}),
'file_extension': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}),
'file_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'file_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"})
},
'name.docinfotagname': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.docstatename': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.docstreamname': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.doctypename': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.groupstatename': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.grouptypename': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.ianadocstatename': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.iesgdocstatename': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.iesggroupstatename': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.intendedstdlevelname': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.liaisonstatementpurposename': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.rfcdocstatename': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.stdlevelname': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'name.wgdocstatename': {
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'})
},
'person.email': {
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
'person.person': {
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['liaisons']

View file

@ -5,6 +5,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.template.loader import render_to_string
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse as urlreverse
from ietf.idtracker.models import Acronym, PersonOrOrgInfo, Area, IESGLogin
from ietf.liaisons.mail import IETFEmailMessage
from ietf.ietfauth.models import LegacyLiaisonUser
@ -141,9 +142,11 @@ class LiaisonDetail(models.Model):
to_email.append('%s <%s>' % person.email())
subject = 'New Liaison Statement, "%s" needs your approval' % (self.title)
from_email = settings.LIAISON_UNIVERSAL_FROM
body = render_to_string('liaisons/pending_liaison_mail.txt',
{'liaison': self,
})
body = render_to_string('liaisons/pending_liaison_mail.txt', {
'liaison': self,
'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=self.pk)),
'referenced_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=self.related_to.pk)) if self.related_to else None,
})
mail = IETFEmailMessage(subject=subject,
to=to_email,
from_email=from_email,
@ -164,9 +167,11 @@ class LiaisonDetail(models.Model):
if self.response_contact:
cc += self.response_contact.split(',')
bcc = ['statements@ietf.org']
body = render_to_string('liaisons/liaison_mail.txt',
{'liaison': self,
})
body = render_to_string('liaisons/liaison_mail.txt', {
'liaison': self,
'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=self.pk)),
'referenced_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=self.related_to.pk)) if self.related_to else None,
})
mail = IETFEmailMessage(subject=subject,
to=to_email,
from_email=from_email,
@ -289,6 +294,8 @@ class Uploads(models.Model):
detail = models.ForeignKey(LiaisonDetail)
def __str__(self):
return self.file_title
def filename(self):
return "file%s%s" % (self.file_id, self.file_extension)
class Meta:
db_table = 'uploads'
@ -305,3 +312,55 @@ class Uploads(models.Model):
# removed edit_inline
# removed num_in_admin
# removed raw_id_admin
if settings.USE_DB_REDESIGN_PROXY_CLASSES or hasattr(settings, "IMPORTING_FROM_OLD_SCHEMA"):
from redesign.name.models import LiaisonStatementPurposeName
from redesign.doc.models import Document
from redesign.person.models import Email
from redesign.group.models import Group
class LiaisonStatement(models.Model):
title = models.CharField(blank=True, max_length=255)
purpose = models.ForeignKey(LiaisonStatementPurposeName)
body = models.TextField(blank=True)
deadline = models.DateField(null=True, blank=True)
related_to = models.ForeignKey('LiaisonStatement', blank=True, null=True)
from_group = models.ForeignKey(Group, related_name="liaisonstatement_from_set", null=True, blank=True, help_text="Sender group, if it exists")
from_name = models.CharField(max_length=255, help_text="Name of the sender body")
from_contact = models.ForeignKey(Email, blank=True, null=True)
to_group = models.ForeignKey(Group, related_name="liaisonstatement_to_set", null=True, blank=True, help_text="Recipient group, if it exists")
to_name = models.CharField(max_length=255, help_text="Name of the recipient body")
to_contact = models.CharField(blank=True, max_length=255, help_text="Contacts at recipient body")
reply_to = models.CharField(blank=True, max_length=255)
response_contact = models.CharField(blank=True, max_length=255)
technical_contact = models.CharField(blank=True, max_length=255)
cc = models.TextField(blank=True)
submitted = models.DateTimeField(null=True, blank=True)
modified = models.DateTimeField(null=True, blank=True)
approved = models.DateTimeField(null=True, blank=True)
action_taken = models.BooleanField(default=False)
attachments = models.ManyToManyField(Document, blank=True)
def name(self):
from django.template.defaultfilters import slugify
if self.from_group:
frm = self.from_group.acronym or self.from_group.name
else:
frm = self.from_name
if self.to_group:
to = self.to_group.acronym or self.to_group.name
else:
to = self.to_name
return slugify("liaison" + " " + self.submitted.strftime("%Y-%m-%d") + " " + frm[:50] + " " + to[:50] + " " + self.title[:115])
def __unicode__(self):
return self.title or "<no title>"
LiaisonDetailOld = LiaisonDetail

182
ietf/liaisons/proxy.py Normal file
View file

@ -0,0 +1,182 @@
from redesign.proxy_utils import TranslatingManager
from ietf.liaisons.models import LiaisonStatement
from redesign.doc.models import Document
class LiaisonDetailProxy(LiaisonStatement):
objects = TranslatingManager(dict(submitted_date="submitted",
deadline_date="deadline",
to_body="to_name",
from_raw_body="from_name"))
def from_object(self, base):
for f in base._meta.fields:
setattr(self, f.name, getattr(base, f.name))
return self
#detail_id = models.AutoField(primary_key=True)
@property
def detail_id(self):
return self.id
#person = models.ForeignKey(PersonOrOrgInfo, null=True, db_column='person_or_org_tag')
@property
def person(self):
return self.from_contact.person if self.from_contact else ""
#submitted_date = models.DateField(null=True, blank=True)
@property
def submitted_date(self):
return self.submitted.date() if self.submitted else None
#last_modified_date = models.DateField(null=True, blank=True)
@property
def last_modified_date(self):
return self.modified.date() if self.modified else None
#from_id = models.IntegerField(null=True, blank=True)
@property
def from_id(self):
return self.from_group_id
#to_body = models.CharField(blank=True, null=True, max_length=255)
@property
def to_body(self):
return self.to_name
#title = models.CharField(blank=True, null=True, max_length=255) # same name
#response_contact = models.CharField(blank=True, null=True, max_length=255) # same name
#technical_contact = models.CharField(blank=True, null=True, max_length=255) # same name
#purpose_text = models.TextField(blank=True, null=True, db_column='purpose')
@property
def purpose_text(self):
return ""
#body = models.TextField(blank=True,null=True) # same name
#deadline_date = models.DateField(null=True, blank=True)
@property
def deadline_date(self):
return self.deadline
#cc1 = models.TextField(blank=True, null=True)
@property
def cc1(self):
return self.cc
#cc2 = models.CharField(blank=True, null=True, max_length=50) # unused
@property
def cc2(self):
return ""
#submitter_name = models.CharField(blank=True, null=True, max_length=255)
@property
def submitter_name(self):
i = self.to_name.find('<')
if i > 0:
return self.to_name[:i - 1]
else:
return self.to_name
#submitter_email = models.CharField(blank=True, null=True, max_length=255)
@property
def submitter_email(self):
import re
re_email = re.compile("<(.*)>")
match = re_email.search(self.to_name)
if match:
return match.group(1)
else:
return ""
#by_secretariat = models.IntegerField(null=True, blank=True)
@property
def by_secretariat(self):
return not self.from_contact
#to_poc = models.CharField(blank=True, null=True, max_length=255)
@property
def to_poc(self):
return self.to_contact
#to_email = models.CharField(blank=True, null=True, max_length=255)
@property
def to_email(self):
return ""
#purpose = models.ForeignKey(LiaisonPurpose,null=True)
#replyto = models.CharField(blank=True, null=True, max_length=255)
@property
def replyto(self):
return self.reply_to
#from_raw_body = models.CharField(blank=True, null=True, max_length=255)
@property
def from_raw_body(self):
return self.from_name
def raw_codify(self, group):
if not group:
return ""
if group.type_id in ("sdo", "wg", "area"):
return "%s_%s" % (group.type_id, group.id)
return group.acronym
#from_raw_code = models.CharField(blank=True, null=True, max_length=255)
@property
def from_raw_code(self):
return self.raw_codify(self.from_group)
#to_raw_code = models.CharField(blank=True, null=True, max_length=255)
@property
def to_raw_code(self):
return self.raw_codify(self.to_group)
#approval = models.ForeignKey(OutgoingLiaisonApproval, blank=True, null=True)
@property
def approval(self):
return bool(self.approved)
#action_taken = models.BooleanField(default=False, db_column='taken_care') # same name
#related_to = models.ForeignKey('LiaisonDetail', blank=True, null=True) # same name
@property
def uploads_set(self):
return UploadsProxy.objects.filter(liaisonstatement=self).order_by('name')
@property
def liaisondetail_set(self):
return self.liaisonstatement_set
def __str__(self):
return unicode(self)
def __unicode__(self):
return self.title or "<no title>"
def from_body(self):
return self.from_name
def from_sdo(self):
return self.from_group if self.from_group and self.from_group.type_id == "sdo" else None
def from_email(self):
self.from_contact.address
def get_absolute_url(self):
return '/liaison/%d/' % self.detail_id
class Meta:
proxy = True
def send_by_email(self, fake=False):
# grab this from module instead of stuffing in on the model
from ietf.liaisons.mails import send_liaison_by_email
# we don't have a request so just pass None for the time being
return send_liaison_by_email(None, self, fake)
def is_pending(self):
return not self.approved
class UploadsProxy(Document):
#file_id = models.AutoField(primary_key=True)
@property
def file_id(self):
if self.external_url.startswith(self.name):
return self.name # new data
else:
return int(self.external_url.split(".")[0][len(file):]) # old data
#file_title = models.CharField(blank=True, max_length=255)
@property
def file_title(self):
return self.title
#person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
#file_extension = models.CharField(blank=True, max_length=10)
@property
def file_extension(self):
t = self.external_url.split(".")
if len(t) > 1:
return "." + t[1]
else:
return ""
#detail = models.ForeignKey(LiaisonDetail)
@property
def detail(self):
return self.liaisonstatement_set.all()[0]
def filename(self):
return self.external_url
class Meta:
proxy = True

View file

@ -1,8 +1,12 @@
# Copyright The IETF Trust 2007, All Rights Reserved
#
from django.contrib.sitemaps import Sitemap
from django.conf import settings
from ietf.liaisons.models import LiaisonDetail
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail
class LiaisonMap(Sitemap):
changefreq = "never"
def items(self):

View file

@ -1,5 +1,15 @@
import datetime, os, shutil
from ietf.utils.test_utils import SimpleUrlTestCase, canonicalize_feed, canonicalize_sitemap
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse as urlreverse
import django.test
from StringIO import StringIO
from pyquery import PyQuery
from ietf.utils.test_utils import SimpleUrlTestCase, canonicalize_feed, canonicalize_sitemap, login_testing_unauthorized
from ietf.utils.test_data import make_test_data
from ietf.utils.mail import outbox
class LiaisonsUrlTestCase(SimpleUrlTestCase):
def testUrls(self):
@ -11,3 +21,404 @@ class LiaisonsUrlTestCase(SimpleUrlTestCase):
return canonicalize_sitemap(content)
else:
return content
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.liaisons.models import LiaisonStatement, LiaisonStatementPurposeName
from redesign.person.models import Person, Email
from redesign.group.models import Group, Role
def make_liaison_models():
sdo = Group.objects.create(
name="United League of Marsmen",
acronym="",
state_id="active",
type_id="sdo",
)
# liaison manager
u = User.objects.create(username="zrk")
p = Person.objects.create(
name="Zrk Brekkk",
ascii="Zrk Brekkk",
user=u)
manager = email = Email.objects.create(
address="zrk@ulm.mars",
person=p)
Role.objects.create(
name_id="liaiman",
group=sdo,
person=p,
email=email)
# authorized individual
u = User.objects.create(username="rkz")
p = Person.objects.create(
name="Rkz Kkkreb",
ascii="Rkz Kkkreb",
user=u)
email = Email.objects.create(
address="rkz@ulm.mars",
person=p)
Role.objects.create(
name_id="auth",
group=sdo,
person=p,
email=email)
mars_group = Group.objects.get(acronym="mars")
l = LiaisonStatement.objects.create(
title="Comment from United League of Marsmen",
purpose_id="comment",
body="The recently proposed Martian Standard for Communication Links neglects the special ferro-magnetic conditions of the Martian soil.",
deadline=datetime.date.today() + datetime.timedelta(days=7),
related_to=None,
from_group=sdo,
from_name=sdo.name,
from_contact=manager,
to_group=mars_group,
to_name=mars_group.name,
to_contact="%s@ietf.org" % mars_group.acronym,
reply_to=email.address,
response_contact="",
technical_contact="",
cc="",
submitted=datetime.datetime.now(),
modified=datetime.datetime.now(),
approved=datetime.datetime.now(),
action_taken=False,
)
return l
class LiaisonManagementTestCase(django.test.TestCase):
fixtures = ['names']
def setUp(self):
self.liaison_dir = os.path.abspath("tmp-liaison-dir")
os.mkdir(self.liaison_dir)
settings.LIAISON_ATTACH_PATH = self.liaison_dir
def tearDown(self):
shutil.rmtree(self.liaison_dir)
def test_taken_care_of(self):
make_test_data()
liaison = make_liaison_models()
url = urlreverse('liaison_detail', kwargs=dict(object_id=liaison.pk))
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form input[name=do_action_taken]')), 0)
# log in and get
self.client.login(remote_user="secretary")
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form input[name=do_action_taken]')), 1)
# mark action taken
r = self.client.post(url, dict(do_action_taken="1"))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form input[name=do_action_taken]')), 0)
liaison = LiaisonStatement.objects.get(id=liaison.id)
self.assertTrue(liaison.action_taken)
def test_approval(self):
make_test_data()
liaison = make_liaison_models()
# has to come from WG to need approval
liaison.from_group = Group.objects.get(acronym="mars")
liaison.approved = None
liaison.save()
url = urlreverse('liaison_approval_detail', kwargs=dict(object_id=liaison.pk))
# this liaison is for a WG so we need the AD for the area
login_testing_unauthorized(self, "ad", url)
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form input[name=do_approval]')), 1)
# approve
mailbox_before = len(outbox)
r = self.client.post(url, dict(do_approval="1"))
self.assertEquals(r.status_code, 302)
liaison = LiaisonStatement.objects.get(id=liaison.id)
self.assertTrue(liaison.approved)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("Liaison Statement" in outbox[-1]["Subject"])
def test_edit_liaison(self):
make_test_data()
liaison = make_liaison_models()
url = urlreverse('liaison_edit', kwargs=dict(object_id=liaison.pk))
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form input[name=from_field]')), 1)
# edit
attachments_before = liaison.attachments.count()
test_file = StringIO("hello world")
test_file.name = "unnamed"
r = self.client.post(url,
dict(from_field="from",
replyto="replyto@example.com",
organization="org",
to_poc="to_poc@example.com",
response_contact="responce_contact@example.com",
technical_contact="technical_contact@example.com",
cc1="cc@example.com",
purpose="4",
deadline_date=(liaison.deadline + datetime.timedelta(days=1)).strftime("%Y-%m-%d"),
title="title",
submitted_date=(liaison.submitted + datetime.timedelta(days=1)).strftime("%Y-%m-%d"),
body="body",
attach_file_1=test_file,
attach_title_1="attachment",
))
self.assertEquals(r.status_code, 302)
new_liaison = LiaisonStatement.objects.get(id=liaison.id)
self.assertEquals(new_liaison.from_name, "from")
self.assertEquals(new_liaison.reply_to, "replyto@example.com")
self.assertEquals(new_liaison.to_name, "org")
self.assertEquals(new_liaison.to_contact, "to_poc@example.com")
self.assertEquals(new_liaison.response_contact, "responce_contact@example.com")
self.assertEquals(new_liaison.technical_contact, "technical_contact@example.com")
self.assertEquals(new_liaison.cc, "cc@example.com")
self.assertEquals(new_liaison.purpose, LiaisonStatementPurposeName.objects.get(order=4))
self.assertEquals(new_liaison.deadline, liaison.deadline + datetime.timedelta(days=1)),
self.assertEquals(new_liaison.title, "title")
self.assertEquals(new_liaison.submitted.date(), (liaison.submitted + datetime.timedelta(days=1)).date())
self.assertEquals(new_liaison.body, "body")
self.assertEquals(new_liaison.attachments.count(), attachments_before + 1)
attachment = new_liaison.attachments.order_by("-name")[0]
self.assertEquals(attachment.title, "attachment")
with open(os.path.join(self.liaison_dir, attachment.external_url)) as f:
written_content = f.read()
test_file.seek(0)
self.assertEquals(written_content, test_file.read())
def test_add_incoming_liaison(self):
make_test_data()
liaison = make_liaison_models()
url = urlreverse('add_liaison') + "?incoming=1"
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form textarea[name=body]')), 1)
# add new
mailbox_before = len(outbox)
test_file = StringIO("hello world")
test_file.name = "unnamed"
from_group = Group.objects.filter(type="sdo")[0]
to_group = Group.objects.get(acronym="mars")
submitter = Person.objects.get(user__username="marschairman")
today = datetime.date.today()
related_liaison = liaison
r = self.client.post(url,
dict(from_field="%s_%s" % (from_group.type_id, from_group.pk),
from_fake_user=str(submitter.pk),
replyto="replyto@example.com",
organization="%s_%s" % (to_group.type_id, to_group.pk),
response_contact="responce_contact@example.com",
technical_contact="technical_contact@example.com",
cc1="cc@example.com",
purpose="4",
deadline_date=(today + datetime.timedelta(days=1)).strftime("%Y-%m-%d"),
related_to=str(related_liaison.pk),
title="title",
submitted_date=today.strftime("%Y-%m-%d"),
body="body",
attach_file_1=test_file,
attach_title_1="attachment",
send="1",
))
self.assertEquals(r.status_code, 302)
l = LiaisonStatement.objects.all().order_by("-id")[0]
self.assertEquals(l.from_group, from_group)
self.assertEquals(l.from_contact, submitter.email_address())
self.assertEquals(l.reply_to, "replyto@example.com")
self.assertEquals(l.to_group, to_group)
self.assertEquals(l.response_contact, "responce_contact@example.com")
self.assertEquals(l.technical_contact, "technical_contact@example.com")
self.assertEquals(l.cc, "cc@example.com")
self.assertEquals(l.purpose, LiaisonStatementPurposeName.objects.get(order=4))
self.assertEquals(l.deadline, today + datetime.timedelta(days=1)),
self.assertEquals(l.related_to, liaison),
self.assertEquals(l.title, "title")
self.assertEquals(l.submitted.date(), today)
self.assertEquals(l.body, "body")
self.assertTrue(l.approved)
self.assertEquals(l.attachments.count(), 1)
attachment = l.attachments.all()[0]
self.assertEquals(attachment.title, "attachment")
with open(os.path.join(self.liaison_dir, attachment.external_url)) as f:
written_content = f.read()
test_file.seek(0)
self.assertEquals(written_content, test_file.read())
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("Liaison Statement" in outbox[-1]["Subject"])
def test_add_outgoing_liaison(self):
make_test_data()
liaison = make_liaison_models()
url = urlreverse('add_liaison')
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form textarea[name=body]')), 1)
# add new
mailbox_before = len(outbox)
test_file = StringIO("hello world")
test_file.name = "unnamed"
from_group = Group.objects.get(acronym="mars")
to_group = Group.objects.filter(type="sdo")[0]
submitter = Person.objects.get(user__username="marschairman")
today = datetime.date.today()
related_liaison = liaison
r = self.client.post(url,
dict(from_field="%s_%s" % (from_group.type_id, from_group.pk),
from_fake_user=str(submitter.pk),
approved="",
replyto="replyto@example.com",
to_poc="to_poc@example.com",
organization="%s_%s" % (to_group.type_id, to_group.pk),
other_organization="",
response_contact="responce_contact@example.com",
technical_contact="technical_contact@example.com",
cc1="cc@example.com",
purpose="4",
deadline_date=(today + datetime.timedelta(days=1)).strftime("%Y-%m-%d"),
related_to=str(related_liaison.pk),
title="title",
submitted_date=today.strftime("%Y-%m-%d"),
body="body",
attach_file_1=test_file,
attach_title_1="attachment",
send="1",
))
self.assertEquals(r.status_code, 302)
l = LiaisonStatement.objects.all().order_by("-id")[0]
self.assertEquals(l.from_group, from_group)
self.assertEquals(l.from_contact, submitter.email_address())
self.assertEquals(l.reply_to, "replyto@example.com")
self.assertEquals(l.to_group, to_group)
self.assertEquals(l.to_contact, "to_poc@example.com")
self.assertEquals(l.response_contact, "responce_contact@example.com")
self.assertEquals(l.technical_contact, "technical_contact@example.com")
self.assertEquals(l.cc, "cc@example.com")
self.assertEquals(l.purpose, LiaisonStatementPurposeName.objects.get(order=4))
self.assertEquals(l.deadline, today + datetime.timedelta(days=1)),
self.assertEquals(l.related_to, liaison),
self.assertEquals(l.title, "title")
self.assertEquals(l.submitted.date(), today)
self.assertEquals(l.body, "body")
self.assertTrue(not l.approved)
self.assertEquals(l.attachments.count(), 1)
attachment = l.attachments.all()[0]
self.assertEquals(attachment.title, "attachment")
with open(os.path.join(self.liaison_dir, attachment.external_url)) as f:
written_content = f.read()
test_file.seek(0)
self.assertEquals(written_content, test_file.read())
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("Liaison Statement" in outbox[-1]["Subject"])
# try adding statement to non-predefined organization
r = self.client.post(url,
dict(from_field="%s_%s" % (from_group.type_id, from_group.pk),
from_fake_user=str(submitter.pk),
approved="1",
replyto="replyto@example.com",
to_poc="to_poc@example.com",
organization="othersdo",
other_organization="Mars Institute",
response_contact="responce_contact@example.com",
technical_contact="technical_contact@example.com",
cc1="cc@example.com",
purpose="4",
deadline_date=(today + datetime.timedelta(days=1)).strftime("%Y-%m-%d"),
related_to=str(related_liaison.pk),
title="new title",
submitted_date=today.strftime("%Y-%m-%d"),
body="body",
))
self.assertEquals(r.status_code, 302)
l = LiaisonStatement.objects.all().order_by("-id")[0]
self.assertEquals(l.to_group, None)
self.assertEquals(l.to_name, "Mars Institute")
def test_send_sdo_reminder(self):
make_test_data()
liaison = make_liaison_models()
from ietf.liaisons.mails import send_sdo_reminder
mailbox_before = len(outbox)
send_sdo_reminder(Group.objects.filter(type="sdo")[0])
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("authorized individuals" in outbox[-1]["Subject"])
def test_send_liaison_deadline_reminder(self):
make_test_data()
liaison = make_liaison_models()
from ietf.liaisons.mails import possibly_send_deadline_reminder
from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail
l = LiaisonDetail.objects.all()[0]
mailbox_before = len(outbox)
possibly_send_deadline_reminder(l)
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("deadline" in outbox[-1]["Subject"])
# try pushing the deadline
l.deadline = l.deadline + datetime.timedelta(days=30)
l.save()
mailbox_before = len(outbox)
possibly_send_deadline_reminder(l)
self.assertEquals(len(outbox), mailbox_before)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
# the above tests only work with the new schema
del LiaisonManagementTestCase

View file

@ -1,3 +1,5 @@
from django.conf import settings
from ietf.idtracker.models import Area, IETFWG
from ietf.liaisons.models import SDOs, LiaisonManagers
from ietf.liaisons.accounts import (is_ietfchair, is_iabchair, is_iab_executive_director,
@ -420,3 +422,6 @@ class IETFHierarchyManager(object):
IETFHM = IETFHierarchyManager()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from utilsREDESIGN import *

View file

@ -0,0 +1,429 @@
from redesign.group.models import Group, Role
from redesign.person.models import Person
from redesign.proxy_utils import proxy_personify_role
from ietf.liaisons.accounts import (is_ietfchair, is_iabchair, is_iab_executive_director,
get_ietf_chair, get_iab_chair, get_iab_executive_director,
is_secretariat)
IETFCHAIR = {'name': u'The IETF Chair', 'address': u'chair@ietf.org'}
IESG = {'name': u'The IESG', 'address': u'iesg@ietf.org'}
IAB = {'name': u'The IAB', 'address': u'iab@iab.org'}
IABCHAIR = {'name': u'The IAB Chair', 'address': u'iab-chair@iab.org'}
IABEXECUTIVEDIRECTOR = {'name': u'The IAB Executive Director', 'address': u'execd@iab.org'}
class FakePerson(object):
def __init__(self, name, address):
self.name = name
self.address = address
def email(self):
return (self.name, self.address)
# the following is a biggish object hierarchy abstracting the entity
# names and auth rules for posting liaison statements in a sort of
# semi-declarational (and perhaps overengineered given the revamped
# schema) way - unfortunately, it's never been strong enough to do so
# fine-grained enough so the form code also has some rules
def all_sdo_managers():
return [proxy_personify_role(r) for r in Role.objects.filter(group__type="sdo", name="liaiman").select_related("person").distinct()]
def role_persons_with_fixed_email(group, role_name):
return [proxy_personify_role(r) for r in Role.objects.filter(group=group, name=role_name).select_related("person").distinct()]
class Entity(object):
poc = []
cc = []
def __init__(self, name, obj=None):
self.name = name
self.obj = obj
def get_poc(self):
if not isinstance(self.poc, list):
return [self.poc]
return self.poc
def get_cc(self, person=None):
if not isinstance(self.cc, list):
return [self.cc]
return self.cc
def get_from_cc(self, person=None):
return []
def needs_approval(self, person=None):
return False
def can_approve(self):
return []
def post_only(self, person, user):
return False
def full_user_list(self):
return False
class IETFEntity(Entity):
poc = FakePerson(**IETFCHAIR)
cc = FakePerson(**IESG)
def get_from_cc(self, person):
result = []
if not is_ietfchair(person):
result.append(self.poc)
result.append(self.cc)
return result
def needs_approval(self, person=None):
if is_ietfchair(person):
return False
return True
def can_approve(self):
return [self.poc]
def full_user_list(self):
result = all_sdo_managers()
result.append(get_ietf_chair())
return result
class IABEntity(Entity):
chair = FakePerson(**IABCHAIR)
director = FakePerson(**IABEXECUTIVEDIRECTOR)
poc = [chair, director]
cc = FakePerson(**IAB)
def get_from_cc(self, person):
result = []
if not is_iabchair(person):
result.append(self.chair)
result.append(self.cc)
if not is_iab_executive_director(person):
result.append(self.director)
return result
def needs_approval(self, person=None):
if is_iabchair(person) or is_iab_executive_director(person):
return False
return True
def can_approve(self):
return [self.chair]
def full_user_list(self):
result = all_sdo_managers()
result += [get_iab_chair(), get_iab_executive_director()]
return result
class AreaEntity(Entity):
def get_poc(self):
return role_persons_with_fixed_email(self.obj, "ad")
def get_cc(self, person=None):
return [FakePerson(**IETFCHAIR)]
def get_from_cc(self, person):
result = [p for p in role_persons_with_fixed_email(self.obj, "ad") if p != person]
result.append(FakePerson(**IETFCHAIR))
return result
def needs_approval(self, person=None):
# Check if person is an area director
if self.obj.role_set.filter(person=person, name="ad"):
return False
return True
def can_approve(self):
return self.get_poc()
def full_user_list(self):
result = all_sdo_managers()
result += self.get_poc()
return result
class WGEntity(Entity):
def get_poc(self):
return role_persons_with_fixed_email(self.obj, "chair")
def get_cc(self, person=None):
if self.obj.parent:
result = [p for p in role_persons_with_fixed_email(self.obj.parent, "ad") if p != person]
else:
result = []
if self.obj.list_subscribe:
result.append(FakePerson(name ='%s Discussion List' % self.obj.name,
address = self.obj.list_subscribe))
return result
def get_from_cc(self, person):
result = [p for p in role_persons_with_fixed_email(self.obj, "chair") if p != person]
result += role_persons_with_fixed_email(self.obj.parent, "ad") if self.obj.parent else []
if self.obj.list_subscribe:
result.append(FakePerson(name ='%s Discussion List' % self.obj.name,
address = self.obj.list_subscribe))
return result
def needs_approval(self, person=None):
# Check if person is director of this wg area
if self.obj.parent and self.obj.parent.role_set.filter(person=person, name="ad"):
return False
return True
def can_approve(self):
return role_persons_with_fixed_email(self.obj.parent, "ad") if self.obj.parent else []
def full_user_list(self):
result = all_sdo_managers()
result += self.get_poc()
return result
class SDOEntity(Entity):
def get_poc(self):
return []
def get_cc(self, person=None):
return role_persons_with_fixed_email(self.obj, "liaiman")
def get_from_cc(self, person=None):
return [p for p in role_persons_with_fixed_email(self.obj, "liaiman") if p != person]
def post_only(self, person, user):
if is_secretariat(user) or self.obj.role_set.filter(person=person, name="auth"):
return False
return True
def full_user_list(self):
result = role_persons_with_fixed_email(self.obj, "liaiman")
result += role_persons_with_fixed_email(self.obj, "auth")
return result
class EntityManager(object):
def __init__(self, pk=None, name=None, queryset=None):
self.pk = pk
self.name = name
self.queryset = queryset
def get_entity(self, pk=None):
return Entity(name=self.name)
def get_managed_list(self):
return [(self.pk, self.name)]
def can_send_on_behalf(self, person):
return []
def can_approve_list(self, person):
return []
class IETFEntityManager(EntityManager):
def __init__(self, *args, **kwargs):
super(IETFEntityManager, self).__init__(*args, **kwargs)
self.entity = IETFEntity(name=self.name)
def get_entity(self, pk=None):
return self.entity
def can_send_on_behalf(self, person):
if is_ietfchair(person):
return self.get_managed_list()
return []
def can_approve_list(self, person):
if is_ietfchair(person):
return self.get_managed_list()
return []
class IABEntityManager(EntityManager):
def __init__(self, *args, **kwargs):
super(IABEntityManager, self).__init__(*args, **kwargs)
self.entity = IABEntity(name=self.name)
def get_entity(self, pk=None):
return self.entity
def can_send_on_behalf(self, person):
if (is_iabchair(person) or
is_iab_executive_director(person)):
return self.get_managed_list()
return []
def can_approve_list(self, person):
if (is_iabchair(person) or
is_iab_executive_director(person)):
return self.get_managed_list()
return []
class AreaEntityManager(EntityManager):
def __init__(self, pk=None, name=None, queryset=None):
super(AreaEntityManager, self).__init__(pk, name, queryset)
from redesign.group.proxy import Area
if self.queryset == None:
self.queryset = Area.active_areas()
def get_managed_list(self, query_filter=None):
if not query_filter:
query_filter = {}
return [(u'%s_%s' % (self.pk, i.pk), i.area_acronym.name) for i in self.queryset.filter(**query_filter).order_by('area_acronym__name')]
def get_entity(self, pk=None):
if not pk:
return None
try:
obj = self.queryset.get(pk=pk)
except self.queryset.model.DoesNotExist:
return None
return AreaEntity(name=obj.area_acronym.name, obj=obj)
def can_send_on_behalf(self, person):
query_filter = dict(role__person=person, role__name="ad")
return self.get_managed_list(query_filter)
def can_approve_list(self, person):
query_filter = dict(role__person=person, role__name="ad")
return self.get_managed_list(query_filter)
class WGEntityManager(EntityManager):
def __init__(self, pk=None, name=None, queryset=None):
super(WGEntityManager, self).__init__(pk, name, queryset)
if self.queryset == None:
from redesign.group.proxy import IETFWG, Area
self.queryset = IETFWG.objects.filter(group_type=1, status=IETFWG.ACTIVE, areagroup__area__status=Area.ACTIVE)
def get_managed_list(self, query_filter=None):
if not query_filter:
query_filter = {}
return [(u'%s_%s' % (self.pk, i.pk), '%s - %s' % (i.group_acronym.acronym, i.group_acronym.name)) for i in self.queryset.filter(**query_filter).order_by('group_acronym__acronym')]
def get_entity(self, pk=None):
if not pk:
return None
try:
obj = self.queryset.get(pk=pk)
except self.queryset.model.DoesNotExist:
return None
return WGEntity(name=obj.group_acronym.name, obj=obj)
def can_send_on_behalf(self, person):
wgs = Group.objects.filter(role__person=person, role__name__in=("chair", "secretary")).values_list('pk', flat=True)
query_filter = {'pk__in': wgs}
return self.get_managed_list(query_filter)
def can_approve_list(self, person):
query_filter = dict(parent__role__person=person, parent__role__name="ad")
return self.get_managed_list(query_filter)
class SDOEntityManager(EntityManager):
def __init__(self, pk=None, name=None, queryset=None):
super(SDOEntityManager, self).__init__(pk, name, queryset)
if self.queryset == None:
self.queryset = Group.objects.filter(type="sdo")
def get_managed_list(self):
return [(u'%s_%s' % (self.pk, i.pk), i.name) for i in self.queryset.order_by('name')]
def get_entity(self, pk=None):
if not pk:
return None
try:
obj = self.queryset.get(pk=pk)
except self.queryset.model.DoesNotExist:
return None
return SDOEntity(name=obj.name, obj=obj)
class IETFHierarchyManager(object):
def __init__(self):
self.managers = {'ietf': IETFEntityManager(pk='ietf', name=u'The IETF'),
'iesg': IETFEntityManager(pk='iesg', name=u'The IESG'),
'iab': IABEntityManager(pk='iab', name=u'The IAB'),
'area': AreaEntityManager(pk='area', name=u'IETF Areas'),
'wg': WGEntityManager(pk='wg', name=u'IETF Working Groups'),
'sdo': SDOEntityManager(pk='sdo', name=u'Standards Development Organizations'),
'othersdo': EntityManager(pk='othersdo', name=u'Other SDOs'),
}
def get_entity_by_key(self, entity_id):
if not entity_id:
return None
id_list = entity_id.split('_', 1)
key = id_list[0]
pk = None
if len(id_list)==2:
pk = id_list[1]
if key not in self.managers.keys():
return None
return self.managers[key].get_entity(pk)
def get_all_entities(self):
entities = []
for manager in self.managers.values():
entities += manager.get_managed_list()
return entities
def get_all_incoming_entities(self):
entities = []
results = []
for key in ['ietf', 'iesg', 'iab']:
results += self.managers[key].get_managed_list()
entities.append(('Main IETF Entities', results))
entities.append(('IETF Areas', self.managers['area'].get_managed_list()))
entities.append(('IETF Working Groups', self.managers['wg'].get_managed_list()))
return entities
def get_all_outgoing_entities(self):
entities = [(self.managers['sdo'].name, self.managers['sdo'].get_managed_list())]
entities += [(self.managers['othersdo'].name, self.managers['othersdo'].get_managed_list())]
return entities
def get_entities_for_person(self, person):
entities = []
results = []
for key in ['ietf', 'iesg', 'iab']:
results += self.managers[key].can_send_on_behalf(person)
if results:
entities.append(('Main IETF Entities', results))
areas = self.managers['area'].can_send_on_behalf(person)
if areas:
entities.append(('IETF Areas', areas))
wgs = self.managers['wg'].can_send_on_behalf(person)
if wgs:
entities.append(('IETF Working Groups', wgs))
return entities
def get_all_can_approve_codes(self, person):
entities = []
for key in ['ietf', 'iesg', 'iab']:
entities += self.managers[key].can_approve_list(person)
entities += self.managers['area'].can_approve_list(person)
entities += self.managers['wg'].can_approve_list(person)
return [i[0] for i in entities]
IETFHM = IETFHierarchyManager()

View file

@ -21,6 +21,9 @@ from ietf.liaisons.forms import liaison_form_factory
from ietf.liaisons.models import LiaisonDetail, OutgoingLiaisonApproval
from ietf.liaisons.utils import IETFHM
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail
@can_submit_liaison
def add_liaison(request, liaison=None):
@ -77,10 +80,11 @@ def get_info(request):
'needs_approval': from_entity.needs_approval(person=person),
'post_only': from_entity.post_only(person=person, user=request.user)})
if is_secretariat(request.user):
full_list = [(i.pk, i.email()) for i in from_entity.full_user_list()]
full_list = [(i.pk, i.email()) for i in set(from_entity.full_user_list())]
full_list.sort(lambda x,y: cmp(x[1], y[1]))
full_list = [(person.pk, person.email())] + full_list
result.update({'full_list': full_list})
json_result = simplejson.dumps(result)
return HttpResponse(json_result, mimetype='text/javascript')
@ -93,6 +97,19 @@ def _fake_email_view(request, liaison):
'liaison': liaison},
context_instance=RequestContext(request))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
def approvable_liaison_statements(group_codes):
# this is a bit complicated because IETFHM encodes the
# groups, it should just give us a list of ids or acronyms
group_acronyms = []
group_ids = []
for x in group_codes:
if "_" in x:
group_ids.append(x.split("_")[1])
else:
group_acronyms.append(x)
return LiaisonDetail.objects.filter(approved=None).filter(Q(from_group__acronym__in=group_acronyms) | Q (from_group__pk__in=group_ids))
def liaison_list(request):
user = request.user
@ -104,7 +121,10 @@ def liaison_list(request):
person = get_person_for_user(request.user)
if person:
approval_codes = IETFHM.get_all_can_approve_codes(person)
can_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).count()
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
can_approve = approvable_liaison_statements(approval_codes).count()
else:
can_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).count()
order = request.GET.get('order_by', 'submitted_date')
plain_order = order
@ -121,7 +141,10 @@ def liaison_list(request):
order = plain_order
else:
order = '-%s' % plain_order
public_liaisons = LiaisonDetail.objects.filter(Q(approval__isnull=True)|Q(approval__approved=True)).order_by(order)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
public_liaisons = LiaisonDetail.objects.exclude(approved=None).order_by(order)
else:
public_liaisons = LiaisonDetail.objects.filter(Q(approval__isnull=True)|Q(approval__approved=True)).order_by(order)
return object_list(request, public_liaisons,
allow_empty=True,
@ -139,7 +162,10 @@ def liaison_list(request):
def liaison_approval_list(request):
person = get_person_for_user(request.user)
approval_codes = IETFHM.get_all_can_approve_codes(person)
to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date")
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
to_approve = approvable_liaison_statements(approval_codes).order_by("-submitted")
else:
to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date")
return object_list(request, to_approve,
allow_empty=True,
@ -151,19 +177,26 @@ def liaison_approval_list(request):
def liaison_approval_detail(request, object_id):
person = get_person_for_user(request.user)
approval_codes = IETFHM.get_all_can_approve_codes(person)
to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date")
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
to_approve = approvable_liaison_statements(approval_codes).order_by("-submitted")
else:
to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date")
if request.method=='POST' and request.POST.get('do_approval', False):
try:
liaison = to_approve.get(pk=object_id)
approval = liaison.approval
if not approval:
approval = OutgoingLiaisonApproval.objects.create(approved=True, approval_date=datetime.datetime.now())
liaison.approval = approval
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
liaison.approved = datetime.datetime.now()
liaison.save()
else:
approval.approved=True
approval.save()
approval = liaison.approval
if not approval:
approval = OutgoingLiaisonApproval.objects.create(approved=True, approval_date=datetime.datetime.now())
liaison.approval = approval
liaison.save()
else:
approval.approved=True
approval.save()
if not settings.DEBUG:
liaison.send_by_email()
else:
@ -183,7 +216,7 @@ def _can_take_care(liaison, user):
return False
if user.is_authenticated():
if user.groups.filter(name__in=LIAISON_EDIT_GROUPS):
if is_secretariat(user):
return True
else:
return _find_person_in_emails(liaison, get_person_for_user(user))
@ -199,6 +232,8 @@ def _find_person_in_emails(liaison, person):
liaison.technical_contact] if e ])
for email in emails.split(','):
name, addr = parseaddr(email)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
person.emailaddress_set = person.email_set
if email_re.search(addr) and person.emailaddress_set.filter(address=addr):
return True
elif addr in ('chair@ietf.org', 'iesg@ietf.org') and is_ietfchair(person):
@ -211,8 +246,12 @@ def _find_person_in_emails(liaison, person):
def liaison_detail(request, object_id):
qfilter = Q(approval__isnull=True)|Q(approval__approved=True)
public_liaisons = LiaisonDetail.objects.filter(qfilter).order_by("-submitted_date")
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
qfilter = Q()
public_liaisons = LiaisonDetail.objects.exclude(approved=None).order_by("-submitted_date")
else:
qfilter = Q(approval__isnull=True)|Q(approval__approved=True)
public_liaisons = LiaisonDetail.objects.filter(qfilter).order_by("-submitted_date")
liaison = get_object_or_404(public_liaisons, pk=object_id)
can_edit = False
user = request.user
@ -226,6 +265,7 @@ def liaison_detail(request, object_id):
relations = liaison.liaisondetail_set.filter(qfilter)
return object_detail(request,
public_liaisons,
template_name="liaisons/liaisondetail_detail.html",
object_id=object_id,
extra_context = {'can_edit': can_edit,
'relations': relations,
@ -255,7 +295,11 @@ def ajax_liaison_list(request):
order = plain_order
else:
order = '-%s' % plain_order
public_liaisons = LiaisonDetail.objects.filter(Q(approval__isnull=True)|Q(approval__approved=True)).order_by(order)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
public_liaisons = LiaisonDetail.objects.exclude(approved=None).order_by(order)
else:
public_liaisons = LiaisonDetail.objects.filter(Q(approval__isnull=True)|Q(approval__approved=True)).order_by(order)
return object_list(request, public_liaisons,
allow_empty=True,

View file

@ -88,7 +88,8 @@ class RelatedLiaisonWidget(TextInput):
noliaison = 'inline'
deselect = 'none'
else:
from ietf.liaisons.models import LiaisonDetail
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail
liaison = LiaisonDetail.objects.get(pk=value)
title = liaison.title
if not title:

View file

@ -3,8 +3,10 @@
from django.conf.urls.defaults import patterns
from ietf.idtracker.models import IETFWG
http_archive_wg_queryset = IETFWG.objects.filter(email_archive__startswith='http')
urlpatterns = patterns('django.views.generic.list_detail',
(r'^wg/$', 'object_list', { 'queryset': IETFWG.objects.filter(email_archive__startswith='http'), 'template_name': 'mailinglists/wgwebmail_list.html' }),
(r'^wg/$', 'object_list', { 'queryset': http_archive_wg_queryset, 'template_name': 'mailinglists/wgwebmail_list.html' }),
)
urlpatterns += patterns('',
(r'^nonwg/$', 'django.views.generic.simple.redirect_to', { 'url': 'http://www.ietf.org/list/nonwg.html'}),

View file

@ -1,3 +1,160 @@
# Copyright The IETF Trust 2007, All Rights Reserved
# old meeting models can be found in ../proceedings/models.py
import pytz, datetime
from django.db import models
from django.conf import settings
from timedeltafield import TimedeltaField
from redesign.group.models import Group
from redesign.person.models import Person
from redesign.doc.models import Document
from redesign.name.models import MeetingTypeName, TimeSlotTypeName, SessionStatusName, ConstraintName
countries = pytz.country_names.items()
countries.sort(lambda x,y: cmp(x[1], y[1]))
timezones = [(name, name) for name in pytz.common_timezones]
timezones.sort()
class Meeting(models.Model):
# number is either the number for IETF meetings, or some other
# identifier for interim meetings/IESG retreats/liaison summits/...
number = models.CharField(max_length=64)
type = models.ForeignKey(MeetingTypeName)
# Date is useful when generating a set of timeslot for this meeting, but
# is not used to determine date for timeslot instances thereafter, as
# they have their own datetime field.
date = models.DateField()
city = models.CharField(blank=True, max_length=255)
country = models.CharField(blank=True, max_length=2, choices=countries)
# We can't derive time-zone from country, as there are some that have
# more than one timezone, and the pytz module doesn't provide timezone
# lookup information for all relevant city/country combinations.
time_zone = models.CharField(blank=True, max_length=255, choices=timezones)
venue_name = models.CharField(blank=True, max_length=255)
venue_addr = models.TextField(blank=True)
break_area = models.CharField(blank=True, max_length=255)
reg_area = models.CharField(blank=True, max_length=255)
def __str__(self):
return "IETF-%s" % (self.number)
def time_zone_offset(self):
return pytz.timezone(self.time_zone).localize(datetime.datetime.combine(self.date, datetime.time(0, 0))).strftime("%z")
def get_meeting_date (self,offset):
return self.date + datetime.timedelta(days=offset)
@classmethod
def get_first_cut_off(cls):
date = cls.objects.all().order_by('-date')[0].date
offset = datetime.timedelta(days=settings.FIRST_CUTOFF_DAYS)
return date - offset
@classmethod
def get_second_cut_off(cls):
date = cls.objects.all().order_by('-date')[0].date
offset = datetime.timedelta(days=settings.SECOND_CUTOFF_DAYS)
return date - offset
@classmethod
def get_ietf_monday(cls):
date = cls.objects.all().order_by('-date')[0].date
return date + datetime.timedelta(days=-date.weekday(), weeks=1)
# the various dates are currently computed
def get_submission_start_date(self):
return self.date + datetime.timedelta(days=-90)
def get_submission_cut_off_date(self):
return self.date + datetime.timedelta(days=33)
def get_submission_correction_date(self):
return self.date + datetime.timedelta(days=59)
class Room(models.Model):
meeting = models.ForeignKey(Meeting)
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
class TimeSlot(models.Model):
"""
Everything that would appear on the meeting agenda of a meeting is
mapped to a time slot, including breaks. Sessions are connected to
TimeSlots during scheduling. A template function to populate a
meeting with an appropriate set of TimeSlots is probably also
needed.
"""
meeting = models.ForeignKey(Meeting)
type = models.ForeignKey(TimeSlotTypeName)
name = models.CharField(max_length=255)
time = models.DateTimeField()
duration = TimedeltaField()
location = models.ForeignKey(Room, blank=True, null=True)
show_location = models.BooleanField(default=True, help_text="Show location in agenda")
materials = models.ManyToManyField(Document, blank=True)
session = models.ForeignKey('Session', null=True, blank=True, help_text=u"Scheduled group session, if any")
modified = models.DateTimeField(default=datetime.datetime.now)
def __unicode__(self):
location = self.get_location()
if not location:
location = "(no location)"
return u"%s: %s-%s %s, %s" % (self.meeting.number, self.time.strftime("%m-%d %H:%M"), (self.time + self.duration).strftime("%H:%M"), self.name, location)
def get_location(self):
location = self.location
if location:
location = location.name
elif self.type_id == "reg":
location = self.meeting.reg_area
elif self.type_id == "break":
location = self.meeting.break_area
if not self.show_location:
location = ""
return location
class Constraint(models.Model):
"""Specifies a constraint on the scheduling between source and
target, e.g. some kind of conflict."""
meeting = models.ForeignKey(Meeting)
source = models.ForeignKey(Group, related_name="constraint_source_set")
target = models.ForeignKey(Group, related_name="constraint_target_set")
name = models.ForeignKey(ConstraintName)
def __unicode__(self):
return u"%s %s %s" % (self.source, self.name.lower(), self.target)
class Session(models.Model):
"""Session records that a group should have a session on the
meeting (the actual period of time and location is stored in
TimeSlot) - if multiple timeslots are needed, multiple sessions
will have to be created."""
meeting = models.ForeignKey(Meeting)
group = models.ForeignKey(Group) # The group type determines the session type. BOFs also need to be added as a group.
attendees = models.IntegerField(null=True, blank=True)
agenda_note = models.CharField(blank=True, max_length=255)
#
requested = models.DateTimeField()
requested_by = models.ForeignKey(Person)
requested_duration = TimedeltaField()
comments = models.TextField()
#
status = models.ForeignKey(SessionStatusName)
scheduled = models.DateTimeField(null=True, blank=True)
modified = models.DateTimeField(default=datetime.datetime.now)
# contains the materials while the session is being requested,
# when it is scheduled, timeslot.materials should be used
materials = models.ManyToManyField(Document, blank=True)
def __unicode__(self):
timeslots = self.timeslot_set.order_by('time')
return u"%s: %s %s" % (self.meeting, self.group.acronym, timeslots[0].time.strftime("%H%M") if timeslots else "(unscheduled)")
# IESG history is extracted from GroupHistory, rather than hand coded in a
# separate table.
# Meeting models can be found under ../proceedings/

526
ietf/meeting/proxy.py Normal file
View file

@ -0,0 +1,526 @@
import datetime
from django.conf import settings
from redesign.proxy_utils import TranslatingManager
from models import *
class MeetingProxy(Meeting):
objects = TranslatingManager(dict(meeting_num="number"))
def from_object(self, base):
for f in base._meta.fields:
setattr(self, f.name, getattr(base, f.name))
return self
#meeting_num = models.IntegerField(primary_key=True)
@property
def meeting_num(self):
return self.number
#start_date = models.DateField()
@property
def start_date(self):
return self.date
#end_date = models.DateField()
@property
def end_date(self):
return self.date + datetime.timedelta(days=5)
#city = models.CharField(blank=True, max_length=255)
#state = models.CharField(blank=True, max_length=255)
#country = models.CharField(blank=True, max_length=255)
#time_zone = models.IntegerField(null=True, blank=True, choices=TIME_ZONE_CHOICES)
#ack = models.TextField(blank=True)
#agenda_html = models.TextField(blank=True)
#agenda_text = models.TextField(blank=True)
#future_meeting = models.TextField(blank=True)
#overview1 = models.TextField(blank=True)
#overview2 = models.TextField(blank=True)
def __str__(self):
return "IETF-%s" % (self.meeting_num)
def get_meeting_date (self,offset):
return self.start_date + datetime.timedelta(days=offset)
def num(self):
return self.number
@property
def meeting_venue(self):
return MeetingVenueProxy().from_object(self)
@classmethod
def get_first_cut_off(cls):
start_date = cls.objects.all().order_by('-date')[0].start_date
offset = datetime.timedelta(days=settings.FIRST_CUTOFF_DAYS)
return start_date - offset
@classmethod
def get_second_cut_off(cls):
start_date = cls.objects.all().order_by('-date')[0].start_date
offset = datetime.timedelta(days=settings.SECOND_CUTOFF_DAYS)
return start_date - offset
@classmethod
def get_ietf_monday(cls):
start_date = cls.objects.all().order_by('-date')[0].start_date
return start_date + datetime.timedelta(days=-start_date.weekday(), weeks=1)
class Meta:
proxy = True
class ProceedingProxy(Meeting):
objects = TranslatingManager(dict(meeting_num="number"))
#meeting_num = models.ForeignKey(Meeting, db_column='meeting_num', unique=True, primary_key=True)
@property
def meeting_num(self):
return MeetingProxy().from_object(self)
@property
def meeting_num_id(self):
return self.number
#dir_name = models.CharField(blank=True, max_length=25)
@property
def dir_name(self):
return self.number
#sub_begin_date = models.DateField(null=True, blank=True)
@property
def sub_begin_date(self):
return self.get_submission_start_date()
#sub_cut_off_date = models.DateField(null=True, blank=True)
@property
def sub_cut_off_date(self):
return self.get_submission_cut_off_date()
#frozen = models.IntegerField(null=True, blank=True)
#c_sub_cut_off_date = models.DateField(null=True, blank=True)
@property
def c_sub_cut_off_date(self):
return self.get_submission_correction_date()
#pr_from_date = models.DateField(null=True, blank=True)
#pr_to_date = models.DateField(null=True, blank=True)
def __str__(self):
return "IETF %s" % (self.meeting_num_id)
class Meta:
proxy = True
class SwitchesProxy(Meeting):
def from_object(self, base):
for f in base._meta.fields:
setattr(self, f.name, getattr(base, f.name))
return self
#name = models.CharField(max_length=100)
#val = models.IntegerField(null=True, blank=True)
#updated_date = models.DateField(null=True, blank=True)
#updated_time = models.TimeField(null=True, blank=True)
def updated(self):
from django.db.models import Max
return max(self.timeslot_set.aggregate(Max('modified'))["modified__max"],
self.session_set.aggregate(Max('modified'))["modified__max"])
class Meta:
proxy = True
class MeetingVenueProxy(Meeting):
objects = TranslatingManager(dict(meeting_num="number"))
def from_object(self, base):
for f in base._meta.fields:
setattr(self, f.name, getattr(base, f.name))
return self
#meeting_num = models.ForeignKey(Meeting, db_column='meeting_num', unique=True)
@property
def meeting_num(self):
return self.number
#break_area_name = models.CharField(max_length=255)
@property
def break_area_name(self):
return self.break_area
#reg_area_name = models.CharField(max_length=255)
@property
def reg_area_name(self):
return self.reg_area
def __str__(self):
return "IETF %s" % (self.meeting_num)
class Meta:
proxy = True
class MeetingTimeProxy(TimeSlot):
# the old MeetingTimes did not include a room, so there we can't
# do a proper mapping - instead this proxy is one TimeSlot and
# uses the information in that to emulate a MeetingTime and enable
# retrieval of the other related TimeSlots
objects = TranslatingManager(dict(day_id="time", time_desc="time"))
def from_object(self, base):
for f in base._meta.fields:
setattr(self, f.name, getattr(base, f.name))
return self
#time_id = models.AutoField(primary_key=True)
@property
def time_id(self):
return self.pk
#time_desc = models.CharField(max_length=100)
@property
def time_desc(self):
return u"%s-%s" % (self.time.strftime("%H%M"), (self.time + self.duration).strftime("%H%M"))
#meeting = models.ForeignKey(Meeting, db_column='meeting_num') # same name
#day_id = models.IntegerField()
@property
def day_id(self):
return (self.time.date() - self.meeting.date).days
#session_name = models.ForeignKey(SessionName,null=True)
@property
def session_name(self):
if self.type_id not in ("session", "plenary"):
return None
class Dummy(object):
def __unicode__(self):
return self.session_name
d = Dummy()
d.session_name = self.name
return d
def __str__(self):
return "[%d] |%s| %s" % (self.meeting.number, self.time.strftime('%A'), self.time_desc)
def sessions(self):
if not hasattr(self, "sessions_cache"):
self.sessions_cache = WgMeetingSessionProxy.objects.filter(meeting=self.meeting, time=self.time, type__in=("session", "plenary", "other"))
return self.sessions_cache
def sessions_by_area(self):
return [ {"area":session.area()+session.acronym(), "info":session} for session in self.sessions() ]
def meeting_date(self):
return self.time.date()
def registration(self):
if not hasattr(self, '_reg_info'):
try:
self._reg_info = MeetingTimeProxy.objects.get(meeting=self.meeting, time__month=self.time.month, time__day=self.time.day, type="reg")
except MeetingTimeProxy.DoesNotExist:
self._reg_info = None
return self._reg_info
def reg_info(self):
reg_info = self.registration()
if reg_info and reg_info.time_desc:
return "%s %s" % (reg_info.time_desc, reg_info.name)
else:
return ""
def break_info(self):
breaks = MeetingTimeProxy.objects.filter(meeting=self.meeting, time__month=self.time.month, time__day=self.time.day, type="break").order_by("time")
for brk in breaks:
if brk.time_desc[-4:] == self.time_desc[:4]:
return brk
return None
def is_plenary(self):
return self.type_id == "plenary"
# from NonSession
#non_session_id = models.AutoField(primary_key=True)
@property
def non_session_id(self):
return self.id
#day_id = models.IntegerField(blank=True, null=True) # already wrapped
#non_session_ref = models.ForeignKey(NonSessionRef)
@property
def non_session_ref(self):
return 1 if self.type_id == "reg" else 3
#meeting = models.ForeignKey(Meeting, db_column='meeting_num') 3 same name
#time_desc = models.CharField(blank=True, max_length=75) # already wrapped
#show_break_location = models.BooleanField()
@property
def show_break_location(self):
return self.show_location
def day(self):
return self.time.strftime("%A")
class Meta:
proxy = True
NonSessionProxy = MeetingTimeProxy
class WgMeetingSessionProxy(TimeSlot):
# we model WgMeetingSession as a TimeSlot - we need to do this
# because some previous sessions are now really time slots, to
# make the illusion complete we thus have to forward all the
# session stuff to the real session
objects = TranslatingManager(dict(group_acronym_id="session__group",
status__id=lambda v: ("state", {4: "sched"}[v])))
def from_object(self, base):
for f in base._meta.fields:
setattr(self, f.name, getattr(base, f.name))
return self
#session_id = models.AutoField(primary_key=True) # same name
#meeting = models.ForeignKey(Meeting, db_column='meeting_num') # same name
#group_acronym_id = models.IntegerField()
@property
def group_acronym_id(self):
return self.session.group_id if self.session else -1
#irtf = models.NullBooleanField()
@property
def irtf(self):
return 1 if self.session and self.session.group and self.session.group.type == "rg" else 0
#num_session = models.IntegerField()
@property
def num_session(self):
return 1 if self.session else 0
#length_session1 = models.CharField(blank=True, max_length=100)
@property
def length_session1(self):
if not self.session:
return "0"
secs = self.session.requested_duration.seconds
if secs == 0:
return "0"
return str((secs / 60 - 30) / 30)
#length_session2 = models.CharField(blank=True, max_length=100)
@property
def length_session2(self):
return "0"
#length_session3 = models.CharField(blank=True, max_length=100)
@property
def length_session3(self):
return "0"
def conflicting_group_acronyms(self, level):
if not self.session:
return ""
conflicts = Constraint.objects.filter(meeting=self.meeting_id,
target=self.session.group,
name=level)
return " ".join(c.source.acronym for c in conflicts)
#conflict1 = models.CharField(blank=True, max_length=255)
@property
def conflict1(self):
return self.conflicting_group_acronyms("conflict")
#conflict2 = models.CharField(blank=True, max_length=255)
@property
def conflict2(self):
return self.conflicting_group_acronyms("conflic2")
#conflict3 = models.CharField(blank=True, max_length=255)
@property
def conflict3(self):
return self.conflicting_group_acronyms("conflic3")
#conflict_other = models.TextField(blank=True)
@property
def conflict_other(self):
return ""
#special_req = models.TextField(blank=True)
@property
def special_req(self):
return self.session.comments if self.session else ""
#number_attendee = models.IntegerField(null=True, blank=True)
@property
def number_attendee(self):
return self.session.attendees if self.session else 0
#approval_ad = models.IntegerField(null=True, blank=True)
#status = models.ForeignKey(SessionStatus, null=True, blank=True) # same name
#ts_status_id = models.IntegerField(null=True, blank=True)
#requested_date = models.DateField(null=True, blank=True)
@property
def requested_date(self):
return self.session.requested.date() if self.session else None
#approved_date = models.DateField(null=True, blank=True)
#requested_by = BrokenForeignKey(PersonOrOrgInfo, db_column='requested_by', null=True, null_values=(0, 888888))
@property
def requested_by(self):
return self.session.requested_by if self.session else None
#scheduled_date = models.DateField(null=True, blank=True)
@property
def scheduled_date(self):
return self.session.scheduled.date() if self.session else ""
#last_modified_date = models.DateField(null=True, blank=True)
@property
def last_modified_date(self):
return self.session.modified.date() if self.session else ""
#ad_comments = models.TextField(blank=True,null=True)
#sched_room_id1 = models.ForeignKey(MeetingRoom, db_column='sched_room_id1', null=True, blank=True, related_name='here1')
#sched_time_id1 = BrokenForeignKey(MeetingTime, db_column='sched_time_id1', null=True, blank=True, related_name='now1')
#sched_date1 = models.DateField(null=True, blank=True)
#sched_room_id2 = models.ForeignKey(MeetingRoom, db_column='sched_room_id2', null=True, blank=True, related_name='here2')
#sched_time_id2 = BrokenForeignKey(MeetingTime, db_column='sched_time_id2', null=True, blank=True, related_name='now2')
#sched_date2 = models.DateField(null=True, blank=True)
#sched_room_id3 = models.ForeignKey(MeetingRoom, db_column='sched_room_id3', null=True, blank=True, related_name='here3')
#sched_time_id3 = BrokenForeignKey(MeetingTime, db_column='sched_time_id3', null=True, blank=True, related_name='now3')
#sched_date3 = models.DateField(null=True, blank=True)
#special_agenda_note = models.CharField(blank=True, max_length=255)
@property
def special_agenda_note(self):
return self.session.agenda_note if self.session else ""
#combined_room_id1 = models.ForeignKey(MeetingRoom, db_column='combined_room_id1', null=True, blank=True, related_name='here4')
#combined_time_id1 = models.ForeignKey(MeetingTime, db_column='combined_time_id1', null=True, blank=True, related_name='now4')
#combined_room_id2 = models.ForeignKey(MeetingRoom, db_column='combined_room_id2', null=True, blank=True, related_name='here5')
#combined_time_id2 = models.ForeignKey(MeetingTime, db_column='combined_time_id2', null=True, blank=True, related_name='now5')
def __str__(self):
return "%s at %s" % (self.acronym(), self.meeting)
def agenda_file(self,interimvar=0):
if not hasattr(self, '_agenda_file'):
docs = self.materials.filter(type="agenda", states__type="agenda", states__slug="active")
if not docs:
return ""
# we use external_url at the moment, should probably regularize
# the filenames to match the document name instead
filename = docs[0].external_url
self._agenda_file = "%s/agenda/%s" % (self.meeting.number, filename)
return self._agenda_file
def minute_file(self,interimvar=0):
docs = self.materials.filter(type="minutes", states__type="minutes", states__slug="active")
if not docs:
return ""
# we use external_url at the moment, should probably regularize
# the filenames to match the document name instead
filename = docs[0].external_url
return "%s/minutes/%s" % (self.meeting.number, filename)
def slides(self,interimvar=0):
return SlideProxy.objects.filter(timeslot=self).order_by("order")
def interim_meeting(self):
return False
def length_session1_desc(self):
l = self.length_session1
return { "0": "", "1": "1 hour", "2": "1.5 hours", "3": "2 hours", "4": "2.5 hours"}[l]
def length_session2_desc(self):
return ""
def length_session3_desc(self):
return ""
@property
def ordinality(self):
return 1
@property
def room_id(self):
class Dummy: pass
d = Dummy()
d.room_name = self.location.name
return d
# from ResolveAcronym:
def acronym(self):
if not self.session:
if self.type_id == "plenary":
if "Operations and Administration" in self.name:
return "plenaryw"
if "Technical" in self.name:
return "plenaryt"
for m in self.materials.filter(type="agenda", states__type="agenda", states__slug="active"):
if "plenaryw" in m.name:
return "plenaryw"
if "plenaryt" in m.name:
return "plenaryt"
return "%s" % self.pk
if hasattr(self, "interim"):
return "i" + self.session.group.acronym
else:
return self.session.group.acronym
def acronym_lower(self):
return self.acronym().lower()
def acronym_name(self):
if not self.session:
return self.name
if hasattr(self, "interim"):
return self.session.group.name + " (interim)"
else:
return self.session.group.name
def area(self):
if not self.session:
return ""
return self.session.group.parent.acronym
def area_name(self):
if self.type_id == "plenary":
return "Plenary Sessions"
elif self.type_id == "other":
return "Training"
elif not self.session:
return ""
return self.session.group.parent.name
def isWG(self):
if not self.session or not self.session.group:
return False
if self.session.group.type_id == "wg" and self.session.group.state_id != "bof":
return True
def group_type_str(self):
if not self.session or not self.session.group:
return ""
if self.session.group and self.session.group.type_id == "wg":
if self.session.group.state_id == "bof":
return "BOF"
else:
return "WG"
return ""
class Meta:
proxy = True
class SlideProxy(Document):
objects = TranslatingManager(dict(), always_filter=dict(type="slides"))
SLIDE_TYPE_CHOICES=(
('1', '(converted) HTML'),
('2', 'PDF'),
('3', 'Text'),
('4', 'PowerPoint -2003 (PPT)'),
('5', 'Microsoft Word'),
('6', 'PowerPoint 2007- (PPTX)'),
)
#meeting = models.ForeignKey(Meeting, db_column='meeting_num')
@property
def meeting_id(self):
return self.name.split("-")[1]
#group_acronym_id = models.IntegerField(null=True, blank=True)
#slide_num = models.IntegerField(null=True, blank=True)
@property
def slide_name(self):
return int(self.name.split("-")[3])
#slide_type_id = models.IntegerField(choices=SLIDE_TYPE_CHOICES)
#slide_name = models.CharField(blank=True, max_length=255)
@property
def slide_name(self):
return self.title
#irtf = models.IntegerField()
#interim = models.BooleanField()
#order_num = models.IntegerField(null=True, blank=True)
@property
def order_num(self):
return self.order
#in_q = models.IntegerField(null=True, blank=True)
def acronym():
return self.name.split("-")[2]
def __str__(self):
return "IETF%d: %s slides (%s)" % (self.meeting_id, self.acronym(), self.slide_name)
def file_loc(self):
return "%s/slides/%s" % (self.meeting_id, self.external_url)
class Meta:
proxy = True
class IESGHistoryProxy(Person):
def from_object(self, base):
for f in self._meta.fields: # self here to enable us to copy a history object
setattr(self, f.name, getattr(base, f.name))
return self
#meeting = models.ForeignKey(Meeting, db_column='meeting_num')
def from_role(self, role, time):
from ietf.utils.history import find_history_active_at
personhistory = find_history_active_at(role.email.person, time)
self.from_object(personhistory or role.email.person)
from redesign.group.proxy import Area
self.area = Area().from_object(role.group)
return self
#area = models.ForeignKey(Area, db_column='area_acronym_id')
#person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
@property
def person(self):
return self
#def __str__(self):
# return "IESG%s: %s (%s)" % (self.meeting_id, self.person,self.area)
class Meta:
proxy = True

View file

@ -0,0 +1,191 @@
# -*- coding: iso-8859-1 -*-
# $Id: TimedeltaField.py 1787 2011-04-20 07:09:57Z tguettler $
# $HeadURL: svn+ssh://svnserver/svn/djangotools/trunk/dbfields/TimedeltaField.py $
# from http://djangosnippets.org/snippets/1060/ with some fixes
# Python
import datetime
# Django
from django import forms
from django.db import models
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
#Djangotools
#from djangotools.utils.southutils import add_introspection_rules_from_baseclass
SECS_PER_DAY=3600*24
class TimedeltaField(models.Field):
u'''
Store Python's datetime.timedelta in an integer column.
Most database systems only support 32 bit integers by default.
'''
__metaclass__ = models.SubfieldBase
empty_strings_allowed = False
def __init__(self, *args, **kwargs):
super(TimedeltaField, self).__init__(*args, **kwargs)
def to_python(self, value):
if (value is None) or isinstance(value, datetime.timedelta):
return value
try:
# else try to convert to int (e.g. from string)
value = int(value)
except (TypeError, ValueError):
raise exceptions.ValidationError(
_("This value must be an integer or a datetime.timedelta."))
return datetime.timedelta(seconds=value)
def get_internal_type(self):
return 'IntegerField'
def get_db_prep_lookup(self, lookup_type, value, connection=None, prepared=False):
raise NotImplementedError() # SQL WHERE
def get_db_prep_save(self, value, connection=None, prepared=False):
if (value is None) or isinstance(value, int):
return value
return SECS_PER_DAY*value.days+value.seconds
def formfield(self, *args, **kwargs):
defaults={'form_class': TimedeltaFormField}
defaults.update(kwargs)
return super(TimedeltaField, self).formfield(*args, **defaults)
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value)
#South Plugin registrieren
#add_introspection_rules_from_baseclass(TimedeltaField, ["^djangotools\.dbfields\.TimedeltaField"])
class TimedeltaFormField(forms.Field):
default_error_messages = {
'invalid': _(u'Enter a whole number.'),
}
def __init__(self, *args, **kwargs):
defaults={'widget': TimedeltaWidget}
defaults.update(kwargs)
super(TimedeltaFormField, self).__init__(*args, **defaults)
def clean(self, value):
# value comes from Timedelta.Widget.value_from_datadict(): tuple of strings
super(TimedeltaFormField, self).clean(value)
assert len(value)==len(self.widget.inputs), (value, self.widget.inputs)
i=0
for value, multiply in zip(value, self.widget.multiply):
try:
i+=int(value)*multiply
except ValueError, TypeError:
raise forms.ValidationError(self.error_messages['invalid'])
return i
class TimedeltaWidget(forms.Widget):
INPUTS=['days', 'hours', 'minutes', 'seconds']
MULTIPLY=[60*60*24, 60*60, 60, 1]
def __init__(self, attrs=None):
self.widgets=[]
if not attrs:
attrs={}
inputs=attrs.get('inputs', self.INPUTS)
multiply=[]
for input in inputs:
assert input in self.INPUTS, (input, self.INPUT)
self.widgets.append(forms.TextInput(attrs=attrs))
multiply.append(self.MULTIPLY[self.INPUTS.index(input)])
self.inputs=inputs
self.multiply=multiply
super(TimedeltaWidget, self).__init__(attrs)
def render(self, name, value, attrs):
if value is None:
values=[0 for i in self.inputs]
elif isinstance(value, datetime.timedelta):
values=split_seconds(value.days*SECS_PER_DAY+value.seconds, self.inputs, self.multiply)
elif isinstance(value, int):
# initial data from model
values=split_seconds(value, self.inputs, self.multiply)
else:
assert isinstance(value, tuple), (value, type(value))
assert len(value)==len(self.inputs), (value, self.inputs)
values=value
id=attrs.pop('id')
assert not attrs, attrs
rendered=[]
for input, widget, val in zip(self.inputs, self.widgets, values):
rendered.append(u'%s %s' % (_(input), widget.render('%s_%s' % (name, input), val)))
return mark_safe('<div id="%s">%s</div>' % (id, ' '.join(rendered)))
def value_from_datadict(self, data, files, name):
# Don't throw ValidationError here, just return a tuple of strings.
ret=[]
for input, multi in zip(self.inputs, self.multiply):
ret.append(data.get('%s_%s' % (name, input), 0))
return tuple(ret)
def _has_changed(self, initial_value, data_value):
# data_value comes from value_from_datadict(): A tuple of strings.
if initial_value is None:
return bool(set(data_value)!=set([u'0']))
assert isinstance(initial_value, datetime.timedelta), initial_value
initial=tuple([unicode(i) for i in split_seconds(initial_value.days*SECS_PER_DAY+initial_value.seconds, self.inputs, self.multiply)])
assert len(initial)==len(data_value), (initial, data_value)
return bool(initial!=data_value)
def main():
assert split_seconds(1000000)==[11, 13, 46, 40]
field=TimedeltaField()
td=datetime.timedelta(days=10, seconds=11)
s=field.get_db_prep_save(td)
assert isinstance(s, int), (s, type(s))
td_again=field.to_python(s)
assert td==td_again, (td, td_again)
td=datetime.timedelta(seconds=11)
s=field.get_db_prep_save(td)
td_again=field.to_python(s)
assert td==td_again, (td, td_again)
field=TimedeltaFormField()
assert field.widget._has_changed(datetime.timedelta(seconds=0), (u'0', u'0', u'0', u'0',)) is False
assert field.widget._has_changed(None, (u'0', u'0', u'0', u'0',)) is False
assert field.widget._has_changed(None, (u'0', u'0')) is False
assert field.widget._has_changed(datetime.timedelta(days=1, hours=2, minutes=3, seconds=4), (u'1', u'2', u'3', u'4',)) is False
for secs, soll, kwargs in [
(100, [0, 0, 1, 40], dict()),
(100, ['0days', '0hours', '1minutes', '40seconds'], dict(with_unit=True)),
(100, ['1minutes', '40seconds'], dict(with_unit=True, remove_leading_zeros=True)),
(100000, ['1days', '3hours'], dict(inputs=['days', 'hours'], with_unit=True, remove_leading_zeros=True)),
]:
ist=split_seconds(secs, **kwargs)
if ist!=soll:
raise Exception('geg=%s soll=%s ist=%s kwargs=%s' % (secs, soll, ist, kwargs))
print "unittest OK"
def split_seconds(secs, inputs=TimedeltaWidget.INPUTS, multiply=TimedeltaWidget.MULTIPLY,
with_unit=False, remove_leading_zeros=False):
ret=[]
assert len(inputs)<=len(multiply), (inputs, multiply)
for input, multi in zip(inputs, multiply):
count, secs = divmod(secs, multi)
if remove_leading_zeros and not ret and not count:
continue
if with_unit:
ret.append('%s%s' % (count, input))
else:
ret.append(count)
return ret
if __name__=='__main__':
main()

View file

@ -1,6 +1,5 @@
# Copyright The IETF Trust 2007, All Rights Reserved
# Create your views here.
#import models
import datetime
import os
@ -20,7 +19,7 @@ from django.template.loader import render_to_string
from django.conf import settings
from django.utils.decorators import decorator_from_middleware
from django.middleware.gzip import GZipMiddleware
from django.db.models import Count
from django.db.models import Count, Max
from ietf.idtracker.models import InternetDraft
from ietf.idrfc.idrfc_wrapper import IdWrapper
from ietf.utils.pipe import pipe
@ -43,19 +42,38 @@ def show_html_materials(request, meeting_num=None):
if now > begin_date:
sub_began = 1
# List of WG sessions and Plenary sessions
queryset_list = WgMeetingSession.objects.filter(Q(meeting=meeting_num, group_acronym_id__gte = -2, status__id=4), Q(irtf__isnull=True) | Q(irtf=0))
queryset_irtf = WgMeetingSession.objects.filter(meeting=meeting_num, group_acronym_id__gte = -2, status__id=4, irtf__gt=0)
queryset_interim = []
queryset_training = []
for item in list(WgMeetingSession.objects.filter(meeting=meeting_num)):
if item.interim_meeting():
item.interim=1
queryset_interim.append(item)
if item.group_acronym_id < -2:
if item.slides():
queryset_training.append(item)
cache_version = WgProceedingsActivities.objects.aggregate(Count('id'))
return object_list(request,queryset=queryset_list, template_name="meeting/list.html",allow_empty=True, extra_context={'meeting_num':meeting_num,'irtf_list':queryset_irtf, 'interim_list':queryset_interim, 'training_list':queryset_training, 'begin_date':begin_date, 'cut_off_date':cut_off_date, 'cor_cut_off_date':cor_cut_off_date,'sub_began':sub_began,'cache_version':cache_version})
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
queryset_list = []
queryset_irtf = []
queryset_interim = [] # currently ignored, have no way of handling interim here
queryset_training = []
for item in WgMeetingSession.objects.filter(meeting=meeting_num):
if item.type_id in ("session", "plenary"):
if item.session and item.session.group and item.session.group.type_id == "rg":
queryset_irtf.append(item)
else:
queryset_list.append(item)
else:
if item.type_id == "other" and item.slides():
queryset_training.append(item)
from redesign.doc.models import Document
cache_version = Document.objects.filter(timeslot__meeting__number=meeting_num).aggregate(Max('time'))["time__max"]
else:
queryset_list = WgMeetingSession.objects.filter(Q(meeting=meeting_num, group_acronym_id__gte = -2, status__id=4), Q(irtf__isnull=True) | Q(irtf=0))
queryset_irtf = WgMeetingSession.objects.filter(meeting=meeting_num, group_acronym_id__gte = -2, status__id=4, irtf__gt=0)
queryset_interim = []
queryset_training = []
for item in list(WgMeetingSession.objects.filter(meeting=meeting_num)):
if item.interim_meeting():
item.interim=1
queryset_interim.append(item)
if item.group_acronym_id < -2:
if item.slides():
queryset_training.append(item)
cache_version = WgProceedingsActivities.objects.aggregate(Count('id'))
return render_to_response("meeting/list.html",
{'meeting_num':meeting_num,'object_list': queryset_list, 'irtf_list':queryset_irtf, 'interim_list':queryset_interim, 'training_list':queryset_training, 'begin_date':begin_date, 'cut_off_date':cut_off_date, 'cor_cut_off_date':cor_cut_off_date,'sub_began':sub_began,'cache_version':cache_version},
context_instance=RequestContext(request))
def current_materials(request):
meeting = Meeting.objects.order_by('-meeting_num')[0]
@ -100,6 +118,64 @@ def agenda_info(num=None):
plenaryt_agenda = get_plenary_agenda(n, -2)
return timeslots, update, meeting, venue, ads, plenaryw_agenda, plenaryt_agenda
def agenda_infoREDESIGN(num=None):
try:
if num != None:
meeting = Meeting.objects.get(number=num)
else:
meeting = Meeting.objects.all().order_by('-date')[:1].get()
except Meeting.DoesNotExist:
raise Http404("No meeting information for meeting %s available" % num)
# now go through the timeslots, only keeping those that are
# sessions/plenary/training and don't occur at the same time
timeslots = []
time_seen = set()
for t in MeetingTime.objects.filter(meeting=meeting, type__in=("session", "plenary", "other")).order_by("time").select_related():
if not t.time in time_seen:
time_seen.add(t.time)
timeslots.append(t)
update = Switches().from_object(meeting)
venue = meeting.meeting_venue
ads = []
meeting_time = datetime.datetime.combine(meeting.date, datetime.time(0, 0, 0))
from redesign.group.models import Group
from ietf.utils.history import find_history_active_at
for g in Group.objects.filter(type="area").order_by("acronym"):
history = find_history_active_at(g, meeting_time)
if history:
if history.state_id == "active":
ads.extend(IESGHistory().from_role(x, meeting_time) for x in history.rolehistory_set.filter(name="ad"))
else:
if g.state_id == "active":
ads.extend(IESGHistory().from_role(x, meeting_time) for x in g.role_set.filter(name="ad"))
from redesign.doc.models import Document
plenary_agendas = Document.objects.filter(timeslot__meeting=meeting, timeslot__type="plenary", type="agenda").distinct()
plenaryw_agenda = plenaryt_agenda = "The Plenary has not been scheduled"
for agenda in plenary_agendas:
# we use external_url at the moment, should probably regularize
# the filenames to match the document name instead
path = os.path.join(settings.AGENDA_PATH, meeting.number, "agenda", agenda.external_url)
try:
f = open(path)
s = f.read()
f.close()
except IOError:
s = "THE AGENDA HAS NOT BEEN UPLOADED YET"
if "plenaryw" in agenda.name:
plenaryw_agenda = s
elif "plenaryt" in agenda.name:
plenaryt_agenda = s
return timeslots, update, meeting, venue, ads, plenaryw_agenda, plenaryt_agenda
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
agenda_info = agenda_infoREDESIGN
@decorator_from_middleware(GZipMiddleware)
def html_agenda(request, num=None):
timeslots, update, meeting, venue, ads, plenaryw_agenda, plenaryt_agenda = agenda_info(num)
@ -348,7 +424,7 @@ def ical_agenda(request, num=None):
if session.area() == '' or session.area().find('plenary') > 0 or (session.area().lower() in include):
filter.append(session.acronym())
return HttpResponse(render_to_string("meeting/agenda.ics",
return HttpResponse(render_to_string("meeting/agendaREDESIGN.ics" if settings.USE_DB_REDESIGN_PROXY_CLASSES else "meeting/agenda.ics",
{"filter":set(filter), "timeslots":timeslots, "update":update, "meeting":meeting, "venue":venue, "ads":ads,
"plenaryw_agenda":plenaryw_agenda, "plenaryt_agenda":plenaryt_agenda,
"now":now},

View file

@ -1,32 +1,38 @@
#coding: utf-8
from django.contrib import admin
from django.conf import settings
from ietf.proceedings.models import *
class IESGHistoryAdmin(admin.ModelAdmin):
list_display = ['meeting', 'area', 'person']
list_filter = ['meeting', ]
raw_id_fields = ["person", ]
admin.site.register(IESGHistory, IESGHistoryAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(IESGHistory, IESGHistoryAdmin)
class MeetingAdmin(admin.ModelAdmin):
list_display=('meeting_num', 'start_date', 'city', 'state', 'country', 'time_zone')
admin.site.register(Meeting, MeetingAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(Meeting, MeetingAdmin)
class MeetingRoomAdmin(admin.ModelAdmin):
list_display = ['room_id', 'meeting', 'room_name']
list_filter = ['meeting', ]
pass
admin.site.register(MeetingRoom, MeetingRoomAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(MeetingRoom, MeetingRoomAdmin)
class MeetingTimeAdmin(admin.ModelAdmin):
list_filter = ['meeting', ]
pass
admin.site.register(MeetingTime, MeetingTimeAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(MeetingTime, MeetingTimeAdmin)
class MeetingVenueAdmin(admin.ModelAdmin):
list_display = [ 'meeting_num', 'break_area_name', 'reg_area_name' ]
pass
admin.site.register(MeetingVenue, MeetingVenueAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(MeetingVenue, MeetingVenueAdmin)
class MinuteAdmin(admin.ModelAdmin):
list_filter = ['meeting', ]
@ -54,7 +60,8 @@ admin.site.register(SessionName, SessionNameAdmin)
class SlideAdmin(admin.ModelAdmin):
list_filter = ['meeting', ]
pass
admin.site.register(Slide, SlideAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(Slide, SlideAdmin)
class SwitchesAdmin(admin.ModelAdmin):
pass
@ -75,7 +82,8 @@ class WgMeetingSessionAdmin(admin.ModelAdmin):
list_display = ['session_id', 'meeting', 'acronym', 'number_attendee', 'status', 'approval_ad', 'scheduled_date', 'last_modified_date', 'special_req', 'ad_comments']
list_filter = ['meeting', ]
pass
admin.site.register(WgMeetingSession, WgMeetingSessionAdmin)
if not settings.USE_DB_REDESIGN_PROXY_CLASSES:
admin.site.register(WgMeetingSession, WgMeetingSessionAdmin)
class WgProceedingsActivitiesAdmin(admin.ModelAdmin):
list_display = ['meeting', 'group_acronym', 'activity', 'act_date', 'act_time', 'act_by', ]

View file

@ -1,6 +1,7 @@
import re
import re, os
from django.contrib.syndication.feeds import Feed
from django.utils.feedgenerator import Atom1Feed
from django.conf import settings
from ietf.proceedings.models import WgProceedingsActivities
from ietf.proceedings.models import Slide, WgAgenda, Proceeding
from datetime import datetime, time
@ -14,6 +15,21 @@ class LatestWgProceedingsActivity(Feed):
base_url = "http://www3.ietf.org/proceedings/"
def items(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
objs = []
from redesign.doc.models import Document
for doc in Document.objects.filter(type__in=("agenda", "minutes", "slides")).order_by('-time')[:60]:
obj = dict(
title=doc.type_id,
group_acronym=doc.name.split("-")[2],
date=doc.time,
link=self.base_url + os.path.join(doc.get_file_path(), doc.external_url)[len(settings.AGENDA_PATH):],
author=""
)
objs.append(obj)
return objs
objs = []
for act in WgProceedingsActivities.objects.order_by('-act_date')[:60]:
obj = {}

View file

@ -3,6 +3,7 @@
from django.db import models
from django.conf import settings
from ietf.idtracker.models import Acronym, PersonOrOrgInfo, IRTF, AreaGroup, Area, IETFWG
from ietf.utils.broken_foreign_key import BrokenForeignKey
import datetime
#from ietf.utils import log
@ -188,7 +189,7 @@ class NonSessionRef(models.Model):
class NonSession(models.Model):
non_session_id = models.AutoField(primary_key=True)
day_id = models.IntegerField(blank=True, null=True)
day_id = models.IntegerField(blank=True, null=True) # NULL means all days
non_session_ref = models.ForeignKey(NonSessionRef)
meeting = models.ForeignKey(Meeting, db_column='meeting_num')
time_desc = models.CharField(blank=True, max_length=75)
@ -363,24 +364,24 @@ class WgMeetingSession(models.Model, ResolveAcronym):
ts_status_id = models.IntegerField(null=True, blank=True)
requested_date = models.DateField(null=True, blank=True)
approved_date = models.DateField(null=True, blank=True)
requested_by = models.ForeignKey(PersonOrOrgInfo, db_column='requested_by')
requested_by = BrokenForeignKey(PersonOrOrgInfo, db_column='requested_by', null=True, null_values=(0, 888888))
scheduled_date = models.DateField(null=True, blank=True)
last_modified_date = models.DateField(null=True, blank=True)
ad_comments = models.TextField(blank=True,null=True)
sched_room_id1 = models.ForeignKey(MeetingRoom, db_column='sched_room_id1', null=True, blank=True, related_name='here1')
sched_time_id1 = models.ForeignKey(MeetingTime, db_column='sched_time_id1', null=True, blank=True, related_name='now1')
sched_time_id1 = BrokenForeignKey(MeetingTime, db_column='sched_time_id1', null=True, blank=True, related_name='now1')
sched_date1 = models.DateField(null=True, blank=True)
sched_room_id2 = models.ForeignKey(MeetingRoom, db_column='sched_room_id2', null=True, blank=True, related_name='here2')
sched_time_id2 = models.ForeignKey(MeetingTime, db_column='sched_time_id2', null=True, blank=True, related_name='now2')
sched_time_id2 = BrokenForeignKey(MeetingTime, db_column='sched_time_id2', null=True, blank=True, related_name='now2')
sched_date2 = models.DateField(null=True, blank=True)
sched_room_id3 = models.ForeignKey(MeetingRoom, db_column='sched_room_id3', null=True, blank=True, related_name='here3')
sched_time_id3 = models.ForeignKey(MeetingTime, db_column='sched_time_id3', null=True, blank=True, related_name='now3')
sched_time_id3 = BrokenForeignKey(MeetingTime, db_column='sched_time_id3', null=True, blank=True, related_name='now3')
sched_date3 = models.DateField(null=True, blank=True)
special_agenda_note = models.CharField(blank=True, max_length=255)
combined_room_id1 = models.ForeignKey(MeetingRoom, db_column='combined_room_id1', null=True, blank=True, related_name='here4')
combined_time_id1 = models.ForeignKey(MeetingTime, db_column='combined_time_id1', null=True, blank=True, related_name='now4')
combined_time_id1 = BrokenForeignKey(MeetingTime, db_column='combined_time_id1', null=True, blank=True, related_name='now4')
combined_room_id2 = models.ForeignKey(MeetingRoom, db_column='combined_room_id2', null=True, blank=True, related_name='here5')
combined_time_id2 = models.ForeignKey(MeetingTime, db_column='combined_time_id2', null=True, blank=True, related_name='now5')
combined_time_id2 = BrokenForeignKey(MeetingTime, db_column='combined_time_id2', null=True, blank=True, related_name='now5')
def __str__(self):
return "%s at %s" % (self.acronym(), self.meeting)
def agenda_file(self,interimvar=0):
@ -583,6 +584,18 @@ class MeetingHour(models.Model):
class Meta:
db_table = u'meeting_hours'
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
MeetingOld = Meeting
ProceedingOld = Proceeding
MeetingVenueOld = MeetingVenue
MeetingTimeOld = MeetingTime
WgMeetingSessionOld = WgMeetingSession
SlideOld = Slide
SwitchesOld = Switches
IESGHistoryOld = IESGHistory
from ietf.meeting.proxy import MeetingProxy as Meeting, ProceedingProxy as Proceeding, MeetingVenueProxy as MeetingVenue, MeetingTimeProxy as MeetingTime, WgMeetingSessionProxy as WgMeetingSession, SlideProxy as Slide, SwitchesProxy as Switches, IESGHistoryProxy as IESGHistory
# changes done by convert-096.py:changed maxlength to max_length
# removed core
# removed raw_id_admin

View file

@ -8,9 +8,12 @@ import os
import syslog
syslog.openlog("django", syslog.LOG_PID, syslog.LOG_LOCAL0)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
import sys
sys.path.append(os.path.abspath(BASE_DIR + "/.."))
sys.path.append(os.path.abspath(BASE_DIR + "/../redesign"))
DEBUG = True
TEMPLATE_DEBUG = DEBUG
@ -122,6 +125,11 @@ INSTALLED_APPS = (
'south',
'workflows',
'permissions',
'redesign.person',
'redesign.name',
'redesign.group',
'redesign.doc',
# 'redesign.issue',
'ietf.announcements',
'ietf.idindex',
'ietf.idtracker',
@ -235,6 +243,13 @@ DAYS_TO_EXPIRE_REGISTRATION_LINK = 3
HTPASSWD_COMMAND = "/usr/bin/htpasswd2"
HTPASSWD_FILE = "/www/htpasswd"
# DB redesign
USE_DB_REDESIGN_PROXY_CLASSES = True
if USE_DB_REDESIGN_PROXY_CLASSES:
AUTH_PROFILE_MODULE = 'person.Person'
AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.RemoteUserBackend', )
# Put SECRET_KEY in here, or any other sensitive or site-specific
# changes. DO NOT commit settings_local.py to svn.
from settings_local import *

View file

@ -1,3 +1,4 @@
from django.core.urlresolvers import reverse as urlreverse
from django.contrib import admin
from ietf.submit.models import *
@ -9,7 +10,16 @@ class IdSubmissionDetailAdmin(admin.ModelAdmin):
list_display = ['submission_id', 'draft_link', 'status_link', 'submission_date', 'last_updated_date',]
ordering = [ '-submission_date' ]
search_fields = ['filename', ]
admin.site.register(IdSubmissionDetail, IdSubmissionDetailAdmin)
raw_id_fields = ['group_acronym']
def status_link(self, instance):
url = urlreverse('draft_status_by_hash',
kwargs=dict(submission_id=instance.submission_id,
submission_hash=instance.get_hash()))
return '<a href="%s">%s</a>' % (url, instance.status)
status_link.allow_tags = True
admin.site.register(IdSubmissionDetail, IdSubmissionDetailAdmin)
class IdApprovedDetailAdmin(admin.ModelAdmin):
pass
@ -17,5 +27,5 @@ admin.site.register(IdApprovedDetail, IdApprovedDetailAdmin)
class TempIdAuthorsAdmin(admin.ModelAdmin):
ordering = ["-id"]
pass
admin.site.register(TempIdAuthors, TempIdAuthorsAdmin)
admin.site.register(TempIdAuthors, TempIdAuthorsAdmin)

View file

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
<object pk="-4" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Cancelled</field>
</object>
<object pk="-3" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Dead</field>
</object>
<object pk="-2" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Posted by the Secretariat</field>
</object>
<object pk="-1" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Posted</field>
</object>
<object pk="0" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Ready To Post</field>
</object>
<object pk="1" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Uploaded</field>
</object>
<object pk="2" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">ID NITS Passed</field>
</object>
<object pk="3" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Initial Version Approval Required</field>
</object>
<object pk="4" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Submitter Authentication Required</field>
</object>
<object pk="5" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Manual Post Requested</field>
</object>
<object pk="6" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">External Meta-Data Required</field>
</object>
<object pk="7" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Internal Database Has Been Updated</field>
</object>
<object pk="8" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">ID Announcement Scheduled</field>
</object>
<object pk="9" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">ID Tracker Notification Scheduled</field>
</object>
<object pk="10" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Initial Version Approval Requested</field>
</object>
<object pk="101" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - Plain text version does not exist</field>
</object>
<object pk="102" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">File size is larger than 20 MB</field>
</object>
<object pk="103" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Duplicate Internet-Draft submission is currently in process.</field>
</object>
<object pk="104" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - Simultaneous submission from the same IP address</field>
</object>
<object pk="105" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - Auth key does not match</field>
</object>
<object pk="106" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - No such Internet-Draft is currently in process</field>
</object>
<object pk="107" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - Draft is not in an appropriate status for the requested page</field>
</object>
<object pk="108" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - Unknown Request</field>
</object>
<object pk="109" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - Invalid Email Address</field>
</object>
<object pk="110" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - Direct Access is prohibited</field>
</object>
<object pk="201" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - Invalid version number</field>
</object>
<object pk="202" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - Invalid filename</field>
</object>
<object pk="203" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error - The document failed idnits verification</field>
</object>
<object pk="204" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Creation Date must be within 3 days of the submission date.</field>
</object>
<object pk="205" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Error Not a valid submitter</field>
</object>
<object pk="206" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Incorrect Meta-Data</field>
</object>
<object pk="111" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">The document does not contain a legitimate filename that start with draft-*.</field>
</object>
<object pk="11" model="submit.idsubmissionstatus">
<field type="CharField" name="status_value">Initial Version Approved</field>
</object>
</django-objects>

View file

@ -1,4 +1,4 @@
import sha
import hashlib
import random
import os
import subprocess
@ -10,6 +10,7 @@ from django.conf import settings
from django.contrib.sites.models import Site
from django.template.loader import render_to_string
from django.utils.html import mark_safe
from django.core.urlresolvers import reverse as urlreverse
from ietf.idtracker.models import InternetDraft, IETFWG
from ietf.proceedings.models import Meeting
@ -249,7 +250,10 @@ class UploadForm(forms.Form):
document_id = 0
existing_draft = InternetDraft.objects.filter(filename=draft.filename)
if existing_draft:
document_id = existing_draft[0].id_document_tag
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
document_id = -1
else:
document_id = existing_draft[0].id_document_tag
detail = IdSubmissionDetail.objects.create(
id_document_name=draft.get_title(),
filename=draft.filename,
@ -271,22 +275,34 @@ class UploadForm(forms.Form):
for author in draft.get_author_info():
full_name, first_name, middle_initial, last_name, name_suffix, email = author
order += 1
TempIdAuthors.objects.create(
id_document_tag=document_id,
first_name=first_name,
middle_initial=middle_initial,
last_name=last_name,
name_suffix=name_suffix,
email_address=email,
author_order=order,
submission=detail)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
# save full name
TempIdAuthors.objects.create(
id_document_tag=document_id,
first_name=full_name.strip(),
email_address=email,
author_order=order,
submission=detail)
else:
TempIdAuthors.objects.create(
id_document_tag=document_id,
first_name=first_name,
middle_initial=middle_initial,
last_name=last_name,
name_suffix=name_suffix,
email_address=email,
author_order=order,
submission=detail)
return detail
class AutoPostForm(forms.Form):
first_name = forms.CharField(label=u'Given name', required=True)
last_name = forms.CharField(label=u'Last name', required=True)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
name = forms.CharField(required=True)
else:
first_name = forms.CharField(label=u'Given name', required=True)
last_name = forms.CharField(label=u'Last name', required=True)
email = forms.EmailField(label=u'Email address', required=True)
def __init__(self, *args, **kwargs):
@ -295,10 +311,21 @@ class AutoPostForm(forms.Form):
super(AutoPostForm, self).__init__(*args, **kwargs)
def get_author_buttons(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
buttons = []
for i in self.validation.authors:
buttons.append('<input type="button" data-name="%(name)s" data-email="%(email)s" value="%(name)s" />'
% dict(name=i.get_full_name(),
email=i.email()[1] or ''))
return "".join(buttons)
# this should be moved to a Javascript file and attributes like data-first-name ...
button_template = '<input type="button" onclick="jQuery(\'#id_first_name\').val(\'%(first_name)s\');jQuery(\'#id_last_name\').val(\'%(last_name)s\');jQuery(\'#id_email\').val(\'%(email)s\');" value="%(full_name)s" />'
buttons = []
for i in self.validation.authors:
full_name = '%s. %s' % (i.first_name[0], i.last_name)
full_name = u'%s. %s' % (i.first_name[0], i.last_name)
buttons.append(button_template % {'first_name': i.first_name,
'last_name': i.last_name,
'email': i.email()[1] or '',
@ -314,10 +341,22 @@ class AutoPostForm(forms.Form):
subject = 'Confirmation for Auto-Post of I-D %s' % self.draft.filename
from_email = settings.IDSUBMIT_FROM_EMAIL
to_email = self.cleaned_data['email']
confirm_url = settings.IDTRACKER_BASE_URL + urlreverse('draft_confirm', kwargs=dict(submission_id=self.draft.submission_id, auth_key=self.draft.auth_key))
status_url = settings.IDTRACKER_BASE_URL + urlreverse('draft_status_by_hash', kwargs=dict(submission_id=self.draft.submission_id, submission_hash=self.draft.get_hash()))
send_mail(request, to_email, from_email, subject, 'submit/confirm_autopost.txt',
{'draft': self.draft, 'domain': Site.objects.get_current().domain })
{ 'draft': self.draft, 'confirm_url': confirm_url, 'status_url': status_url })
def save_submitter_info(self):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
return TempIdAuthors.objects.create(
id_document_tag=self.draft.temp_id_document_tag,
first_name=self.cleaned_data['name'],
email_address=self.cleaned_data['email'],
author_order=0,
submission=self.draft)
return TempIdAuthors.objects.create(
id_document_tag=self.draft.temp_id_document_tag,
first_name=self.cleaned_data['first_name'],
@ -327,8 +366,8 @@ class AutoPostForm(forms.Form):
submission=self.draft)
def save_new_draft_info(self):
salt = sha.new(str(random.random())).hexdigest()[:5]
self.draft.auth_key = sha.new(salt+self.cleaned_data['email']).hexdigest()
salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
self.draft.auth_key = hashlib.sha1(salt+self.cleaned_data['email']).hexdigest()
self.draft.status_id = WAITING_AUTHENTICATION
self.draft.save()
@ -340,11 +379,18 @@ class MetaDataForm(AutoPostForm):
creation_date = forms.DateField(label=u'Creation date', required=True)
pages = forms.IntegerField(label=u'Pages', required=True)
abstract = forms.CharField(label=u'Abstract', widget=forms.Textarea, required=True)
first_name = forms.CharField(label=u'Given name', required=True)
last_name = forms.CharField(label=u'Last name', required=True)
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
name = forms.CharField(required=True)
else:
first_name = forms.CharField(label=u'Given name', required=True)
last_name = forms.CharField(label=u'Last name', required=True)
email = forms.EmailField(label=u'Email address', required=True)
comments = forms.CharField(label=u'Comments to the secretariat', widget=forms.Textarea, required=False)
fields = ['title', 'version', 'creation_date', 'pages', 'abstract', 'first_name', 'last_name', 'email', 'comments']
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
fields = ['title', 'version', 'creation_date', 'pages', 'abstract', 'name', 'email', 'comments']
else:
fields = ['title', 'version', 'creation_date', 'pages', 'abstract', 'first_name', 'last_name', 'email', 'comments']
def __init__(self, *args, **kwargs):
super(MetaDataForm, self).__init__(*args, **kwargs)
@ -355,26 +401,44 @@ class MetaDataForm(AutoPostForm):
authors=[]
if self.is_bound:
for key, value in self.data.items():
if key.startswith('first_name_'):
author = {'errors': {}}
index = key.replace('first_name_', '')
first_name = value.strip()
if not first_name:
author['errors']['first_name'] = 'This field is required'
last_name = self.data.get('last_name_%s' % index, '').strip()
if not last_name:
author['errors']['last_name'] = 'This field is required'
email = self.data.get('email_%s' % index, '').strip()
if email and not email_re.search(email):
author['errors']['email'] = 'Enter a valid e-mail address'
if first_name or last_name or email:
author.update({'first_name': first_name,
'last_name': last_name,
'email': ('%s %s' % (first_name, last_name), email),
'index': index,
})
authors.append(author)
authors.sort(lambda x,y: cmp(int(x['index']), int(y['index'])))
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
if key.startswith('name_'):
author = {'errors': {}}
index = key.replace('name_', '')
name = value.strip()
if not name:
author['errors']['name'] = 'This field is required'
email = self.data.get('email_%s' % index, '').strip()
if email and not email_re.search(email):
author['errors']['email'] = 'Enter a valid e-mail address'
if name or email:
author.update({'get_full_name': name,
'email': (name, email),
'index': index,
})
authors.append(author)
else:
if key.startswith('first_name_'):
author = {'errors': {}}
index = key.replace('first_name_', '')
first_name = value.strip()
if not first_name:
author['errors']['first_name'] = 'This field is required'
last_name = self.data.get('last_name_%s' % index, '').strip()
if not last_name:
author['errors']['last_name'] = 'This field is required'
email = self.data.get('email_%s' % index, '').strip()
if email and not email_re.search(email):
author['errors']['email'] = 'Enter a valid e-mail address'
if first_name or last_name or email:
author.update({'first_name': first_name,
'last_name': last_name,
'email': ('%s %s' % (first_name, last_name), email),
'index': index,
})
authors.append(author)
authors.sort(key=lambda x: x['index'])
return authors
def set_initials(self):
@ -434,7 +498,7 @@ class MetaDataForm(AutoPostForm):
def save_new_draft_info(self):
draft = self.draft
draft.id_documen_name = self.cleaned_data['title']
draft.id_document_name = self.cleaned_data['title']
if draft.revision != self.cleaned_data['version']:
self.move_docs(draft, self.cleaned_data['version'])
draft.revision = self.cleaned_data['version']
@ -444,7 +508,21 @@ class MetaDataForm(AutoPostForm):
draft.comment_to_sec = self.cleaned_data['comments']
draft.status_id = MANUAL_POST_REQUESTED
draft.save()
self.save_submitter_info()
# sync authors
draft.tempidauthors_set.all().delete()
self.save_submitter_info() # submitter is author 0
for i, author in enumerate(self.authors):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
# save full name
TempIdAuthors.objects.create(
id_document_tag=draft.temp_id_document_tag,
first_name=author["get_full_name"],
email_address=author["email"][1],
author_order=i + 1,
submission=draft)
def save(self, request):
self.save_new_draft_info()
@ -459,5 +537,11 @@ class MetaDataForm(AutoPostForm):
if self.draft.group_acronym:
cc += [i.person.email()[1] for i in self.draft.group_acronym.wgchair_set.all()]
cc = list(set(cc))
send_mail(request, to_email, from_email, subject, 'submit/manual_post_mail.txt',
{'form': self, 'draft': self.draft, 'domain': Site.objects.get_current().domain }, cc=cc)
submitter = self.draft.tempidauthors_set.get(author_order=0)
send_mail(request, to_email, from_email, subject, 'submit/manual_post_mail.txt', {
'form': self,
'draft': self.draft,
'url': settings.IDTRACKER_BASE_URL + urlreverse('draft_status', kwargs=dict(submission_id=self.draft.submission_id)),
'submitter': submitter
},
cc=cc)

View file

@ -0,0 +1,34 @@
#!/usr/bin/python
# boiler plate
import os, sys
ietf_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../ietf'))
sys.path.insert(0, ietf_path)
from django.core.management import setup_environ
import settings
setup_environ(settings)
# script
from django.core.serializers import serialize
from django.db.models import Q
def output(name, qs):
try:
f = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures/%s.xml" % name), 'w')
f.write(serialize("xml", qs, indent=4))
f.close()
except:
from django.db import connection
from pprint import pprint
pprint(connection.queries)
raise
# pick all name models directly out of the module
names = []
from ietf.submit.models import IdSubmissionStatus
output("idsubmissionstatus", IdSubmissionStatus.objects.all())

View file

@ -92,7 +92,7 @@ class IdApprovedDetail(models.Model):
class TempIdAuthors(models.Model):
id_document_tag = models.IntegerField()
first_name = models.CharField(blank=True, max_length=255)
first_name = models.CharField(blank=True, max_length=255) # with new schema, this contains the full name while the other name fields are empty to avoid loss of information
last_name = models.CharField(blank=True, max_length=255)
email_address = models.CharField(blank=True, max_length=255)
last_modified_date = models.DateField(null=True, blank=True)
@ -106,13 +106,11 @@ class TempIdAuthors(models.Model):
db_table = 'temp_id_authors'
def email(self):
return ('%s %s' % (self.first_name, self.last_name), self.email_address)
return (self.get_full_name(), self.email_address)
def get_full_name(self):
full_name = ('%s %s %s %s') % (self.first_name, self.middle_initial or '',
self.last_name, self.name_suffix or '')
full_name = re.sub(' +', ' ', full_name).strip()
return full_name
parts = (self.first_name or '', self.middle_initial or '', self.last_name or '', self.name_suffix or '')
return u" ".join(x.strip() for x in parts if x.strip())
def __unicode__(self):
return u"%s <%s>" % self.email()
return u"%s <%s>" % self.email()

View file

@ -65,23 +65,3 @@ class PlainParser(FileParser):
self.parsed_info.metadraft.filename = filename
return
self.parsed_info.add_error('The first page of the document does not contain a legitimate filename that start with draft-*')
def parse_wg(self):
filename = self.parsed_info.metadraft.filename
try:
existing_draft = InternetDraft.objects.get(filename=filename)
self.parsed_info.metadraft.wg = existing_draft.group
except InternetDraft.DoesNotExist:
if filename.startswith('draft-ietf-'):
# Extra check for WG that contains dashes
for group in IETFWG.objects.filter(group_acronym__acronym__contains='-'):
if filename.startswith('draft-ietf-%s-' % group.group_acronym.acronym):
self.parsed_info.metadraft.wg = group
return
group_acronym = filename.split('-')[2]
try:
self.parsed_info.metadraft.wg = IETFWG.objects.get(group_acronym__acronym=group_acronym)
except IETFWG.DoesNotExist:
self.parsed_info.add_error('Invalid WG ID: %s' % group_acronym)
else:
self.parsed_info.metadraft.wg = IETFWG.objects.get(pk=NONE_WG_PK)

View file

@ -25,9 +25,6 @@ def show_submission_files(context, submission):
'url': '%s%s-%s%s' % (settings.IDSUBMIT_STAGING_URL, submission.filename, submission.revision, ext)})
return {'files': result}
def show_two_pages(context, two_pages, validation):
result
@register.filter
def two_pages_decorated_with_validation(value, validation):

View file

@ -0,0 +1,111 @@
Informational Test Name
Internet-Draft Test Center Inc.
Intended status: Informational %(date)s
Expires: %(expire)s
Testing tests
%(filename)s
Abstract
This document describes how to test tests.
Status of this Memo
This Internet-Draft is submitted in full conformance with the
provisions of BCP 78 and BCP 79.
Internet-Drafts are working documents of the Internet Engineering
Task Force (IETF). Note that other groups may also distribute
working documents as Internet-Drafts. The list of current Internet-
Drafts is at http://datatracker.ietf.org/drafts/current/.
Internet-Drafts are draft documents valid for a maximum of six months
and may be updated, replaced, or obsoleted by other documents at any
time. It is inappropriate to use Internet-Drafts as reference
material or to cite them other than as "work in progress."
This Internet-Draft will expire on %(expire)s.
Copyright Notice
Copyright (c) %(year)s IETF Trust and the persons identified as the
document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal
Provisions Relating to IETF Documents
(http://trustee.ietf.org/license-info) in effect on the date of
publication of this document. Please review these documents
carefully, as they describe your rights and restrictions with respect
to this document. Code Components extracted from this document must
Name Expires %(expire)s [Page 1]
Internet-Draft Testing tests %(month_year)s
include Simplified BSD License text as described in Section 4.e of
the Trust Legal Provisions and are provided without warranty as
described in the Simplified BSD License.
This document may contain material from IETF Documents or IETF
Contributions published or made publicly available before November
10, 2008. The person(s) controlling the copyright in some of this
material may not have granted the IETF Trust the right to allow
modifications of such material outside the IETF Standards Process.
Without obtaining an adequate license from the person(s) controlling
the copyright in such materials, this document may not be modified
outside the IETF Standards Process, and derivative works of it may
not be created outside the IETF Standards Process, except to format
it for publication as an RFC or to translate it into languages other
than English.
Table of Contents
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3
2. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 4
3. Security Considerations . . . . . . . . . . . . . . . . . . . 4
Name Expires %(expire)s [Page 2]
Internet-Draft Testing tests %(month_year)s
1. Introduction
This document describes a protocol for testing tests.
Name Expires %(expire)s [Page 3]
Internet-Draft Testing tests %(month_year)s
2. Security Considerations
There are none.
3. IANA Considerations
No new registrations for IANA.
Authors' Addresses
Test Name
Test Center Inc.
Some way 42
Some Where, NY
Email: testname@example.com
Name Expires %(expire)s [Page 4]

Some files were not shown because too many files have changed in this diff Show more