Skip to content

Commit 0e576f4

Browse files
committed
Add models for new meeting schema and an importer, clean up person
import slightly - Legacy-Id: 3282
1 parent 5aa189a commit 0e576f4

8 files changed

Lines changed: 668 additions & 49 deletions

File tree

ietf/meeting/models.py

Lines changed: 138 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,139 @@
1-
# Copyright The IETF Trust 2007, All Rights Reserved
1+
# old meeting models can be found in ../proceedings/models.py
2+
3+
import pytz
4+
5+
from django.db import models
6+
from timedeltafield import TimedeltaField
7+
8+
from redesign.group.models import Group
9+
from redesign.person.models import Person
10+
from redesign.name.models import TimeSlotTypeName, SessionStatusName, ConstraintName
11+
12+
countries = pytz.country_names.items()
13+
countries.sort(lambda x,y: cmp(x[1], y[1]))
14+
15+
timezones = [(name, name) for name in pytz.common_timezones]
16+
timezones.sort()
17+
18+
class Meeting(models.Model):
19+
# Number is not an integer any more, in order to be able to accomodate
20+
# interim meetings (and other variations?)
21+
number = models.CharField(max_length=64)
22+
# Date is useful when generating a set of timeslot for this meeting, but
23+
# is not used to determine date for timeslot instances thereafter, as
24+
# they have their own datetime field.
25+
date = models.DateField()
26+
city = models.CharField(blank=True, max_length=255)
27+
country = models.CharField(blank=True, max_length=2, choices=countries)
28+
# We can't derive time-zone from country, as there are some that have
29+
# more than one timezone, and the pytz module doesn't provide timezone
30+
# lookup information for all relevant city/country combinations.
31+
time_zone = models.CharField(blank=True, max_length=255, choices=timezones)
32+
venue_name = models.CharField(blank=True, max_length=255)
33+
venue_addr = models.TextField(blank=True)
34+
break_area = models.CharField(blank=True, max_length=255)
35+
reg_area = models.CharField(blank=True, max_length=255)
36+
37+
def __str__(self):
38+
return "IETF-%s" % (self.number)
39+
def get_meeting_date (self,offset):
40+
return self.date + datetime.timedelta(days=offset)
41+
# cut-off dates (draft submission cut-of, wg agenda cut-off, minutes
42+
# submission cut-off), and more, are probably methods of this class,
43+
# rather than fields on a Proceedings class.
44+
45+
@classmethod
46+
def get_first_cut_off(cls):
47+
date = cls.objects.all().order_by('-date')[0].date
48+
offset = datetime.timedelta(days=settings.FIRST_CUTOFF_DAYS)
49+
return date - offset
50+
51+
@classmethod
52+
def get_second_cut_off(cls):
53+
date = cls.objects.all().order_by('-date')[0].date
54+
offset = datetime.timedelta(days=settings.SECOND_CUTOFF_DAYS)
55+
return date - offset
56+
57+
@classmethod
58+
def get_ietf_monday(cls):
59+
date = cls.objects.all().order_by('-date')[0].date
60+
return date + datetime.timedelta(days=-date.weekday(), weeks=1)
61+
62+
63+
class Room(models.Model):
64+
meeting = models.ForeignKey(Meeting)
65+
name = models.CharField(max_length=255)
66+
67+
def __unicode__(self):
68+
return self.name
69+
70+
class TimeSlot(models.Model):
71+
"""
72+
Everything that would appear on the meeting agenda of a meeting is mapped
73+
to a time slot, including breaks (i.e., also NonSession+NonSessionRef.
74+
Sessions are connected to TimeSlots during scheduling.
75+
A template function to populate a meeting with an appropriate set of TimeSlots
76+
is probably also needed.
77+
"""
78+
meeting = models.ForeignKey(Meeting)
79+
type = models.ForeignKey(TimeSlotTypeName)
80+
name = models.CharField(max_length=255)
81+
time = models.DateTimeField()
82+
duration = TimedeltaField()
83+
location = models.ForeignKey(Room, blank=True, null=True)
84+
show_location = models.BooleanField(default=True)
85+
86+
def __unicode__(self):
87+
location = self.get_location()
88+
if not location:
89+
location = "(no location)"
90+
91+
return u"%s: %s-%s %s, %s" % (self.meeting.number, self.time.strftime("%m-%d %H:%M"), (self.time + self.duration).strftime("%H:%M"), self.name, location)
92+
93+
def get_location(self):
94+
location = self.location
95+
if location:
96+
location = location.name
97+
elif self.type_id == "reg":
98+
location = self.meeting.reg_area
99+
elif self.type_id == "break":
100+
location = self.meeting.break_area
101+
102+
if not self.show_location:
103+
location = ""
104+
105+
return location
106+
107+
108+
class Constraint(models.Model):
109+
"""Specifies a constraint on the scheduling between source and
110+
target, e.g. some kind of conflict."""
111+
meeting = models.ForeignKey(Meeting)
112+
source = models.ForeignKey(Group, related_name="constraint_source_set")
113+
target = models.ForeignKey(Group, related_name="constraint_target_set")
114+
name = models.ForeignKey(ConstraintName)
115+
116+
def __unicode__(self):
117+
return u"%s %s %s" % (self.source, self.name.lower(), self.target)
118+
119+
class Session(models.Model):
120+
meeting = models.ForeignKey(Meeting)
121+
timeslot = models.ForeignKey(TimeSlot, null=True, blank=True) # Null until session has been scheduled
122+
group = models.ForeignKey(Group) # The group type determines the session type. BOFs also need to be added as a group.
123+
attendees = models.IntegerField(null=True, blank=True)
124+
agenda_note = models.CharField(blank=True, max_length=255)
125+
#
126+
requested = models.DateTimeField()
127+
requested_by = models.ForeignKey(Person)
128+
requested_duration = TimedeltaField()
129+
comments = models.TextField()
130+
#
131+
status = models.ForeignKey(SessionStatusName)
132+
scheduled = models.DateTimeField(null=True, blank=True)
133+
modified = models.DateTimeField(null=True, blank=True)
134+
135+
# Agendas, Minutes and Slides are all mapped to Document.
136+
137+
# IESG history is extracted from GroupHistory, rather than hand coded in a
138+
# separate table.
2139

3-
# Meeting models can be found under ../proceedings/

ietf/meeting/timedeltafield.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# -*- coding: iso-8859-1 -*-
2+
# $Id: TimedeltaField.py 1787 2011-04-20 07:09:57Z tguettler $
3+
# $HeadURL: svn+ssh://svnserver/svn/djangotools/trunk/dbfields/TimedeltaField.py $
4+
5+
# from http://djangosnippets.org/snippets/1060/ with some fixes
6+
7+
# Python
8+
import datetime
9+
10+
# Django
11+
from django import forms
12+
from django.db import models
13+
from django.utils.safestring import mark_safe
14+
from django.utils.translation import ugettext_lazy as _
15+
16+
#Djangotools
17+
#from djangotools.utils.southutils import add_introspection_rules_from_baseclass
18+
19+
SECS_PER_DAY=3600*24
20+
21+
class TimedeltaField(models.Field):
22+
u'''
23+
Store Python's datetime.timedelta in an integer column.
24+
Most database systems only support 32 bit integers by default.
25+
'''
26+
__metaclass__ = models.SubfieldBase
27+
empty_strings_allowed = False
28+
29+
def __init__(self, *args, **kwargs):
30+
super(TimedeltaField, self).__init__(*args, **kwargs)
31+
32+
def to_python(self, value):
33+
if (value is None) or isinstance(value, datetime.timedelta):
34+
return value
35+
36+
try:
37+
# else try to convert to int (e.g. from string)
38+
value = int(value)
39+
except (TypeError, ValueError):
40+
raise exceptions.ValidationError(
41+
_("This value must be an integer or a datetime.timedelta."))
42+
43+
return datetime.timedelta(seconds=value)
44+
45+
def get_internal_type(self):
46+
return 'IntegerField'
47+
48+
def get_db_prep_lookup(self, lookup_type, value, connection=None, prepared=False):
49+
raise NotImplementedError() # SQL WHERE
50+
51+
def get_db_prep_save(self, value, connection=None, prepared=False):
52+
if (value is None) or isinstance(value, int):
53+
return value
54+
return SECS_PER_DAY*value.days+value.seconds
55+
56+
def formfield(self, *args, **kwargs):
57+
defaults={'form_class': TimedeltaFormField}
58+
defaults.update(kwargs)
59+
return super(TimedeltaField, self).formfield(*args, **defaults)
60+
61+
def value_to_string(self, obj):
62+
value = self._get_val_from_obj(obj)
63+
return self.get_db_prep_value(value)
64+
65+
#South Plugin registrieren
66+
#add_introspection_rules_from_baseclass(TimedeltaField, ["^djangotools\.dbfields\.TimedeltaField"])
67+
68+
class TimedeltaFormField(forms.Field):
69+
default_error_messages = {
70+
'invalid': _(u'Enter a whole number.'),
71+
}
72+
73+
def __init__(self, *args, **kwargs):
74+
defaults={'widget': TimedeltaWidget}
75+
defaults.update(kwargs)
76+
super(TimedeltaFormField, self).__init__(*args, **defaults)
77+
78+
def clean(self, value):
79+
# value comes from Timedelta.Widget.value_from_datadict(): tuple of strings
80+
super(TimedeltaFormField, self).clean(value)
81+
assert len(value)==len(self.widget.inputs), (value, self.widget.inputs)
82+
i=0
83+
for value, multiply in zip(value, self.widget.multiply):
84+
try:
85+
i+=int(value)*multiply
86+
except ValueError, TypeError:
87+
raise forms.ValidationError(self.error_messages['invalid'])
88+
return i
89+
90+
class TimedeltaWidget(forms.Widget):
91+
INPUTS=['days', 'hours', 'minutes', 'seconds']
92+
MULTIPLY=[60*60*24, 60*60, 60, 1]
93+
def __init__(self, attrs=None):
94+
self.widgets=[]
95+
if not attrs:
96+
attrs={}
97+
inputs=attrs.get('inputs', self.INPUTS)
98+
multiply=[]
99+
for input in inputs:
100+
assert input in self.INPUTS, (input, self.INPUT)
101+
self.widgets.append(forms.TextInput(attrs=attrs))
102+
multiply.append(self.MULTIPLY[self.INPUTS.index(input)])
103+
self.inputs=inputs
104+
self.multiply=multiply
105+
super(TimedeltaWidget, self).__init__(attrs)
106+
107+
def render(self, name, value, attrs):
108+
if value is None:
109+
values=[0 for i in self.inputs]
110+
elif isinstance(value, datetime.timedelta):
111+
values=split_seconds(value.days*SECS_PER_DAY+value.seconds, self.inputs, self.multiply)
112+
elif isinstance(value, int):
113+
# initial data from model
114+
values=split_seconds(value, self.inputs, self.multiply)
115+
else:
116+
assert isinstance(value, tuple), (value, type(value))
117+
assert len(value)==len(self.inputs), (value, self.inputs)
118+
values=value
119+
id=attrs.pop('id')
120+
assert not attrs, attrs
121+
rendered=[]
122+
for input, widget, val in zip(self.inputs, self.widgets, values):
123+
rendered.append(u'%s %s' % (_(input), widget.render('%s_%s' % (name, input), val)))
124+
return mark_safe('<div id="%s">%s</div>' % (id, ' '.join(rendered)))
125+
126+
def value_from_datadict(self, data, files, name):
127+
# Don't throw ValidationError here, just return a tuple of strings.
128+
ret=[]
129+
for input, multi in zip(self.inputs, self.multiply):
130+
ret.append(data.get('%s_%s' % (name, input), 0))
131+
return tuple(ret)
132+
133+
def _has_changed(self, initial_value, data_value):
134+
# data_value comes from value_from_datadict(): A tuple of strings.
135+
if initial_value is None:
136+
return bool(set(data_value)!=set([u'0']))
137+
assert isinstance(initial_value, datetime.timedelta), initial_value
138+
initial=tuple([unicode(i) for i in split_seconds(initial_value.days*SECS_PER_DAY+initial_value.seconds, self.inputs, self.multiply)])
139+
assert len(initial)==len(data_value), (initial, data_value)
140+
return bool(initial!=data_value)
141+
142+
def main():
143+
assert split_seconds(1000000)==[11, 13, 46, 40]
144+
145+
field=TimedeltaField()
146+
147+
td=datetime.timedelta(days=10, seconds=11)
148+
s=field.get_db_prep_save(td)
149+
assert isinstance(s, int), (s, type(s))
150+
td_again=field.to_python(s)
151+
assert td==td_again, (td, td_again)
152+
153+
td=datetime.timedelta(seconds=11)
154+
s=field.get_db_prep_save(td)
155+
td_again=field.to_python(s)
156+
assert td==td_again, (td, td_again)
157+
158+
field=TimedeltaFormField()
159+
assert field.widget._has_changed(datetime.timedelta(seconds=0), (u'0', u'0', u'0', u'0',)) is False
160+
assert field.widget._has_changed(None, (u'0', u'0', u'0', u'0',)) is False
161+
assert field.widget._has_changed(None, (u'0', u'0')) is False
162+
assert field.widget._has_changed(datetime.timedelta(days=1, hours=2, minutes=3, seconds=4), (u'1', u'2', u'3', u'4',)) is False
163+
164+
for secs, soll, kwargs in [
165+
(100, [0, 0, 1, 40], dict()),
166+
(100, ['0days', '0hours', '1minutes', '40seconds'], dict(with_unit=True)),
167+
(100, ['1minutes', '40seconds'], dict(with_unit=True, remove_leading_zeros=True)),
168+
(100000, ['1days', '3hours'], dict(inputs=['days', 'hours'], with_unit=True, remove_leading_zeros=True)),
169+
]:
170+
ist=split_seconds(secs, **kwargs)
171+
if ist!=soll:
172+
raise Exception('geg=%s soll=%s ist=%s kwargs=%s' % (secs, soll, ist, kwargs))
173+
174+
print "unittest OK"
175+
176+
def split_seconds(secs, inputs=TimedeltaWidget.INPUTS, multiply=TimedeltaWidget.MULTIPLY,
177+
with_unit=False, remove_leading_zeros=False):
178+
ret=[]
179+
assert len(inputs)<=len(multiply), (inputs, multiply)
180+
for input, multi in zip(inputs, multiply):
181+
count, secs = divmod(secs, multi)
182+
if remove_leading_zeros and not ret and not count:
183+
continue
184+
if with_unit:
185+
ret.append('%s%s' % (count, input))
186+
else:
187+
ret.append(count)
188+
return ret
189+
190+
if __name__=='__main__':
191+
main()

0 commit comments

Comments
 (0)