Michael Hall has proposed merging
lp:~mhall119/loco-directory/meeting-in-localtime into lp:loco-directory.
Requested reviews:
loco-directory-dev (loco-directory-dev)
Related bugs:
Bug #711754 in LoCo Team Directory: "meeting start/end time uses UTC, not
local time"
https://bugs.launchpad.net/loco-directory/+bug/711754
Bug #758733 in LoCo Team Directory: "ics file of Loco Calendar does not
contain timezone data"
https://bugs.launchpad.net/loco-directory/+bug/758733
For more details, see:
https://code.launchpad.net/~mhall119/loco-directory/meeting-in-localtime/+merge/65038
Overview
========
Allow Meetings and Virtual Events to be in localtime, fix iCal timezone data.
Details
=======
1)Adds a timezone field to the Team object to serve as the default timezone for
that team.
2)Extracts Venue localization code into common.mixins.LocalTimeMixin for reuse
3)Updates TeamEvent to use the team's default timezone if no venue is defined
(Virtual Event)
4)Adds timezone field to Meeting to specify it's local timezone. If set to
'team' it will use the team's default timezone.
5)Updates ical generation code to specify that times are in UTC, let the client
calendar convert as desired.
--
https://code.launchpad.net/~mhall119/loco-directory/meeting-in-localtime/+merge/65038
Your team loco-directory-dev is requested to review the proposed merge of
lp:~mhall119/loco-directory/meeting-in-localtime into lp:loco-directory.
=== added file 'loco_directory/common/mixins.py'
--- loco_directory/common/mixins.py 1970-01-01 00:00:00 +0000
+++ loco_directory/common/mixins.py 2011-06-17 17:21:18 +0000
@@ -0,0 +1,31 @@
+import pytz
+
+class LocalTimeMixin(object):
+ '''Provides methods for converting between UTC and localtime
+
+ Classes using this should provide the timezone string as self.tz
+ '''
+
+ def get_timezone(self):
+ 'Returns the pytz timezone instance for this object\'s timezone'
+ try:
+ return pytz.timezone(self.tz)
+ except:
+ return pytz.utc
+ timezone = property(get_timezone)
+
+ def tolocaltime(self, dt):
+ 'Converts a datetime in UTC to a datetime in this object\'s timezone'
+ if dt is None:
+ return None
+ as_utc = pytz.utc.localize(dt)
+ return as_utc.astimezone(self.timezone)
+
+ def fromlocaltime(self, dt):
+ 'Converts a datetime in this object\'s timezone to a datetime in UTC'
+ if dt is None:
+ return None
+ local = self.timezone.localize(dt)
+ return local.astimezone(pytz.utc)
+
+
=== modified file 'loco_directory/events/forms.py'
--- loco_directory/events/forms.py 2011-02-17 21:24:49 +0000
+++ loco_directory/events/forms.py 2011-06-17 17:21:18 +0000
@@ -82,6 +82,12 @@
if begin is not None and end is not None:
self.cleaned_data['date_begin'] = venue.fromlocaltime(begin)
self.cleaned_data['date_end'] = venue.fromlocaltime(end)
+ elif self.teams:
+ begin = self.cleaned_data.get('date_begin', None)
+ end = self.cleaned_data.get('date_end', None)
+ if begin is not None and end is not None:
+ self.cleaned_data['date_begin'] = self.teams[0].fromlocaltime(begin)
+ self.cleaned_data['date_end'] = self.teams[0].fromlocaltime(end)
return self.cleaned_data
def grouped_venue_list(self):
=== modified file 'loco_directory/events/models.py'
--- loco_directory/events/models.py 2011-06-10 18:09:20 +0000
+++ loco_directory/events/models.py 2011-06-17 17:21:18 +0000
@@ -4,6 +4,7 @@
from venues.models import Venue
from django.utils.translation import ugettext_lazy as _
from common.shortcuts import queryset_sum, queryset_count
+from common.mixins import LocalTimeMixin
ATTENDEE_PROMISE_CHOICES = (
('sure', _('attending')),
@@ -11,6 +12,7 @@
('not', _('not attending')),
)
+import pytz
import datetime
class BaseEvent(models.Model):
@@ -33,8 +35,8 @@
"""
event = cal.add('vevent')
event.add('uid').value = str(self.id)
- event.add('dtstart').value = self.date_begin
- event.add('dtend').value = self.date_end
+ event.add('dtstart').value = pytz.utc.localize(self.date_begin)
+ event.add('dtend').value = pytz.utc.localize(self.date_end)
event.add('categories').value = ['Ubuntu Loco Team Event']
event.add('summary').value = self.name or ''
event.add('description').value = self.description or ''
@@ -115,7 +117,7 @@
def has_location(self):
return self.filter(venue__longitude__isnull=False, venue__latitude__isnull=False)
-class TeamEvent(BaseEvent):
+class TeamEvent(BaseEvent, LocalTimeMixin):
"""
a event of one or more teams
"""
@@ -129,28 +131,31 @@
def __unicode__(self):
return "%s %s %s" % (self.name, self.venue, self.date_begin or "")
+ def get_tz(self):
+ if self.venue:
+ return self.venue.tz
+ elif self.channel:
+ first = None
+ for t in self.teams.all():
+ if not first:
+ first = t
+ if t.tz != 'UTC':
+ return t.tz
+ return first.tz
+ else:
+ return 'UTC'
+ tz = property(get_tz)
+
def get_local_begin(self):
- if self.venue:
- return self.venue.tolocaltime(self.date_begin)
- else:
- return self.date_begin
+ return self.tolocaltime(self.date_begin)
def set_local_begin(self, local_begin):
- if self.venue:
- self.date_begin = self.venue.fromlocaltime(local_begin)
- else:
- self.date_begin = local_begin
+ self.date_begin = self.fromlocaltime(local_begin)
local_date_begin = property(get_local_begin)
def get_local_end(self):
- if self.venue:
- return self.venue.tolocaltime(self.date_end)
- else:
- return self.date_end
+ return self.tolocaltime(self.date_end)
def set_local_end(self, local_end):
- if self.venue:
- self.date_end = self.venue.fromlocaltime(local_end)
- else:
- self.date_end = local_end
+ self.date_end = self.fromlocaltime(local_end)
local_date_end = property(get_local_end, set_local_end)
def first_team(self):
=== modified file 'loco_directory/meetings/forms.py'
--- loco_directory/meetings/forms.py 2011-01-25 17:05:58 +0000
+++ loco_directory/meetings/forms.py 2011-06-17 17:21:18 +0000
@@ -65,14 +65,51 @@
"""
class Meta(BaseMeetingForm.Meta):
model = TeamMeeting
+ fields = (
+ 'name',
+ 'date_begin',
+ 'date_end',
+ 'meeting_tz',
+ 'channel',
+ 'minutes',
+ 'chair',
+ )
exclude = ('teams', 'date_created', 'logs')
def __init__(self, teams=None, *args, **kargs):
super(TeamMeetingForm, self).__init__(*args, **kargs)
- if teams:
- self.fields['chair'].choices = grouped_user_list(teams)
+ self.teams = teams
+ if self.teams:
+ self.fields['chair'].choices = grouped_user_list(self.teams)
elif self.instance.teams:
self.fields['chair'].choices = grouped_user_list(self.instance.teams.iterator())
+ self.initial['date_begin'] = self.instance.local_date_begin
+ self.initial['date_end'] = self.instance.local_date_end
+
+ def clean(self):
+ tz = self.cleaned_data.get('meeting_tz', 'team')
+ if tz is not None:
+ begin = self.cleaned_data.get('date_begin', None)
+ end = self.cleaned_data.get('date_end', None)
+ # If this is a new meeting it won't have any teams set at this point
+ # So if we're using the team default timezone, we need to look it
+ # up from the passed list of teams
+ if tz == 'team' and not self.instance.pk:
+ if self.teams:
+ local_to = self.teams[0]
+ else:
+ # we don't have enough information to localize the time
+ return self.cleaned_data
+ # If this is an existing meeting, or we specified a timezone for it
+ # we just need to tell the instance what timezone to localize to
+ else:
+ self.instance.meeting_tz = tz
+ local_to = self.instance
+
+ if begin is not None and end is not None:
+ self.cleaned_data['date_begin'] = local_to.fromlocaltime(begin)
+ self.cleaned_data['date_end'] = local_to.fromlocaltime(end)
+ return self.cleaned_data
def save(self):
start_date = self.cleaned_data['date_begin']
=== added file 'loco_directory/meetings/migrations/0006_add_timezone.py'
--- loco_directory/meetings/migrations/0006_add_timezone.py 1970-01-01 00:00:00 +0000
+++ loco_directory/meetings/migrations/0006_add_timezone.py 2011-06-17 17:21:18 +0000
@@ -0,0 +1,154 @@
+
+from south.db import db
+from django.db import models
+from meetings.models import *
+
+class Migration:
+
+ def forwards(self, orm):
+
+ # Adding field 'TeamMeeting.meeting_tz'
+ db.add_column('meetings_teammeeting', 'meeting_tz', orm['meetings.teammeeting:meeting_tz'])
+
+ # Changing field 'BaseMeeting.date_created'
+ # (to signature: django.db.models.fields.DateTimeField(default=datetime.datetime(2011, 6, 17, 15, 22, 56, 255453), db_index=True))
+ db.alter_column('meetings_basemeeting', 'date_created', orm['meetings.basemeeting:date_created'])
+
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'TeamMeeting.meeting_tz'
+ db.delete_column('meetings_teammeeting', 'meeting_tz')
+
+ # Changing field 'BaseMeeting.date_created'
+ # (to signature: django.db.models.fields.DateTimeField(default=datetime.datetime(2010, 12, 25, 17, 7, 54, 656457), db_index=True))
+ db.alter_column('meetings_basemeeting', 'date_created', orm['meetings.basemeeting:date_created'])
+
+
+
+ models = {
+ 'auth.group': {
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'unique_together': "(('content_type', 'codename'),)"},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'meetings.agendaitem': {
+ 'created_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'log': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'meeting': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'agenda'", 'to': "orm['meetings.BaseMeeting']"}),
+ 'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['userprofiles.UserProfile']"}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['meetings.AgendaItem']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'meetings.basemeeting': {
+ 'chair': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['userprofiles.UserProfile']", 'null': 'True', 'blank': 'True'}),
+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '150', 'null': 'True'}),
+ 'date_begin': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 6, 17, 15, 22, 56, 255453)', 'db_index': 'True'}),
+ 'date_end': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'logs': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'minutes': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'})
+ },
+ 'meetings.teammeeting': {
+ 'basemeeting_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['meetings.BaseMeeting']", 'unique': 'True', 'primary_key': 'True'}),
+ 'meeting_tz': ('django.db.models.fields.CharField', [], {'default': "'team'", 'max_length': '32'}),
+ 'teams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['teams.Team']", 'db_index': 'True', 'symmetrical': 'False'})
+ },
+ 'teams.continent': {
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.TextField', [], {'max_length': '50'})
+ },
+ 'teams.country': {
+ 'continents': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['teams.Continent']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.TextField', [], {'max_length': '100'})
+ },
+ 'teams.language': {
+ 'code': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150', 'null': 'True'})
+ },
+ 'teams.team': {
+ 'Meta': {'db_table': "'teams'"},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'admin_profiles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['userprofiles.UserProfile']", 'symmetrical': 'False'}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'approved_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'city': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'contact_profiles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['userprofiles.UserProfile']", 'symmetrical': 'False'}),
+ 'countries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['teams.Country']", 'symmetrical': 'False'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'expires_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'flickr_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'forum_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'irc_chan': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True', 'blank': 'True'}),
+ 'languages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['teams.Language']", 'symmetrical': 'False'}),
+ 'lp_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True'}),
+ 'microbloghashtag': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'ml_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'mugshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'null': 'True'}),
+ 'owner_profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owner'", 'null': 'True', 'to': "orm['userprofiles.UserProfile']"}),
+ 'picasa_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'pixie_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'provides_support': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'spr': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'tz': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '32'}),
+ 'web_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'wiki_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
+ },
+ 'userprofiles.userprofile': {
+ 'aim': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'blog': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'flickr': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'identica': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'irc': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'mugshot': ('django.db.models.fields.URLField', [], {'max_length': '150', 'null': 'True', 'blank': 'True'}),
+ 'picasa': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'realname': ('django.db.models.fields.CharField', [], {'max_length': '150', 'blank': 'True'}),
+ 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'tz': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '32'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}),
+ 'xmpp': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['meetings']
=== modified file 'loco_directory/meetings/models.py'
--- loco_directory/meetings/models.py 2011-03-19 13:42:01 +0000
+++ loco_directory/meetings/models.py 2011-06-17 17:21:18 +0000
@@ -3,10 +3,12 @@
from teams.models import *
from django.utils.translation import ugettext_lazy as _
from common.shortcuts import queryset_sum, queryset_count
+from common.mixins import LocalTimeMixin
+import pytz
import datetime
-class BaseMeeting(models.Model):
+class BaseMeeting(models.Model, LocalTimeMixin):
"""
a simple basic meeting
"""
@@ -19,6 +21,20 @@
date_created = models.DateTimeField(help_text=_('the date and time when the event was created'), default=datetime.datetime.now(), db_index=True)
chair = models.ForeignKey(UserProfile, verbose_name=_('Meeting Chair'), blank=True, null=True)
+ tz = 'UTC'
+
+ def get_local_begin(self):
+ return self.tolocaltime(self.date_begin)
+ def set_local_begin(self, local_begin):
+ self.date_begin = self.fromlocaltime(local_begin)
+ local_date_begin = property(get_local_begin)
+
+ def get_local_end(self):
+ return self.tolocaltime(self.date_end)
+ def set_local_end(self, local_end):
+ self.date_end = self.fromlocaltime(local_end)
+ local_date_end = property(get_local_end, set_local_end)
+
def __unicode__(self):
return self.name
@@ -28,8 +44,8 @@
"""
event = cal.add('vevent')
event.add('uid').value = str(self.id)
- event.add('dtstart').value = self.date_begin
- event.add('dtend').value = self.date_end
+ event.add('dtstart').value = pytz.utc.localize(self.date_begin)
+ event.add('dtend').value = pytz.utc.localize(self.date_end)
event.add('categories').value = ['Ubuntu Team Meeting']
event.add('summary').value = self.name or ''
@@ -59,8 +75,24 @@
a meeting of one or more teams
"""
teams = models.ManyToManyField(Team, help_text=_('teams of this meeting.'), verbose_name=_('Teams Participating'), db_index=True)
+ meeting_tz = models.CharField(max_length=32, verbose_name=_('Timezone'), default='', choices=[('team', 'Team Default')]+[(tz, tz) for tz in pytz.all_timezones], blank=False, null=False)
template = 'meetings/team_meeting_li.inc.html'
-
+
+ def get_tz(self):
+ if self.meeting_tz != 'team':
+ return self.meeting_tz
+ elif self.teams:
+ first = None
+ for t in self.teams.all():
+ if not first:
+ first = t
+ if t.tz != 'UTC':
+ return t.tz
+ return first.tz
+ else:
+ return 'UTC'
+ tz = property(get_tz)
+
def __unicode__(self):
return "%s %s" % (self.name, self.date_begin or "")
=== added file 'loco_directory/teams/migrations/0013_add_timezone.py'
--- loco_directory/teams/migrations/0013_add_timezone.py 1970-01-01 00:00:00 +0000
+++ loco_directory/teams/migrations/0013_add_timezone.py 2011-06-17 17:21:18 +0000
@@ -0,0 +1,119 @@
+
+from south.db import db
+from django.db import models
+from teams.models import *
+
+class Migration:
+
+ def forwards(self, orm):
+
+ # Adding field 'Team.tz'
+ db.add_column('teams', 'tz', orm['teams.team:tz'])
+
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'Team.tz'
+ db.delete_column('teams', 'tz')
+
+
+
+ models = {
+ 'auth.group': {
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'unique_together': "(('content_type', 'codename'),)"},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'teams.continent': {
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.TextField', [], {'max_length': '50'})
+ },
+ 'teams.country': {
+ 'continents': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['teams.Continent']", 'symmetrical': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.TextField', [], {'max_length': '100'})
+ },
+ 'teams.language': {
+ 'code': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150', 'null': 'True'})
+ },
+ 'teams.team': {
+ 'Meta': {'db_table': "'teams'"},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'admin_profiles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['userprofiles.UserProfile']", 'symmetrical': 'False'}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'approved_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'city': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'contact_profiles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['userprofiles.UserProfile']", 'symmetrical': 'False'}),
+ 'countries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['teams.Country']", 'symmetrical': 'False'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'expires_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'flickr_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'forum_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'irc_chan': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True', 'blank': 'True'}),
+ 'languages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['teams.Language']", 'symmetrical': 'False'}),
+ 'lp_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True'}),
+ 'microbloghashtag': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'ml_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'mugshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'null': 'True'}),
+ 'owner_profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owner'", 'null': 'True', 'to': "orm['userprofiles.UserProfile']"}),
+ 'picasa_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'pixie_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'provides_support': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'spr': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'tz': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '32'}),
+ 'web_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'wiki_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
+ },
+ 'userprofiles.userprofile': {
+ 'aim': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'blog': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'flickr': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'identica': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'irc': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'mugshot': ('django.db.models.fields.URLField', [], {'max_length': '150', 'null': 'True', 'blank': 'True'}),
+ 'picasa': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'realname': ('django.db.models.fields.CharField', [], {'max_length': '150', 'blank': 'True'}),
+ 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'tz': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '32'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}),
+ 'xmpp': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['teams']
=== modified file 'loco_directory/teams/models.py'
--- loco_directory/teams/models.py 2011-06-10 18:09:20 +0000
+++ loco_directory/teams/models.py 2011-06-17 17:21:18 +0000
@@ -1,7 +1,10 @@
+import pytz
+
from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.utils import flat_list
+from common.mixins import LocalTimeMixin
from userprofiles.models import UserProfile
class Language(models.Model):
@@ -66,7 +69,7 @@
list_of_teams = [list(a.related_teams) for a in countries_without_continent()]
return len(flat_list(list_of_teams))>0
-class Team(models.Model):
+class Team(models.Model, LocalTimeMixin):
lp_name = models.CharField(_("Launchpad Team ID"), max_length=40, null=True)
name = models.CharField(_("Team Name"), max_length=80, null=True)
countries = models.ManyToManyField(Country)
@@ -99,6 +102,7 @@
help_text=_("Your Pix.ie Username"))
microbloghashtag = models.CharField(max_length=50, help_text=_('Hash Tag used for microbloggers to mention this team'), verbose_name=_('Microblogging Hash Tag'), null=True, blank=True)
active = models.BooleanField(_("Active Team"), default=True)
+ tz = models.CharField(max_length=32, verbose_name=_('Default Timezone'), default='UTC', choices=[(tz, tz) for tz in pytz.all_timezones], blank=False, null=False, help_text=_('The most commonly used timezone for this Team.'))
objects = TeamManager()
=== modified file 'loco_directory/templates/meetings/team_meeting_detail_basic.inc.html'
--- loco_directory/templates/meetings/team_meeting_detail_basic.inc.html 2011-03-18 15:13:56 +0000
+++ loco_directory/templates/meetings/team_meeting_detail_basic.inc.html 2011-06-17 17:21:18 +0000
@@ -5,10 +5,10 @@
<tr>
<th class="form-item-label" scope="row">{% trans "When:" %}</th>
<td class="form-item-value">
- {% ifequal team_meeting_object.date_begin|date team_meeting_object.date_end|date %}
- {{ team_meeting_object.date_begin|date:"D, d N Y H:i" }} - {{ team_meeting_object.date_end|date:"H:i T (O)" }}
+ {% ifequal team_meeting_object.local_date_begin|date team_meeting_object.local_date_end|date %}
+ {{ team_meeting_object.local_date_begin|date:"D, d N Y H:i" }} - {{ team_meeting_object.local_date_end|date:"H:i T (O)" }}
{% else %}
- {{ team_meeting_object.date_begin|date:"D, d N Y H:i" }} - {{ team_meeting_object.date_end|date:"D, d N Y H:i T (O)" }}
+ {{ team_meeting_object.local_date_begin|date:"D, d N Y H:i" }} - {{ team_meeting_object.local_date_end|date:"D, d N Y H:i T (O)" }}
{% endifequal %}
</td>
</tr>
=== modified file 'loco_directory/templates/meetings/team_meeting_li.inc.html'
--- loco_directory/templates/meetings/team_meeting_li.inc.html 2011-03-16 18:35:53 +0000
+++ loco_directory/templates/meetings/team_meeting_li.inc.html 2011-06-17 17:21:18 +0000
@@ -1,5 +1,5 @@
{% load i18n %}
{% comment %}TRANSLATORS: If event has a venue, show: date "in" city{% endcomment %}
<li>
- <a title="{{team_event.date_begin|date:"M d"}}" href="{% url team-meeting-detail team_event.first_team.lp_name team_event.id %}">{{ team_event.name }}</a> {% trans "on" %} {{ team_event.date_begin|date:"l, d N Y" }} {% trans "at " %}{{ team_event.date_begin|date:"H:i T" }}
+ <a title="{{team_event.date_begin|date:"M d"}}" href="{% url team-meeting-detail team_event.first_team.lp_name team_event.id %}">{{ team_event.name }}</a> {% trans "on" %} {{ team_event.local_date_begin|date:"l, d N Y" }} {% trans "at " %}{{ team_event.local_date_begin|date:"H:i T" }}
</li>
=== modified file 'loco_directory/templates/meetings/team_meeting_list.inc.html'
--- loco_directory/templates/meetings/team_meeting_list.inc.html 2011-04-07 13:10:41 +0000
+++ loco_directory/templates/meetings/team_meeting_list.inc.html 2011-06-17 17:21:18 +0000
@@ -24,8 +24,8 @@
{% endif %}
{% endfor %}
</td>
- <td class="meeting_begin no-wrap">{{team_meeting.date_begin|date}} {{ team_meeting.date_begin|date:"H:i T"}}</td>
- <td class="meeting_end no-wrap">{{team_meeting.date_end|date}} {{ team_meeting.date_end|date:"H:i T"}}</td>
+ <td class="meeting_begin no-wrap">{{team_meeting.local_date_begin|date}} {{ team_meeting.local_date_begin|date:"H:i T"}}</td>
+ <td class="meeting_end no-wrap">{{team_meeting.local_date_end|date}} {{ team_meeting.local_date_end|date:"H:i T"}}</td>
</tr>
{% endfor %}
</tbody>
=== modified file 'loco_directory/templates/meetings/team_meetings_rss.xml'
--- loco_directory/templates/meetings/team_meetings_rss.xml 2011-03-16 18:35:53 +0000
+++ loco_directory/templates/meetings/team_meetings_rss.xml 2011-06-17 17:21:18 +0000
@@ -10,10 +10,10 @@
<title> {{ e.name }}</title>
<link>{{base}}{% url team-meeting-detail e.first_team.lp_name e.id %}</link>
<description>{% trans "When:" %} {% spaceless %}
- {% ifequal e.date_begin|date e.date_end|date %}
- {{ e.date_begin|date:"D, d N Y H:i" }} - {{ e.date_end|date:"H:i O" }}
+ {% ifequal e.date_begin|date e.local_date_end|date %}
+ {{ e.local_date_begin|date:"D, d N Y H:i" }} - {{ e.local_date_end|date:"H:i T" }}
{% else %}
- {{ e.date_begin|date:"D, d N Y H:i" }} - {{ e.date_end|date:"D, d N Y H:i O" }}
+ {{ e.local_date_begin|date:"D, d N Y H:i" }} - {{ e.local_date_end|date:"D, d N Y H:i T" }}
{% endifequal %}
{% endspaceless %}</description>
<guid>{{base}}{% url team-meeting-detail e.first_team.lp_name e.id %}</guid>
=== modified file 'loco_directory/venues/models.py'
--- loco_directory/venues/models.py 2011-03-16 18:35:53 +0000
+++ loco_directory/venues/models.py 2011-06-17 17:21:18 +0000
@@ -3,6 +3,7 @@
from django.db.models import permalink
from teams.models import Country
+from common.mixins import LocalTimeMixin
import pytz
@@ -12,7 +13,7 @@
"""
pass
-class Venue(models.Model):
+class Venue(models.Model, LocalTimeMixin):
"""
a venue
"""
@@ -46,21 +47,6 @@
""" get the absolute url for the venue """
return ('venue-detail', [self.country or 'no-country', self.id])
- def get_timezone(self):
- try:
- return pytz.timezone(self.tz)
- except:
- return pytz.utc
- timezone = property(get_timezone)
-
- def tolocaltime(self, dt):
- as_utc = pytz.utc.localize(dt)
- return as_utc.astimezone(self.timezone)
-
- def fromlocaltime(self, dt):
- local = self.timezone.localize(dt)
- return local.astimezone(pytz.utc)
-
def save(self, *args, **kargs):
if self.id:
from events.models import TeamEvent
=== added file 'requirements.txt'
--- requirements.txt 1970-01-01 00:00:00 +0000
+++ requirements.txt 2011-06-17 17:21:18 +0000
@@ -0,0 +1,23 @@
+#
+# These version numbers have been copied from the packages on our Ubuntu Lucid
+# deployment machine.
+#
+# NOTE: They are only approximate to the real package versions. They do not
+# include patches applied by the package maintainers.
+#
+
+Django==1.1.2
+South==0.6
+distribute==0.6.10
+django-openid-auth==0.2
+psycopg2==2.0.13
+python-openid==2.2.4
+pytz==2010b
+python-dateutil==1.5
+vobject==0.8.1c
+simplejson==2.0.9
+
+# Non-standdard dependencies
+-f http://launchpad.net/launchpadlib/trunk/1.6.0/+download/launchpadlib-1.6.0.tar.gz#egg=launchpadlib-1.6.0
+launchpadlib==1.6.0
+
_______________________________________________
Mailing list: https://launchpad.net/~loco-directory-dev
Post to : [email protected]
Unsubscribe : https://launchpad.net/~loco-directory-dev
More help : https://help.launchpad.net/ListHelp