Merged the latest secretariat code from rcross@amsl.com, from branch/amsl/trunkmerge@6149.

- Legacy-Id: 6168
This commit is contained in:
Henrik Levkowetz 2013-09-18 19:33:36 +00:00
commit d8074d39d3
98 changed files with 766 additions and 660 deletions

View file

@ -1,4 +1,4 @@
__version__ = "1.33"
__version__ = "1.41"
__date__ = "$Date: 2011/07/26 14:29:17 $"

View file

@ -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

View file

@ -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},

View file

@ -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):

View file

@ -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()

View file

@ -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:**

View file

@ -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, {}),

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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 }}
&raquo; 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 -->

View file

@ -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 }}
&raquo; <a href="../../">Drafts</a>
&raquo; <a href="../">{{ draft.name }}</a>
&raquo; Edit
&raquo; 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>

View file

@ -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 }}
&raquo; <a href="../">Drafts</a>
&raquo; {{ 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>

View file

@ -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>

View file

@ -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>

View file

@ -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 %}

View file

@ -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">

View file

@ -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>

View file

@ -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:

View file

@ -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 }}.

View file

@ -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" %}

View file

@ -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>

View file

@ -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

View file

@ -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.

View file

Before

Width:  |  Height:  |  Size: 80 B

After

Width:  |  Height:  |  Size: 80 B

View file

Before

Width:  |  Height:  |  Size: 838 B

After

Width:  |  Height:  |  Size: 838 B

View file

Before

Width:  |  Height:  |  Size: 58 B

After

Width:  |  Height:  |  Size: 58 B

View file

Before

Width:  |  Height:  |  Size: 75 B

After

Width:  |  Height:  |  Size: 75 B

View file

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 199 B

View file

Before

Width:  |  Height:  |  Size: 212 B

After

Width:  |  Height:  |  Size: 212 B

View file

Before

Width:  |  Height:  |  Size: 843 B

After

Width:  |  Height:  |  Size: 843 B

View file

Before

Width:  |  Height:  |  Size: 844 B

After

Width:  |  Height:  |  Size: 844 B

View file

Before

Width:  |  Height:  |  Size: 45 B

After

Width:  |  Height:  |  Size: 45 B

View file

Before

Width:  |  Height:  |  Size: 176 B

After

Width:  |  Height:  |  Size: 176 B

View file

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 130 B

View file

Before

Width:  |  Height:  |  Size: 299 B

After

Width:  |  Height:  |  Size: 299 B

View file

Before

Width:  |  Height:  |  Size: 119 B

After

Width:  |  Height:  |  Size: 119 B

View file

Before

Width:  |  Height:  |  Size: 145 B

After

Width:  |  Height:  |  Size: 145 B

View file

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 192 B

View file

Before

Width:  |  Height:  |  Size: 119 B

After

Width:  |  Height:  |  Size: 119 B

View file

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 390 B

View file

Before

Width:  |  Height:  |  Size: 181 B

After

Width:  |  Height:  |  Size: 181 B

View file

Before

Width:  |  Height:  |  Size: 319 B

After

Width:  |  Height:  |  Size: 319 B

View file

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 667 B

View file

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 341 B

View file

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 477 B

View file

Before

Width:  |  Height:  |  Size: 781 B

After

Width:  |  Height:  |  Size: 781 B

View file

Before

Width:  |  Height:  |  Size: 447 B

After

Width:  |  Height:  |  Size: 447 B

View file

Before

Width:  |  Height:  |  Size: 623 B

After

Width:  |  Height:  |  Size: 623 B

View file

Before

Width:  |  Height:  |  Size: 102 B

After

Width:  |  Height:  |  Size: 102 B

View file

Before

Width:  |  Height:  |  Size: 116 B

After

Width:  |  Height:  |  Size: 116 B

View file

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 186 B

View file

Before

Width:  |  Height:  |  Size: 273 B

After

Width:  |  Height:  |  Size: 273 B

View file

Before

Width:  |  Height:  |  Size: 606 B

After

Width:  |  Height:  |  Size: 606 B

View file

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 358 B

View file

Before

Width:  |  Height:  |  Size: 398 B

After

Width:  |  Height:  |  Size: 398 B

View file

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 355 B

View file

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 552 B

View file

Before

Width:  |  Height:  |  Size: 612 B

After

Width:  |  Height:  |  Size: 612 B

View file

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 401 B

View file

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 197 B

View file

Before

Width:  |  Height:  |  Size: 203 B

After

Width:  |  Height:  |  Size: 203 B

View file

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

View file

Before

Width:  |  Height:  |  Size: 200 B

After

Width:  |  Height:  |  Size: 200 B

View file

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 932 B

View file

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 336 B

View file

Before

Width:  |  Height:  |  Size: 351 B

After

Width:  |  Height:  |  Size: 351 B

View file

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 354 B

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

Before

Width:  |  Height:  |  Size: 655 B

After

Width:  |  Height:  |  Size: 655 B

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB