Merge with trunk
- Legacy-Id: 3676
This commit is contained in:
commit
5a4c23ea92
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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+"/")
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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("<", "<").replace(">", ">").replace("&", "&").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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
1394
ietf/idrfc/testsREDESIGN.py
Normal file
File diff suppressed because it is too large
Load diff
88
ietf/idrfc/testurlREDESIGN.list
Normal file
88
ietf/idrfc/testurlREDESIGN.list
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(' ', ' ')
|
||||
|
||||
|
||||
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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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,)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
173
ietf/ietfworkflows/tests.py
Normal 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
|
|
@ -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'),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)"
|
||||
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
127
ietf/liaisons/accountsREDESIGN.py
Normal file
127
ietf/liaisons/accountsREDESIGN.py
Normal 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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 *
|
||||
|
|
454
ietf/liaisons/formsREDESIGN.py
Normal file
454
ietf/liaisons/formsREDESIGN.py
Normal 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
128
ietf/liaisons/mails.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
369
ietf/liaisons/migrations/0011_add_liaison_statement.py
Normal file
369
ietf/liaisons/migrations/0011_add_liaison_statement.py
Normal 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']
|
|
@ -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
182
ietf/liaisons/proxy.py
Normal 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
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 *
|
||||
|
|
429
ietf/liaisons/utilsREDESIGN.py
Normal file
429
ietf/liaisons/utilsREDESIGN.py
Normal 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()
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'}),
|
||||
|
|
|
@ -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
526
ietf/meeting/proxy.py
Normal 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
|
191
ietf/meeting/timedeltafield.py
Normal file
191
ietf/meeting/timedeltafield.py
Normal 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()
|
|
@ -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},
|
||||
|
|
|
@ -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', ]
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
102
ietf/submit/fixtures/idsubmissionstatus.xml
Normal file
102
ietf/submit/fixtures/idsubmissionstatus.xml
Normal 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>
|
|
@ -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)
|
||||
|
|
34
ietf/submit/generate_fixtures.py
Executable file
34
ietf/submit/generate_fixtures.py
Executable 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())
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
111
ietf/submit/test_submission.txt
Normal file
111
ietf/submit/test_submission.txt
Normal 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
Loading…
Reference in a new issue