Merged the latest secretariat code from rcross@amsl.com, from branch/amsl/trunkmerge@6149.
- Legacy-Id: 6168
|
@ -1,4 +1,4 @@
|
|||
__version__ = "1.33"
|
||||
__version__ = "1.41"
|
||||
|
||||
__date__ = "$Date: 2011/07/26 14:29:17 $"
|
||||
|
||||
|
|
|
@ -13,7 +13,9 @@ from ietf.wgchairs.accounts import get_person_for_user
|
|||
# Globals
|
||||
# ---------------------------------------------
|
||||
|
||||
#ANNOUNCE_FROM_GROUPS = ['ietf','rsoc','iab',current_nomcom().acronym]
|
||||
ANNOUNCE_FROM_GROUPS = ['ietf','rsoc','iab']
|
||||
if current_nomcom():
|
||||
ANNOUNCE_FROM_GROUPS += [ current_nomcom().acronym ]
|
||||
ANNOUNCE_TO_GROUPS= ['ietf']
|
||||
|
||||
# this list isn't currently available as a Role query so it's hardcoded
|
||||
|
@ -32,8 +34,9 @@ FROM_LIST = ('IETF Secretariat <ietf-secretariat@ietf.org>',
|
|||
'The IETF Trust <tme@multicasttech.com>',
|
||||
'RSOC Chair <rsoc-chair@iab.org>',
|
||||
'ISOC Board of Trustees <eburger@standardstrack.com>',
|
||||
'RFC Series Editor <rse@rfc-editor.org>')
|
||||
|
||||
'RFC Series Editor <rse@rfc-editor.org>',
|
||||
'IAB Executive Director <execd@iab.org>')
|
||||
|
||||
TO_LIST = ('IETF Announcement List <ietf-announce@ietf.org>',
|
||||
'I-D Announcement List <i-d-announce@ietf.org>',
|
||||
'The IESG <iesg@ietf.org>',
|
||||
|
@ -51,14 +54,14 @@ class MultiEmailField(forms.Field):
|
|||
# Return an empty list if no input was given.
|
||||
if not value:
|
||||
return []
|
||||
|
||||
|
||||
import types
|
||||
if isinstance(value, types.StringTypes):
|
||||
values = value.split(',')
|
||||
return [ x.strip() for x in values ]
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def validate(self, value):
|
||||
"Check if value consists only of valid emails."
|
||||
|
||||
|
@ -67,7 +70,7 @@ class MultiEmailField(forms.Field):
|
|||
|
||||
for email in value:
|
||||
validate_email(email)
|
||||
|
||||
|
||||
# ---------------------------------------------
|
||||
# Helper Functions
|
||||
# ---------------------------------------------
|
||||
|
@ -87,13 +90,7 @@ def get_from_choices(user):
|
|||
f = (FROM_LIST[6],)
|
||||
elif has_role(user,'IAD'):
|
||||
f = (FROM_LIST[9],)
|
||||
# NomCom, RSOC Chair, IAOC Chair aren't supported by has_role()
|
||||
elif Role.objects.filter(name="chair",
|
||||
group__acronym__startswith="nomcom",
|
||||
group__state="active",
|
||||
group__type="ietf",
|
||||
person=person):
|
||||
f = (FROM_LIST[7],)
|
||||
#RSOC Chair, IAOC Chair aren't supported by has_role()
|
||||
elif Role.objects.filter(person=person,
|
||||
group__acronym='rsoc',
|
||||
name="chair"):
|
||||
|
@ -106,17 +103,33 @@ def get_from_choices(user):
|
|||
group__acronym='rse',
|
||||
name="chair"):
|
||||
f = (FROM_LIST[15],)
|
||||
elif Role.objects.filter(person=person,
|
||||
group__acronym='iab',
|
||||
name='execdir'):
|
||||
f = (FROM_LIST[6],FROM_LIST[16])
|
||||
|
||||
# NomCom
|
||||
nomcoms = Role.objects.filter(name="chair",
|
||||
group__acronym__startswith="nomcom",
|
||||
group__state="active",
|
||||
group__type="ietf",
|
||||
person=person)
|
||||
if nomcoms:
|
||||
year = nomcoms[0].group.acronym[-4:]
|
||||
alias = 'NomCom Chair %s <nomcom-chair-%s@ietf.org>' % (year,year)
|
||||
f = (alias,)
|
||||
|
||||
return zip(f,f)
|
||||
|
||||
|
||||
def get_to_choices():
|
||||
#groups = Group.objects.filter(acronym__in=ANNOUNCE_TO_GROUPS)
|
||||
#roles = Role.objects.filter(group__in=(groups),name="Announce")
|
||||
#choices = [ (r.email, r.person.name) for r in roles ]
|
||||
#choices.append(('Other...','Other...'),)
|
||||
return zip(TO_LIST,TO_LIST)
|
||||
|
||||
|
||||
# ---------------------------------------------
|
||||
# Select Choices
|
||||
# Select Choices
|
||||
# ---------------------------------------------
|
||||
#TO_CHOICES = tuple(AnnouncedTo.objects.values_list('announced_to_id','announced_to'))
|
||||
TO_CHOICES = get_to_choices()
|
||||
|
@ -127,24 +140,33 @@ TO_CHOICES = get_to_choices()
|
|||
# ---------------------------------------------
|
||||
|
||||
class AnnounceForm(forms.ModelForm):
|
||||
nomcom = forms.BooleanField(required=False)
|
||||
#nomcom = forms.BooleanField(required=False)
|
||||
nomcom = forms.ModelChoiceField(queryset=Group.objects.filter(acronym__startswith='nomcom',type='ietf',state='active'),required=False)
|
||||
to_custom = MultiEmailField(required=False,label='')
|
||||
#cc = MultiEmailField(required=False)
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = ('nomcom', 'to','to_custom','frm','cc','bcc','reply_to','subject','body')
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop('user')
|
||||
person = user.get_profile()
|
||||
super(AnnounceForm, self).__init__(*args, **kwargs)
|
||||
self.fields['to'].widget = forms.Select(choices=TO_CHOICES)
|
||||
self.fields['to'].help_text = 'Select name OR select Other... and enter email below'
|
||||
self.fields['cc'].help_text = 'Use comma separated lists for emails (Cc, Bcc, Reply To)'
|
||||
self.fields['frm'].widget = forms.Select(choices=get_from_choices(user))
|
||||
self.fields['frm'].label = 'From'
|
||||
self.fields['nomcom'].label = 'NomCom message?'
|
||||
|
||||
self.fields['nomcom'].label = 'NomCom message:'
|
||||
nomcom_roles = person.role_set.filter(group__in=self.fields['nomcom'].queryset,name='chair')
|
||||
secr_roles = person.role_set.filter(group__acronym='secretariat',name='secr')
|
||||
if nomcom_roles:
|
||||
self.initial['nomcom'] = nomcom_roles[0].group.pk
|
||||
if not nomcom_roles and not secr_roles:
|
||||
self.fields['nomcom'].widget = forms.HiddenInput()
|
||||
self.initial['reply_to'] = 'ietf@ietf.org'
|
||||
|
||||
def clean(self):
|
||||
super(AnnounceForm, self).clean()
|
||||
data = self.cleaned_data
|
||||
|
@ -152,9 +174,9 @@ class AnnounceForm(forms.ModelForm):
|
|||
return self.cleaned_data
|
||||
if data['to'] == 'Other...' and not data['to_custom']:
|
||||
raise forms.ValidationError('You must enter a "To" email address')
|
||||
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
user = kwargs.pop('user')
|
||||
message = super(AnnounceForm, self).save(commit=False)
|
||||
|
@ -163,10 +185,10 @@ class AnnounceForm(forms.ModelForm):
|
|||
message.to = self.cleaned_data['to_custom']
|
||||
if kwargs['commit']:
|
||||
message.save()
|
||||
|
||||
# add nomcom to related groups if checked
|
||||
if self.cleaned_data.get('nomcom', False):
|
||||
nomcom = current_nomcom()
|
||||
|
||||
# handle nomcom message
|
||||
nomcom = self.cleaned_data.get('nomcom',False)
|
||||
if nomcom:
|
||||
message.related_groups.add(nomcom)
|
||||
|
||||
|
||||
return message
|
||||
|
|
|
@ -34,9 +34,13 @@ def check_access(user):
|
|||
group__type="ietf",
|
||||
person=person):
|
||||
return True
|
||||
|
||||
if Role.objects.filter(person=person,
|
||||
group__acronym='iab',
|
||||
name='execdir'):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# --------------------------------------------------
|
||||
# STANDARD VIEW FUNCTIONS
|
||||
# --------------------------------------------------
|
||||
|
@ -50,12 +54,16 @@ def main(request):
|
|||
'''
|
||||
if not check_access(request.user):
|
||||
return HttpResponseForbidden('Restricted to: Secretariat, IAD, or chair of IETF, IAB, RSOC, RSE, IAOC, NomCom.')
|
||||
|
||||
|
||||
form = AnnounceForm(request.POST or None,user=request.user)
|
||||
|
||||
|
||||
if form.is_valid():
|
||||
request.session['data'] = form.cleaned_data
|
||||
|
||||
# nomcom is a ModelChoice, store pk, not Group object
|
||||
data = form.cleaned_data
|
||||
if data['nomcom']:
|
||||
data['nomcom'] = data['nomcom'].pk
|
||||
request.session['data'] = data
|
||||
|
||||
url = reverse('announcement_confirm')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
@ -66,40 +74,39 @@ def main(request):
|
|||
|
||||
@check_for_cancel('../')
|
||||
def confirm(request):
|
||||
|
||||
# testing
|
||||
#assert False, (request.session.get_expiry_age(),request.session.get_expiry_date())
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
form = AnnounceForm(request.session['data'],user=request.user)
|
||||
message = form.save(user=request.user,commit=True)
|
||||
send_mail_text(None,
|
||||
extra = {'Reply-To':message.reply_to}
|
||||
send_mail_text(None,
|
||||
message.to,
|
||||
message.frm,
|
||||
message.subject,
|
||||
message.body,
|
||||
cc=message.cc,
|
||||
bcc=message.bcc)
|
||||
bcc=message.bcc,
|
||||
extra=extra)
|
||||
|
||||
# clear session
|
||||
request.session.clear()
|
||||
|
||||
|
||||
messages.success(request, 'The announcement was sent.')
|
||||
url = reverse('announcement')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
if request.session.get('data',None):
|
||||
data = request.session['data']
|
||||
else:
|
||||
messages.error(request, 'No session data. Your session may have expired or cookies are disallowed.')
|
||||
redirect_url = reverse('announcement')
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
|
||||
if data['to'] == 'Other...':
|
||||
to = ','.join(data['to_custom'])
|
||||
else:
|
||||
to = data['to']
|
||||
|
||||
|
||||
return render_to_response('announcement/confirm.html', {
|
||||
'message': data,
|
||||
'to': to},
|
||||
|
|
|
@ -13,16 +13,16 @@ import re
|
|||
from os.path import splitext
|
||||
|
||||
# ---------------------------------------------
|
||||
# Select Choices
|
||||
# Select Choices
|
||||
# ---------------------------------------------
|
||||
WITHDRAW_CHOICES = (('ietf','Withdraw by IETF'),('author','Withdraw by Author'))
|
||||
|
||||
# ---------------------------------------------
|
||||
# Custom Fields
|
||||
# Custom Fields
|
||||
# ---------------------------------------------
|
||||
class DocumentField(forms.FileField):
|
||||
'''A validating document upload field'''
|
||||
|
||||
|
||||
def __init__(self, unique=False, *args, **kwargs):
|
||||
self.extension = kwargs.pop('extension')
|
||||
self.filename = kwargs.pop('filename')
|
||||
|
@ -36,7 +36,7 @@ class DocumentField(forms.FileField):
|
|||
m = re.search(r'.*-\d{2}\.(txt|pdf|ps|xml)', file.name)
|
||||
if not m:
|
||||
raise forms.ValidationError('File name must be in the form base-NN.[txt|pdf|ps|xml]')
|
||||
|
||||
|
||||
# ensure file extension is correct
|
||||
base,ext = os.path.splitext(file.name)
|
||||
if ext != self.extension:
|
||||
|
@ -51,44 +51,44 @@ class DocumentField(forms.FileField):
|
|||
next_revision = str(int(self.rev)+1).zfill(2)
|
||||
if base[-2:] != next_revision:
|
||||
raise forms.ValidationError, "Expected revision # %s" % (next_revision)
|
||||
|
||||
|
||||
return file
|
||||
|
||||
class GroupModelChoiceField(forms.ModelChoiceField):
|
||||
'''
|
||||
Custom ModelChoiceField sets queryset to include all active workgroups and the
|
||||
Custom ModelChoiceField sets queryset to include all active workgroups and the
|
||||
individual submission group, none. Displays group acronyms as choices. Call it without the
|
||||
queryset argument, for example:
|
||||
|
||||
|
||||
group = GroupModelChoiceField(required=True)
|
||||
'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['queryset'] = Group.objects.filter(type__in=('wg','individ'),state__in=('bof','proposed','active')).order_by('acronym')
|
||||
super(GroupModelChoiceField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
return obj.acronym
|
||||
|
||||
class AliasModelChoiceField(forms.ModelChoiceField):
|
||||
'''
|
||||
Custom ModelChoiceField, just uses Alias name in the select choices as opposed to the
|
||||
Custom ModelChoiceField, just uses Alias name in the select choices as opposed to the
|
||||
more confusing alias -> doc format used by DocAlias.__unicode__
|
||||
'''
|
||||
'''
|
||||
def label_from_instance(self, obj):
|
||||
return obj.name
|
||||
|
||||
|
||||
# ---------------------------------------------
|
||||
# Forms
|
||||
# Forms
|
||||
# ---------------------------------------------
|
||||
class AddModelForm(forms.ModelForm):
|
||||
start_date = forms.DateField()
|
||||
group = GroupModelChoiceField(required=True,help_text='Use group "none" for Individual Submissions')
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
fields = ('title','group','stream','start_date','pages','abstract','internal_comments')
|
||||
|
||||
# use this method to set attrs which keeps other meta info from model.
|
||||
|
||||
# use this method to set attrs which keeps other meta info from model.
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AddModelForm, self).__init__(*args, **kwargs)
|
||||
self.fields['title'].label='Document Name'
|
||||
|
@ -104,17 +104,17 @@ class AuthorForm(forms.Form):
|
|||
'''
|
||||
person = forms.CharField(max_length=50,widget=forms.TextInput(attrs={'class':'name-autocomplete'}),help_text="To see a list of people type the first name, or last name, or both.")
|
||||
email = forms.CharField(widget=forms.Select(),help_text="Select an email")
|
||||
|
||||
# check for id within parenthesis to ensure name was selected from the list
|
||||
|
||||
# check for id within parenthesis to ensure name was selected from the list
|
||||
def clean_person(self):
|
||||
person = self.cleaned_data.get('person', '')
|
||||
m = re.search(r'(\d+)', person)
|
||||
if person and not m:
|
||||
raise forms.ValidationError("You must select an entry from the list!")
|
||||
|
||||
raise forms.ValidationError("You must select an entry from the list!")
|
||||
|
||||
# return person object
|
||||
return get_person(person)
|
||||
|
||||
|
||||
# check that email exists and return the Email object
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data['email']
|
||||
|
@ -122,7 +122,7 @@ class AuthorForm(forms.Form):
|
|||
obj = Email.objects.get(address=email)
|
||||
except Email.ObjectDoesNoExist:
|
||||
raise forms.ValidationError("Email address not found!")
|
||||
|
||||
|
||||
# return email object
|
||||
return obj
|
||||
|
||||
|
@ -133,12 +133,12 @@ class EditModelForm(forms.ModelForm):
|
|||
group = GroupModelChoiceField(required=True)
|
||||
review_by_rfc_editor = forms.BooleanField(required=False)
|
||||
shepherd = forms.CharField(max_length=100,widget=forms.TextInput(attrs={'class':'name-autocomplete'}),help_text="To see a list of people type the first name, or last name, or both.",required=False)
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
fields = ('title','group','ad','shepherd','notify','stream','review_by_rfc_editor','name','rev','pages','intended_std_level','abstract','internal_comments')
|
||||
|
||||
# use this method to set attrs which keeps other meta info from model.
|
||||
fields = ('title','group','ad','shepherd','notify','stream','review_by_rfc_editor','name','rev','pages','intended_std_level','std_level','abstract','internal_comments')
|
||||
|
||||
# use this method to set attrs which keeps other meta info from model.
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EditModelForm, self).__init__(*args, **kwargs)
|
||||
self.fields['ad'].queryset = Person.objects.filter(role__name='ad')
|
||||
|
@ -146,40 +146,41 @@ class EditModelForm(forms.ModelForm):
|
|||
self.fields['title'].widget=forms.Textarea()
|
||||
self.fields['rev'].widget.attrs['size'] = 2
|
||||
self.fields['abstract'].widget.attrs['cols'] = 72
|
||||
self.initial['state'] = self.instance.get_state()
|
||||
self.initial['iesg_state'] = self.instance.get_state('draft-iesg')
|
||||
self.initial['state'] = self.instance.get_state().pk
|
||||
if self.instance.get_state('draft-iesg'):
|
||||
self.initial['iesg_state'] = self.instance.get_state('draft-iesg').pk
|
||||
if self.instance.shepherd:
|
||||
self.initial['shepherd'] = "%s - (%s)" % (self.instance.shepherd.name, self.instance.shepherd.id)
|
||||
|
||||
|
||||
# setup special fields
|
||||
if self.instance:
|
||||
# setup replaced
|
||||
self.fields['review_by_rfc_editor'].initial = bool(self.instance.tags.filter(slug='rfc-rev'))
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, commit=True):
|
||||
m = super(EditModelForm, self).save(commit=False)
|
||||
state = self.cleaned_data['state']
|
||||
iesg_state = self.cleaned_data['iesg_state']
|
||||
|
||||
|
||||
if 'state' in self.changed_data:
|
||||
m.set_state(state)
|
||||
|
||||
|
||||
# note we're not sending notices here, is this desired
|
||||
if 'iesg_state' in self.changed_data:
|
||||
if iesg_state == None:
|
||||
m.unset_state('draft-iesg')
|
||||
else:
|
||||
m.set_state(iesg_state)
|
||||
|
||||
|
||||
if 'review_by_rfc_editor' in self.changed_data:
|
||||
if self.cleaned_data.get('review_by_rfc_editor',''):
|
||||
m.tags.add('rfc-rev')
|
||||
else:
|
||||
m.tags.remove('rfc-rev')
|
||||
|
||||
|
||||
m.time = datetime.datetime.now()
|
||||
# handle replaced by
|
||||
|
||||
|
||||
if commit:
|
||||
m.save()
|
||||
return m
|
||||
|
@ -188,19 +189,19 @@ class EditModelForm(forms.ModelForm):
|
|||
def clean_replaced_by(self):
|
||||
name = self.cleaned_data.get('replaced_by', '')
|
||||
if name and not InternetDraft.objects.filter(filename=name):
|
||||
raise forms.ValidationError("ERROR: Draft does not exist")
|
||||
raise forms.ValidationError("ERROR: Draft does not exist")
|
||||
return name
|
||||
|
||||
# check for id within parenthesis to ensure name was selected from the list
|
||||
|
||||
# check for id within parenthesis to ensure name was selected from the list
|
||||
def clean_shepherd(self):
|
||||
person = self.cleaned_data.get('shepherd', '')
|
||||
m = re.search(r'(\d+)', person)
|
||||
if person and not m:
|
||||
raise forms.ValidationError("You must select an entry from the list!")
|
||||
|
||||
raise forms.ValidationError("You must select an entry from the list!")
|
||||
|
||||
# return person object
|
||||
return get_person(person)
|
||||
|
||||
|
||||
def clean(self):
|
||||
super(EditModelForm, self).clean()
|
||||
cleaned_data = self.cleaned_data
|
||||
|
@ -232,7 +233,7 @@ class EmailForm(forms.Form):
|
|||
|
||||
class ExtendForm(forms.Form):
|
||||
expiration_date = forms.DateField()
|
||||
|
||||
|
||||
class ReplaceForm(forms.Form):
|
||||
replaced = AliasModelChoiceField(DocAlias.objects.none(),empty_label=None,help_text='This document may have more than one alias. Be sure to select the correct alias to replace.')
|
||||
replaced_by = forms.CharField(max_length=100,help_text='Enter the filename of the Draft which replaces this one.')
|
||||
|
@ -241,7 +242,7 @@ class ReplaceForm(forms.Form):
|
|||
self.draft = kwargs.pop('draft')
|
||||
super(ReplaceForm, self).__init__(*args, **kwargs)
|
||||
self.fields['replaced'].queryset = DocAlias.objects.filter(document=self.draft)
|
||||
|
||||
|
||||
# field must contain filename of existing draft
|
||||
def clean_replaced_by(self):
|
||||
name = self.cleaned_data.get('replaced_by', '')
|
||||
|
@ -262,49 +263,49 @@ class RevisionModelForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = Document
|
||||
fields = ('title','pages','abstract')
|
||||
|
||||
# use this method to set attrs which keeps other meta info from model.
|
||||
|
||||
# use this method to set attrs which keeps other meta info from model.
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RevisionModelForm, self).__init__(*args, **kwargs)
|
||||
self.fields['title'].label='Document Name'
|
||||
self.fields['title'].widget=forms.Textarea()
|
||||
self.fields['pages'].label='Number of Pages'
|
||||
|
||||
|
||||
class RfcModelForm(forms.ModelForm):
|
||||
rfc_number = forms.IntegerField()
|
||||
rfc_published_date = forms.DateField(initial=datetime.datetime.now)
|
||||
group = GroupModelChoiceField(required=True)
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
fields = ('title','group','pages','std_level','internal_comments')
|
||||
|
||||
# use this method to set attrs which keeps other meta info from model.
|
||||
|
||||
# use this method to set attrs which keeps other meta info from model.
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RfcModelForm, self).__init__(*args, **kwargs)
|
||||
self.fields['title'].widget = forms.Textarea()
|
||||
self.fields['std_level'].required = True
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, commit=True):
|
||||
obj = super(RfcModelForm, self).save(commit=False)
|
||||
|
||||
|
||||
# create DocAlias
|
||||
DocAlias.objects.create(document=self.instance,name="rfc%d" % self.cleaned_data['rfc_number'])
|
||||
|
||||
|
||||
if commit:
|
||||
obj.save()
|
||||
return obj
|
||||
|
||||
|
||||
def clean_rfc_number(self):
|
||||
rfc_number = self.cleaned_data['rfc_number']
|
||||
if DocAlias.objects.filter(name='rfc' + str(rfc_number)):
|
||||
raise forms.ValidationError("RFC %d already exists" % rfc_number)
|
||||
return rfc_number
|
||||
|
||||
|
||||
class RfcObsoletesForm(forms.Form):
|
||||
relation = forms.ModelChoiceField(queryset=DocRelationshipName.objects.filter(slug__in=('updates','obs')),required=False)
|
||||
rfc = forms.IntegerField(required=False)
|
||||
|
||||
|
||||
# ensure that RFC exists
|
||||
def clean_rfc(self):
|
||||
rfc = self.cleaned_data.get('rfc','')
|
||||
|
@ -312,7 +313,7 @@ class RfcObsoletesForm(forms.Form):
|
|||
if not Document.objects.filter(docalias__name="rfc%s" % rfc):
|
||||
raise forms.ValidationError("RFC does not exist")
|
||||
return rfc
|
||||
|
||||
|
||||
def clean(self):
|
||||
super(RfcObsoletesForm, self).clean()
|
||||
cleaned_data = self.cleaned_data
|
||||
|
@ -347,8 +348,8 @@ class UploadForm(forms.Form):
|
|||
for field in self.fields.itervalues():
|
||||
field.filename = self.draft.name
|
||||
field.rev = self.draft.rev
|
||||
|
||||
|
||||
|
||||
|
||||
def clean(self):
|
||||
# Checks that all files have the same base
|
||||
if any(self.errors):
|
||||
|
@ -358,7 +359,7 @@ class UploadForm(forms.Form):
|
|||
xml = self.cleaned_data['xml']
|
||||
pdf = self.cleaned_data['pdf']
|
||||
ps = self.cleaned_data['ps']
|
||||
|
||||
|
||||
# we only need to do these validations for new drafts
|
||||
if not self.draft:
|
||||
names = []
|
||||
|
@ -367,19 +368,19 @@ class UploadForm(forms.Form):
|
|||
base = splitext(file.name)[0]
|
||||
if base not in names:
|
||||
names.append(base)
|
||||
|
||||
|
||||
if len(names) > 1:
|
||||
raise forms.ValidationError, "All files must have the same base name"
|
||||
|
||||
|
||||
# ensure that the basename is unique
|
||||
base = splitext(txt.name)[0]
|
||||
if Document.objects.filter(name=base[:-3]):
|
||||
raise forms.ValidationError, "This doucment filename already exists: %s" % base[:-3]
|
||||
|
||||
|
||||
# ensure that rev is 00
|
||||
if base[-2:] != '00':
|
||||
raise forms.ValidationError, "New Drafts must start with 00 revision number."
|
||||
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
class WithdrawForm(forms.Form):
|
||||
|
|
|
@ -797,7 +797,8 @@ def edit(request, id):
|
|||
save_document_in_history(draft)
|
||||
DocEvent.objects.create(type='changed_document',
|
||||
by=request.user.get_profile(),
|
||||
doc=draft)
|
||||
doc=draft,
|
||||
desc='Changed field(s): %s' % ','.join(form.changed_data))
|
||||
# see EditModelForm.save() for detailed logic
|
||||
form.save()
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ def add_legacy_fields(group):
|
|||
query = GroupEvent.objects.filter(group=group, type="changed_state").order_by('time')
|
||||
proposed = query.filter(changestategroupevent__state="proposed")
|
||||
meeting = get_current_meeting()
|
||||
|
||||
|
||||
if proposed:
|
||||
group.proposed_date = proposed[0].time
|
||||
active = query.filter(changestategroupevent__state="active")
|
||||
|
@ -46,27 +46,27 @@ def add_legacy_fields(group):
|
|||
concluded = query.filter(changestategroupevent__state="conclude")
|
||||
if concluded:
|
||||
group.concluded_date = concluded[0].time
|
||||
|
||||
|
||||
if group.session_set.filter(meeting__number=meeting.number):
|
||||
group.meeting_scheduled = 'YES'
|
||||
else:
|
||||
group.meeting_scheduled = 'NO'
|
||||
|
||||
|
||||
group.chairs = group.role_set.filter(name="chair")
|
||||
group.techadvisors = group.role_set.filter(name="techadv")
|
||||
group.editors = group.role_set.filter(name="editor")
|
||||
group.secretaries = group.role_set.filter(name="secretaries")
|
||||
|
||||
group.secretaries = group.role_set.filter(name="secr")
|
||||
|
||||
#fill_in_charter_info(group)
|
||||
|
||||
|
||||
#--------------------------------------------------
|
||||
# AJAX Functions
|
||||
# -------------------------------------------------
|
||||
'''
|
||||
def get_ads(request):
|
||||
""" AJAX function which takes a URL parameter, "area" and returns the area directors
|
||||
in the form of a list of dictionaries with "id" and "value" keys(in json format).
|
||||
Used to populate select options.
|
||||
in the form of a list of dictionaries with "id" and "value" keys(in json format).
|
||||
Used to populate select options.
|
||||
"""
|
||||
|
||||
results=[]
|
||||
|
@ -83,7 +83,7 @@ def get_ads(request):
|
|||
# -------------------------------------------------
|
||||
|
||||
def add(request):
|
||||
'''
|
||||
'''
|
||||
Add a new IETF or IRTF Group
|
||||
|
||||
**Templates:**
|
||||
|
@ -119,11 +119,11 @@ def add(request):
|
|||
by=request.user.get_profile(),
|
||||
state=group.state,
|
||||
desc='Started group')
|
||||
|
||||
|
||||
messages.success(request, 'The Group was created successfully!')
|
||||
url = reverse('groups_view', kwargs={'acronym':group.acronym})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
else:
|
||||
form = GroupModelForm(initial={'state':'active','type':'wg'})
|
||||
awp_formset = AWPFormSet(prefix='awp')
|
||||
|
@ -150,17 +150,17 @@ def blue_dot(request):
|
|||
entry = {'name':'%s, %s' % (parts[3], parts[1]),
|
||||
'groups': ', '.join(groups)}
|
||||
chairs.append(entry)
|
||||
|
||||
|
||||
# sort the list
|
||||
sorted_chairs = sorted(chairs, key = lambda a: a['name'])
|
||||
|
||||
|
||||
return render_to_response('groups/blue_dot_report.txt', {
|
||||
'chairs':sorted_chairs},
|
||||
RequestContext(request, {}), mimetype="text/plain",
|
||||
)
|
||||
|
||||
|
||||
def charter(request, acronym):
|
||||
"""
|
||||
"""
|
||||
View Group Charter
|
||||
|
||||
**Templates:**
|
||||
|
@ -179,7 +179,7 @@ def charter(request, acronym):
|
|||
charter_text = get_charter_text(group)
|
||||
else:
|
||||
charter_text = ''
|
||||
|
||||
|
||||
return render_to_response('groups/charter.html', {
|
||||
'group': group,
|
||||
'charter_text': charter_text},
|
||||
|
@ -187,7 +187,7 @@ def charter(request, acronym):
|
|||
)
|
||||
|
||||
def delete_role(request, acronym, id):
|
||||
"""
|
||||
"""
|
||||
Handle deleting roles for groups (chair, editor, advisor, secretary)
|
||||
|
||||
**Templates:**
|
||||
|
@ -199,18 +199,18 @@ def delete_role(request, acronym, id):
|
|||
"""
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
role = get_object_or_404(Role, id=id)
|
||||
|
||||
|
||||
# save group
|
||||
save_group_in_history(group)
|
||||
|
||||
|
||||
role.delete()
|
||||
|
||||
|
||||
messages.success(request, 'The entry was deleted successfully')
|
||||
url = reverse('groups_people', kwargs={'acronym':acronym})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
def edit(request, acronym):
|
||||
"""
|
||||
"""
|
||||
Edit Group details
|
||||
|
||||
**Templates:**
|
||||
|
@ -235,16 +235,16 @@ def edit(request, acronym):
|
|||
form = GroupModelForm(request.POST, instance=group)
|
||||
awp_formset = AWPFormSet(request.POST, instance=group)
|
||||
if form.is_valid() and awp_formset.is_valid():
|
||||
|
||||
|
||||
awp_formset.save()
|
||||
if form.changed_data:
|
||||
state = form.cleaned_data['state']
|
||||
|
||||
|
||||
# save group
|
||||
save_group_in_history(group)
|
||||
|
||||
|
||||
form.save()
|
||||
|
||||
|
||||
# create appropriate GroupEvent
|
||||
if 'state' in form.changed_data:
|
||||
if state.name == 'Active':
|
||||
|
@ -257,27 +257,29 @@ def edit(request, acronym):
|
|||
state=state,
|
||||
desc=desc)
|
||||
form.changed_data.remove('state')
|
||||
|
||||
|
||||
# if anything else was changed
|
||||
if form.changed_data:
|
||||
GroupEvent.objects.create(group=group,
|
||||
type='info_changed',
|
||||
by=request.user.get_profile(),
|
||||
desc='Info Changed')
|
||||
|
||||
|
||||
# if the acronym was changed we'll want to redirect using the new acronym below
|
||||
if 'acronym' in form.changed_data:
|
||||
acronym = form.cleaned_data['acronym']
|
||||
|
||||
|
||||
messages.success(request, 'The Group was changed successfully')
|
||||
|
||||
|
||||
url = reverse('groups_view', kwargs={'acronym':acronym})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
else:
|
||||
form = GroupModelForm(instance=group)
|
||||
awp_formset = AWPFormSet(instance=group)
|
||||
|
||||
messages.warning(request, "WARNING: don't use this tool to change group names. Use Datatracker when possible.")
|
||||
|
||||
return render_to_response('groups/edit.html', {
|
||||
'group': group,
|
||||
'awp_formset': awp_formset,
|
||||
|
@ -286,7 +288,7 @@ def edit(request, acronym):
|
|||
)
|
||||
|
||||
def edit_gm(request, acronym):
|
||||
"""
|
||||
"""
|
||||
Edit IETF Group Goal and Milestone details
|
||||
|
||||
**Templates:**
|
||||
|
@ -295,7 +297,7 @@ def edit_gm(request, acronym):
|
|||
|
||||
**Template Variables:**
|
||||
|
||||
* group, formset
|
||||
* group, formset
|
||||
|
||||
"""
|
||||
|
||||
|
@ -316,7 +318,7 @@ def edit_gm(request, acronym):
|
|||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
formset = GMFormset(instance=group, prefix='goalmilestone')
|
||||
|
||||
|
||||
return render_to_response('groups/edit_gm.html', {
|
||||
'group': group,
|
||||
'formset': formset},
|
||||
|
@ -324,7 +326,7 @@ def edit_gm(request, acronym):
|
|||
)
|
||||
|
||||
def people(request, acronym):
|
||||
"""
|
||||
"""
|
||||
Edit Group Roles (Chairs, Secretary, etc)
|
||||
|
||||
**Templates:**
|
||||
|
@ -338,7 +340,7 @@ def people(request, acronym):
|
|||
"""
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
# we need to pass group for form validation
|
||||
form = RoleForm(request.POST,group=group)
|
||||
|
@ -346,10 +348,10 @@ def people(request, acronym):
|
|||
name = form.cleaned_data['name']
|
||||
person = form.cleaned_data['person']
|
||||
email = form.cleaned_data['email']
|
||||
|
||||
|
||||
# save group
|
||||
save_group_in_history(group)
|
||||
|
||||
|
||||
Role.objects.create(name=name,
|
||||
person=person,
|
||||
email=email,
|
||||
|
@ -368,7 +370,7 @@ def people(request, acronym):
|
|||
)
|
||||
|
||||
def search(request):
|
||||
"""
|
||||
"""
|
||||
Search IETF Groups
|
||||
|
||||
**Templates:**
|
||||
|
@ -386,9 +388,9 @@ def search(request):
|
|||
if request.POST['submit'] == 'Add':
|
||||
url = reverse('groups_add')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
if form.is_valid():
|
||||
kwargs = {}
|
||||
kwargs = {}
|
||||
group_acronym = form.cleaned_data['group_acronym']
|
||||
group_name = form.cleaned_data['group_name']
|
||||
primary_area = form.cleaned_data['primary_area']
|
||||
|
@ -396,7 +398,7 @@ def search(request):
|
|||
state = form.cleaned_data['state']
|
||||
type = form.cleaned_data['type']
|
||||
meeting = get_current_meeting()
|
||||
|
||||
|
||||
# construct seach query
|
||||
if group_acronym:
|
||||
kwargs['acronym__istartswith'] = group_acronym
|
||||
|
@ -410,7 +412,7 @@ def search(request):
|
|||
kwargs['type'] = type
|
||||
#else:
|
||||
# kwargs['type__in'] = ('wg','rg','ietf','ag','sdo','team')
|
||||
|
||||
|
||||
if meeting_scheduled == 'YES':
|
||||
kwargs['session__meeting__number'] = meeting.number
|
||||
# perform query
|
||||
|
@ -422,13 +424,13 @@ def search(request):
|
|||
else:
|
||||
qs = Group.objects.all()
|
||||
results = qs.order_by('acronym')
|
||||
|
||||
|
||||
# if there's just one result go straight to view
|
||||
if len(results) == 1:
|
||||
url = reverse('groups_view', kwargs={'acronym':results[0].acronym})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# process GET argument to support link from area app
|
||||
|
||||
# process GET argument to support link from area app
|
||||
elif 'primary_area' in request.GET:
|
||||
area = request.GET.get('primary_area','')
|
||||
results = Group.objects.filter(parent__id=area,type='wg',state__in=('bof','active','proposed')).order_by('name')
|
||||
|
@ -440,7 +442,7 @@ def search(request):
|
|||
# attribute of the meeting model
|
||||
for result in results:
|
||||
add_legacy_fields(result)
|
||||
|
||||
|
||||
return render_to_response('groups/search.html', {
|
||||
'results': results,
|
||||
'form': form},
|
||||
|
@ -448,7 +450,7 @@ def search(request):
|
|||
)
|
||||
|
||||
def view(request, acronym):
|
||||
"""
|
||||
"""
|
||||
View IETF Group details
|
||||
|
||||
**Templates:**
|
||||
|
@ -462,16 +464,16 @@ def view(request, acronym):
|
|||
"""
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
||||
|
||||
add_legacy_fields(group)
|
||||
|
||||
|
||||
return render_to_response('groups/view.html', {
|
||||
'group': group},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
def view_gm(request, acronym):
|
||||
"""
|
||||
"""
|
||||
View IETF Group Goals and Milestones details
|
||||
|
||||
**Templates:**
|
||||
|
|
|
@ -28,14 +28,15 @@ from forms import *
|
|||
import os
|
||||
import datetime
|
||||
|
||||
# prep for agenda changes
|
||||
# --------------------------------------------------
|
||||
# Helper Functions
|
||||
# --------------------------------------------------
|
||||
def build_timeslots(meeting,room=None):
|
||||
'''
|
||||
This function takes a Meeting object and an optional room argument. If room isn't passed we
|
||||
pre-create the full set of timeslot records using the last meeting as a template.
|
||||
If room is passed pre-create timeslots for the new room. Call this after saving new rooms
|
||||
This function takes a Meeting object and an optional room argument. If room isn't passed we
|
||||
pre-create the full set of timeslot records using the last meeting as a template.
|
||||
If room is passed pre-create timeslots for the new room. Call this after saving new rooms
|
||||
or adding a room.
|
||||
'''
|
||||
slots = meeting.timeslot_set.filter(type='session')
|
||||
|
@ -50,7 +51,7 @@ def build_timeslots(meeting,room=None):
|
|||
source_meeting = meeting
|
||||
else:
|
||||
source_meeting = get_last_meeting(meeting)
|
||||
|
||||
|
||||
delta = meeting.date - source_meeting.date
|
||||
initial = []
|
||||
timeslots = []
|
||||
|
@ -89,7 +90,7 @@ def build_nonsession(meeting):
|
|||
requested_by=system,
|
||||
status_id='sched')
|
||||
session.save()
|
||||
|
||||
|
||||
TimeSlot.objects.create(type=slot.type,
|
||||
meeting=meeting,
|
||||
session=session,
|
||||
|
@ -97,11 +98,11 @@ def build_nonsession(meeting):
|
|||
time=new_time,
|
||||
duration=slot.duration,
|
||||
show_location=slot.show_location)
|
||||
|
||||
|
||||
def get_last_meeting(meeting):
|
||||
last_number = int(meeting.number) - 1
|
||||
return Meeting.objects.get(number=last_number)
|
||||
|
||||
|
||||
def is_combined(session):
|
||||
'''
|
||||
Check to see if this session is using two combined timeslots
|
||||
|
@ -110,7 +111,7 @@ def is_combined(session):
|
|||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def make_directories(meeting):
|
||||
'''
|
||||
This function takes a meeting object and creates the appropriate materials directories
|
||||
|
@ -141,21 +142,21 @@ def send_notification(request, sessions):
|
|||
else:
|
||||
subject = '%s - Requested sessions have been scheduled for IETF %s' % (group.acronym, sessions[0].meeting.number)
|
||||
template = 'meetings/session_schedule_notification.txt'
|
||||
|
||||
|
||||
# easier to populate template from timeslot perspective. assuming one-to-one timeslot-session
|
||||
count = 0
|
||||
session_info = ''
|
||||
data = [ (s,s.timeslot_set.all()[0]) for s in sessions ]
|
||||
for s,t in data:
|
||||
count += 1
|
||||
session_info += session_info_template.format(group.acronym,
|
||||
count,
|
||||
session_info += session_info_template.format(group.acronym,
|
||||
count,
|
||||
s.requested_duration,
|
||||
t.time.strftime('%A'),
|
||||
t.name,
|
||||
'%s-%s' % (t.time.strftime('%H%M'),(t.time + t.duration).strftime('%H%M')),
|
||||
t.location)
|
||||
|
||||
|
||||
# send email
|
||||
context = {}
|
||||
context['to_name'] = sessions[0].requested_by
|
||||
|
@ -191,9 +192,9 @@ def sort_groups(meeting):
|
|||
scheduled_groups.append(group)
|
||||
else:
|
||||
unscheduled_groups.append(group)
|
||||
|
||||
|
||||
return scheduled_groups, unscheduled_groups
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# AJAX Functions
|
||||
# -------------------------------------------------
|
||||
|
@ -202,14 +203,14 @@ def ajax_get_times(request, meeting_id, day):
|
|||
Ajax function to get timeslot times for a given day.
|
||||
returns JSON format response: [{id:start_time, value:start_time-end_time},...]
|
||||
'''
|
||||
# TODO strip duplicates if there are any
|
||||
# TODO strip duplicates if there are any
|
||||
results=[]
|
||||
room = Room.objects.filter(meeting__number=meeting_id)[0]
|
||||
slots = TimeSlot.objects.filter(meeting__number=meeting_id,time__week_day=day,location=room).order_by('time')
|
||||
for slot in slots:
|
||||
d = {'id': slot.time.strftime('%H%M'), 'value': '%s-%s' % (slot.time.strftime('%H%M'), slot.end_time().strftime('%H%M'))}
|
||||
results.append(d)
|
||||
|
||||
|
||||
return HttpResponse(simplejson.dumps(results), mimetype='application/javascript')
|
||||
# --------------------------------------------------
|
||||
# STANDARD VIEW FUNCTIONS
|
||||
|
@ -236,7 +237,7 @@ def add(request):
|
|||
form = MeetingModelForm(request.POST)
|
||||
if form.is_valid():
|
||||
meeting = form.save()
|
||||
|
||||
|
||||
#Create Physical new meeting directory and subdirectories
|
||||
make_directories(meeting)
|
||||
|
||||
|
@ -258,9 +259,9 @@ def blue_sheet(request, meeting_id):
|
|||
Blue Sheet view. The user can generate blue sheets or upload scanned bluesheets
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
|
||||
|
||||
url = settings.SECR_BLUE_SHEET_URL
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
form = UploadBlueSheetForm(request.POST,request.FILES)
|
||||
if form.is_valid():
|
||||
|
@ -269,26 +270,26 @@ def blue_sheet(request, meeting_id):
|
|||
messages.success(request, 'File Uploaded')
|
||||
url = reverse('meetings_blue_sheet', kwargs={'meeting_id':meeting.number})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
else:
|
||||
form = UploadBlueSheetForm()
|
||||
|
||||
|
||||
return render_to_response('meetings/blue_sheet.html', {
|
||||
'meeting': meeting,
|
||||
'url': url,
|
||||
'form': form},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def blue_sheet_generate(request, meeting_id):
|
||||
'''
|
||||
Generate bluesheets
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
|
||||
|
||||
groups = Group.objects.filter(session__meeting=meeting).order_by('acronym')
|
||||
create_blue_sheets(meeting, groups)
|
||||
|
||||
|
||||
messages.success(request, 'Blue Sheets generated')
|
||||
url = reverse('meetings_blue_sheet', kwargs={'meeting_id':meeting.number})
|
||||
return HttpResponseRedirect(url)
|
||||
|
@ -349,14 +350,14 @@ def main(request):
|
|||
In this view the user can choose a meeting to manage or elect to create a new meeting.
|
||||
'''
|
||||
meetings = Meeting.objects.filter(type='ietf').order_by('-number')
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
redirect_url = reverse('meetings_view', kwargs={'meeting_id':request.POST['group']})
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
|
||||
choices = [ (str(x.number),str(x.number)) for x in meetings ]
|
||||
form = GroupSelectForm(choices=choices)
|
||||
|
||||
|
||||
return render_to_response('meetings/main.html', {
|
||||
'form': form,
|
||||
'meetings': meetings},
|
||||
|
@ -368,13 +369,13 @@ def non_session(request, meeting_id):
|
|||
Display and add "non-session" time slots, ie. registration, beverage and snack breaks
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
|
||||
|
||||
# if the Break/Registration records don't exist yet (new meeting) create them
|
||||
if not TimeSlot.objects.filter(meeting=meeting,type__in=('break','reg','other')):
|
||||
build_nonsession(meeting)
|
||||
|
||||
|
||||
slots = TimeSlot.objects.filter(meeting=meeting,type__in=('break','reg','other','plenary')).order_by('-type__name','time')
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
form = NonSessionForm(request.POST)
|
||||
if form.is_valid():
|
||||
|
@ -387,9 +388,9 @@ def non_session(request, meeting_id):
|
|||
duration = form.cleaned_data['duration']
|
||||
t = meeting.date + datetime.timedelta(days=int(day))
|
||||
new_time = datetime.datetime(t.year,t.month,t.day,time.hour,time.minute)
|
||||
|
||||
|
||||
# create a dummy Session object to hold materials
|
||||
# NOTE: we're setting group to none here, but the set_room page will force user
|
||||
# NOTE: we're setting group to none here, but the set_room page will force user
|
||||
# to pick a legitimate group
|
||||
session = None
|
||||
if type.slug in ('other','plenary'):
|
||||
|
@ -400,7 +401,7 @@ def non_session(request, meeting_id):
|
|||
requested_by=Person.objects.get(name='(system)'),
|
||||
status_id='sched')
|
||||
session.save()
|
||||
|
||||
|
||||
# create TimeSlot object
|
||||
TimeSlot.objects.create(type=form.cleaned_data['type'],
|
||||
meeting=meeting,
|
||||
|
@ -409,16 +410,16 @@ def non_session(request, meeting_id):
|
|||
time=new_time,
|
||||
duration=duration,
|
||||
show_location=form.cleaned_data['show_location'])
|
||||
|
||||
|
||||
messages.success(request, 'Non-Sessions updated successfully')
|
||||
url = reverse('meetings_non_session', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
else:
|
||||
form = NonSessionForm(initial={'show_location':True})
|
||||
|
||||
|
||||
if TimeSlot.objects.filter(meeting=meeting,type='other',location__isnull=True):
|
||||
messages.warning(request, 'There are non-session items which do not have a room assigned')
|
||||
|
||||
|
||||
return render_to_response('meetings/non_session.html', {
|
||||
'slots': slots,
|
||||
'form': form,
|
||||
|
@ -429,7 +430,7 @@ def non_session(request, meeting_id):
|
|||
def non_session_delete(request, meeting_id, slot_id):
|
||||
'''
|
||||
This function deletes the non-session TimeSlot. For "other" and "plenary" timeslot types
|
||||
we need to delete the corresponding Session object as well. Check for uploaded material
|
||||
we need to delete the corresponding Session object as well. Check for uploaded material
|
||||
first.
|
||||
'''
|
||||
slot = get_object_or_404(TimeSlot, id=slot_id)
|
||||
|
@ -438,11 +439,11 @@ def non_session_delete(request, meeting_id, slot_id):
|
|||
messages.error(request, 'Materials have already been uploaded for "%s". You must delete those before deleting the timeslot.' % slot.name)
|
||||
url = reverse('meetings_non_session', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
else:
|
||||
slot.session.delete()
|
||||
slot.delete()
|
||||
|
||||
|
||||
messages.success(request, 'Non-Session timeslot deleted successfully')
|
||||
url = reverse('meetings_non_session', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
@ -459,7 +460,7 @@ def non_session_edit(request, meeting_id, slot_id):
|
|||
if button_text == 'Cancel':
|
||||
url = reverse('meetings_non_session', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
form = NonSessionEditForm(request.POST,meeting=meeting, session=slot.session)
|
||||
if form.is_valid():
|
||||
location = form.cleaned_data['location']
|
||||
|
@ -475,11 +476,11 @@ def non_session_edit(request, meeting_id, slot_id):
|
|||
session.name = name
|
||||
session.short = short
|
||||
session.save()
|
||||
|
||||
|
||||
messages.success(request, 'Location saved')
|
||||
url = reverse('meetings_non_session', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
else:
|
||||
# we need to pass the session to the form in order to disallow changing
|
||||
# of group after materials have been uploaded
|
||||
|
@ -488,14 +489,14 @@ def non_session_edit(request, meeting_id, slot_id):
|
|||
'name':slot.session.name,
|
||||
'short':slot.session.short}
|
||||
form = NonSessionEditForm(meeting=meeting,session=slot.session,initial=initial)
|
||||
|
||||
|
||||
return render_to_response('meetings/non_session_edit.html', {
|
||||
'meeting': meeting,
|
||||
'form': form,
|
||||
'slot': slot},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def remove_session(request, meeting_id, acronym):
|
||||
'''
|
||||
Remove session from agenda. Disassociate session from timeslot and set status.
|
||||
|
@ -506,16 +507,16 @@ def remove_session(request, meeting_id, acronym):
|
|||
group = get_object_or_404(Group, acronym=acronym)
|
||||
sessions = Session.objects.filter(meeting=meeting,group=group)
|
||||
now = datetime.datetime.now()
|
||||
|
||||
|
||||
for session in sessions:
|
||||
timeslot = session.timeslot_set.all()[0]
|
||||
timeslot.session = None
|
||||
timeslot.modified = now
|
||||
timeslot.save()
|
||||
for timeslot in session.timeslot_set.all():
|
||||
timeslot.session = None
|
||||
timeslot.modified = now
|
||||
timeslot.save()
|
||||
session.status_id = 'canceled'
|
||||
session.modified = now
|
||||
session.save()
|
||||
|
||||
|
||||
messages.success(request, '%s Session removed from agenda' % (group.acronym))
|
||||
url = reverse('meetings_select_group', kwargs={'meeting_id':meeting.number})
|
||||
return HttpResponseRedirect(url)
|
||||
|
@ -525,7 +526,7 @@ def rooms(request, meeting_id):
|
|||
Display and edit MeetingRoom records for the specified meeting
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
|
||||
|
||||
# if no rooms exist yet (new meeting) formset extra=10
|
||||
first_time = not bool(meeting.room_set.all())
|
||||
extra = 10 if first_time else 0
|
||||
|
@ -540,18 +541,18 @@ def rooms(request, meeting_id):
|
|||
formset = RoomFormset(request.POST, instance=meeting, prefix='room')
|
||||
if formset.is_valid():
|
||||
formset.save()
|
||||
|
||||
|
||||
# if we are creating rooms for the first time create full set of timeslots
|
||||
if first_time:
|
||||
build_timeslots(meeting)
|
||||
|
||||
|
||||
# otherwise if we're modifying rooms
|
||||
else:
|
||||
# add timeslots for new rooms, deleting rooms automatically deletes timeslots
|
||||
for form in formset.forms[formset.initial_form_count():]:
|
||||
if form.instance.pk:
|
||||
build_timeslots(meeting,room=form.instance)
|
||||
|
||||
|
||||
messages.success(request, 'Meeting Rooms changed successfully')
|
||||
url = reverse('meetings_rooms', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
@ -574,7 +575,7 @@ def schedule(request, meeting_id, acronym):
|
|||
legacy_session = get_initial_session(sessions)
|
||||
session_conflicts = session_conflicts_as_string(group, meeting)
|
||||
now = datetime.datetime.now()
|
||||
|
||||
|
||||
# build initial
|
||||
initial = []
|
||||
for s in sessions:
|
||||
|
@ -590,11 +591,11 @@ def schedule(request, meeting_id, acronym):
|
|||
if is_combined(s):
|
||||
d['combine'] = True
|
||||
initial.append(d)
|
||||
|
||||
|
||||
# need to use curry here to pass custom variable to form init
|
||||
NewSessionFormset = formset_factory(NewSessionForm, extra=0)
|
||||
NewSessionFormset.form = staticmethod(curry(NewSessionForm, meeting=meeting))
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Cancel':
|
||||
|
@ -602,8 +603,8 @@ def schedule(request, meeting_id, acronym):
|
|||
return HttpResponseRedirect(url)
|
||||
|
||||
formset = NewSessionFormset(request.POST,initial=initial)
|
||||
extra_form = ExtraSessionForm(request.POST)
|
||||
|
||||
extra_form = ExtraSessionForm(request.POST)
|
||||
|
||||
if formset.is_valid() and extra_form.is_valid():
|
||||
# TODO formsets don't have has_changed until Django 1.3
|
||||
has_changed = False
|
||||
|
@ -617,11 +618,13 @@ def schedule(request, meeting_id, acronym):
|
|||
day = form.cleaned_data['day']
|
||||
combine = form.cleaned_data.get('combine',None)
|
||||
session = Session.objects.get(id=id)
|
||||
if session.timeslot_set.all():
|
||||
initial_timeslot = session.timeslot_set.all()[0]
|
||||
was_combined = is_combined(session)
|
||||
initial_timeslots = session.timeslot_set.all()
|
||||
if initial_timeslots:
|
||||
initial_timeslot = initial_timeslots[0]
|
||||
else:
|
||||
initial_timeslot = None
|
||||
|
||||
|
||||
# find new timeslot
|
||||
new_day = meeting.date + datetime.timedelta(days=int(day)-1)
|
||||
hour = datetime.time(int(time[:2]),int(time[2:]))
|
||||
|
@ -640,68 +643,82 @@ def schedule(request, meeting_id, acronym):
|
|||
show_location=qs[0].show_location,
|
||||
modified=now)
|
||||
messages.warning(request, 'WARNING: There are now two sessions scheduled for the timeslot: %s' % timeslot)
|
||||
|
||||
|
||||
# COMBINE SECTION - BEFORE --------------
|
||||
if 'combine' in form.changed_data and not combine:
|
||||
next_slot = get_next_slot(initial_timeslot)
|
||||
next_slot.session = None
|
||||
next_slot.modified = now
|
||||
next_slot.save()
|
||||
# ---------------------------------------
|
||||
|
||||
if any(x in form.changed_data for x in ('day','time','room')):
|
||||
# clear the old timeslot
|
||||
if initial_timeslot:
|
||||
# clear the old timeslot(s)
|
||||
for ts in initial_timeslots:
|
||||
# if the initial timeslot is one of multiple we should delete it
|
||||
tqs = TimeSlot.objects.filter(meeting=meeting,
|
||||
type='session',
|
||||
time=initial_timeslot.time,
|
||||
location=initial_timeslot.location)
|
||||
time=ts.time,
|
||||
location=ts.location)
|
||||
if tqs.count() > 1:
|
||||
initial_timeslot.delete()
|
||||
ts.delete()
|
||||
else:
|
||||
initial_timeslot.session = None
|
||||
initial_timeslot.modified = now
|
||||
initial_timeslot.save()
|
||||
ts.session = None
|
||||
ts.modified = now
|
||||
ts.save()
|
||||
# assign new timeslot(s)
|
||||
new_slots = []
|
||||
if timeslot:
|
||||
new_slots.append(timeslot)
|
||||
if was_combined:
|
||||
new_slots.append(get_next_slot(timeslot))
|
||||
for ts in new_slots:
|
||||
timeslot.session = session
|
||||
timeslot.modified = now
|
||||
timeslot.save()
|
||||
|
||||
if new_slots:
|
||||
session.status_id = 'sched'
|
||||
else:
|
||||
session.status_id = 'schedw'
|
||||
|
||||
|
||||
session.modified = now
|
||||
session.save()
|
||||
|
||||
|
||||
|
||||
if 'note' in form.changed_data:
|
||||
session.agenda_note = note
|
||||
session.modified = now
|
||||
session.save()
|
||||
|
||||
# COMBINE SECTION ----------------------
|
||||
if 'combine' in form.changed_data:
|
||||
|
||||
# COMBINE SECTION - AFTER ---------------
|
||||
if 'combine' in form.changed_data and combine:
|
||||
next_slot = get_next_slot(timeslot)
|
||||
if combine:
|
||||
next_slot.session = session
|
||||
else:
|
||||
next_slot.session = None
|
||||
next_slot.session = session
|
||||
next_slot.modified = now
|
||||
next_slot.save()
|
||||
# ---------------------------------------
|
||||
|
||||
|
||||
# notify. dont send if Tutorial, BOF or indicated on form
|
||||
notification_message = "No notification has been sent to anyone for this session."
|
||||
if (has_changed
|
||||
if (has_changed
|
||||
and not extra_form.cleaned_data.get('no_notify',False)
|
||||
and group.state.slug != 'bof'
|
||||
and session.timeslot_set.all()): # and the session is scheduled, else skip
|
||||
|
||||
|
||||
send_notification(request, sessions)
|
||||
notification_message = "Notification sent."
|
||||
|
||||
|
||||
if has_changed:
|
||||
messages.success(request, 'Session(s) Scheduled for %s. %s' % (group.acronym, notification_message))
|
||||
|
||||
|
||||
url = reverse('meetings_select_group', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
else:
|
||||
formset = NewSessionFormset(initial=initial)
|
||||
extra_form = ExtraSessionForm()
|
||||
|
||||
|
||||
return render_to_response('meetings/schedule.html', {
|
||||
'extra_form': extra_form,
|
||||
'group': group,
|
||||
|
@ -711,16 +728,16 @@ def schedule(request, meeting_id, acronym):
|
|||
'formset': formset},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def select_group(request, meeting_id):
|
||||
'''
|
||||
In this view the user can select the group to schedule. Only those groups that have
|
||||
submitted session requests appear in the dropdowns.
|
||||
|
||||
|
||||
NOTE: BOF list includes Proposed Working Group type, per Wanda
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
group = request.POST.get('group',None)
|
||||
if group:
|
||||
|
@ -728,24 +745,24 @@ def select_group(request, meeting_id):
|
|||
else:
|
||||
redirect_url = reverse('meetings_select_group',kwargs={'meeting_id':meeting_id})
|
||||
messages.error(request, 'No group selected')
|
||||
|
||||
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
|
||||
# split groups into scheduled / unscheduled
|
||||
scheduled_groups, unscheduled_groups = sort_groups(meeting)
|
||||
|
||||
|
||||
# prep group form
|
||||
wgs = filter(lambda a: a.type_id in ('wg','ag') and a.state_id=='active', unscheduled_groups)
|
||||
group_form = GroupSelectForm(choices=build_choices(wgs))
|
||||
|
||||
|
||||
# prep BOFs form
|
||||
bofs = filter(lambda a: a.type_id=='wg' and a.state_id in ('bof','proposed'), unscheduled_groups)
|
||||
bof_form = GroupSelectForm(choices=build_choices(bofs))
|
||||
|
||||
|
||||
# prep IRTF form
|
||||
irtfs = filter(lambda a: a.type_id=='rg' and a.state_id in ('active','proposed'), unscheduled_groups)
|
||||
irtf_form = GroupSelectForm(choices=build_choices(irtfs))
|
||||
|
||||
|
||||
return render_to_response('meetings/select_group.html', {
|
||||
'group_form': group_form,
|
||||
'bof_form': bof_form,
|
||||
|
@ -754,17 +771,17 @@ def select_group(request, meeting_id):
|
|||
'meeting': meeting},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def times(request, meeting_id):
|
||||
'''
|
||||
Display and edit time slots (TimeSlots). It doesn't display every TimeSlot
|
||||
object for the meeting because there is one timeslot per time per room,
|
||||
object for the meeting because there is one timeslot per time per room,
|
||||
rather it displays all the unique times.
|
||||
The first time this view is called for a meeting it creates a form with times
|
||||
prepopulated from the last meeting
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
|
||||
|
||||
# build list of timeslots
|
||||
slots = []
|
||||
timeslots = []
|
||||
|
@ -778,7 +795,7 @@ def times(request, meeting_id):
|
|||
'time':t.time,
|
||||
'end_time':t.end_time()})
|
||||
times = sorted(slots, key=lambda a: a['time'])
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
form = TimeSlotForm(request.POST)
|
||||
if form.is_valid():
|
||||
|
@ -786,17 +803,17 @@ def times(request, meeting_id):
|
|||
time = form.cleaned_data['time']
|
||||
duration = form.cleaned_data['duration']
|
||||
name = form.cleaned_data['name']
|
||||
|
||||
|
||||
t = meeting.date + datetime.timedelta(days=int(day))
|
||||
new_time = datetime.datetime(t.year,t.month,t.day,time.hour,time.minute)
|
||||
|
||||
|
||||
# don't allow creation of timeslots with same start time as existing timeslots
|
||||
# assert False, (new_time, time_seen)
|
||||
if new_time in time_seen:
|
||||
messages.error(request, 'There is already a timeslot for %s. To change you must delete the old one first.' % new_time.strftime('%a %H:%M'))
|
||||
url = reverse('meetings_times', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
for room in meeting.room_set.all():
|
||||
TimeSlot.objects.create(type_id='session',
|
||||
meeting=meeting,
|
||||
|
@ -804,11 +821,11 @@ def times(request, meeting_id):
|
|||
time=new_time,
|
||||
location=room,
|
||||
duration=duration)
|
||||
|
||||
|
||||
messages.success(request, 'Timeslots created')
|
||||
url = reverse('meetings_times', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
else:
|
||||
form = TimeSlotForm()
|
||||
|
||||
|
@ -818,28 +835,28 @@ def times(request, meeting_id):
|
|||
'times': times},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def times_delete(request, meeting_id, time):
|
||||
'''
|
||||
This view handles bulk delete of all timeslots matching time (datetime) for the given
|
||||
meeting. There is one timeslot for each room.
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
|
||||
|
||||
parts = [ int(x) for x in time.split(':') ]
|
||||
dtime = datetime.datetime(*parts)
|
||||
|
||||
|
||||
if Session.objects.filter(timeslot__time=dtime,timeslot__meeting=meeting):
|
||||
messages.error(request, 'ERROR deleting timeslot. There is one or more sessions scheduled for this timeslot.')
|
||||
url = reverse('meetings_times', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
TimeSlot.objects.filter(meeting=meeting,time=dtime).delete()
|
||||
|
||||
|
||||
messages.success(request, 'Timeslot deleted')
|
||||
url = reverse('meetings_times', kwargs={'meeting_id':meeting_id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
def view(request, meeting_id):
|
||||
'''
|
||||
View Meeting information.
|
||||
|
@ -854,7 +871,7 @@ def view(request, meeting_id):
|
|||
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
|
||||
|
||||
return render_to_response('meetings/view.html', {
|
||||
'meeting': meeting},
|
||||
RequestContext(request, {}),
|
||||
|
|
|
@ -13,7 +13,7 @@ import re
|
|||
# Globals
|
||||
# ---------------------------------------------
|
||||
|
||||
VALID_SLIDE_EXTENSIONS = ('.doc','.docx','.pdf','.ppt','.pptx','.txt')
|
||||
VALID_SLIDE_EXTENSIONS = ('.doc','.docx','.pdf','.ppt','.pptx','.txt','.zip')
|
||||
VALID_MINUTES_EXTENSIONS = ('.txt','.html','.htm','.pdf')
|
||||
VALID_AGENDA_EXTENSIONS = ('.txt','.html','.htm')
|
||||
|
||||
|
@ -101,4 +101,4 @@ class UnifiedUploadForm(forms.Form):
|
|||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ def mycomp(timeslot):
|
|||
except AttributeError:
|
||||
key = None
|
||||
return key
|
||||
|
||||
|
||||
def get_progress_stats(sdate,edate):
|
||||
'''
|
||||
This function takes a date range and produces a dictionary of statistics / objects for use
|
||||
|
@ -46,7 +46,7 @@ def get_progress_stats(sdate,edate):
|
|||
data = {}
|
||||
data['sdate'] = sdate
|
||||
data['edate'] = edate
|
||||
|
||||
|
||||
# Activty Report Section
|
||||
new_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision',
|
||||
docevent__newrevisiondocevent__rev='00',
|
||||
|
@ -61,20 +61,20 @@ def get_progress_stats(sdate,edate):
|
|||
data['updated'] += 1
|
||||
if updates > 2:
|
||||
data['updated_more'] +=1
|
||||
|
||||
|
||||
# calculate total documents updated, not counting new, rev=00
|
||||
result = set()
|
||||
events = DocEvent.objects.filter(doc__type='draft',time__gte=sdate,time__lte=edate)
|
||||
for e in events.filter(type='new_revision').exclude(newrevisiondocevent__rev='00'):
|
||||
result.add(e.doc)
|
||||
data['total_updated'] = len(result)
|
||||
|
||||
|
||||
# calculate sent last call
|
||||
data['last_call'] = events.filter(type='sent_last_call').count()
|
||||
|
||||
|
||||
# calculate approved
|
||||
data['approved'] = events.filter(type='iesg_approved').count()
|
||||
|
||||
|
||||
# get 4 weeks
|
||||
monday = Meeting.get_ietf_monday()
|
||||
cutoff = monday + datetime.timedelta(days=3)
|
||||
|
@ -82,14 +82,14 @@ def get_progress_stats(sdate,edate):
|
|||
ff2_date = cutoff - datetime.timedelta(days=21)
|
||||
ff3_date = cutoff - datetime.timedelta(days=14)
|
||||
ff4_date = cutoff - datetime.timedelta(days=7)
|
||||
|
||||
|
||||
ff_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision',
|
||||
docevent__newrevisiondocevent__rev='00',
|
||||
docevent__time__gte=ff1_date,
|
||||
docevent__time__lte=cutoff)
|
||||
ff_new_count = ff_docs.count()
|
||||
ff_new_percent = format(ff_new_count / float(data['new']),'.0%')
|
||||
|
||||
|
||||
# calculate total documents updated in final four weeks, not counting new, rev=00
|
||||
result = set()
|
||||
events = DocEvent.objects.filter(doc__type='draft',time__gte=ff1_date,time__lte=cutoff)
|
||||
|
@ -97,48 +97,48 @@ def get_progress_stats(sdate,edate):
|
|||
result.add(e.doc)
|
||||
ff_update_count = len(result)
|
||||
ff_update_percent = format(ff_update_count / float(data['total_updated']),'.0%')
|
||||
|
||||
|
||||
data['ff_new_count'] = ff_new_count
|
||||
data['ff_new_percent'] = ff_new_percent
|
||||
data['ff_update_count'] = ff_update_count
|
||||
data['ff_update_percent'] = ff_update_percent
|
||||
|
||||
|
||||
# Progress Report Section
|
||||
data['docevents'] = DocEvent.objects.filter(doc__type='draft',time__gte=sdate,time__lte=edate)
|
||||
data['action_events'] = data['docevents'].filter(type='iesg_approved')
|
||||
data['lc_events'] = data['docevents'].filter(type='sent_last_call')
|
||||
|
||||
|
||||
data['new_groups'] = Group.objects.filter(type='wg',
|
||||
groupevent__changestategroupevent__state='active',
|
||||
groupevent__time__gte=sdate,
|
||||
groupevent__time__lte=edate)
|
||||
|
||||
|
||||
data['concluded_groups'] = Group.objects.filter(type='wg',
|
||||
groupevent__changestategroupevent__state='conclude',
|
||||
groupevent__time__gte=sdate,
|
||||
groupevent__time__lte=edate)
|
||||
|
||||
|
||||
data['new_docs'] = Document.objects.filter(type='draft').filter(docevent__type='new_revision',
|
||||
docevent__time__gte=sdate,
|
||||
docevent__time__lte=edate).distinct()
|
||||
|
||||
|
||||
data['rfcs'] = DocEvent.objects.filter(type='published_rfc',
|
||||
doc__type='draft',
|
||||
time__gte=sdate,
|
||||
time__lte=edate)
|
||||
|
||||
|
||||
# attach the ftp URL for use in the template
|
||||
for event in data['rfcs']:
|
||||
num = get_rfc_num(event.doc)
|
||||
event.ftp_url = 'ftp://ftp.ietf.org/rfc/rfc%s.txt' % num
|
||||
|
||||
|
||||
data['counts'] = {'std':data['rfcs'].filter(doc__intended_std_level__in=('ps','ds','std')).count(),
|
||||
'bcp':data['rfcs'].filter(doc__intended_std_level='bcp').count(),
|
||||
'exp':data['rfcs'].filter(doc__intended_std_level='exp').count(),
|
||||
'inf':data['rfcs'].filter(doc__intended_std_level='inf').count()}
|
||||
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def write_html(path,content):
|
||||
f = open(path,'w')
|
||||
f.write(content)
|
||||
|
@ -156,7 +156,7 @@ def create_interim_directory():
|
|||
Create static Interim Meeting directory pages that will live in a different URL space than
|
||||
the secretariat Django project
|
||||
'''
|
||||
|
||||
|
||||
# produce date sorted output
|
||||
page = 'proceedings.html'
|
||||
meetings = InterimMeeting.objects.order_by('-date')
|
||||
|
@ -165,7 +165,7 @@ def create_interim_directory():
|
|||
f = open(path,'w')
|
||||
f.write(response.content)
|
||||
f.close()
|
||||
|
||||
|
||||
# produce group sorted output
|
||||
page = 'proceedings-bygroup.html'
|
||||
qs = InterimMeeting.objects.all()
|
||||
|
@ -175,7 +175,7 @@ def create_interim_directory():
|
|||
f = open(path,'w')
|
||||
f.write(response.content)
|
||||
f.close()
|
||||
|
||||
|
||||
def create_proceedings(meeting, group, is_final=False):
|
||||
'''
|
||||
This function creates the proceedings html document. It gets called anytime there is an
|
||||
|
@ -185,7 +185,7 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
# abort, proceedings from meetings before 79 have a different format, don't overwrite
|
||||
if meeting.type_id == 'ietf' and int(meeting.number) < 79:
|
||||
return
|
||||
|
||||
|
||||
sessions = Session.objects.filter(meeting=meeting,group=group)
|
||||
if sessions:
|
||||
session = sessions[0]
|
||||
|
@ -194,7 +194,7 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
agenda = None
|
||||
minutes = None
|
||||
slides = None
|
||||
|
||||
|
||||
chairs = group.role_set.filter(name='chair')
|
||||
secretaries = group.role_set.filter(name='secr')
|
||||
if group.parent: # Certain groups like Tools Team do no have a parent
|
||||
|
@ -202,7 +202,7 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
else:
|
||||
ads = None
|
||||
tas = group.role_set.filter(name='techadv')
|
||||
|
||||
|
||||
docs = Document.objects.filter(group=group,type='draft').order_by('time')
|
||||
|
||||
meeting_root = get_upload_root(meeting)
|
||||
|
@ -213,15 +213,15 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
settings.MEDIA_URL,
|
||||
meeting.date.strftime('%Y/%m/%d'),
|
||||
group.acronym)
|
||||
|
||||
|
||||
# Only do these tasks if we are running official proceedings generation,
|
||||
# otherwise skip them for expediency. This procedure is called any time meeting
|
||||
# otherwise skip them for expediency. This procedure is called any time meeting
|
||||
# materials are uploaded/deleted, and we don't want to do all this work each time.
|
||||
|
||||
|
||||
if is_final:
|
||||
# ----------------------------------------------------------------------
|
||||
# Find active Drafts and RFCs, copy them to id and rfc directories
|
||||
|
||||
|
||||
drafts = docs.filter(states__slug='active')
|
||||
for draft in drafts:
|
||||
source = os.path.join(draft.get_file_path(),draft.filename_with_rev())
|
||||
|
@ -234,7 +234,7 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
else:
|
||||
draft.bytes = 0
|
||||
draft.url = url_root + "id/%s" % draft.filename_with_rev()
|
||||
|
||||
|
||||
rfcs = docs.filter(states__slug='rfc')
|
||||
for rfc in rfcs:
|
||||
# TODO should use get_file_path() here but is incorrect for rfcs
|
||||
|
@ -245,7 +245,7 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
target = os.path.join(meeting_root,'rfc')
|
||||
rfc.rmsg = ''
|
||||
rfc.msg = ''
|
||||
|
||||
|
||||
if not os.path.exists(target):
|
||||
os.makedirs(target)
|
||||
shutil.copy(source,target)
|
||||
|
@ -282,7 +282,7 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
# ----------------------------------------------------------------------
|
||||
else:
|
||||
drafts = rfcs = bluesheets = None
|
||||
|
||||
|
||||
# the simplest way to display the charter is to place it in a <pre> block
|
||||
# however, because this forces a fixed-width font, different than the rest of
|
||||
# the document we modify the charter by adding replacing linefeeds with <br>'s
|
||||
|
@ -292,8 +292,8 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
else:
|
||||
charter = None
|
||||
ctime = None
|
||||
|
||||
|
||||
|
||||
|
||||
# rather than return the response as in a typical view function we save it as the snapshot
|
||||
# proceedings.html
|
||||
response = render_to_response('proceedings/proceedings.html',{
|
||||
|
@ -312,10 +312,10 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
'minutes': minutes,
|
||||
'agenda': agenda}
|
||||
)
|
||||
|
||||
|
||||
# save proceedings
|
||||
proceedings_path = get_proceedings_path(meeting,group)
|
||||
|
||||
|
||||
f = open(proceedings_path,'w')
|
||||
f.write(response.content)
|
||||
f.close()
|
||||
|
@ -323,7 +323,7 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
os.chmod(proceedings_path, 0664)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
# rebuild the directory
|
||||
if meeting.type.slug == 'interim':
|
||||
create_interim_directory()
|
||||
|
@ -335,20 +335,20 @@ def create_proceedings(meeting, group, is_final=False):
|
|||
def gen_areas(context):
|
||||
meeting = context['meeting']
|
||||
gmet, gnot = groups_by_session(None,meeting)
|
||||
|
||||
|
||||
# append proceedings URL
|
||||
for group in gmet + gnot:
|
||||
group.proceedings_url = "%s/proceedings/%s/%s.html" % (settings.MEDIA_URL,meeting.number,group.acronym)
|
||||
|
||||
for (counter,area) in enumerate(context['areas'], start=1):
|
||||
groups_met = {'wg':filter(lambda a: a.parent==area and a.state.slug!='bof' and a.type_id=='wg',gmet),
|
||||
'bof':filter(lambda a: a.parent==area and a.state.slug=='bof' and a.type_id=='wg',gmet),
|
||||
|
||||
for (counter,area) in enumerate(context['areas'], start=1):
|
||||
groups_met = {'wg':filter(lambda a: a.parent==area and a.state.slug not in ('bof','bof-conc') and a.type_id=='wg',gmet),
|
||||
'bof':filter(lambda a: a.parent==area and a.state.slug in ('bof','bof-conc') and a.type_id=='wg',gmet),
|
||||
'ag':filter(lambda a: a.parent==area and a.type_id=='ag',gmet)}
|
||||
|
||||
groups_not = {'wg':filter(lambda a: a.parent==area and a.state.slug!='bof' and a.type_id=='wg',gnot),
|
||||
|
||||
groups_not = {'wg':filter(lambda a: a.parent==area and a.state.slug not in ('bof','bof-conc') and a.type_id=='wg',gnot),
|
||||
'bof':filter(lambda a: a.parent==area and a.state.slug=='bof' and a.type_id=='wg',gnot),
|
||||
'ag':filter(lambda a: a.parent==area and a.type_id=='ag',gnot)}
|
||||
|
||||
|
||||
html = render_to_response('proceedings/area.html',{
|
||||
'area': area,
|
||||
'meeting': meeting,
|
||||
|
@ -356,63 +356,63 @@ def gen_areas(context):
|
|||
'groups_not': groups_not,
|
||||
'index': counter}
|
||||
)
|
||||
|
||||
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'%s.html' % area.acronym)
|
||||
write_html(path,html.content)
|
||||
|
||||
def gen_acknowledgement(context):
|
||||
meeting = context['meeting']
|
||||
|
||||
|
||||
html = render_to_response('proceedings/acknowledgement.html',{
|
||||
'meeting': meeting}
|
||||
)
|
||||
|
||||
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'acknowledgement.html')
|
||||
write_html(path,html.content)
|
||||
|
||||
|
||||
def gen_agenda(context):
|
||||
meeting = context['meeting']
|
||||
|
||||
|
||||
#timeslots, update, meeting, venue, ads, plenaryw_agenda, plenaryt_agenda = agenda_info(meeting.number)
|
||||
timeslots = TimeSlot.objects.filter(meeting=meeting)
|
||||
|
||||
|
||||
# sort by area:group then time
|
||||
sort1 = sorted(timeslots, key = mycomp)
|
||||
sort2 = sorted(sort1, key = lambda a: a.time)
|
||||
|
||||
|
||||
html = render_to_response('proceedings/agenda.html',{
|
||||
'meeting': meeting,
|
||||
'timeslots': sort2}
|
||||
)
|
||||
|
||||
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'agenda.html')
|
||||
write_html(path,html.content)
|
||||
|
||||
|
||||
# get the text agenda from datatracker
|
||||
url = 'https://datatracker.ietf.org/meeting/%s/agenda.txt' % meeting.number
|
||||
text = urlopen(url).read()
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'agenda.txt')
|
||||
write_html(path,text)
|
||||
|
||||
|
||||
def gen_attendees(context):
|
||||
meeting = context['meeting']
|
||||
|
||||
|
||||
attendees = Registration.objects.using('ietf' + meeting.number).all().order_by('lname')
|
||||
|
||||
|
||||
html = render_to_response('proceedings/attendee.html',{
|
||||
'meeting': meeting,
|
||||
'attendees': attendees}
|
||||
)
|
||||
|
||||
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'attendee.html')
|
||||
write_html(path,html.content)
|
||||
|
||||
|
||||
def gen_group_pages(context):
|
||||
meeting = context['meeting']
|
||||
|
||||
|
||||
for group in Group.objects.filter(type__in=('wg','ag','rg'), state__in=('bof','proposed','active')):
|
||||
create_proceedings(meeting,group,is_final=True)
|
||||
|
||||
|
||||
def gen_index(context):
|
||||
index = render_to_response('proceedings/index.html',context)
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,context['meeting'].number,'index.html')
|
||||
|
@ -421,36 +421,36 @@ def gen_index(context):
|
|||
def gen_irtf(context):
|
||||
meeting = context['meeting']
|
||||
irtf_chair = Role.objects.filter(group__acronym='irtf',name='chair')[0]
|
||||
|
||||
|
||||
html = render_to_response('proceedings/irtf.html',{
|
||||
'irtf_chair':irtf_chair}
|
||||
)
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'irtf.html')
|
||||
write_html(path,html.content)
|
||||
|
||||
|
||||
def gen_overview(context):
|
||||
meeting = context['meeting']
|
||||
|
||||
|
||||
ietf_chair = Role.objects.get(group__acronym='ietf',name='chair')
|
||||
ads = Role.objects.filter(group__type='area',group__state='active',name='ad')
|
||||
sorted_ads = sorted(ads, key = lambda a: a.person.name_parts()[3])
|
||||
|
||||
|
||||
html = render_to_response('proceedings/overview.html',{
|
||||
'meeting': meeting,
|
||||
'ietf_chair': ietf_chair,
|
||||
'ads': sorted_ads}
|
||||
)
|
||||
|
||||
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'overview.html')
|
||||
write_html(path,html.content)
|
||||
|
||||
|
||||
def gen_plenaries(context):
|
||||
'''
|
||||
This function generates pages for the Plenaries. At meeting 85 the Plenary sessions
|
||||
This function generates pages for the Plenaries. At meeting 85 the Plenary sessions
|
||||
were combined into one, so we need to handle not finding one of the sessions.
|
||||
'''
|
||||
meeting = context['meeting']
|
||||
|
||||
|
||||
# Administration Plenary
|
||||
try:
|
||||
admin_session = Session.objects.get(meeting=meeting,name__contains='Administration Plenary')
|
||||
|
@ -466,7 +466,7 @@ def gen_plenaries(context):
|
|||
write_html(path,admin.content)
|
||||
except Session.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
# Technical Plenary
|
||||
try:
|
||||
tech_session = Session.objects.get(meeting=meeting,name__contains='Technical Plenary')
|
||||
|
@ -482,16 +482,16 @@ def gen_plenaries(context):
|
|||
write_html(path,tech.content)
|
||||
except Session.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
def gen_progress(context, final=True):
|
||||
'''
|
||||
This function generates the Progress Report. This report is actually produced twice. First
|
||||
for inclusion in the Admin Plenary, then for the final proceedings. When produced the first
|
||||
time we want to exclude the headers because they are broken links until all the proceedings
|
||||
time we want to exclude the headers because they are broken links until all the proceedings
|
||||
are generated.
|
||||
'''
|
||||
meeting = context['meeting']
|
||||
|
||||
|
||||
# proceedings are run sometime after the meeting, so end date = the previous meeting
|
||||
# date and start date = the date of the meeting before that
|
||||
now = datetime.date.today()
|
||||
|
@ -501,30 +501,30 @@ def gen_progress(context, final=True):
|
|||
data = get_progress_stats(start_date,end_date)
|
||||
data['meeting'] = meeting
|
||||
data['final'] = final
|
||||
|
||||
|
||||
html = render_to_response('proceedings/progress.html',data)
|
||||
|
||||
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'progress-report.html')
|
||||
write_html(path,html.content)
|
||||
|
||||
|
||||
def gen_research(context):
|
||||
meeting = context['meeting']
|
||||
gmet, gnot = groups_by_session(None,meeting)
|
||||
|
||||
|
||||
groups = filter(lambda a: a.type_id=='rg', gmet)
|
||||
|
||||
|
||||
# append proceedings URL
|
||||
for group in groups:
|
||||
group.proceedings_url = "%s/proceedings/%s/%s.html" % (settings.MEDIA_URL,meeting.number,group.acronym)
|
||||
|
||||
|
||||
html = render_to_response('proceedings/rg_irtf.html',{
|
||||
'meeting': meeting,
|
||||
'groups': groups}
|
||||
)
|
||||
|
||||
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'rg_irtf.html')
|
||||
write_html(path,html.content)
|
||||
|
||||
|
||||
def gen_training(context):
|
||||
meeting = context['meeting']
|
||||
timeslots = context['others']
|
||||
|
@ -540,4 +540,4 @@ def gen_training(context):
|
|||
)
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'train-%s.html' % counter )
|
||||
write_html(path,html.content)
|
||||
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ import zipfile
|
|||
|
||||
def build_choices(queryset):
|
||||
'''
|
||||
This function takes a queryset (or list) of Groups and builds a list of tuples for use
|
||||
This function takes a queryset (or list) of Groups and builds a list of tuples for use
|
||||
as choices in a select widget. Using acronym for both value and label.
|
||||
'''
|
||||
choices = [ (g.acronym,g.acronym) for g in queryset ]
|
||||
|
@ -79,6 +79,17 @@ def get_doc_filename(doc):
|
|||
# TODO we might want to choose from among multiple files using some logic
|
||||
return files[0]
|
||||
|
||||
def get_extras(meeting):
|
||||
'''
|
||||
Gather "extras" which are one off groups. ie iab-wcit(86)
|
||||
'''
|
||||
groups = []
|
||||
sessions = Session.objects.filter(meeting=meeting).exclude(group__parent__acronym__in=('app','gen','int','ops','rai','rtg','sec','tsv','irtf')).filter(timeslot__type='session')
|
||||
for session in sessions:
|
||||
if session.materials.all():
|
||||
groups.append(session.group)
|
||||
return groups
|
||||
|
||||
def get_next_interim_num(acronym,date):
|
||||
'''
|
||||
This function takes a group acronym and date object and returns the next number to use for an
|
||||
|
@ -92,13 +103,13 @@ def get_next_interim_num(acronym,date):
|
|||
return base + str(int(parts[-1]) + 1)
|
||||
else:
|
||||
return base + '1'
|
||||
|
||||
|
||||
def get_next_slide_num(session):
|
||||
'''
|
||||
This function takes a session object and returns the
|
||||
next slide number to use for a newly added slide as a string.
|
||||
'''
|
||||
|
||||
|
||||
"""
|
||||
slides = session.materials.filter(type='slides').order_by('-name')
|
||||
if slides:
|
||||
|
@ -127,7 +138,7 @@ def get_next_order_num(session):
|
|||
next slide order number to use for a newly added slide as an integer.
|
||||
'''
|
||||
max_order = session.materials.aggregate(Max('order'))['order__max']
|
||||
|
||||
|
||||
return max_order + 1 if max_order else 1
|
||||
|
||||
# --- These could be properties/methods on meeting
|
||||
|
@ -143,15 +154,15 @@ def get_proceedings_url(meeting,group=None):
|
|||
url = "%s/proceedings/%s/" % (settings.MEDIA_URL,meeting.number)
|
||||
if group:
|
||||
url = url + "%s.html" % group.acronym
|
||||
|
||||
|
||||
elif meeting.type_id == 'interim':
|
||||
url = "%s/proceedings/interim/%s/%s/proceedings.html" % (
|
||||
settings.MEDIA_URL,
|
||||
meeting.date.strftime('%Y/%m/%d'),
|
||||
group.acronym)
|
||||
return url
|
||||
|
||||
def handle_upload_file(file,filename,meeting,subdir):
|
||||
|
||||
def handle_upload_file(file,filename,meeting,subdir):
|
||||
'''
|
||||
This function takes a file object, a filename and a meeting object and subdir as string.
|
||||
It saves the file to the appropriate directory, get_upload_root() + subdir.
|
||||
|
@ -159,21 +170,21 @@ def handle_upload_file(file,filename,meeting,subdir):
|
|||
zip file and unzips the file in the new directory.
|
||||
'''
|
||||
base, extension = os.path.splitext(filename)
|
||||
|
||||
|
||||
if extension == '.zip':
|
||||
path = os.path.join(get_upload_root(meeting),subdir,base)
|
||||
if not os.path.exists(path):
|
||||
os.mkdir(path)
|
||||
else:
|
||||
path = os.path.join(get_upload_root(meeting),subdir)
|
||||
|
||||
|
||||
# agendas and minutes can only have one file instance so delete file if it already exists
|
||||
if subdir in ('agenda','minutes'):
|
||||
old_files = glob.glob(os.path.join(path,base) + '.*')
|
||||
for f in old_files:
|
||||
os.remove(f)
|
||||
|
||||
destination = open(os.path.join(path,filename), 'wb+')
|
||||
|
||||
destination = open(os.path.join(path,filename), 'wb+')
|
||||
for chunk in file.chunks():
|
||||
destination.write(chunk)
|
||||
destination.close()
|
||||
|
@ -193,13 +204,13 @@ def make_directories(meeting):
|
|||
target = os.path.join(path,leaf)
|
||||
if not os.path.exists(target):
|
||||
os.makedirs(target)
|
||||
|
||||
|
||||
def parsedate(d):
|
||||
'''
|
||||
This function takes a date object and returns a tuple of year,month,day
|
||||
'''
|
||||
return (d.strftime('%Y'),d.strftime('%m'),d.strftime('%d'))
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# AJAX Functions
|
||||
# -------------------------------------------------
|
||||
|
@ -207,17 +218,19 @@ def parsedate(d):
|
|||
def ajax_generate_proceedings(request, meeting_num):
|
||||
'''
|
||||
Ajax function which takes a meeting number and generates the proceedings
|
||||
pages for the meeting. It returns a snippet of HTML that gets placed in the
|
||||
pages for the meeting. It returns a snippet of HTML that gets placed in the
|
||||
Secretariat Only section of the select page.
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_num)
|
||||
areas = Group.objects.filter(type='area',state='active').order_by('name')
|
||||
others = TimeSlot.objects.filter(meeting=meeting,type='other').order_by('time')
|
||||
extras = get_extras(meeting)
|
||||
context = {'meeting':meeting,
|
||||
'areas':areas,
|
||||
'others':others}
|
||||
'others':others,
|
||||
'extras':extras}
|
||||
proceedings_url = get_proceedings_url(meeting)
|
||||
|
||||
|
||||
# the acknowledgement page can be edited manually so only produce if it doesn't already exist
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'acknowledgement.html')
|
||||
if not os.path.exists(path):
|
||||
|
@ -233,16 +246,16 @@ def ajax_generate_proceedings(request, meeting_num):
|
|||
gen_irtf(context)
|
||||
gen_research(context)
|
||||
gen_group_pages(context)
|
||||
|
||||
|
||||
# get the time proceedings were generated
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'index.html')
|
||||
last_run = datetime.datetime.fromtimestamp(os.path.getmtime(path))
|
||||
|
||||
|
||||
return render_to_response('includes/proceedings_functions.html',{
|
||||
'meeting':meeting,
|
||||
'last_run':last_run,
|
||||
'proceedings_url':proceedings_url},
|
||||
RequestContext(request,{}),
|
||||
RequestContext(request,{}),
|
||||
)
|
||||
|
||||
@jsonapi
|
||||
|
@ -258,13 +271,13 @@ def ajax_order_slide(request):
|
|||
slide_name = request.POST.get('slide_name',None)
|
||||
order = request.POST.get('order',None)
|
||||
slide = get_object_or_404(Document, name=slide_name)
|
||||
|
||||
|
||||
# get all the slides for this session
|
||||
session = slide.session_set.all()[0]
|
||||
meeting = session.meeting
|
||||
group = session.group
|
||||
qs = session.materials.exclude(states__slug='deleted').filter(type='slides').order_by('order')
|
||||
|
||||
|
||||
# move slide and reorder list
|
||||
slides = list(qs)
|
||||
index = slides.index(slide)
|
||||
|
@ -274,9 +287,9 @@ def ajax_order_slide(request):
|
|||
if item.order != ord:
|
||||
item.order = ord
|
||||
item.save()
|
||||
|
||||
|
||||
return {'success':True,'order':order,'slide':slide_name}
|
||||
|
||||
|
||||
# --------------------------------------------------
|
||||
# STANDARD VIEW FUNCTIONS
|
||||
# --------------------------------------------------
|
||||
|
@ -288,13 +301,13 @@ def build(request,meeting_num,acronym):
|
|||
'''
|
||||
meeting = Meeting.objects.get(number=meeting_num)
|
||||
group = get_object_or_404(Group,acronym=acronym)
|
||||
|
||||
|
||||
create_proceedings(meeting,group)
|
||||
|
||||
|
||||
messages.success(request,'proceedings.html was rebuilt')
|
||||
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting_num,'acronym':acronym})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
@check_permissions
|
||||
def delete_material(request,slide_id):
|
||||
'''
|
||||
|
@ -306,30 +319,30 @@ def delete_material(request,slide_id):
|
|||
session = doc.session_set.all()[0]
|
||||
meeting = session.meeting
|
||||
group = session.group
|
||||
|
||||
|
||||
path = get_full_path(doc)
|
||||
if path and os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
|
||||
# leave it related
|
||||
#session.materials.remove(doc)
|
||||
|
||||
|
||||
state = State.objects.get(type=doc.type,slug='deleted')
|
||||
doc.set_state(state)
|
||||
|
||||
|
||||
# create deleted_document
|
||||
DocEvent.objects.create(doc=doc,
|
||||
by=request.user.get_profile(),
|
||||
type='deleted')
|
||||
|
||||
|
||||
create_proceedings(meeting,group)
|
||||
|
||||
|
||||
messages.success(request,'The material was deleted successfully')
|
||||
if group.type.slug in ('wg','rg'):
|
||||
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':group.acronym})
|
||||
else:
|
||||
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'session_id':session.id})
|
||||
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@sec_only
|
||||
|
@ -342,17 +355,17 @@ def delete_interim_meeting(request, meeting_num):
|
|||
meeting = get_object_or_404(Meeting, number=meeting_num)
|
||||
sessions = Session.objects.filter(meeting=meeting)
|
||||
group = sessions[0].group
|
||||
|
||||
|
||||
# delete directories
|
||||
path = get_upload_root(meeting)
|
||||
|
||||
|
||||
# do a quick sanity check on this path before we go and delete it
|
||||
parts = path.split('/')
|
||||
assert parts[-1] == group.acronym
|
||||
|
||||
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
meeting.delete()
|
||||
sessions.delete()
|
||||
|
||||
|
@ -369,27 +382,27 @@ def edit_slide(request, slide_id):
|
|||
session = slide.session_set.all()[0]
|
||||
meeting = session.meeting
|
||||
group = session.group
|
||||
|
||||
|
||||
if group.type.slug in ('wg','rg'):
|
||||
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':group.acronym})
|
||||
else:
|
||||
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'session_id':session.id})
|
||||
|
||||
|
||||
if request.method == 'POST': # If the form has been submitted...
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Cancel':
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
form = EditSlideForm(request.POST, instance=slide) # A form bound to the POST data
|
||||
if form.is_valid():
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
|
||||
|
||||
# rebuild proceedings.html
|
||||
create_proceedings(meeting,group)
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
form = EditSlideForm(instance=slide)
|
||||
|
||||
|
||||
return render_to_response('proceedings/edit_slide.html',{
|
||||
'group': group,
|
||||
'meeting':meeting,
|
||||
|
@ -410,7 +423,7 @@ def interim(request, acronym):
|
|||
if button_text == 'Back':
|
||||
url = reverse('proceedings_select_interim')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
form = InterimMeetingForm(request.POST) # A form bound to the POST data
|
||||
if form.is_valid():
|
||||
date = form.cleaned_data['date']
|
||||
|
@ -418,13 +431,13 @@ def interim(request, acronym):
|
|||
meeting=Meeting.objects.create(type_id='interim',
|
||||
date=date,
|
||||
number=number)
|
||||
|
||||
|
||||
# create session to associate this meeting with a group and hold material
|
||||
Session.objects.create(meeting=meeting,
|
||||
group=group,
|
||||
requested_by=request.user.get_profile(),
|
||||
status_id='sched')
|
||||
|
||||
|
||||
create_interim_directory()
|
||||
make_directories(meeting)
|
||||
|
||||
|
@ -433,9 +446,9 @@ def interim(request, acronym):
|
|||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
form = InterimMeetingForm(initial={'group_acronym_id':acronym}) # An unbound form
|
||||
|
||||
|
||||
meetings = Meeting.objects.filter(type='interim',session__group__acronym=acronym).order_by('date')
|
||||
|
||||
|
||||
return render_to_response('proceedings/interim_meeting.html',{
|
||||
'group': group,
|
||||
'meetings':meetings,
|
||||
|
@ -444,7 +457,7 @@ def interim(request, acronym):
|
|||
)
|
||||
|
||||
def interim_directory(request, sortby=None):
|
||||
|
||||
|
||||
if sortby == 'group':
|
||||
qs = InterimMeeting.objects.all()
|
||||
meetings = sorted(qs, key=lambda a: a.group.acronym)
|
||||
|
@ -474,27 +487,27 @@ def main(request):
|
|||
person = request.user.get_profile()
|
||||
except Person.DoesNotExist:
|
||||
return HttpResponseForbidden('ACCESS DENIED: user=%s' % request.META['REMOTE_USER'])
|
||||
|
||||
|
||||
if has_role(request.user,'Secretariat'):
|
||||
meetings = Meeting.objects.filter(type='ietf').order_by('-number')
|
||||
else:
|
||||
# select meetings still within the cutoff period
|
||||
meetings = Meeting.objects.filter(type='ietf',date__gt=datetime.datetime.today() - datetime.timedelta(days=settings.SUBMISSION_CORRECTION_DAYS)).order_by('number')
|
||||
|
||||
|
||||
groups = get_my_groups(request.user)
|
||||
interim_meetings = Meeting.objects.filter(type='interim',session__group__in=groups).order_by('-date')
|
||||
# tac on group for use in templates
|
||||
for m in interim_meetings:
|
||||
m.group = m.session_set.all()[0].group
|
||||
|
||||
|
||||
# we today's date to see if we're past the submissio cutoff
|
||||
today = datetime.date.today()
|
||||
|
||||
|
||||
return render_to_response('proceedings/main.html',{
|
||||
'meetings': meetings,
|
||||
'interim_meetings': interim_meetings,
|
||||
'today': today},
|
||||
RequestContext(request,{}),
|
||||
RequestContext(request,{}),
|
||||
)
|
||||
|
||||
@check_permissions
|
||||
|
@ -504,13 +517,13 @@ def move_slide(request, slide_id, direction):
|
|||
a direction argument which is a string [up|down].
|
||||
'''
|
||||
slide = get_object_or_404(Document, name=slide_id)
|
||||
|
||||
|
||||
# derive other objects
|
||||
session = slide.session_set.all()[0]
|
||||
meeting = session.meeting
|
||||
group = session.group
|
||||
qs = session.materials.exclude(states__slug='deleted').filter(type='slides').order_by('order')
|
||||
|
||||
|
||||
# if direction is up and we aren't already the first slide
|
||||
if direction == 'up' and slide_id != str(qs[0].pk):
|
||||
index = find_index(slide_id, qs)
|
||||
|
@ -526,7 +539,7 @@ def move_slide(request, slide_id, direction):
|
|||
slide_after.order, slide.order = slide.order, slide_after.order
|
||||
slide.save()
|
||||
slide_after.save()
|
||||
|
||||
|
||||
if group.type.slug in ('wg','rg'):
|
||||
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':group.acronym})
|
||||
else:
|
||||
|
@ -539,7 +552,7 @@ def process_pdfs(request, meeting_num):
|
|||
This function is used to update the database once meeting materials in PPT format
|
||||
are converted to PDF format and uploaded to the server. It basically finds every PowerPoint
|
||||
slide document for the given meeting and checks to see if there is a PDF version. If there
|
||||
is external_url is changed. Then when proceedings are generated the URL will refer to the
|
||||
is external_url is changed. Then when proceedings are generated the URL will refer to the
|
||||
PDF document.
|
||||
'''
|
||||
warn_count = 0
|
||||
|
@ -557,14 +570,14 @@ def process_pdfs(request, meeting_num):
|
|||
count += 1
|
||||
else:
|
||||
warn_count += 1
|
||||
|
||||
|
||||
if warn_count:
|
||||
messages.warning(request, '%s PDF files processed. %s PowerPoint files still not converted.' % (count, warn_count))
|
||||
else:
|
||||
messages.success(request, '%s PDF files processed' % count)
|
||||
url = reverse('proceedings_select', kwargs={'meeting_num':meeting_num})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
@sec_only
|
||||
def progress_report(request, meeting_num):
|
||||
'''
|
||||
|
@ -572,10 +585,10 @@ def progress_report(request, meeting_num):
|
|||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_num)
|
||||
gen_progress({'meeting':meeting},final=False)
|
||||
|
||||
|
||||
url = reverse('proceedings_select', kwargs={'meeting_num':meeting_num})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
@check_permissions
|
||||
def replace_slide(request, slide_id):
|
||||
'''
|
||||
|
@ -591,37 +604,37 @@ def replace_slide(request, slide_id):
|
|||
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':group.acronym})
|
||||
else:
|
||||
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'session_id':session.id})
|
||||
|
||||
|
||||
if request.method == 'POST': # If the form has been submitted...
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Cancel':
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
form = ReplaceSlideForm(request.POST,request.FILES,instance=slide) # A form bound to the POST data
|
||||
if form.is_valid():
|
||||
if form.is_valid():
|
||||
new_slide = form.save(commit=False)
|
||||
new_slide.time = datetime.datetime.now()
|
||||
|
||||
|
||||
file = request.FILES[request.FILES.keys()[0]]
|
||||
file_ext = os.path.splitext(file.name)[1]
|
||||
disk_filename = new_slide.name + file_ext
|
||||
handle_upload_file(file,disk_filename,meeting,'slides')
|
||||
|
||||
|
||||
new_slide.external_url = disk_filename
|
||||
new_slide.save()
|
||||
|
||||
|
||||
# create DocEvent uploaded
|
||||
DocEvent.objects.create(doc=slide,
|
||||
by=request.user.get_profile(),
|
||||
type='uploaded')
|
||||
|
||||
|
||||
# rebuild proceedings.html
|
||||
create_proceedings(meeting,group)
|
||||
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
form = ReplaceSlideForm(instance=slide)
|
||||
|
||||
|
||||
return render_to_response('proceedings/replace_slide.html',{
|
||||
'group': group,
|
||||
'meeting':meeting,
|
||||
|
@ -645,31 +658,31 @@ def select(request, meeting_num):
|
|||
else:
|
||||
messages.error(request, 'No Group selected')
|
||||
|
||||
|
||||
|
||||
meeting = get_object_or_404(Meeting, number=meeting_num)
|
||||
user = request.user
|
||||
person = user.get_profile()
|
||||
groups_session, groups_no_session = groups_by_session(user, meeting)
|
||||
proceedings_url = get_proceedings_url(meeting)
|
||||
|
||||
|
||||
# get the time proceedings were generated
|
||||
path = os.path.join(settings.SECR_PROCEEDINGS_DIR,meeting.number,'index.html')
|
||||
if os.path.exists(path):
|
||||
last_run = datetime.datetime.fromtimestamp(os.path.getmtime(path))
|
||||
else:
|
||||
last_run = None
|
||||
|
||||
|
||||
# initialize group form
|
||||
wgs = filter(lambda x: x.type_id in ('wg','ag','team'),groups_session)
|
||||
group_form = GroupSelectForm(choices=build_choices(wgs))
|
||||
|
||||
|
||||
# intialize IRTF form, only show if user is sec or irtf chair
|
||||
if has_role(user,'Secretariat') or person.role_set.filter(name__slug='chair',group__type__slug__in=('irtf','rg')):
|
||||
rgs = filter(lambda x: x.type_id == 'rg',groups_session)
|
||||
irtf_form = GroupSelectForm(choices=build_choices(rgs))
|
||||
else:
|
||||
irtf_form = None
|
||||
|
||||
|
||||
# initialize Training form, this select widget needs to have a session id, because it's
|
||||
# utilmately the session that we associate material with
|
||||
# NOTE: there are two ways to query for the groups we want, the later seems more specific
|
||||
|
@ -681,7 +694,7 @@ def select(request, meeting_num):
|
|||
training_form = GroupSelectForm(choices=choices)
|
||||
else:
|
||||
training_form = None
|
||||
|
||||
|
||||
# iniialize plenary form
|
||||
if has_role(user,['Secretariat','IETF Chair','IAB Chair']):
|
||||
choices = []
|
||||
|
@ -691,7 +704,7 @@ def select(request, meeting_num):
|
|||
plenary_form = GroupSelectForm(choices=choices)
|
||||
else:
|
||||
plenary_form = None
|
||||
|
||||
|
||||
# count PowerPoint files waiting to be converted
|
||||
if has_role(user,'Secretariat'):
|
||||
ppt = Document.objects.filter(session__meeting=meeting,type='slides',external_url__endswith='.ppt').exclude(states__slug='deleted')
|
||||
|
@ -699,7 +712,7 @@ def select(request, meeting_num):
|
|||
ppt_count = ppt.count() + pptx.count()
|
||||
else:
|
||||
ppt_count = 0
|
||||
|
||||
|
||||
return render_to_response('proceedings/select.html', {
|
||||
'group_form': group_form,
|
||||
'irtf_form': irtf_form,
|
||||
|
@ -709,7 +722,7 @@ def select(request, meeting_num):
|
|||
'last_run': last_run,
|
||||
'proceedings_url': proceedings_url,
|
||||
'ppt_count': ppt_count},
|
||||
RequestContext(request,{}),
|
||||
RequestContext(request,{}),
|
||||
)
|
||||
|
||||
def select_interim(request):
|
||||
|
@ -720,17 +733,17 @@ def select_interim(request):
|
|||
if request.method == 'POST':
|
||||
redirect_url = reverse('proceedings_interim', kwargs={'acronym':request.POST['group']})
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
|
||||
if request.user_is_secretariat:
|
||||
# initialize working groups form
|
||||
choices = build_choices(Group.objects.active_wgs())
|
||||
group_form = GroupSelectForm(choices=choices)
|
||||
|
||||
|
||||
# per Alexa, not supporting Interim IRTF meetings at this time
|
||||
# intialize IRTF form
|
||||
#choices = build_choices(Group.objects.filter(type='wg', state='active')
|
||||
#irtf_form = GroupSelectForm(choices=choices)
|
||||
|
||||
|
||||
else:
|
||||
# these forms aren't used for non-secretariat
|
||||
groups = get_my_groups(request.user)
|
||||
|
@ -738,11 +751,11 @@ def select_interim(request):
|
|||
group_form = GroupSelectForm(choices=choices)
|
||||
irtf_form = None
|
||||
training_form = None
|
||||
|
||||
|
||||
return render_to_response('proceedings/interim_select.html', {
|
||||
'group_form': group_form},
|
||||
#'irtf_form': irtf_form,
|
||||
RequestContext(request,{}),
|
||||
RequestContext(request,{}),
|
||||
)
|
||||
|
||||
@check_permissions
|
||||
|
@ -750,9 +763,9 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
|
|||
'''
|
||||
This view is the main view for uploading / re-ordering material for regular and interim
|
||||
meetings. There are two urls.py entries which map to this view. The acronym_id option is used
|
||||
most often for groups of regular and interim meetings. session_id is used for uploading
|
||||
most often for groups of regular and interim meetings. session_id is used for uploading
|
||||
material for Training sessions (where group is not a unique identifier). We could have used
|
||||
session_id all the time but this makes for an ugly URL which most of the time would be
|
||||
session_id all the time but this makes for an ugly URL which most of the time would be
|
||||
avoided by using acronym.
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_num)
|
||||
|
@ -767,7 +780,7 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
|
|||
session = get_object_or_404(Session, id=int(session_id))
|
||||
group = session.group
|
||||
session_name = session.name
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
button_text = request.POST.get('submit','')
|
||||
if button_text == 'Back':
|
||||
|
@ -776,12 +789,12 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
|
|||
else:
|
||||
url = reverse('proceedings_select', kwargs={'meeting_num':meeting_num})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
form = UnifiedUploadForm(request.POST,request.FILES)
|
||||
if form.is_valid():
|
||||
material_type = form.cleaned_data['material_type']
|
||||
slide_name = form.cleaned_data['slide_name']
|
||||
|
||||
|
||||
file = request.FILES[request.FILES.keys()[0]]
|
||||
file_ext = os.path.splitext(file.name)[1]
|
||||
|
||||
|
@ -790,21 +803,21 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
|
|||
filename = '%s-%s-%s' % (material_type.slug,meeting.number,group.acronym)
|
||||
elif meeting.type.slug == 'interim':
|
||||
filename = '%s-%s' % (material_type.slug,meeting.number)
|
||||
|
||||
|
||||
# NonSession material, use short name for shorter URLs
|
||||
if session.short:
|
||||
filename += "-%s" % session.short
|
||||
elif session_name:
|
||||
filename += "-%s" % slugify(session_name)
|
||||
# --------------------------------
|
||||
|
||||
|
||||
if material_type.slug == 'slides':
|
||||
order_num = get_next_order_num(session)
|
||||
slide_num = get_next_slide_num(session)
|
||||
filename += "-%s" % slide_num
|
||||
|
||||
|
||||
disk_filename = filename + file_ext
|
||||
|
||||
|
||||
# create the Document object, in the case of slides the name will always be unique
|
||||
# so you'll get a new object, agenda and minutes will reuse doc object if it exists
|
||||
doc, created = Document.objects.get_or_create(type=material_type,
|
||||
|
@ -827,13 +840,13 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
|
|||
doc.save()
|
||||
|
||||
DocAlias.objects.get_or_create(name=doc.name, document=doc)
|
||||
|
||||
|
||||
handle_upload_file(file,disk_filename,meeting,material_type.slug)
|
||||
|
||||
|
||||
# set Doc state
|
||||
state = State.objects.get(type=doc.type,slug='active')
|
||||
doc.set_state(state)
|
||||
|
||||
|
||||
# create session relationship, per Henrik we should associate documents to all sessions
|
||||
# for the current meeting (until tools support different materials for diff sessions)
|
||||
if sessions:
|
||||
|
@ -841,7 +854,7 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
|
|||
s.materials.add(doc)
|
||||
else:
|
||||
session.materials.add(doc)
|
||||
|
||||
|
||||
# create NewRevisionDocEvent instead of uploaded, per Ole
|
||||
NewRevisionDocEvent.objects.create(type='new_revision',
|
||||
by=request.user.get_profile(),
|
||||
|
@ -849,26 +862,26 @@ def upload_unified(request, meeting_num, acronym=None, session_id=None):
|
|||
rev=doc.rev,
|
||||
desc='New revision available',
|
||||
time=now)
|
||||
|
||||
|
||||
create_proceedings(meeting,group)
|
||||
messages.success(request,'File uploaded sucessfully')
|
||||
|
||||
|
||||
else:
|
||||
form = UnifiedUploadForm(initial={'meeting_id':meeting.id,'acronym':group.acronym,'material_type':'slides'})
|
||||
|
||||
|
||||
agenda,minutes,slides = get_material(session)
|
||||
|
||||
|
||||
# gather DocEvents
|
||||
# include deleted material to catch deleted doc events
|
||||
docs = session.materials.all()
|
||||
docevents = DocEvent.objects.filter(doc__in=docs)
|
||||
|
||||
|
||||
path = get_proceedings_path(meeting,group)
|
||||
if os.path.exists(path):
|
||||
proceedings_url = get_proceedings_url(meeting,group)
|
||||
else:
|
||||
else:
|
||||
proceedings_url = ''
|
||||
|
||||
|
||||
return render_to_response('proceedings/upload_unified.html', {
|
||||
'docevents': docevents,
|
||||
'meeting': meeting,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.forms.formsets import formset_factory
|
||||
from django.forms.models import inlineformset_factory, modelformset_factory
|
||||
|
|
|
@ -15,7 +15,7 @@ from ietf.ietfauth.decorators import has_role
|
|||
from ietf.utils.mail import send_mail
|
||||
from ietf.meeting.models import Meeting, Session, Constraint
|
||||
|
||||
from ietf.group.models import Group, Role
|
||||
from ietf.group.models import Group, Role
|
||||
from ietf.name.models import SessionStatusName, ConstraintName
|
||||
|
||||
from forms import *
|
||||
|
@ -50,7 +50,7 @@ def get_initial_session(sessions):
|
|||
initial = {}
|
||||
# even if there are three sessions requested, the old form has 2 in this field
|
||||
initial['num_session'] = sessions.count() if sessions.count() <= 2 else 2
|
||||
|
||||
|
||||
# accessing these foreign key fields throw errors if they are unset so we
|
||||
# need to catch these
|
||||
initial['length_session1'] = str(sessions[0].requested_duration.seconds)
|
||||
|
@ -65,7 +65,7 @@ def get_initial_session(sessions):
|
|||
initial['conflict3'] = ' '.join([ c.target.acronym for c in conflicts.filter(name__slug='conflic3') ])
|
||||
initial['comments'] = sessions[0].comments
|
||||
return initial
|
||||
|
||||
|
||||
def get_lock_message():
|
||||
'''
|
||||
Returns the message to display to non-secretariat users when the tool is locked.
|
||||
|
@ -84,6 +84,19 @@ def get_meeting():
|
|||
'''
|
||||
return Meeting.objects.filter(type='ietf').order_by('-date')[0]
|
||||
|
||||
def get_requester_text(person,group):
|
||||
'''
|
||||
This function takes a Person object and a Group object and returns the text to use in the
|
||||
session request notification email, ie. Joe Smith, a Chair of the ancp working group
|
||||
'''
|
||||
roles = group.role_set.filter(name__in=('chair','secr'),person=person)
|
||||
if roles:
|
||||
return '%s, a %s of the %s working group' % (person, roles[0].name, group.acronym)
|
||||
if group.parent.role_set.filter(name='ad',person=person):
|
||||
return '%s, a %s Area Director' % (person, group.parent.acronym.upper())
|
||||
if person.role_set.filter(name='secr',group__acronym='secretariat'):
|
||||
return '%s, on behalf of the %s working group' % (person, group.acronym)
|
||||
|
||||
def save_conflicts(group, meeting, conflicts, name):
|
||||
'''
|
||||
This function takes a Group, Meeting a string which is a list of Groups acronyms (conflicts),
|
||||
|
@ -93,7 +106,7 @@ def save_conflicts(group, meeting, conflicts, name):
|
|||
acronyms = conflicts.replace(',',' ').split()
|
||||
for acronym in acronyms:
|
||||
target = Group.objects.get(acronym=acronym)
|
||||
|
||||
|
||||
constraint = Constraint(source=group,
|
||||
target=target,
|
||||
meeting=meeting,
|
||||
|
@ -111,7 +124,7 @@ def send_notification(group,meeting,login,session,action):
|
|||
from_email = ('"IETF Meeting Session Request Tool"','session_request_developers@ietf.org')
|
||||
subject = '%s - New Meeting Session Request for IETF %s' % (group.acronym, meeting.number)
|
||||
template = 'sreq/session_request_notification.txt'
|
||||
|
||||
|
||||
# send email
|
||||
context = {}
|
||||
context['session'] = session
|
||||
|
@ -119,12 +132,13 @@ def send_notification(group,meeting,login,session,action):
|
|||
context['meeting'] = meeting
|
||||
context['login'] = login
|
||||
context['header'] = 'A new'
|
||||
|
||||
context['requester'] = get_requester_text(login,group)
|
||||
|
||||
# update overrides
|
||||
if action == 'update':
|
||||
subject = '%s - Update to a Meeting Session Request for IETF %s' % (group.acronym, meeting.number)
|
||||
context['header'] = 'An update to a'
|
||||
|
||||
|
||||
# if third session requested approval is required
|
||||
# change headers TO=ADs, CC=session-request, submitter and cochairs
|
||||
if session.get('length_session3',None):
|
||||
|
@ -164,11 +178,11 @@ def approve(request, acronym):
|
|||
meeting = get_meeting()
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
session = Session.objects.get(meeting=meeting,group=group,status='apprw')
|
||||
|
||||
|
||||
if has_role(request.user,'Secretariat') or group.parent.role_set.filter(name='ad',person=request.user.get_profile()):
|
||||
session.status = SessionStatusName.objects.get(slug='appr')
|
||||
session.save()
|
||||
|
||||
|
||||
messages.success(request, 'Third session approved')
|
||||
url = reverse('sessions_view', kwargs={'acronym':acronym})
|
||||
return HttpResponseRedirect(url)
|
||||
|
@ -184,33 +198,33 @@ def cancel(request, acronym):
|
|||
This view cancels a session request and sends a notification.
|
||||
To cancel, or withdraw the request set status = deleted.
|
||||
"canceled" status is used by the secretariat.
|
||||
|
||||
|
||||
NOTE: this function can also be called after a session has been
|
||||
scheduled during the period when the session request tool is
|
||||
scheduled during the period when the session request tool is
|
||||
reopened. In this case be sure to clear the timeslot assignment as well.
|
||||
'''
|
||||
meeting = get_meeting()
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
sessions = Session.objects.filter(meeting=meeting,group=group).order_by('id')
|
||||
login = request.user.get_profile()
|
||||
|
||||
|
||||
# delete conflicts
|
||||
Constraint.objects.filter(meeting=meeting,source=group).delete()
|
||||
|
||||
|
||||
# mark sessions as deleted
|
||||
for session in sessions:
|
||||
session.status_id = 'deleted'
|
||||
session.save()
|
||||
|
||||
|
||||
# clear timeslot assignment if already scheduled
|
||||
if session.timeslot_set.all():
|
||||
timeslot = session.timeslot_set.all()[0]
|
||||
timeslot.session = None
|
||||
timeslot.save()
|
||||
|
||||
|
||||
# log activity
|
||||
#add_session_activity(group,'Session was cancelled',meeting,user)
|
||||
|
||||
|
||||
# send notifitcation
|
||||
to_email = SESSION_REQUEST_EMAIL
|
||||
cc_list = get_cc_list(group, login)
|
||||
|
@ -220,7 +234,7 @@ def cancel(request, acronym):
|
|||
{'login':login,
|
||||
'group':group,
|
||||
'meeting':meeting}, cc=cc_list)
|
||||
|
||||
|
||||
messages.success(request, 'The %s Session Request has been canceled' % group.acronym)
|
||||
url = reverse('sessions')
|
||||
return HttpResponseRedirect(url)
|
||||
|
@ -237,23 +251,23 @@ def confirm(request, acronym):
|
|||
meeting = get_meeting()
|
||||
group = get_object_or_404(Group,acronym=acronym)
|
||||
login = request.user.get_profile()
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
# clear http session data
|
||||
del request.session['session_form']
|
||||
|
||||
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Cancel':
|
||||
messages.success(request, 'Session Request has been canceled')
|
||||
url = reverse('sessions')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# delete any existing session records with status = canceled or notmeet
|
||||
Session.objects.filter(group=group,meeting=meeting,status__in=('canceled','notmeet')).delete()
|
||||
|
||||
|
||||
# create new session records
|
||||
count = 0
|
||||
# lenth_session2 and length_session3 fields might be disabled by javascript and so
|
||||
# lenth_session2 and length_session3 fields might be disabled by javascript and so
|
||||
# wouldn't appear in form data
|
||||
for duration in (form.get('length_session1',None),form.get('length_session2',None),form.get('length_session3',None)):
|
||||
count += 1
|
||||
|
@ -268,30 +282,30 @@ def confirm(request, acronym):
|
|||
comments=form['comments'],
|
||||
status=SessionStatusName.objects.get(slug=slug))
|
||||
new_session.save()
|
||||
|
||||
|
||||
# write constraint records
|
||||
save_conflicts(group,meeting,form['conflict1'],'conflict')
|
||||
save_conflicts(group,meeting,form['conflict2'],'conflic2')
|
||||
save_conflicts(group,meeting,form['conflict3'],'conflic3')
|
||||
|
||||
|
||||
# deprecated in new schema
|
||||
# log activity
|
||||
#add_session_activity(group,'New session was requested',meeting,user)
|
||||
|
||||
|
||||
# clear not meeting
|
||||
Session.objects.filter(group=group,meeting=meeting,status='notmeet').delete()
|
||||
|
||||
|
||||
# send notification
|
||||
send_notification(group,meeting,login,form,'new')
|
||||
|
||||
|
||||
status_text = 'IETF Agenda to be scheduled'
|
||||
messages.success(request, 'Your request has been sent to %s' % status_text)
|
||||
url = reverse('sessions')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# GET logic
|
||||
session_conflicts = session_conflicts_as_string(group, meeting)
|
||||
|
||||
|
||||
return render_to_response('sreq/confirm.html', {
|
||||
'session': form,
|
||||
'group': group,
|
||||
|
@ -300,7 +314,7 @@ def confirm(request, acronym):
|
|||
)
|
||||
|
||||
@check_permissions
|
||||
def edit(request, acronym):
|
||||
def edit(request, acronym):
|
||||
'''
|
||||
This view allows the user to edit details of the session request
|
||||
'''
|
||||
|
@ -311,13 +325,13 @@ def edit(request, acronym):
|
|||
initial = get_initial_session(sessions)
|
||||
session_conflicts = session_conflicts_as_string(group, meeting)
|
||||
login = request.user.get_profile()
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Cancel':
|
||||
url = reverse('sessions_view', kwargs={'acronym':acronym})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
form = SessionForm(request.POST,initial=initial)
|
||||
if form.is_valid():
|
||||
if form.has_changed():
|
||||
|
@ -328,7 +342,7 @@ def edit(request, acronym):
|
|||
session = sessions[0]
|
||||
session.requested_duration = datetime.timedelta(0,int(form.cleaned_data['length_session1']))
|
||||
session.save()
|
||||
|
||||
|
||||
# session 2
|
||||
if 'length_session2' in form.changed_data:
|
||||
length_session2 = form.cleaned_data['length_session2']
|
||||
|
@ -350,7 +364,7 @@ def edit(request, acronym):
|
|||
session = sessions[1]
|
||||
session.requested_duration = duration
|
||||
session.save()
|
||||
|
||||
|
||||
# session 3
|
||||
if 'length_session3' in form.changed_data:
|
||||
length_session3 = form.cleaned_data['length_session3']
|
||||
|
@ -372,8 +386,8 @@ def edit(request, acronym):
|
|||
session = sessions[2]
|
||||
session.requested_duration = duration
|
||||
session.save()
|
||||
|
||||
|
||||
|
||||
|
||||
if 'attendees' in form.changed_data:
|
||||
sessions.update(attendees=form.cleaned_data['attendees'])
|
||||
if 'comments' in form.changed_data:
|
||||
|
@ -387,21 +401,21 @@ def edit(request, acronym):
|
|||
if 'conflict3' in form.changed_data:
|
||||
Constraint.objects.filter(meeting=meeting,source=group,name='conflic3').delete()
|
||||
save_conflicts(group,meeting,form.cleaned_data['conflict3'],'conflic3')
|
||||
|
||||
|
||||
# deprecated
|
||||
# log activity
|
||||
#add_session_activity(group,'Session Request was updated',meeting,user)
|
||||
|
||||
|
||||
# send notification
|
||||
send_notification(group,meeting,login,form.cleaned_data,'update')
|
||||
|
||||
|
||||
messages.success(request, 'Session Request updated')
|
||||
url = reverse('sessions_view', kwargs={'acronym':acronym})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
else:
|
||||
form = SessionForm(initial=initial)
|
||||
|
||||
|
||||
return render_to_response('sreq/edit.html', {
|
||||
'meeting': meeting,
|
||||
'form': form,
|
||||
|
@ -413,22 +427,22 @@ def edit(request, acronym):
|
|||
def main(request):
|
||||
'''
|
||||
Display list of groups the user has access to.
|
||||
|
||||
|
||||
Template variables
|
||||
form: a select box populated with unscheduled groups
|
||||
meeting: the current meeting
|
||||
scheduled_sessions:
|
||||
scheduled_sessions:
|
||||
'''
|
||||
# check for locked flag
|
||||
is_locked = check_app_locked()
|
||||
|
||||
|
||||
if is_locked and not has_role(request.user,'Secretariat'):
|
||||
message = get_lock_message()
|
||||
return render_to_response('sreq/locked.html', {
|
||||
'message': message},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
# TODO this is not currently used in the main template
|
||||
if request.method == 'POST':
|
||||
button_text = request.POST.get('submit', '')
|
||||
|
@ -438,15 +452,15 @@ def main(request):
|
|||
else:
|
||||
redirect_url = reverse('sessions_new', kwargs={'acronym':request.POST['group']})
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
|
||||
meeting = get_meeting()
|
||||
scheduled_groups,unscheduled_groups = groups_by_session(request.user, meeting)
|
||||
|
||||
|
||||
# load form select with unscheduled groups
|
||||
choices = zip([ g.pk for g in unscheduled_groups ],
|
||||
[ str(g) for g in unscheduled_groups ])
|
||||
form = GroupSelectForm(choices=choices)
|
||||
|
||||
|
||||
# add session status messages for use in template
|
||||
for group in scheduled_groups:
|
||||
sessions = group.session_set.filter(meeting=meeting)
|
||||
|
@ -454,12 +468,12 @@ def main(request):
|
|||
group.status_message = sessions[0].status
|
||||
else:
|
||||
group.status_message = 'First two sessions: %s, Third session: %s' % (sessions[0].status,sessions[2].status)
|
||||
|
||||
|
||||
# add not meeting indicators for use in template
|
||||
for group in unscheduled_groups:
|
||||
if group.session_set.filter(meeting=meeting,status='notmeet'):
|
||||
group.not_meeting = True
|
||||
|
||||
|
||||
return render_to_response('sreq/main.html', {
|
||||
'is_locked': is_locked,
|
||||
'form': form,
|
||||
|
@ -475,18 +489,18 @@ def new(request, acronym):
|
|||
This view gathers details for a new session request. The user proceeds to confirm()
|
||||
to create the request.
|
||||
'''
|
||||
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
meeting = get_meeting()
|
||||
session_conflicts = session_conflicts_as_string(group, meeting)
|
||||
user = request.user
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Cancel':
|
||||
url = reverse('sessions')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
form = SessionForm(request.POST)
|
||||
if form.is_valid():
|
||||
# check if request already exists for this group
|
||||
|
@ -494,13 +508,13 @@ def new(request, acronym):
|
|||
messages.warning(request, 'Sessions for working group %s have already been requested once.' % group.acronym)
|
||||
url = reverse('sessions')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# save in user session
|
||||
request.session['session_form'] = form.data
|
||||
|
||||
|
||||
url = reverse('sessions_confirm',kwargs={'acronym':acronym})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# the "previous" querystring causes the form to be returned
|
||||
# pre-populated with data from last meeeting's session request
|
||||
elif request.method == 'GET' and request.GET.has_key('previous'):
|
||||
|
@ -513,10 +527,10 @@ def new(request, acronym):
|
|||
|
||||
initial = get_initial_session(previous_sessions)
|
||||
form = SessionForm(initial=initial)
|
||||
|
||||
|
||||
else:
|
||||
form = SessionForm()
|
||||
|
||||
|
||||
return render_to_response('sreq/new.html', {
|
||||
'meeting': meeting,
|
||||
'form': form,
|
||||
|
@ -536,7 +550,7 @@ def no_session(request, acronym):
|
|||
meeting = get_meeting()
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
login = request.user.get_profile()
|
||||
|
||||
|
||||
# delete canceled record if there is one
|
||||
Session.objects.filter(group=group,meeting=meeting,status='canceled').delete()
|
||||
|
||||
|
@ -545,7 +559,7 @@ def no_session(request, acronym):
|
|||
messages.info(request, 'The group %s is already marked as not meeting' % group.acronym)
|
||||
url = reverse('sessions')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
session = Session(group=group,
|
||||
meeting=meeting,
|
||||
requested=datetime.datetime.now(),
|
||||
|
@ -553,7 +567,7 @@ def no_session(request, acronym):
|
|||
requested_duration=0,
|
||||
status=SessionStatusName.objects.get(slug='notmeet'))
|
||||
session.save()
|
||||
|
||||
|
||||
# send notification
|
||||
to_email = SESSION_REQUEST_EMAIL
|
||||
cc_list = get_cc_list(group, login)
|
||||
|
@ -563,12 +577,12 @@ def no_session(request, acronym):
|
|||
{'login':login,
|
||||
'group':group,
|
||||
'meeting':meeting}, cc=cc_list)
|
||||
|
||||
|
||||
# deprecated?
|
||||
# log activity
|
||||
#text = 'A message was sent to notify not having a session at IETF %d' % meeting.meeting_num
|
||||
#add_session_activity(group,text,meeting,request.person)
|
||||
|
||||
|
||||
# redirect
|
||||
messages.success(request, 'A message was sent to notify not having a session at IETF %s' % meeting.number)
|
||||
url = reverse('sessions')
|
||||
|
@ -580,32 +594,32 @@ def tool_status(request):
|
|||
This view handles locking and unlocking of the tool to the public.
|
||||
'''
|
||||
is_locked = check_app_locked()
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Done':
|
||||
url = reverse('sessions')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
form = ToolStatusForm(request.POST)
|
||||
|
||||
|
||||
if button_text == 'Lock':
|
||||
if form.is_valid():
|
||||
f = open(LOCKFILE,'w')
|
||||
f.write(form.cleaned_data['message'])
|
||||
f.close()
|
||||
|
||||
|
||||
messages.success(request, 'Session Request Tool is now Locked')
|
||||
url = reverse('sessions')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
elif button_text == 'Unlock':
|
||||
os.remove(LOCKFILE)
|
||||
|
||||
|
||||
messages.success(request, 'Session Request Tool is now Unlocked')
|
||||
url = reverse('sessions')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
else:
|
||||
if is_locked:
|
||||
message = get_lock_message()
|
||||
|
@ -613,7 +627,7 @@ def tool_status(request):
|
|||
form = ToolStatusForm(initial=initial)
|
||||
else:
|
||||
form = ToolStatusForm()
|
||||
|
||||
|
||||
return render_to_response('sreq/tool_status.html', {
|
||||
'is_locked': is_locked,
|
||||
'form': form},
|
||||
|
@ -627,12 +641,12 @@ def view(request, acronym):
|
|||
meeting = get_meeting()
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
sessions = Session.objects.filter(~Q(status__in=('canceled','notmeet','deleted')),meeting=meeting,group=group).order_by('id')
|
||||
|
||||
|
||||
# if there are no session requests yet, redirect to new session request page
|
||||
if not sessions:
|
||||
redirect_url = reverse('sessions_new', kwargs={'acronym':acronym})
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
|
||||
# TODO simulate activity records
|
||||
activities = [{'act_date':sessions[0].requested.strftime('%b %d, %Y'),
|
||||
'act_time':sessions[0].requested.strftime('%H:%M:%S'),
|
||||
|
@ -643,20 +657,20 @@ def view(request, acronym):
|
|||
'act_time':sessions[0].scheduled.strftime('%H:%M:%S'),
|
||||
'activity':'Session was scheduled',
|
||||
'act_by':'Secretariat'})
|
||||
|
||||
|
||||
# other groups that list this group in their conflicts
|
||||
session_conflicts = session_conflicts_as_string(group, meeting)
|
||||
show_approve_button = False
|
||||
|
||||
|
||||
# if sessions include a 3rd session waiting approval and the user is a secretariat or AD of the group
|
||||
# display approve button
|
||||
if sessions.filter(status='apprw'):
|
||||
if has_role(request.user,'Secretariat') or group.parent.role_set.filter(name='ad',person=request.user.get_profile()):
|
||||
show_approve_button = True
|
||||
|
||||
|
||||
# build session dictionary (like querydict from new session request form) for use in template
|
||||
session = get_initial_session(sessions)
|
||||
|
||||
|
||||
return render_to_response('sreq/view.html', {
|
||||
'session': session,
|
||||
'activities': activities,
|
||||
|
|
|
@ -54,9 +54,9 @@ def get_doc_list(agenda):
|
|||
docs = []
|
||||
for key in sorted(agenda['docs']):
|
||||
docs.extend(agenda['docs'][key])
|
||||
|
||||
|
||||
return [x['obj'] for x in docs]
|
||||
|
||||
|
||||
def get_doc_writeup(doc):
|
||||
'''
|
||||
This function takes a Document object and returns the ballot writeup for display
|
||||
|
@ -72,7 +72,7 @@ def get_doc_writeup(doc):
|
|||
path = os.path.join(doc.get_file_path(),doc.filename_with_rev())
|
||||
writeup = get_document_content(doc.name,path,split=False,markup=False)
|
||||
return writeup
|
||||
|
||||
|
||||
def get_last_telechat_date():
|
||||
'''
|
||||
This function returns the date of the last telechat
|
||||
|
@ -80,37 +80,37 @@ def get_last_telechat_date():
|
|||
'''
|
||||
return TelechatDate.objects.filter(date__lt=datetime.date.today()).order_by('-date')[0].date
|
||||
#return '2011-11-01' # uncomment for testing
|
||||
|
||||
|
||||
def get_next_telechat_date():
|
||||
'''
|
||||
This function returns the date of the next telechat
|
||||
'''
|
||||
return TelechatDate.objects.filter(date__gte=datetime.date.today()).order_by('date')[0].date
|
||||
|
||||
|
||||
def get_section_header(file,agenda):
|
||||
'''
|
||||
This function takes a filename and an agenda dictionary and returns the
|
||||
This function takes a filename and an agenda dictionary and returns the
|
||||
agenda section header as a string for use in the doc template
|
||||
'''
|
||||
h1 = {'2':'Protocol Actions','3':'Document Actions','4':'Working Group Actions'}
|
||||
h2a = {'1':'WG Submissions','2':'Individual Submissions'}
|
||||
h2b = {'1':'WG Submissions','2':'Individual Submissions via AD','3':'IRTF and Independent Submission Stream Documents'}
|
||||
h2a = {'1':'WG Submissions','2':'Individual Submissions','3':'Status Changes'}
|
||||
h2b = {'1':'WG Submissions','2':'Individual Submissions via AD','3':'Status Changes','4':'IRTF and Independent Submission Stream Documents'}
|
||||
h2c = {'1':'WG Creation','2':'WG Chartering'}
|
||||
h3a = {'1':'New Item','2':'Returning Item','3':'For Action'}
|
||||
h3b = {'1':'Proposed for IETF Review','2':'Proposed for Approval'}
|
||||
h3c = {'1':'Under Evaluation for IETF Review','2':'Proposed for Approval'}
|
||||
|
||||
|
||||
# Robert updated _agenda_data to return Document objects instead of the ID wrapper
|
||||
#doc = InternetDraft.objects.get(filename=file)
|
||||
doc = Document.objects.get(name=file)
|
||||
|
||||
|
||||
test = {'obj':doc}
|
||||
for k,v in agenda['docs'].iteritems():
|
||||
if test in v:
|
||||
section = k
|
||||
count = '%s of %s' % (v.index(test) + 1, len(v))
|
||||
break
|
||||
|
||||
|
||||
header = [ '%s %s' % (section[1], h1[section[1]]) ]
|
||||
if section[1] == '2':
|
||||
header.append('%s.%s %s' % (section[1], section[2], h2a[section[2]]))
|
||||
|
@ -126,7 +126,7 @@ def get_section_header(file,agenda):
|
|||
else:
|
||||
header.append('%s.%s.%s %s' % (section[1], section[2], section[3], h3a[section[3]]))
|
||||
header.append(count)
|
||||
|
||||
|
||||
return header
|
||||
|
||||
def get_first_doc(agenda):
|
||||
|
@ -136,28 +136,28 @@ def get_first_doc(agenda):
|
|||
for k,v in sorted(agenda['docs'].iteritems()):
|
||||
if v:
|
||||
return v[0]['obj']
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# View Functions
|
||||
# -------------------------------------------------
|
||||
def bash(request, date):
|
||||
|
||||
|
||||
agenda = _agenda_data(request, date=date)
|
||||
|
||||
|
||||
return render_to_response('telechat/bash.html', {
|
||||
'agenda': agenda,
|
||||
'date': date},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def doc(request, date):
|
||||
'''
|
||||
This view redirects to doc_detail using the first document in the agenda or
|
||||
displays the message "No Documents"
|
||||
'''
|
||||
|
||||
|
||||
agenda = _agenda_data(request, date=date)
|
||||
doc = get_first_doc(agenda)
|
||||
if doc:
|
||||
|
@ -170,32 +170,29 @@ def doc(request, date):
|
|||
'document': None},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def doc_detail(request, date, name):
|
||||
'''
|
||||
This view displays the ballot information for the document, and lets the user make
|
||||
changes to ballot positions and document state.
|
||||
'''
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
|
||||
# As of Datatracker v4.32, Conflict Review (conflrev) Document Types can
|
||||
# be added to the Telechat agenda. We need to check the document type here
|
||||
# and set the state_type for use later in the view
|
||||
|
||||
# As of Datatracker v4.32, Conflict Review (conflrev) Document Types can
|
||||
# be added to the Telechat agenda. If Document.type_id == draft use draft-iesg
|
||||
# for state type
|
||||
state_type = doc.type_id
|
||||
if doc.type_id == 'draft':
|
||||
state_type = 'draft-iesg'
|
||||
elif doc.type_id == 'conflrev':
|
||||
state_type = 'conflrev'
|
||||
elif doc.type_id == 'charter':
|
||||
state_type = 'charter'
|
||||
|
||||
|
||||
started_process = doc.latest_event(type="started_iesg_process")
|
||||
login = request.user.get_profile()
|
||||
|
||||
|
||||
if doc.active_ballot():
|
||||
ballots = doc.active_ballot().active_ad_positions() # returns dict of ad:ballotpositiondocevent
|
||||
else:
|
||||
ballots = []
|
||||
|
||||
|
||||
# setup form initials
|
||||
initial_ballot = []
|
||||
open_positions = 0
|
||||
|
@ -205,19 +202,19 @@ def doc_detail(request, date, name):
|
|||
open_positions += 1
|
||||
elif not ballots[key]:
|
||||
open_positions += 1
|
||||
|
||||
|
||||
tags = doc.tags.filter(slug__in=TELECHAT_TAGS)
|
||||
tag = tags[0].pk if tags else None
|
||||
|
||||
|
||||
writeup = get_doc_writeup(doc)
|
||||
|
||||
|
||||
initial_state = {'state':doc.get_state(state_type).pk,
|
||||
'substate':tag}
|
||||
|
||||
|
||||
BallotFormset = formset_factory(BallotForm, extra=0)
|
||||
agenda = _agenda_data(request, date=date)
|
||||
header = get_section_header(name,agenda) if name else ''
|
||||
|
||||
|
||||
# nav button logic
|
||||
doc_list = get_doc_list(agenda)
|
||||
nav_start = nav_end = False
|
||||
|
@ -225,10 +222,10 @@ def doc_detail(request, date, name):
|
|||
nav_start = True
|
||||
if doc == doc_list[-1]:
|
||||
nav_end = True
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
button_text = request.POST.get('submit', '')
|
||||
|
||||
|
||||
# logic from doc/views_ballot.py EditPositionRedesign
|
||||
if button_text == 'update_ballot':
|
||||
formset = BallotFormset(request.POST, initial=initial_ballot)
|
||||
|
@ -250,12 +247,12 @@ def doc_detail(request, date, name):
|
|||
pos.desc = '[Ballot Position Update] Position for %s has been changed to %s by %s' % (ad.name, pos.pos.name, login.name)
|
||||
pos.save()
|
||||
has_changed = True
|
||||
|
||||
|
||||
if has_changed:
|
||||
messages.success(request,'Ballot position changed.')
|
||||
url = reverse('telechat_doc_detail', kwargs={'date':date,'name':name})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# logic from doc/views_draft.py change_state
|
||||
elif button_text == 'update_state':
|
||||
formset = BallotFormset(initial=initial_ballot)
|
||||
|
@ -269,45 +266,45 @@ def doc_detail(request, date, name):
|
|||
# as if IESG tags are a substate
|
||||
prev_tag = doc.tags.filter(slug__in=(TELECHAT_TAGS))
|
||||
prev_tag = prev_tag[0] if prev_tag else None
|
||||
|
||||
|
||||
#if state != prev or tag != prev_tag:
|
||||
if state_form.changed_data:
|
||||
save_document_in_history(doc)
|
||||
old_description = doc.friendly_state()
|
||||
|
||||
|
||||
if 'state' in state_form.changed_data:
|
||||
doc.set_state(state)
|
||||
|
||||
|
||||
if 'substate' in state_form.changed_data:
|
||||
if prev_tag:
|
||||
doc.tags.remove(prev_tag)
|
||||
if tag:
|
||||
doc.tags.add(tag)
|
||||
|
||||
|
||||
new_description = doc.friendly_state()
|
||||
e = log_state_changed(request, doc, login, new_description, old_description)
|
||||
doc.time = e.time
|
||||
doc.save()
|
||||
|
||||
|
||||
email_state_changed(request, doc, e.desc)
|
||||
email_ad(request, doc, doc.ad, login, e.desc)
|
||||
|
||||
if state.slug == "lc-req":
|
||||
request_last_call(request, doc)
|
||||
|
||||
|
||||
messages.success(request,'Document state updated')
|
||||
url = reverse('telechat_doc_detail', kwargs={'date':date,'name':name})
|
||||
return HttpResponseRedirect(url)
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
formset = BallotFormset(initial=initial_ballot)
|
||||
state_form = ChangeStateForm(initial=initial_state)
|
||||
|
||||
|
||||
# if this is a conflict review document add referenced document
|
||||
if doc.type_id == 'conflrev':
|
||||
conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target.document
|
||||
conflictdoc = doc.relateddocument_set.get(relationship__slug='conflrev').target.document
|
||||
else:
|
||||
conflictdoc = None
|
||||
|
||||
|
||||
return render_to_response('telechat/doc.html', {
|
||||
'date': date,
|
||||
'document': doc,
|
||||
|
@ -322,10 +319,10 @@ def doc_detail(request, date, name):
|
|||
'nav_end': nav_end},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def doc_navigate(request, date, name, nav):
|
||||
'''
|
||||
This view takes three arguments:
|
||||
This view takes three arguments:
|
||||
date - the date of the Telechat
|
||||
name - the name of the current document being displayed
|
||||
nav - [next|previous] which direction the user wants to navigate in the list of docs
|
||||
|
@ -334,22 +331,22 @@ def doc_navigate(request, date, name, nav):
|
|||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
agenda = _agenda_data(request, date=date)
|
||||
target = name
|
||||
|
||||
|
||||
docs = get_doc_list(agenda)
|
||||
index = docs.index(doc)
|
||||
|
||||
|
||||
if nav == 'next' and index < len(docs) - 1:
|
||||
target = docs[index + 1].name
|
||||
elif nav == 'previous' and index != 0:
|
||||
target = docs[index - 1].name
|
||||
|
||||
|
||||
url = reverse('telechat_doc_detail', kwargs={'date':date,'name':target})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
def main(request):
|
||||
'''
|
||||
The is the main view where the user selects an existing telechat or creates a new one.
|
||||
|
||||
|
||||
NOTES ON EXTERNAL HELPER FUNCTIONS:
|
||||
_agenda_data(): returns dictionary of agenda sections
|
||||
get_ballot(name): returns a BallotWrapper and RfcWrapper or IdWrapper
|
||||
|
@ -358,7 +355,7 @@ def main(request):
|
|||
date=request.POST['date']
|
||||
url = reverse('telechat_doc', kwargs={'date':date})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
choices = [ (d.date.strftime('%Y-%m-%d'),
|
||||
d.date.strftime('%Y-%m-%d')) for d in TelechatDate.objects.all() ]
|
||||
next_telechat = get_next_telechat_date().strftime('%Y-%m-%d')
|
||||
|
@ -368,22 +365,22 @@ def main(request):
|
|||
'form': form},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def management(request, date):
|
||||
'''
|
||||
This view displays management issues and lets the user update the status
|
||||
'''
|
||||
|
||||
|
||||
agenda = _agenda_data(request, date=date)
|
||||
issues = TelechatAgendaItem.objects.filter(type=3).order_by('id')
|
||||
|
||||
|
||||
return render_to_response('telechat/management.html', {
|
||||
'agenda': agenda,
|
||||
'date': date,
|
||||
'issues': issues},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def minutes(request, date):
|
||||
'''
|
||||
This view shows a list of documents that were approved since the last telechat
|
||||
|
@ -398,9 +395,9 @@ def minutes(request, date):
|
|||
docs = [ e.doc for e in events ]
|
||||
pa_docs = [ d for d in docs if d.intended_std_level.slug not in ('inf','exp','hist') ]
|
||||
da_docs = [ d for d in docs if d.intended_std_level.slug in ('inf','exp','hist') ]
|
||||
|
||||
|
||||
agenda = _agenda_data(request, date=date)
|
||||
|
||||
|
||||
return render_to_response('telechat/minutes.html', {
|
||||
'agenda': agenda,
|
||||
'date': date,
|
||||
|
@ -409,7 +406,7 @@ def minutes(request, date):
|
|||
'da_docs': da_docs},
|
||||
RequestContext(request, {}),
|
||||
)
|
||||
|
||||
|
||||
def new(request):
|
||||
'''
|
||||
This view creates a new telechat agenda and redirects to the default view
|
||||
|
@ -418,17 +415,17 @@ def new(request):
|
|||
date = request.POST['date']
|
||||
# create legacy telechat record
|
||||
Telechat.objects.create(telechat_date=date)
|
||||
|
||||
|
||||
messages.success(request,'New Telechat Agenda created')
|
||||
url = reverse('telechat_doc', kwargs={'date':date,'name':name})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
def roll_call(request, date):
|
||||
|
||||
|
||||
agenda = _agenda_data(request, date=date)
|
||||
ads = Person.objects.filter(role__name='ad')
|
||||
sorted_ads = sorted(ads, key = lambda a: a.name_parts()[3])
|
||||
|
||||
|
||||
return render_to_response('telechat/roll_call.html', {
|
||||
'agenda': agenda,
|
||||
'date': date,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
» Announcement
|
||||
{% endblock %}
|
||||
|
||||
|
@ -14,9 +14,9 @@
|
|||
|
||||
<div class="module">
|
||||
<h2>Announcement</h2>
|
||||
|
||||
|
||||
<form action="" method="POST">
|
||||
|
||||
|
||||
<pre id="announce-confirm">
|
||||
To: {{ to }}
|
||||
From: {{ message.frm }}
|
||||
|
@ -27,10 +27,11 @@ Subject: {{ message.subject }}
|
|||
|
||||
{{ message.body }}
|
||||
</pre>
|
||||
|
||||
|
||||
<div class="button-group">
|
||||
<ul id="announcement-button-list">
|
||||
<li><button type="submit" name="submit" value="Send">Send</button></li>
|
||||
<li><button onclick="history.go(-1);return false">Back</button></li>
|
||||
<li><button type="submit" name="submit" value="Cancel">Cancel</button></li>
|
||||
</ul>
|
||||
</div> <!-- button-group -->
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
» <a href="../../">Drafts</a>
|
||||
» <a href="../">{{ draft.name }}</a>
|
||||
» Edit
|
||||
» Edit
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -36,12 +36,13 @@
|
|||
<tr><th>Abstract:</th><td>{{ form.abstract.errors }}{{ form.abstract }}</td></tr>
|
||||
<tr><th>Expiration Date:</th><td>{{ draft.expires|date:"M. d, Y" }}</td></tr>
|
||||
<tr><th>Intended Std Level:</th><td>{{ form.intended_std_level.errors }}{{ form.intended_std_level }}</td></tr>
|
||||
<tr><th>Standardization Level:</th><td>{{ form.std_level.errors }}{{ form.std_level }}</td></tr>
|
||||
<tr><th>RFC Number:</th><td>{{ draft.rfc_number }}</td></tr>
|
||||
<tr><th>Comments:</th><td>{{ form.internal_comments.errors }}{{ form.internal_comments }}</td></tr>
|
||||
<tr><th>Replaced by:</th><td>{{ form.replaced_by.errors }}{{ form.replaced_by }}</td></tr>
|
||||
<tr><th>Last Modified Date:</th><td>{{ draft.time }}</td></tr>
|
||||
</table>
|
||||
|
||||
|
||||
<div class="button-group">
|
||||
<ul>
|
||||
<li><button type="submit" name="submit" value="Save">Save</button></li>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
» <a href="../">Drafts</a>
|
||||
» {{ draft.name }}
|
||||
{% endblock %}
|
||||
|
@ -51,6 +51,7 @@
|
|||
<tr><td>Abstract:</td><td><a href="{% url drafts_abstract id=draft.name %}">Click here to view the abstract</td></tr>
|
||||
<tr><td>Expiration Date:</td><td>{{ draft.expires|date:"M. d, Y" }}</td></tr>
|
||||
<tr><td>Intended Status:</td><td>{{ draft.intended_std_level|default_if_none:"" }}</td></tr>
|
||||
<tr><td>Standardization Level:</td><td>{{ draft.std_level|default_if_none:"" }}</td></tr>
|
||||
<tr><td>RFC Number:</td><td>{{ draft.rfc_number|default_if_none:"" }}</td></tr>
|
||||
<tr><td>Comments:</td><td>{{ draft.internal_comments|default_if_none:"" }}</td></tr>
|
||||
<tr><td>Last Modified Date:</td><td>{{ draft.time }}</td></tr>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<li><a href="http://www.ietf.org/wg/request-tool-instructions.html" target="_blank">Instructions</a>.</li>
|
||||
<li><a href="{# {% url proceedings %} #}">IETF Meeting Materials Management Tool</a>.</li>
|
||||
<li><a href="{% url proceedings %}">IETF Meeting Materials Management Tool</a>.</li>
|
||||
<li>If you require assistance in using this tool, or wish to report a bug, then please send a message to <a href="mailto:ietf-action@ietf.org">ietf-action@ietf.org</a>.</li>
|
||||
<li>To submit your request via email, please send your request to <a href="mailto:agenda@ietf.org">agenda@ietf.org</a>.</li>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% block title %}
|
||||
IPR Admin Notify Page
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block extrahead %}
|
||||
<link rel="stylesheet" href="{{ SECR_STATIC_URL }}css/ipr.css" type="text/css" media="screen" charset="utf-8" />
|
||||
{% endblock %}
|
||||
|
@ -33,26 +33,28 @@ IPR Admin Notify Page
|
|||
{% ifequal page_id 'detail_notify' %}
|
||||
<form action="" method="post">
|
||||
<input type="hidden" name="command" value="do_send_update_notification">
|
||||
<input type="hidden" name="ipr_id" value="{{ ipr_id }}">
|
||||
<input type="hidden" name="ipr_id" value="{{ ipr_id }}">
|
||||
<h4>Notification to the submitter of IPR that's being updated</h4>
|
||||
<textarea name="notify_original_submitter" rows=25 cols=74>
|
||||
To: {{ to_email }}
|
||||
From: IETF Secretariat <ietf-ipr@ietf.org>
|
||||
Subject: IPR update notification
|
||||
|
||||
Subject: IPR update notification
|
||||
|
||||
Dear {{ to_name }}:
|
||||
|
||||
We have just received a request to update the IPR disclosure,
|
||||
{{ updated_document_title }} (https://datatracker.ietf.org/public/ipr_detail_show.cgi?ipr_id={{ updated_ipr_id }}),
|
||||
which was submitted by you on {{ orig_submitted_date }}. The name and email
|
||||
We have just received a request to update the IPR disclosure,
|
||||
{{ updated_document_title }} (https://datatracker.ietf.org/public/ipr_detail_show.cgi?ipr_id={{ updated_ipr_id }}),
|
||||
which was submitted by you on {{ orig_submitted_date }}. The name and email
|
||||
address of the person who submitted the update to your IPR disclosure are:
|
||||
{{ submitter_name }}, {{ submitter_email }}.
|
||||
|
||||
If the person who submitted the update to your IPR disclosure is *not*
|
||||
authorized to do so, then please let us know by sending a message to
|
||||
ietf-action@ietf.org within the next seven days. Otherwise, we will post
|
||||
the new IPR disclosure and mark it as an update to the IPR disclosure
|
||||
that you submitted.
|
||||
We will not post this update unless we receive positive confirmation from you that
|
||||
{{ submitter_name }} is authorized to update your disclosure.
|
||||
Please send confirmation to ietf-ipr@ietf.org.
|
||||
|
||||
If we do not hear from you within 30 days, we will inform {{ submitter_name }}
|
||||
that we were not able to secure approval for posting and that we are therefore rejecting
|
||||
the update until we can be assured it is authorized.
|
||||
|
||||
Thank you
|
||||
|
||||
|
@ -60,7 +62,7 @@ IETF Secretariat
|
|||
|
||||
</textarea>
|
||||
<br><br>
|
||||
<input type="submit" value=" Send notifications NOW ">
|
||||
<input type="submit" value=" Send notifications NOW ">
|
||||
</form>
|
||||
<br><br>
|
||||
{% endifequal %}
|
||||
|
@ -87,7 +89,7 @@ IETF Secretariat
|
|||
<input type="hidden" name="ipr_id" value="{{ ipr_id }}">
|
||||
<h4> Notification to the submitter of IPR that's being updated</h4>
|
||||
<h4> Please change the DATE and UPDATE NAME<h4>
|
||||
|
||||
|
||||
<textarea name="notify_submitter" rows=25 cols=80>
|
||||
{{ submitter_text }}
|
||||
</textarea>
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
<hr />
|
||||
<h3><a name="intro" id="intro"></a>1. Introduction </h3>
|
||||
<ul>
|
||||
<li><a href="acknowledgement.html">1.1. Acknowledgements</a></li>
|
||||
<li><a href="overview.html">1.2. IETF Overview</a></li>
|
||||
<li><a href="progress-report.html">1.3. Progress Report</a></li>
|
||||
<li><a href="agenda.html">1.4. Agenda</a></li>
|
||||
<li><a href="attendee.html">1.5. Attendees</a></li>
|
||||
<li><a href="acknowledgement.html">1.1 Acknowledgements</a></li>
|
||||
<li><a href="overview.html">1.2 IETF Overview</a></li>
|
||||
<li><a href="progress-report.html">1.3 Progress Report</a></li>
|
||||
<li><a href="agenda.html">1.4 Agenda</a></li>
|
||||
<li><a href="attendee.html">1.5 Attendees</a></li>
|
||||
</ul>
|
||||
<hr />
|
||||
<h3><a name="wgreports" id="wgreports"></a>2. Area, Working Group and BoF Reports </h3>
|
||||
|
@ -26,8 +26,8 @@
|
|||
<hr />
|
||||
<h3><a name="plenary" id="plenary"></a>3. Plenaries</h3>
|
||||
<ul>
|
||||
<li><a href ="administrative-plenary.html">3.1. Administrative Plenary</a></li>
|
||||
<li><a href="technical-plenary.html">3.2. Technical Plenary</a></li>
|
||||
<li><a href ="administrative-plenary.html">3.1 Administrative Plenary</a></li>
|
||||
<li><a href="technical-plenary.html">3.2 Technical Plenary</a></li>
|
||||
</ul>
|
||||
<hr />
|
||||
<h3><a name="training" id="training">4. Training</h3>
|
||||
|
@ -39,9 +39,18 @@
|
|||
<hr />
|
||||
<h3><a name="irtf" id="irtf">5. Internet Research Task Force </h3>
|
||||
<ul>
|
||||
<li><a href="irtf.html">5.1. IRTF introduction</a></li>
|
||||
<li><a href="rg_irtf.html">5.2. Research Groups</a></li>
|
||||
<li><a href="irtf.html">5.1 IRTF introduction</a></li>
|
||||
<li><a href="rg_irtf.html">5.2 Research Groups</a></li>
|
||||
</ul>
|
||||
<hr />
|
||||
{% if extras %}
|
||||
<h3>6. Other</h3>
|
||||
<ul>
|
||||
{% for group in extras %}
|
||||
<li><a href="{{ group.acronym }}.html">6.{{ forloop.counter }} {{ group.name }}</a><
|
||||
/li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -42,7 +42,9 @@ and end with
|
|||
{% endif %}
|
||||
</h3>
|
||||
|
||||
{% if group.type.slug == "wg" %}
|
||||
<p>Additional information is available at <a href="http://tools.ietf.org/wg/{{ group.acronym }}">tools.ietf.org/wg/{{ group.acronym }}</a>
|
||||
{% endif %}
|
||||
|
||||
<table width="60%" border="0" cellspacing="2" cellpadding="2">
|
||||
<tr><td bgcolor="#EEEEFF">
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
|
||||
<div class="inline-related">
|
||||
<h2>Upload Materials</h2>
|
||||
<form enctype="multipart/form-data" action="." method="post">
|
||||
<form id="upload_materials_form" enctype="multipart/form-data" action="." method="post">
|
||||
<table class="center" id="proceedings-upload-table">
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Dear {{ group.parent }} Director(s):
|
||||
|
||||
{{ header }} meeting session request has just been
|
||||
submitted by {{ login }}, a working group chair of {{ group.name }}.
|
||||
submitted by {{ requester }}.
|
||||
The third session requires your approval.
|
||||
|
||||
To approve the session go to the session request view here:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% load ams_filters %}
|
||||
|
||||
A request to cancel a meeting session has just been submitted by {{ login|smart_login }} {{ group.acronym }} working group.
|
||||
A request to cancel a meeting session has just been submitted by {{ requester }}.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% load ams_filters %}
|
||||
|
||||
{{ header }} meeting session request has just been submitted by {{ login|smart_login }} {{ group.acronym }} working group.
|
||||
{{ header }} meeting session request has just been submitted by {{ requester }}.
|
||||
|
||||
{% include "includes/session_info.txt" %}
|
||||
|
|
|
@ -34,6 +34,12 @@
|
|||
<li>2.2.3 For Action</li>
|
||||
{% with agenda.docs.s223 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
|
||||
<li></li>
|
||||
<li>2.3 Status Changes</li>
|
||||
<li>2.3.1 New Item</li>
|
||||
{% with agenda.docs.s231 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
|
||||
<li>2.3.2 Returning Item</li>
|
||||
{% with agenda.docs.232 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
|
||||
<li></li>
|
||||
<li class="level1">3 Document Actions</li>
|
||||
<li>3.1 WG Submissions</li>
|
||||
<li>3.1.1 New Item</li>
|
||||
|
@ -51,13 +57,19 @@
|
|||
<li>3.2.3 For Action</li>
|
||||
{% with agenda.docs.s323 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
|
||||
<li></li>
|
||||
<li>3.3 Independent Submissions via RFC Editor</li>
|
||||
<li>3.3 Status Changes</li>
|
||||
<li>3.3.1 New Item</li>
|
||||
{% with agenda.docs.s331 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
|
||||
<li>3.3.2 Returning Item</li>
|
||||
{% with agenda.docs.s332 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
|
||||
<li>3.3.3 For Action</li>
|
||||
{% with agenda.docs.s333 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
|
||||
<li></li>
|
||||
<li>3.4 Independent Submissions via RFC Editor</li>
|
||||
<li>3.4.1 New Item</li>
|
||||
{% with agenda.docs.s341 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
|
||||
<li>3.4.2 Returning Item</li>
|
||||
{% with agenda.docs.s342 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
|
||||
<li>3.4.3 For Action</li>
|
||||
{% with agenda.docs.s343 as section_docs %}{% include "telechat/doc_template.html" %}{% endwith %}
|
||||
<li></li>
|
||||
<li class="level1">4 Working Group Actions</li>
|
||||
<li>4.1 WG Creation</li>
|
||||
|
|
|
@ -8,8 +8,11 @@ import itertools
|
|||
import os
|
||||
|
||||
def current_nomcom():
|
||||
qs = Group.objects.filter(acronym__startswith='nomcom',state__name="Active").order_by('-time')
|
||||
return qs[0]
|
||||
qs = Group.objects.filter(acronym__startswith='nomcom',state__slug="active").order_by('-time')
|
||||
if qs.count():
|
||||
return qs[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_charter_text(group):
|
||||
'''
|
||||
|
@ -20,9 +23,9 @@ def get_charter_text(group):
|
|||
f = file(path,'r')
|
||||
text = f.read()
|
||||
f.close()
|
||||
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def get_my_groups(user,conclude=False):
|
||||
'''
|
||||
Takes a Django user object (from request)
|
||||
|
@ -31,24 +34,24 @@ def get_my_groups(user,conclude=False):
|
|||
area director - has access to all groups in their area
|
||||
wg chair or secretary - has acceses to their own group
|
||||
chair of irtf has access to all irtf groups
|
||||
|
||||
|
||||
If user=None than all groups are returned.
|
||||
concluded=True means include concluded groups. Need this to upload materials for groups
|
||||
after they've been concluded. it happens.
|
||||
'''
|
||||
my_groups = set()
|
||||
states = ['bof','proposed','active']
|
||||
if conclude:
|
||||
states.append('conclude')
|
||||
if conclude:
|
||||
states.extend(['conclude','bof-conc'])
|
||||
all_groups = Group.objects.filter(type__in=('wg','rg','ag','team'),state__in=states).order_by('acronym')
|
||||
if user == None:
|
||||
return all_groups
|
||||
else:
|
||||
person = user.get_profile()
|
||||
|
||||
|
||||
if has_role(user,'Secretariat'):
|
||||
return all_groups
|
||||
|
||||
|
||||
for group in all_groups:
|
||||
if group.role_set.filter(person=person,name__in=('chair','secr')):
|
||||
my_groups.add(group)
|
||||
|
@ -56,16 +59,16 @@ def get_my_groups(user,conclude=False):
|
|||
if group.parent and group.parent.role_set.filter(person=person,name__in=('ad','chair')):
|
||||
my_groups.add(group)
|
||||
continue
|
||||
|
||||
|
||||
return list(my_groups)
|
||||
|
||||
|
||||
def groups_by_session(user, meeting):
|
||||
'''
|
||||
Takes a Django User object and a Meeting object
|
||||
Returns a tuple scheduled_groups, unscheduled groups. sorted lists of those groups that
|
||||
Returns a tuple scheduled_groups, unscheduled groups. sorted lists of those groups that
|
||||
the user has access to, secretariat defaults to all groups
|
||||
If user=None than all groups are returned.
|
||||
|
||||
|
||||
For groups with a session, we must include "concluded" groups because we still want to know
|
||||
who had a session at a particular meeting even if they are concluded after. This is not true
|
||||
for groups without a session because this function is often used to build select lists (ie.
|
||||
|
@ -80,8 +83,7 @@ def groups_by_session(user, meeting):
|
|||
if group in groups_with_sessions:
|
||||
groups_session.append(group)
|
||||
else:
|
||||
if group.state_id != 'conclude':
|
||||
if group.state_id not in ('conclude','bof-conc'):
|
||||
groups_no_session.append(group)
|
||||
|
||||
|
||||
return groups_session, groups_no_session
|
||||
|
|
@ -86,7 +86,7 @@ MEDIA_ROOT = BASE_DIR + "/../static/"
|
|||
|
||||
# URL that handles the media served from MEDIA_ROOT.
|
||||
# Example: "http://media.lawrence.com"
|
||||
MEDIA_URL = ''
|
||||
MEDIA_URL = 'http://www.ietf.org'
|
||||
|
||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
||||
# trailing slash.
|
||||
|
@ -134,6 +134,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
|||
'django.core.context_processors.debug',
|
||||
'django.core.context_processors.i18n',
|
||||
'django.core.context_processors.request',
|
||||
'django.core.context_processors.media',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'ietf.context_processors.server_mode',
|
||||
'ietf.context_processors.revision_info',
|
||||
|
@ -374,7 +375,7 @@ SECR_BLUE_SHEET_URL = 'https://datatracker.ietf.org/documents/blue_sheet.rtf'
|
|||
SECR_INTERIM_LISTING_DIR = '/a/www/www6/meeting/interim'
|
||||
SECR_MAX_UPLOAD_SIZE = 40960000
|
||||
SECR_PROCEEDINGS_DIR = '/a/www/www6s/proceedings/'
|
||||
SECR_STATIC_URL = '/secr/'
|
||||
SECR_STATIC_URL = '/secretariat/'
|
||||
|
||||
# Put SECRET_KEY in here, or any other sensitive or site-specific
|
||||
# changes. DO NOT commit settings_local.py to svn.
|
||||
|
|
Before Width: | Height: | Size: 80 B After Width: | Height: | Size: 80 B |
Before Width: | Height: | Size: 838 B After Width: | Height: | Size: 838 B |
Before Width: | Height: | Size: 58 B After Width: | Height: | Size: 58 B |
Before Width: | Height: | Size: 75 B After Width: | Height: | Size: 75 B |
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 843 B After Width: | Height: | Size: 843 B |
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 844 B |
Before Width: | Height: | Size: 45 B After Width: | Height: | Size: 45 B |
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 130 B |
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
Before Width: | Height: | Size: 119 B After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 145 B After Width: | Height: | Size: 145 B |
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 192 B |
Before Width: | Height: | Size: 119 B After Width: | Height: | Size: 119 B |
Before Width: | Height: | Size: 390 B After Width: | Height: | Size: 390 B |
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 181 B |
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 319 B |
Before Width: | Height: | Size: 667 B After Width: | Height: | Size: 667 B |
Before Width: | Height: | Size: 341 B After Width: | Height: | Size: 341 B |
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
Before Width: | Height: | Size: 781 B After Width: | Height: | Size: 781 B |
Before Width: | Height: | Size: 447 B After Width: | Height: | Size: 447 B |
Before Width: | Height: | Size: 623 B After Width: | Height: | Size: 623 B |
Before Width: | Height: | Size: 102 B After Width: | Height: | Size: 102 B |
Before Width: | Height: | Size: 116 B After Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 186 B After Width: | Height: | Size: 186 B |
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 273 B |
Before Width: | Height: | Size: 606 B After Width: | Height: | Size: 606 B |
Before Width: | Height: | Size: 358 B After Width: | Height: | Size: 358 B |
Before Width: | Height: | Size: 398 B After Width: | Height: | Size: 398 B |
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 355 B |
Before Width: | Height: | Size: 552 B After Width: | Height: | Size: 552 B |
Before Width: | Height: | Size: 612 B After Width: | Height: | Size: 612 B |
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 401 B |
Before Width: | Height: | Size: 197 B After Width: | Height: | Size: 197 B |
Before Width: | Height: | Size: 203 B After Width: | Height: | Size: 203 B |
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 200 B After Width: | Height: | Size: 200 B |
Before Width: | Height: | Size: 932 B After Width: | Height: | Size: 932 B |
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 354 B |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 655 B After Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |