add tests. fix form validations

- Legacy-Id: 11219
This commit is contained in:
Ryan Cross 2016-05-20 22:49:27 +00:00
parent 6ea92cc5a0
commit ed371396c1
12 changed files with 154 additions and 135 deletions

View file

@ -137,7 +137,7 @@ class GroupModelChoiceField(forms.ModelChoiceField):
class InterimMeetingModelForm(forms.ModelForm):
group = GroupModelChoiceField(queryset=Group.objects.filter(type__in=('wg', 'rg'), state='active').order_by('acronym'))
group = GroupModelChoiceField(queryset=Group.objects.filter(type__in=('wg', 'rg'), state__in=('active', 'proposed')).order_by('acronym'), required=False)
in_person = forms.BooleanField(required=False)
meeting_type = forms.ChoiceField(choices=(
("single", "Single"),
@ -172,9 +172,16 @@ class InterimMeetingModelForm(forms.ModelForm):
self.fields['approved'].initial = False
self.fields['approved'].widget.attrs['disabled'] = True
def clean(self):
super(InterimMeetingModelForm, self).clean()
cleaned_data = self.cleaned_data
if not cleaned_data.get('group'):
raise forms.ValidationError("You must select a group")
return self.cleaned_data
def set_group_options(self):
'''Set group options based on user accessing the form'''
if has_role(self.user, "Secretariat"):
return # don't reduce group options
if has_role(self.user, "Area Director"):
@ -215,9 +222,9 @@ class InterimMeetingModelForm(forms.ModelForm):
class InterimSessionModelForm(forms.ModelForm):
date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1"}, label='Date', required=False)
time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'), required=False)
time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'), required=True)
time_utc = forms.TimeField(required=False)
requested_duration = DurationField(required=False)
requested_duration = DurationField(required=True)
end_time = forms.TimeField(required=False)
end_time_utc = forms.TimeField(required=False)
remote_instructions = forms.CharField(max_length=1024, required=True)
@ -247,6 +254,14 @@ class InterimSessionModelForm(forms.ModelForm):
path = os.path.join(doc.get_file_path(), doc.filename_with_rev())
self.initial['agenda'] = get_document_content(os.path.basename(path), path, markup=False)
def clean_date(self):
'''Date field validator. We can't use required on the input because
it is a datepicker widget'''
date = self.cleaned_data.get('date')
if not date:
raise forms.ValidationError('Required field')
return date
def save(self, *args, **kwargs):
"""NOTE: as the baseform of an inlineformset self.save(commit=True)
never gets called"""
@ -291,68 +306,8 @@ class InterimSessionModelForm(forms.ModelForm):
with open(path, "w") as file:
file.write(self.cleaned_data['agenda'])
'''
class InterimSessionForm(forms.Form):
date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1"}, label='Date', required=False)
time = forms.TimeField(required=False)
time_utc = forms.TimeField(required=False)
duration = DurationField(required=False)
end_time = forms.TimeField(required=False)
end_time_utc = forms.TimeField(required=False)
remote_instructions = forms.CharField(max_length=1024, required=False)
agenda = forms.CharField(required=False, widget=forms.Textarea)
agenda_note = forms.CharField(max_length=255, required=False)
def save(self, request, group, meeting, is_approved):
person = request.user.person
agenda = self.cleaned_data.get('agenda')
agenda_note = self.cleaned_data.get('agenda_note')
date = self.cleaned_data.get('date')
time = self.cleaned_data.get('time')
duration = self.cleaned_data.get('duration')
remote_instructions = self.cleaned_data.get('remote_instructions')
time = datetime.datetime.combine(date, time)
if is_approved:
status_id = 'scheda'
else:
status_id = 'apprw'
session = Session.objects.create(
meeting=meeting,
group=group,
requested_by=person,
requested_duration=duration,
status_id=status_id,
type_id='session',
remote_instructions=remote_instructions,
agenda_note=agenda_note,)
assign_interim_session(session, time)
if agenda:
# create objects
filename = 'agenda-interim-%s-%s' % (group.acronym, time.strftime("%Y-%m-%d-%H%M"))
doc = Document.objects.create(type_id='agenda', group=group, name=filename, rev='00')
doc.set_state(State.objects.get(type=doc.type, slug='active'))
DocAlias.objects.create(name=doc.name, document=doc)
session.sessionpresentation_set.create(document=doc, rev=doc.rev)
NewRevisionDocEvent.objects.create(
type='new_revision',
by=request.user.person,
doc=doc,
rev=doc.rev,
desc='New revision available')
# write file
path = os.path.join(get_upload_root(meeting), 'agenda', doc.filename_with_rev())
directory = os.path.dirname(path)
if not os.path.exists(directory):
os.makedirs(directory)
with open(path, "w") as file:
file.write(agenda)
return session
'''
class InterimAnnounceForm(forms.ModelForm):
class Meta:
model = Message
fields = ('to', 'frm', 'cc', 'bcc', 'reply_to', 'subject', 'body')
@ -367,11 +322,11 @@ class InterimAnnounceForm(forms.ModelForm):
class InterimCancelForm(forms.Form):
group = forms.CharField(max_length=255,required=False)
group = forms.CharField(max_length=255, required=False)
date = forms.DateField(required=False)
comments = forms.CharField(required=False, widget=forms.Textarea(attrs={'placeholder': 'enter optional comments here'}))
def __init__(self, *args, **kwargs):
super(InterimCancelForm, self).__init__(*args, **kwargs)
self.fields['group'].widget.attrs['disabled'] = True
self.fields['date'].widget.attrs['disabled'] = True
self.fields['date'].widget.attrs['disabled'] = True

View file

@ -337,11 +337,21 @@ def can_approve_interim_request(meeting, user):
def can_edit_interim_request(meeting, user):
'''Returns True if the user can edit the interim meeting request'''
if can_approve_interim_request(meeting, user):
if meeting.type.slug != 'interim':
return False
if has_role(user, 'Secretariat'):
return True
return False
person = get_person_for_user(user)
session = meeting.session_set.first()
if not session:
return False
group = session.group
if group.role_set.filter(name='chair', person=person):
return True
elif can_approve_interim_request(meeting, user):
return True
else:
return False
def can_request_interim_meeting(user):

View file

@ -1,3 +1,4 @@
import json
import os
import shutil
import datetime
@ -349,67 +350,67 @@ class InterimTests(TestCase):
# no logged in - no tabs
r = self.client.get(url)
q = PyQuery(r.content)
self.assertEqual(len(q("ul.nav-tabs")),0)
self.assertEqual(len(q("ul.nav-tabs")), 0)
# plain user - no tabs
username = "plain"
self.client.login(username=username, password= username + "+password")
self.client.login(username=username, password=username + "+password")
r = self.client.get(url)
q = PyQuery(r.content)
self.assertEqual(len(q("ul.nav-tabs")),0)
self.assertEqual(len(q("ul.nav-tabs")), 0)
self.client.logout()
# privileged user
username = "ad"
self.client.login(username=username, password= username + "+password")
self.client.login(username=username, password=username + "+password")
r = self.client.get(url)
q = PyQuery(r.content)
self.assertEqual(len(q("a:contains('Pending')")),1)
self.assertEqual(len(q("a:contains('Announce')")),0)
self.assertEqual(len(q("a:contains('Pending')")), 1)
self.assertEqual(len(q("a:contains('Announce')")), 0)
self.client.logout()
# secretariat
username = "secretary"
self.client.login(username=username, password= username + "+password")
self.client.login(username=username, password=username + "+password")
r = self.client.get(url)
q = PyQuery(r.content)
self.assertEqual(len(q("a:contains('Pending')")),1)
self.assertEqual(len(q("a:contains('Announce')")),1)
self.assertEqual(len(q("a:contains('Pending')")), 1)
self.assertEqual(len(q("a:contains('Announce')")), 1)
self.client.logout()
def test_interim_announce(self):
make_meeting_test_data()
url = urlreverse("ietf.meeting.views.interim_announce")
meeting = Meeting.objects.filter(type='interim',session__group__acronym='mars').first()
meeting = Meeting.objects.filter(type='interim', session__group__acronym='mars').first()
session = meeting.session_set.first()
session.status = SessionStatusName.objects.get(slug='scheda')
session.save()
login_testing_unauthorized(self,"secretary",url)
login_testing_unauthorized(self, "secretary", url)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(meeting.number in r.content)
def test_interim_send_announcement(self):
make_meeting_test_data()
meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group__acronym='mars').first()
url = urlreverse("ietf.meeting.views.interim_send_announcement", kwargs={'number':meeting.number})
login_testing_unauthorized(self,"secretary",url)
meeting = Meeting.objects.filter(type='interim', session__status='apprw', session__group__acronym='mars').first()
url = urlreverse("ietf.meeting.views.interim_send_announcement", kwargs={'number': meeting.number})
login_testing_unauthorized(self, "secretary", url)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
initial = r.context['form'].initial
# send announcement
len_before = len(outbox)
r = self.client.post(url,initial)
self.assertRedirects(r,urlreverse('ietf.meeting.views.interim_announce'))
self.assertEqual(len(outbox),len_before+1)
r = self.client.post(url, initial)
self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_announce'))
self.assertEqual(len(outbox), len_before + 1)
self.assertTrue('WG Virtual Meeting' in outbox[-1]['Subject'])
def test_interim_approve(self):
make_meeting_test_data()
meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group__acronym='mars').first()
url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number})
login_testing_unauthorized(self,"secretary",url)
r = self.client.post(url,{'approve':'approve'})
self.assertRedirects(r,urlreverse('ietf.meeting.views.interim_send_announcement',kwargs={'number':meeting.number}))
meeting = Meeting.objects.filter(type='interim', session__status='apprw', session__group__acronym='mars').first()
url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})
login_testing_unauthorized(self, "secretary", url)
r = self.client.post(url, {'approve': 'approve'})
self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_send_announcement', kwargs={'number': meeting.number}))
for session in meeting.session_set.all():
self.assertEqual(session.status.slug,'scheda')
self.assertEqual(session.status.slug, 'scheda')
def test_upcoming(self):
make_meeting_test_data()
@ -478,8 +479,8 @@ class InterimTests(TestCase):
r = self.client.get("/meeting/interim/request/")
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(Group.objects.filter(type__in=('wg','rg'),state='active').count(),
len(q("#id_group option")) -1 ) # -1 for options placeholder
self.assertEqual(Group.objects.filter(type__in=('wg', 'rg'), state__in=('active', 'proposed')).count(),
len(q("#id_group option")) - 1) # -1 for options placeholder
def test_interim_request_single(self):
@ -882,4 +883,29 @@ class InterimTests(TestCase):
length_before = len(outbox)
send_interim_minutes_reminder(meeting=meeting)
self.assertEqual(len(outbox),length_before+1)
self.assertTrue('Action Required: Minutes' in outbox[-1]['Subject'])
self.assertTrue('Action Required: Minutes' in outbox[-1]['Subject'])
class AjaxTests(TestCase):
def test_ajax_get_utc(self):
# test bad queries
url = urlreverse('ietf.meeting.views.ajax_get_utc') + "?date=2016-1-1&time=badtime&timezone=UTC"
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
data = json.loads(r.content)
self.assertEqual(data["error"], True)
url = urlreverse('ietf.meeting.views.ajax_get_utc') + "?date=2016-1-1&time=25:99&timezone=UTC"
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
data = json.loads(r.content)
self.assertEqual(data["error"], True)
# test good query
url = urlreverse('ietf.meeting.views.ajax_get_utc') + "?date=2016-1-1&time=12:00&timezone=US/Pacific"
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
data = json.loads(r.content)
self.assertTrue('timezone' in data)
self.assertTrue('time' in data)
self.assertTrue('utc' in data)
self.assertTrue('error' not in data)
self.assertEqual(data['utc'], '20:00')

