From 0e576f4352cc3998cb8805698d5ac76e2abe916a Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Thu, 4 Aug 2011 11:17:29 +0000 Subject: [PATCH] Add models for new meeting schema and an importer, clean up person import slightly - Legacy-Id: 3282 --- ietf/meeting/models.py | 140 ++++++++++++- ietf/meeting/timedeltafield.py | 191 ++++++++++++++++++ redesign/importing/import-meetings.py | 273 ++++++++++++++++++++++++++ redesign/importing/import-persons.py | 44 ++++- redesign/importing/import-roles.py | 39 +--- redesign/importing/utils.py | 21 +- redesign/name/admin.py | 3 + redesign/name/models.py | 6 + 8 files changed, 668 insertions(+), 49 deletions(-) create mode 100644 ietf/meeting/timedeltafield.py create mode 100755 redesign/importing/import-meetings.py diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index ca2f5d54a..88bc0e229 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -1,3 +1,139 @@ -# Copyright The IETF Trust 2007, All Rights Reserved +# old meeting models can be found in ../proceedings/models.py + +import pytz + +from django.db import models +from timedeltafield import TimedeltaField + +from redesign.group.models import Group +from redesign.person.models import Person +from redesign.name.models import TimeSlotTypeName, SessionStatusName, ConstraintName + +countries = pytz.country_names.items() +countries.sort(lambda x,y: cmp(x[1], y[1])) + +timezones = [(name, name) for name in pytz.common_timezones] +timezones.sort() + +class Meeting(models.Model): + # Number is not an integer any more, in order to be able to accomodate + # interim meetings (and other variations?) + number = models.CharField(max_length=64) + # Date is useful when generating a set of timeslot for this meeting, but + # is not used to determine date for timeslot instances thereafter, as + # they have their own datetime field. + date = models.DateField() + city = models.CharField(blank=True, max_length=255) + country = models.CharField(blank=True, max_length=2, choices=countries) + # We can't derive time-zone from country, as there are some that have + # more than one timezone, and the pytz module doesn't provide timezone + # lookup information for all relevant city/country combinations. + time_zone = models.CharField(blank=True, max_length=255, choices=timezones) + venue_name = models.CharField(blank=True, max_length=255) + venue_addr = models.TextField(blank=True) + break_area = models.CharField(blank=True, max_length=255) + reg_area = models.CharField(blank=True, max_length=255) + + def __str__(self): + return "IETF-%s" % (self.number) + def get_meeting_date (self,offset): + return self.date + datetime.timedelta(days=offset) + # cut-off dates (draft submission cut-of, wg agenda cut-off, minutes + # submission cut-off), and more, are probably methods of this class, + # rather than fields on a Proceedings class. + + @classmethod + def get_first_cut_off(cls): + date = cls.objects.all().order_by('-date')[0].date + offset = datetime.timedelta(days=settings.FIRST_CUTOFF_DAYS) + return date - offset + + @classmethod + def get_second_cut_off(cls): + date = cls.objects.all().order_by('-date')[0].date + offset = datetime.timedelta(days=settings.SECOND_CUTOFF_DAYS) + return date - offset + + @classmethod + def get_ietf_monday(cls): + date = cls.objects.all().order_by('-date')[0].date + return date + datetime.timedelta(days=-date.weekday(), weeks=1) + + +class Room(models.Model): + meeting = models.ForeignKey(Meeting) + name = models.CharField(max_length=255) + + def __unicode__(self): + return self.name + +class TimeSlot(models.Model): + """ + Everything that would appear on the meeting agenda of a meeting is mapped + to a time slot, including breaks (i.e., also NonSession+NonSessionRef. + Sessions are connected to TimeSlots during scheduling. + A template function to populate a meeting with an appropriate set of TimeSlots + is probably also needed. + """ + meeting = models.ForeignKey(Meeting) + type = models.ForeignKey(TimeSlotTypeName) + name = models.CharField(max_length=255) + time = models.DateTimeField() + duration = TimedeltaField() + location = models.ForeignKey(Room, blank=True, null=True) + show_location = models.BooleanField(default=True) + + def __unicode__(self): + location = self.get_location() + if not location: + location = "(no location)" + + return u"%s: %s-%s %s, %s" % (self.meeting.number, self.time.strftime("%m-%d %H:%M"), (self.time + self.duration).strftime("%H:%M"), self.name, location) + + def get_location(self): + location = self.location + if location: + location = location.name + elif self.type_id == "reg": + location = self.meeting.reg_area + elif self.type_id == "break": + location = self.meeting.break_area + + if not self.show_location: + location = "" + + return location + + +class Constraint(models.Model): + """Specifies a constraint on the scheduling between source and + target, e.g. some kind of conflict.""" + meeting = models.ForeignKey(Meeting) + source = models.ForeignKey(Group, related_name="constraint_source_set") + target = models.ForeignKey(Group, related_name="constraint_target_set") + name = models.ForeignKey(ConstraintName) + + def __unicode__(self): + return u"%s %s %s" % (self.source, self.name.lower(), self.target) + +class Session(models.Model): + meeting = models.ForeignKey(Meeting) + timeslot = models.ForeignKey(TimeSlot, null=True, blank=True) # Null until session has been scheduled + group = models.ForeignKey(Group) # The group type determines the session type. BOFs also need to be added as a group. + attendees = models.IntegerField(null=True, blank=True) + agenda_note = models.CharField(blank=True, max_length=255) + # + requested = models.DateTimeField() + requested_by = models.ForeignKey(Person) + requested_duration = TimedeltaField() + comments = models.TextField() + # + status = models.ForeignKey(SessionStatusName) + scheduled = models.DateTimeField(null=True, blank=True) + modified = models.DateTimeField(null=True, blank=True) + +# Agendas, Minutes and Slides are all mapped to Document. + +# IESG history is extracted from GroupHistory, rather than hand coded in a +# separate table. -# Meeting models can be found under ../proceedings/ diff --git a/ietf/meeting/timedeltafield.py b/ietf/meeting/timedeltafield.py new file mode 100644 index 000000000..cc2e538c5 --- /dev/null +++ b/ietf/meeting/timedeltafield.py @@ -0,0 +1,191 @@ +# -*- coding: iso-8859-1 -*- +# $Id: TimedeltaField.py 1787 2011-04-20 07:09:57Z tguettler $ +# $HeadURL: svn+ssh://svnserver/svn/djangotools/trunk/dbfields/TimedeltaField.py $ + +# from http://djangosnippets.org/snippets/1060/ with some fixes + +# Python +import datetime + +# Django +from django import forms +from django.db import models +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext_lazy as _ + +#Djangotools +#from djangotools.utils.southutils import add_introspection_rules_from_baseclass + +SECS_PER_DAY=3600*24 + +class TimedeltaField(models.Field): + u''' + Store Python's datetime.timedelta in an integer column. + Most database systems only support 32 bit integers by default. + ''' + __metaclass__ = models.SubfieldBase + empty_strings_allowed = False + + def __init__(self, *args, **kwargs): + super(TimedeltaField, self).__init__(*args, **kwargs) + + def to_python(self, value): + if (value is None) or isinstance(value, datetime.timedelta): + return value + + try: + # else try to convert to int (e.g. from string) + value = int(value) + except (TypeError, ValueError): + raise exceptions.ValidationError( + _("This value must be an integer or a datetime.timedelta.")) + + return datetime.timedelta(seconds=value) + + def get_internal_type(self): + return 'IntegerField' + + def get_db_prep_lookup(self, lookup_type, value, connection=None, prepared=False): + raise NotImplementedError() # SQL WHERE + + def get_db_prep_save(self, value, connection=None, prepared=False): + if (value is None) or isinstance(value, int): + return value + return SECS_PER_DAY*value.days+value.seconds + + def formfield(self, *args, **kwargs): + defaults={'form_class': TimedeltaFormField} + defaults.update(kwargs) + return super(TimedeltaField, self).formfield(*args, **defaults) + + def value_to_string(self, obj): + value = self._get_val_from_obj(obj) + return self.get_db_prep_value(value) + +#South Plugin registrieren +#add_introspection_rules_from_baseclass(TimedeltaField, ["^djangotools\.dbfields\.TimedeltaField"]) + +class TimedeltaFormField(forms.Field): + default_error_messages = { + 'invalid': _(u'Enter a whole number.'), + } + + def __init__(self, *args, **kwargs): + defaults={'widget': TimedeltaWidget} + defaults.update(kwargs) + super(TimedeltaFormField, self).__init__(*args, **defaults) + + def clean(self, value): + # value comes from Timedelta.Widget.value_from_datadict(): tuple of strings + super(TimedeltaFormField, self).clean(value) + assert len(value)==len(self.widget.inputs), (value, self.widget.inputs) + i=0 + for value, multiply in zip(value, self.widget.multiply): + try: + i+=int(value)*multiply + except ValueError, TypeError: + raise forms.ValidationError(self.error_messages['invalid']) + return i + +class TimedeltaWidget(forms.Widget): + INPUTS=['days', 'hours', 'minutes', 'seconds'] + MULTIPLY=[60*60*24, 60*60, 60, 1] + def __init__(self, attrs=None): + self.widgets=[] + if not attrs: + attrs={} + inputs=attrs.get('inputs', self.INPUTS) + multiply=[] + for input in inputs: + assert input in self.INPUTS, (input, self.INPUT) + self.widgets.append(forms.TextInput(attrs=attrs)) + multiply.append(self.MULTIPLY[self.INPUTS.index(input)]) + self.inputs=inputs + self.multiply=multiply + super(TimedeltaWidget, self).__init__(attrs) + + def render(self, name, value, attrs): + if value is None: + values=[0 for i in self.inputs] + elif isinstance(value, datetime.timedelta): + values=split_seconds(value.days*SECS_PER_DAY+value.seconds, self.inputs, self.multiply) + elif isinstance(value, int): + # initial data from model + values=split_seconds(value, self.inputs, self.multiply) + else: + assert isinstance(value, tuple), (value, type(value)) + assert len(value)==len(self.inputs), (value, self.inputs) + values=value + id=attrs.pop('id') + assert not attrs, attrs + rendered=[] + for input, widget, val in zip(self.inputs, self.widgets, values): + rendered.append(u'%s %s' % (_(input), widget.render('%s_%s' % (name, input), val))) + return mark_safe('
%s
' % (id, ' '.join(rendered))) + + def value_from_datadict(self, data, files, name): + # Don't throw ValidationError here, just return a tuple of strings. + ret=[] + for input, multi in zip(self.inputs, self.multiply): + ret.append(data.get('%s_%s' % (name, input), 0)) + return tuple(ret) + + def _has_changed(self, initial_value, data_value): + # data_value comes from value_from_datadict(): A tuple of strings. + if initial_value is None: + return bool(set(data_value)!=set([u'0'])) + assert isinstance(initial_value, datetime.timedelta), initial_value + initial=tuple([unicode(i) for i in split_seconds(initial_value.days*SECS_PER_DAY+initial_value.seconds, self.inputs, self.multiply)]) + assert len(initial)==len(data_value), (initial, data_value) + return bool(initial!=data_value) + +def main(): + assert split_seconds(1000000)==[11, 13, 46, 40] + + field=TimedeltaField() + + td=datetime.timedelta(days=10, seconds=11) + s=field.get_db_prep_save(td) + assert isinstance(s, int), (s, type(s)) + td_again=field.to_python(s) + assert td==td_again, (td, td_again) + + td=datetime.timedelta(seconds=11) + s=field.get_db_prep_save(td) + td_again=field.to_python(s) + assert td==td_again, (td, td_again) + + field=TimedeltaFormField() + assert field.widget._has_changed(datetime.timedelta(seconds=0), (u'0', u'0', u'0', u'0',)) is False + assert field.widget._has_changed(None, (u'0', u'0', u'0', u'0',)) is False + assert field.widget._has_changed(None, (u'0', u'0')) is False + assert field.widget._has_changed(datetime.timedelta(days=1, hours=2, minutes=3, seconds=4), (u'1', u'2', u'3', u'4',)) is False + + for secs, soll, kwargs in [ + (100, [0, 0, 1, 40], dict()), + (100, ['0days', '0hours', '1minutes', '40seconds'], dict(with_unit=True)), + (100, ['1minutes', '40seconds'], dict(with_unit=True, remove_leading_zeros=True)), + (100000, ['1days', '3hours'], dict(inputs=['days', 'hours'], with_unit=True, remove_leading_zeros=True)), + ]: + ist=split_seconds(secs, **kwargs) + if ist!=soll: + raise Exception('geg=%s soll=%s ist=%s kwargs=%s' % (secs, soll, ist, kwargs)) + + print "unittest OK" + +def split_seconds(secs, inputs=TimedeltaWidget.INPUTS, multiply=TimedeltaWidget.MULTIPLY, + with_unit=False, remove_leading_zeros=False): + ret=[] + assert len(inputs)<=len(multiply), (inputs, multiply) + for input, multi in zip(inputs, multiply): + count, secs = divmod(secs, multi) + if remove_leading_zeros and not ret and not count: + continue + if with_unit: + ret.append('%s%s' % (count, input)) + else: + ret.append(count) + return ret + +if __name__=='__main__': + main() diff --git a/redesign/importing/import-meetings.py b/redesign/importing/import-meetings.py new file mode 100755 index 000000000..980a5ec4f --- /dev/null +++ b/redesign/importing/import-meetings.py @@ -0,0 +1,273 @@ +#!/usr/bin/python + +import sys, os, re, datetime, pytz + +basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) +sys.path = [ basedir ] + sys.path + +from ietf import settings +settings.USE_DB_REDESIGN_PROXY_CLASSES = False + +from django.core import management +management.setup_environ(settings) + + +from ietf.idtracker.models import AreaDirector, IETFWG, Acronym, IRTF +from ietf.meeting.models import * +from ietf.proceedings.models import Meeting as MeetingOld, MeetingVenue, MeetingRoom, NonSession, WgMeetingSession +from redesign.person.models import * +from redesign.importing.utils import get_or_create_email, old_person_to_person +from redesign.name.utils import name + + +# imports Meeting, MeetingVenue, MeetingRoom, NonSession, WgMeetingSession + +# assumptions: +# - persons have been imported +# - groups have been imported + + +session_status_mapping = { + 1: name(SessionStatusName, "schedw", "Waiting for Scheduling"), + 2: name(SessionStatusName, "apprw", "Waiting for Approval"), + 3: name(SessionStatusName, "appr", "Approved"), + 4: name(SessionStatusName, "sched", "Scheduled"), + 5: name(SessionStatusName, "canceled", "Canceled"), + 6: name(SessionStatusName, "disappr", "Disapproved"), + } + +session_status_mapping[0] = session_status_mapping[1] # assume broken statuses of 0 are actually cancelled + +session_slot = name(TimeSlotTypeName, "session", "Session") +break_slot = name(TimeSlotTypeName, "break", "Break") +registration_slot = name(TimeSlotTypeName, "reg", "Registration") +other_slot = name(TimeSlotTypeName, "other", "Other") +conflict_constraint = name(ConstraintName, "conflict", "Conflicts with") + +system_person = Person.objects.get(name="(System)") +obviously_bogus_date = datetime.date(1970, 1, 1) + +for o in MeetingOld.objects.all(): + print "importing Meeting", o.pk + + try: + m = Meeting.objects.get(number=o.meeting_num) + except: + m = Meeting(number="%s" % o.meeting_num) + m.pk = o.pk + + m.date = o.start_date + m.city = o.city + + # convert country to code + country_code = None + for k, v in pytz.country_names.iteritems(): + if v == o.country: + country_code = k + break + + if not country_code: + country_fallbacks = { + 'USA': 'US' + } + + country_code = country_fallbacks.get(o.country) + + if country_code: + m.country = country_code + else: + print "unknown country", o.country + + + time_zone_lookup = { + ("IE", "Dublin"): "Europe/Dublin", + ("FR", "Paris"): "Europe/Paris", + ("CA", "Vancouver"): "America/Vancouver", + ("CZ", "Prague"): "Europe/Prague", + ("US", "Chicago"): "America/Chicago", + ("US", "Anaheim"): "America/Los_Angeles", + ("NL", "Maastricht"): "Europe/Amsterdam", + ("CN", "Beijing"): "Asia/Shanghai", + ("JP", "Hiroshima"): "Asia/Tokyo", + ("SE", "Stockholm"): "Europe/Stockholm", + ("US", "San Francisco"): "America/Los_Angeles", + ("US", "Minneapolis"): "America/Menominee", + } + + m.time_zone = time_zone_lookup.get((m.country, m.city), "") + if not m.time_zone: + print "unknown time zone for", m.get_country_display(), m.city + + m.venue_name = "" # no source for that in the old DB? + m.venue_addr = "" # no source for that in the old DB? + try: + venue = o.meetingvenue_set.get() + m.break_area = venue.break_area_name + m.reg_area = venue.reg_area_name + except MeetingVenue.DoesNotExist: + pass + + # missing following semi-used fields from old Meeting: end_date, + # ack, agenda_html/agenda_text, future_meeting + + m.save() + +for o in MeetingRoom.objects.all(): + print "importing MeetingRoom", o.pk + + try: + r = Room.objects.get(pk=o.pk) + except Room.DoesNotExist: + r = Room(pk=o.pk) + + r.meeting = Meeting.objects.get(number="%s" % o.meeting_id) + r.name = o.room_name + r.save() + +def parse_time_desc(o): + t = o.time_desc.replace(' ', '') + + start_time = datetime.time(int(t[0:2]), int(t[2:4])) + end_time = datetime.time(int(t[5:7]), int(t[7:9])) + + d = o.meeting.start_date + datetime.timedelta(days=o.day_id) + + return (datetime.datetime.combine(d, start_time), datetime.datetime.combine(d, end_time)) + +def get_or_create_session_timeslot(meeting_time, room): + meeting = Meeting.objects.get(number=s.meeting_id) + starts, ends = parse_time_desc(meeting_time) + + try: + slot = TimeSlot.objects.get(meeting=meeting, time=starts, location=room) + except TimeSlot.DoesNotExist: + slot = TimeSlot(meeting=meeting, time=starts, location=room) + + slot.type = session_slot + slot.name = meeting_time.session_name.session_name if meeting_time.session_name_id else "Unknown" + slot.duration = ends - starts + slot.save() + + return slot + +requested_length_mapping = { + None: 0, # assume NULL to mean nothing particular requested + "1": 60 * 60, + "2": 90 * 60, + "3": 120 * 60, + "4": 150 * 60, + } + +for o in WgMeetingSession.objects.all().order_by("pk"): + # num_session is unfortunately not quite reliable, seems to be + # right for 1 or 2 but not 3 and it's sometimes null + sessions = o.num_session or 1 + if o.sched_time_id3: + sessions = 3 + + print "importing WgMeetingSession", o.pk, "subsessions", sessions + + for i in range(1, 1 + sessions): + try: + s = Session.objects.get(pk=o.pk + (i - 1) * 10000) + except: + s = Session(pk=o.pk) + + s.meeting = Meeting.objects.get(number=o.meeting_id) + sched_time_id = getattr(o, "sched_time_id%s" % i) + if sched_time_id: + room = Room.objects.get(pk=getattr(o, "sched_room_id%s_id" % i)) + s.timeslot = get_or_create_session_timeslot(sched_time_id, room) + else: + s.timeslot = None + if o.irtf: + s.group = Group.objects.get(acronym=IRTF.objects.get(pk=o.group_acronym_id).acronym.lower()) + else: + acronym = Acronym.objects.get(pk=o.group_acronym_id) + if o.group_acronym_id < 0: + # this wasn't actually a WG session, but rather a tutorial + # or similar, don't create a session but instead modify + # the time slot appropriately + if not s.timeslot: + print "IGNORING unscheduled non-WG-session", acronym.name + continue + s.timeslot.name = acronym.name + s.timeslot.type = other_slot + s.timeslot.save() + continue + + s.group = Group.objects.get(acronym=acronym.acronym) + s.attendees = o.number_attendee + s.agenda_note = (o.special_agenda_note or "").strip() + s.requested = o.requested_date or obviously_bogus_date + s.requested_by = old_person_to_person(o.requested_by) if o.requested_by else system_person + s.requested_duration = requested_length_mapping[getattr(o, "length_session%s" % i)] + comments = [] + special_req = (o.special_req or "").strip() + if special_req: + comments.append(u"Special requests:\n" + special_req) + conflict_other = (o.conflict_other or "").strip() + if conflict_other: + comments.append(u"Other conflicts:\n" + conflict_other) + s.comments = u"\n\n".join(comments) + s.status = session_status_mapping[o.status_id or 5] + + s.scheduled = o.scheduled_date + s.modified = o.last_modified_date + + s.save() + + conflict = (getattr(o, "conflict%s" % i) or "").replace(",", " ").lower() + conflicting_groups = [g for g in conflict.split() if g] + for target in Group.objects.filter(acronym__in=conflicting_groups): + Constraint.objects.get_or_create( + meeting=s.meeting, + source=target, + target=s.group, + name=conflict_constraint) + + + # missing following fields from old: ts_status_id (= third session + # status id, third session required AD approval), + # combined_room_id1/2, combined_time_id1/2 + +for o in NonSession.objects.all().order_by('pk').select_related("meeting"): + print "importing NonSession", o.pk + + if o.time_desc in ("", "0"): + print "IGNORING non-scheduled NonSession", o.non_session_ref.name + continue + + meeting = Meeting.objects.get(number=o.meeting_id) + + # some non-sessions are scheduled every day, but only if there's a + # session nearby, figure out which days this corresponds to + days = set() + if o.day_id == None: + t = datetime.time(int(o.time_desc[-4:][0:2]), int(o.time_desc[-4:][2:4])) + + for s in TimeSlot.objects.filter(meeting=meeting): + if s.time.time() == t: + days.add((s.time.date() - meeting.date).days) + else: + days.add(o.day_id) + + for day in days: + o.day_id = day + starts, ends = parse_time_desc(o) + name = o.non_session_ref.name + + try: + slot = TimeSlot.objects.get(meeting=meeting, time=starts, name=name) + except TimeSlot.DoesNotExist: + slot = TimeSlot(meeting=meeting, time=starts, name=name) + + slot.location = None + if o.non_session_ref_id == 1: + slot.type = registration_slot + else: + slot.type = break_slot + + slot.duration = ends - starts + slot.show_location = o.show_break_location + slot.save() diff --git a/redesign/importing/import-persons.py b/redesign/importing/import-persons.py index 82e490596..4cb6a935c 100755 --- a/redesign/importing/import-persons.py +++ b/redesign/importing/import-persons.py @@ -11,16 +11,20 @@ settings.USE_DB_REDESIGN_PROXY_CLASSES = False from django.core import management management.setup_environ(settings) -from ietf.idtracker.models import AreaDirector, IETFWG +from ietf.idtracker.models import AreaDirector, IETFWG, PersonOrOrgInfo, IDAuthor from redesign.person.models import * -from redesign.importing.utils import get_or_create_email +from redesign.importing.utils import clean_email_address, get_or_create_email # creates system person and email -# imports AreaDirector persons that are connected to an IETFWG +# imports AreaDirector persons that are connected to an IETFWG, +# persons from IDAuthor, announcement originators from Announcements, +# requesters from WgMeetingSession -# should probably also import the old person/email tables +# should probably import +# PersonOrOrgInfo/PostalAddress/EmailAddress/PhoneNumber fully +# make sure special system user/email is created print "creating (System) person and email" try: system_person = Person.objects.get(name="(System)") @@ -48,7 +52,39 @@ system_email = Email.objects.get_or_create( defaults=dict(active=True, person=system_person) ) +# AreaDirector from IETFWG persons for o in AreaDirector.objects.filter(ietfwg__in=IETFWG.objects.all()).exclude(area=None).distinct().order_by("pk").iterator(): print "importing AreaDirector (from IETFWG) persons", o.pk get_or_create_email(o, create_fake=False) + +# WgMeetingSession persons +for o in PersonOrOrgInfo.objects.filter(wgmeetingsession__pk__gte=1).distinct().order_by("pk").iterator(): + print "importing WgMeetingSession persons", o.pk, o.first_name.encode('utf-8'), o.last_name.encode('utf-8') + + get_or_create_email(o, create_fake=False) + +# Announcement persons +for o in PersonOrOrgInfo.objects.filter(announcement__announcement_id__gte=1).order_by("pk").distinct(): + print "importing Announcement originator", o.pk, o.first_name.encode('utf-8'), o.last_name.encode('utf-8') + + email = get_or_create_email(o, create_fake=False) + +# IDAuthor persons +for o in IDAuthor.objects.all().order_by('id').select_related('person').iterator(): + print "importing IDAuthor", o.id, o.person_id, o.person.first_name.encode('utf-8'), o.person.last_name.encode('utf-8') + email = get_or_create_email(o, create_fake=True) + + # we may also need to import email address used specifically for + # the document + addr = clean_email_address(o.email() or "") + if addr and addr.lower() != email.address.lower(): + try: + e = Email.objects.get(address=addr) + if e.person != email.person or e.active != False: + e.person = email.person + e.active = False + e.save() + except Email.DoesNotExist: + Email.objects.create(address=addr, person=email.person, active=False) + diff --git a/redesign/importing/import-roles.py b/redesign/importing/import-roles.py index c2ce32f3c..a19850086 100755 --- a/redesign/importing/import-roles.py +++ b/redesign/importing/import-roles.py @@ -15,24 +15,19 @@ from redesign.person.models import * from redesign.group.models import * from redesign.name.models import * from redesign.name.utils import name -from redesign.importing.utils import old_person_to_email, clean_email_address, get_or_create_email +from redesign.importing.utils import get_or_create_email -from ietf.idtracker.models import IESGLogin, AreaDirector, IDAuthor, PersonOrOrgInfo, WGChair, WGEditor, WGSecretary, WGTechAdvisor, ChairsHistory, Role as OldRole, Acronym, IRTFChair +from ietf.idtracker.models import IESGLogin, AreaDirector, PersonOrOrgInfo, WGChair, WGEditor, WGSecretary, WGTechAdvisor, ChairsHistory, Role as OldRole, Acronym, IRTFChair # assumptions: +# - persons have been imported # - groups have been imported -# PersonOrOrgInfo/PostalAddress/EmailAddress/PhoneNumber are not -# imported, although some information is retrieved from those - # imports IESGLogin, AreaDirector, WGEditor, WGChair, IRTFChair, # WGSecretary, WGTechAdvisor, NomCom chairs from ChairsHistory, -# -# also imports persons from IDAuthor, announcement originators from -# Announcements -# FIXME: should probably import Role +# FIXME: should probably import Role, LegacyWgPassword, LegacyLiaisonUser area_director_role = name(RoleName, "ad", "Area Director") inactive_area_director_role = name(RoleName, "ex-ad", "Ex-Area Director", desc="Inactive Area Director") @@ -176,29 +171,3 @@ for o in AreaDirector.objects.all(): Role.objects.get_or_create(name=role_type, group=area, email=email) -# Announcement persons -for o in PersonOrOrgInfo.objects.filter(announcement__announcement_id__gte=1).distinct(): - print "importing Announcement originator", o.person_or_org_tag, o.first_name.encode('utf-8'), o.last_name.encode('utf-8') - - o.person = o # satisfy the get_or_create_email interface - - email = get_or_create_email(o, create_fake=False) - -# IDAuthor persons -for o in IDAuthor.objects.all().order_by('id').select_related('person').iterator(): - print "importing IDAuthor", o.id, o.person_id, o.person.first_name.encode('utf-8'), o.person.last_name.encode('utf-8') - email = get_or_create_email(o, create_fake=True) - - # we may also need to import email address used specifically for - # the document - addr = clean_email_address(o.email() or "") - if addr and addr.lower() != email.address.lower(): - try: - e = Email.objects.get(address=addr) - if e.person != email.person or e.active != False: - e.person = email.person - e.active = False - e.save() - except Email.DoesNotExist: - Email.objects.create(address=addr, person=email.person, active=False) - diff --git a/redesign/importing/utils.py b/redesign/importing/utils.py index ceec5a4a5..84a07c0dc 100644 --- a/redesign/importing/utils.py +++ b/redesign/importing/utils.py @@ -11,7 +11,10 @@ def clean_email_address(addr): return addr def old_person_to_person(person): - return Person.objects.get(id=person.pk) + try: + return Person.objects.get(id=person.pk) + except Person.DoesNotExist: + return Person.objects.get(alias__name=u"%s %s" % (person.first_name, person.last_name)) def old_person_to_email(person): hardcoded_emails = { 'Dinara Suleymanova': "dinaras@ietf.org" } @@ -19,25 +22,27 @@ def old_person_to_email(person): return clean_email_address(person.email()[1] or hardcoded_emails.get("%s %s" % (person.first_name, person.last_name)) or "") def get_or_create_email(o, create_fake): - # take person on o and get or create new Email and Person objects - email = old_person_to_email(o.person) + # take o.person (or o) and get or create new Email and Person objects + person = o.person if hasattr(o, "person") else o + + email = old_person_to_email(person) if not email: if create_fake: - email = u"unknown-email-%s-%s" % (o.person.first_name, o.person.last_name) - print ("USING FAKE EMAIL %s for %s %s %s" % (email, o.person.pk, o.person.first_name, o.person.last_name)).encode('utf-8') + email = u"unknown-email-%s-%s" % (person.first_name, person.last_name) + print ("USING FAKE EMAIL %s for %s %s %s" % (email, person.pk, person.first_name, person.last_name)).encode('utf-8') else: - print ("NO EMAIL FOR %s %s %s %s %s" % (o.__class__, o.pk, o.person.pk, o.person.first_name, o.person.last_name)).encode('utf-8') + print ("NO EMAIL FOR %s %s %s %s %s" % (o.__class__, o.pk, person.pk, person.first_name, person.last_name)).encode('utf-8') return None e, _ = Email.objects.select_related("person").get_or_create(address=email) if not e.person: - n = u"%s %s" % (o.person.first_name, o.person.last_name) + n = u"%s %s" % (person.first_name, person.last_name) asciified = unaccent.asciify(n) aliases = Alias.objects.filter(name__in=(n, asciified)) if aliases: p = aliases[0].person else: - p = Person.objects.create(id=o.person.pk, name=n, ascii=asciified) + p = Person.objects.create(id=person.pk, name=n, ascii=asciified) # FIXME: fill in address? Alias.objects.create(name=n, person=p) diff --git a/redesign/name/admin.py b/redesign/name/admin.py index 5f73fb542..347be27d7 100644 --- a/redesign/name/admin.py +++ b/redesign/name/admin.py @@ -21,3 +21,6 @@ admin.site.register(DocInfoTagName, NameAdmin) admin.site.register(StdLevelName, NameAdmin) admin.site.register(IntendedStdLevelName, NameAdmin) admin.site.register(BallotPositionName, NameAdmin) +admin.site.register(SessionStatusName, NameAdmin) +admin.site.register(TimeSlotTypeName, NameAdmin) +admin.site.register(ConstraintName, NameAdmin) diff --git a/redesign/name/models.py b/redesign/name/models.py index 0eb9af699..62997a9ff 100644 --- a/redesign/name/models.py +++ b/redesign/name/models.py @@ -62,6 +62,12 @@ class IntendedStdLevelName(NameModel): Practice, Historic, ...""" class BallotPositionName(NameModel): """ Yes, NoObjection, Abstain, Discuss, Recuse """ +class SessionStatusName(NameModel): + """ Waiting for Approval, Approved, Waiting for Scheduling, Scheduled, Cancelled, Disapproved""" +class TimeSlotTypeName(NameModel): + """Session, Break, Registration""" +class ConstraintName(NameModel): + """Conflict""" def get_next_iesg_states(iesg_state):