View file

@ -22,6 +22,7 @@ from django.db.models import Min, Max
from django.conf import settings
from django.forms.models import modelform_factory, inlineformset_factory
from django.forms import ModelForm
from django.template.loader import render_to_string
from django.utils.functional import curry
from django.views.decorators.csrf import ensure_csrf_cookie
@ -39,6 +40,7 @@ from ietf.meeting.helpers import get_meeting, get_schedule, agenda_permissions,
from ietf.meeting.helpers import preprocess_assignments_for_agenda, read_agenda_file
from ietf.meeting.helpers import convert_draft_to_pdf, get_earliest_session_date
from ietf.meeting.helpers import can_view_interim_request, can_approve_interim_request
from ietf.meeting.helpers import can_edit_interim_request
from ietf.meeting.helpers import can_request_interim_meeting, get_announcement_initial
from ietf.meeting.helpers import sessions_post_save, is_meeting_approved
from ietf.meeting.helpers import send_interim_cancellation_notice
@ -910,8 +912,17 @@ def ajax_get_utc(request):
'''Ajax view that takes arguments time and timezone and returns UTC'''
time = request.GET.get('time')
timezone = request.GET.get('timezone')
date = request.GET.get('date')
time_re = re.compile(r'^\d{2}:\d{2}')
if not time_re.match(time):
return HttpResponse(json.dumps({'error': True}),
content_type='application/json')
hour, minute = time.split(':')
dt = datetime.datetime(2016, 1, 1, int(hour), int(minute))
if not (int(hour) <= 23 and int(minute) <= 59):
return HttpResponse(json.dumps({'error': True}),
content_type='application/json')
year, month, day = date.split('-')
dt = datetime.datetime(int(year), int(month), int(day), int(hour), int(minute))
tz = pytz.timezone(timezone)
aware_dt = tz.localize(dt, is_dst=None)
utc_dt = aware_dt.astimezone(pytz.utc)
@ -1045,8 +1056,7 @@ def interim_request(request):
messages.success(request, 'Interim meeting request submitted')
return redirect(upcoming)
else:
assert False, (form.errors, formset.errors)
else:
form = InterimMeetingModelForm(request=request,
initial={'meeting_type': 'single'})
@ -1091,7 +1101,7 @@ def interim_request_details(request, number):
'''View details of an interim meeting reqeust'''
meeting = get_object_or_404(Meeting, number=number)
sessions = meeting.session_set.all()
can_edit = can_view_interim_request(meeting, request.user)
can_edit = can_edit_interim_request(meeting, request.user)
can_approve = can_approve_interim_request(meeting, request.user)
if request.method == 'POST':
@ -1145,8 +1155,7 @@ def interim_request_edit(request, number):
messages.success(request, 'Interim meeting request saved')
return redirect(interim_request_details, number=number)
else:
assert False, (form.errors, formset.errors)
else:
form = InterimMeetingModelForm(request=request, instance=meeting)
formset = SessionFormset(instance=meeting)
@ -1161,7 +1170,7 @@ def upcoming(request):
'''List of upcoming meetings'''
today = datetime.datetime.today()
meetings = Meeting.objects.filter(date__gte=today).exclude(
session__status__in=('apprw', 'schedpa', 'canceledpa')).order_by('date')
session__status__in=('apprw', 'scheda', 'canceledpa')).order_by('date')
# extract groups hierarchy for display filter
seen = set()
@ -1222,7 +1231,17 @@ def upcoming_ical(request):
a.session.group.acronym in filters or
a.session.group.parent.acronym in filters]
# gather vtimezones
vtimezones = set()
for meeting in meetings:
if meeting.vtimezone():
vtimezones.add(meeting.vtimezone())
vtimezones = ''.join(vtimezones)
return render(request, 'meeting/upcoming.ics', {
'assignments': assignments,
}, content_type='text/calendar')
# icalendar response file should have '\r\n' line endings per RFC5545
response = render_to_string('meeting/upcoming.ics', {
'vtimezones': vtimezones,
'assignments': assignments})
response = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", response)
return HttpResponse(response, content_type='text/calendar')

View file

@ -470,3 +470,8 @@ form.navbar-form input.form-control.input-sm { width: 141px; }
#interim-request-form .time-field {
width: 100px;
}
ul.errorlist {
list-style-type: none;
padding-left:0;
}

View file

@ -81,6 +81,7 @@ var interimRequest = {
}
var url = "/meeting/ajax/get-utc";
var fieldset = $(this).parents(".fieldset");
var date = fieldset.find("input[name$='-date']").val();
var timezone = interimRequest.timezone.val();
var name = $(this).attr("id") + "_utc";
var utc = fieldset.find("#" + name);
@ -90,7 +91,8 @@ var interimRequest = {
cache: false,
async: true,
dataType: 'json',
data: {time: time,
data: {date: date,
time: time,
timezone: timezone},
success: function(response){
if (!response.error) {

View file

@ -54,7 +54,7 @@
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">IETF - {{ meeting.number }}</a>
{% endif %}
</td>
<td>{% if meeting.can_approve %}<span class="label label-warning">can approve</span>{% endif %}</td>
<td>{% if meeting.can_approve %}<span class="label label-success">can approve</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>

View file

@ -18,6 +18,12 @@
<form id="interim-request-form" role="form" method="post" class="form-horizontal">
{% csrf_token %}
{% if form.non_field_errors %}
<div class="form-group alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% bootstrap_field form.group layout='horizontal' %}
<div class="form-group form-inline">
@ -76,16 +82,18 @@
{% for form in formset %}
<div class="fieldset{% if forloop.last %} template{% endif %}" >
<div class="form-group">
<div class="form-group {% if form.date.errors %}alert alert-danger{% endif %}">
<label for="id_session_set-{{ forloop.counter0 }}-date" class="col-md-2 control-label required">Date</label>
<div class="col-md-2">{% render_field form.date class="form-control" %}</div>
{% if form.date.errors %}<span class="help-inline">{{ form.date.errors }}</span>{% endif %}
</div>
<div class="form-group">
<div class="form-group {% if form.time.errors %}alert alert-danger{% endif %}">
<label for="id_session_set-{{ forloop.counter0 }}-time" class="col-md-2 control-label required">Start Time</label>
<div class="col-md-3 form-inline">
{% render_field form.time class="form-control time-field" placeholder="HH:MM" %}
{% render_field form.time_utc class="form-control time-field computed" disabled="disabled" placeholder="UTC" %}
{% if form.time.errors %}<span class="help-inline">{{ form.time.errors }}</span>{% endif %}
</div>
<label for="id_session_set-{{ forloop.counter0 }}-end_time" class="col-md-2 control-label">End Time</label>
<div class="col-md-4 form-inline">
@ -94,14 +102,16 @@
</div>
</div>
<div class="form-group">
<div class="form-group{% if form.requested_duration.errors %} alert alert-danger{% endif %}">
<label for="id_session_set-{{ forloop.counter0 }}-requested_duration" class="col-md-2 control-label required">Duration</label>
<div class="col-md-2">{% render_field form.requested_duration class="form-control time-field" placeholder="HH:MM" %}</div>
{% if form.requested_duration.errors %}<span class="help-inline">{{ form.requested_duration.errors }}</span>{% endif %}
</div>
<div class="form-group">
<div class="form-group{% if form.remote_instructions.errors %} alert alert-danger{% endif %}">
<label for="id_session_set-{{ forloop.counter0 }}-remote_instructions" class="col-md-2 control-label required">Remote Instructions</label>
<div class="col-md-10">{% render_field form.remote_instructions class="form-control" placeholder="Webex URL" %}<p class="help-block">"Remote participation is not supported" or "Remote participation information will be obtained at the time of approval" are acceptable values. See <a href="">here</a> for more on remote participation support.</p></div>
{% if form.remote_instructions.errors %}<span class="help-inline">{{ form.remote_instructions.errors }}</span>{% endif %}
</div>
<div class="form-group">

View file

@ -31,21 +31,6 @@
<div class="col-md-2">
<label class="checkbox-inline">{% render_field form.approved %}<strong>Preapproved by AD</strong></label>
</div>
<!--
<div class="col-md-2 radio-inline"><strong>Meeting Type:</strong></div>
<label class="radio-inline">
<input type="radio" value="single" checked="checked" name="meeting_type">Single
</label>
<label class="radio-inline">
<input type="radio" value="multi-day" name="meeting_type">Multi-Day
</label>
<label class="radio-inline">
<input type="radio" value="series" name="meeting_type">Series
</label>
-->
</div> <!-- col-md-offset-2 -->
</div> <!-- form-group form-inline -->
@ -64,16 +49,18 @@
<input id="id_session_set-{{ forloop.counter0 }}-id" name="session_set-{{ forloop.counter0 }}-id" type="hidden" value="{{ form.instance.pk|default_if_none:"" }}">
<div class="form-group">
<div class="form-group{% if form.date.errors %} alert alert-danger{% endif %}">
<label for="id_session_set-{{ forloop.counter0 }}-date" class="col-md-2 control-label required">Date</label>
<div class="col-md-2">{% render_field form.date class="form-control" %}</div>
{% if form.date.errors %}<span class="help-inline">{{ form.date.errors }}</span>{% endif %}
</div>
<div class="form-group">
<div class="form-group {% if form.time.errors %}alert alert-danger{% endif %}">
<label for="id_session_set-{{ forloop.counter0 }}-time" class="col-md-2 control-label required">Start Time</label>
<div class="col-md-3 form-inline">
{% render_field form.time class="form-control time-field" placeholder="HH:MM" %}
{% render_field form.time_utc class="form-control time-field computed" disabled="disabled" placeholder="UTC" %}
{% if form.time.errors %}<span class="help-inline">{{ form.time.errors }}</span>{% endif %}
</div>
<label for="id_session_set-{{ forloop.counter0 }}-end_time" class="col-md-2 control-label">End Time</label>
<div class="col-md-4 form-inline">
@ -82,14 +69,16 @@
</div>
</div>
<div class="form-group">
<div class="form-group{% if form.requested_duration.errors %} alert alert-danger{% endif %}">
<label for="id_session_set-{{ forloop.counter0 }}-requested_duration" class="col-md-2 control-label required">Duration</label>
<div class="col-md-2">{% render_field form.requested_duration class="form-control time-field" placeholder="HH:MM" %}</div>
{% if form.requested_duration.errors %}<span class="help-inline">{{ form.requested_duration.errors }}</span>{% endif %}
</div>
<div class="form-group">
<label for="id_session_set-{{ forloop.counter0 }}-remote_instructions" class="col-md-2 control-label">Remote Instructions</label>
<div class="form-group{% if form.remote_instructions.errors %} alert alert-danger{% endif %}">
<label for="id_session_set-{{ forloop.counter0 }}-remote_instructions" class="col-md-2 control-label required">Remote Instructions</label>
<div class="col-md-10">{% render_field form.remote_instructions class="form-control" placeholder="ie. Webex address" %}</div>
{% if form.remote_instructions.errors %}<span class="help-inline">{{ form.remote_instructions.errors }}</span>{% endif %}
</div>
<div class="form-group">

View file

@ -1,6 +1,6 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load origin ietf_filters %}
{% block title %}{{ meeting }} : {{ acronym }}{% endblock %}
@ -14,6 +14,9 @@
{% if can_manage_materials %}
{% if session.status.slug == 'sched' or session.status.slug == 'schedw' %}
<div class="buttonlist">
{% if meeting.type.slug == 'interim' and user|has_role:"Secretariat" %}
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">Meeting Details</a>
{% endif %}
<a class="btn btn-default" href="{% url 'ietf.secr.proceedings.views.upload_unified' meeting_num=session.meeting.number acronym=session.group.acronym %}">
Upload/Edit Materials
</a>

View file

@ -125,7 +125,7 @@
{% endif %}
<td>
{% if meeting.type.slug == "interim" %}
<a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.session_set.all.0.group.acronym %}">{{ meeting.number }}{% if meeting.session_set.all.0.status.slug == "canceled" %} -- CANCELLED --{% endif %}</a>
<a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.session_set.all.0.group.acronym %}">{{ meeting.number }}{% if meeting.session_set.all.0.status.slug == "canceled" %}&nbsp&nbsp<span class="label label-warning">CANCELLED</span>{% endif %}</a>
{% else %}
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF - {{ meeting.number }}</a>
{% endif %}

View file

@ -2,7 +2,7 @@
VERSION:2.0
METHOD:PUBLISH
PRODID:-//IETF//datatracker.ietf.org ical upcoming//EN
{% for item in assignments %}BEGIN:VEVENT
{{vtimezones}}{% for item in assignments %}BEGIN:VEVENT
UID:ietf-{{item.session.meeting.number}}-{{item.timeslot.pk}}
SUMMARY:{% if item.session.name %}{{item.session.name|ics_esc}}{% else %}{{item.session.group.acronym|lower}} - {{item.session.group.name}}{%endif%}
{% if item.schedule.meeting.city %}LOCATION:{{item.schedule.meeting.city}},{{item.schedule.meeting.country}}