Convert Event start and end times to UTC, display them in local time but store them in UTC. Show ical feeds for the user and teams. Fixes #28, Fixes #59
This commit is contained in:
parent
2e21c5789e
commit
d440e5b173
29 changed files with 493 additions and 47 deletions
66
events/feeds.py
Normal file
66
events/feeds.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
|
from django_ical.views import ICalFeed
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from .models.events import Event, CommonEvent
|
||||||
|
from .models.profiles import UserProfile, Team, Organization
|
||||||
|
|
||||||
|
class AbstractEventCalendarFeed(ICalFeed):
|
||||||
|
def item_guid(self, event):
|
||||||
|
site = Site.objects.get(id=1)
|
||||||
|
return '%s@%s' % (event.id, site.domain)
|
||||||
|
|
||||||
|
def item_link(self, event):
|
||||||
|
return event.get_full_url()
|
||||||
|
|
||||||
|
def item_title(self, event):
|
||||||
|
return event.name
|
||||||
|
|
||||||
|
def item_description(self, event):
|
||||||
|
return event.summary
|
||||||
|
|
||||||
|
def item_start_datetime(self, event):
|
||||||
|
return event.start_time
|
||||||
|
|
||||||
|
def item_end_datetime(self, event):
|
||||||
|
return event.end_time
|
||||||
|
|
||||||
|
def item_created(self, event):
|
||||||
|
return event.created_time
|
||||||
|
|
||||||
|
def item_location(self, event):
|
||||||
|
if event.place is not None:
|
||||||
|
return str(event.place)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def item_geo(self, event):
|
||||||
|
if event.place is not None:
|
||||||
|
latitude = event.place.latitude or None
|
||||||
|
longitude = event.place.longitude or None
|
||||||
|
return (latitude, longitude)
|
||||||
|
elif event.team.city is not None:
|
||||||
|
latitude = event.team.city.latitude
|
||||||
|
longitude = event.team.city.longitude
|
||||||
|
return (latitude, longitude)
|
||||||
|
return None
|
||||||
|
|
||||||
|
class UserEventsCalendar(AbstractEventCalendarFeed):
|
||||||
|
timezone = 'UTC'
|
||||||
|
|
||||||
|
def get_object(self, request, account_secret):
|
||||||
|
return UserProfile.objects.get(secret_key=account_secret)
|
||||||
|
|
||||||
|
def items(self, user):
|
||||||
|
return Event.objects.filter(attendees=user, end_time__gt=timezone.now()).order_by('-start_time')
|
||||||
|
|
||||||
|
class TeamEventsCalendar(AbstractEventCalendarFeed):
|
||||||
|
timezone = 'UTC'
|
||||||
|
|
||||||
|
def get_object(self, request, team_id):
|
||||||
|
return Team.objects.get(id=team_id)
|
||||||
|
|
||||||
|
def items(self, team):
|
||||||
|
return Event.objects.filter(team=team, end_time__gt=timezone.now()).order_by('-start_time')
|
||||||
|
|
|
@ -2,12 +2,14 @@ from django.utils.safestring import mark_safe
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms.widgets import TextInput, Media
|
from django.forms.widgets import TextInput, Media
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from .models.locale import Country, SPR, City
|
from .models.locale import Country, SPR, City
|
||||||
from .models.profiles import Team, UserProfile
|
from .models.profiles import Team, UserProfile
|
||||||
from .models.events import Event, EventComment ,CommonEvent, Place, EventPhoto
|
from .models.events import Event, EventComment ,CommonEvent, Place, EventPhoto
|
||||||
|
|
||||||
|
import pytz
|
||||||
from datetime import time
|
from datetime import time
|
||||||
from time import strptime, strftime
|
from time import strptime, strftime
|
||||||
|
|
||||||
|
@ -171,6 +173,20 @@ class TeamEventForm(forms.ModelForm):
|
||||||
'start_time': DateTimeWidget,
|
'start_time': DateTimeWidget,
|
||||||
'end_time': DateTimeWidget
|
'end_time': DateTimeWidget
|
||||||
}
|
}
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
super().__init__(*args, **kargs)
|
||||||
|
event_tz = pytz.timezone(self.instance.tz)
|
||||||
|
if self.instance.local_start_time: self.initial['start_time'] = self.instance.local_start_time
|
||||||
|
if self.instance.local_end_time: self.initial['end_time'] = self.instance.local_end_time
|
||||||
|
print("Initial: %s" % self.initial)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
event_tz = pytz.timezone(self.instance.tz)
|
||||||
|
print("Clean: %s" % cleaned_data)
|
||||||
|
cleaned_data['start_time'] = pytz.utc.localize(timezone.make_naive(event_tz.localize(timezone.make_naive(cleaned_data['start_time']))))
|
||||||
|
cleaned_data['end_time'] = pytz.utc.localize(timezone.make_naive(event_tz.localize(timezone.make_naive(cleaned_data['end_time']))))
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
class NewTeamEventForm(forms.ModelForm):
|
class NewTeamEventForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -180,6 +196,20 @@ class NewTeamEventForm(forms.ModelForm):
|
||||||
'start_time': DateTimeWidget,
|
'start_time': DateTimeWidget,
|
||||||
'end_time': DateTimeWidget
|
'end_time': DateTimeWidget
|
||||||
}
|
}
|
||||||
|
def __init__(self, *args, **kargs):
|
||||||
|
super().__init__(*args, **kargs)
|
||||||
|
event_tz = pytz.timezone(self.instance.tz)
|
||||||
|
if self.instance.local_start_time: self.initial['start_time'] = self.instance.local_start_time
|
||||||
|
if self.instance.local_end_time: self.initial['end_time'] = self.instance.local_end_time
|
||||||
|
print("Initial: %s" % self.initial)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
event_tz = pytz.timezone(self.instance.tz)
|
||||||
|
print("Clean: %s" % cleaned_data)
|
||||||
|
cleaned_data['start_time'] = pytz.utc.localize(timezone.make_naive(event_tz.localize(timezone.make_naive(cleaned_data['start_time']))))
|
||||||
|
cleaned_data['end_time'] = pytz.utc.localize(timezone.make_naive(event_tz.localize(timezone.make_naive(cleaned_data['end_time']))))
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
class DeleteEventForm(forms.Form):
|
class DeleteEventForm(forms.Form):
|
||||||
confirm = forms.BooleanField(label="Yes, delete event", required=True)
|
confirm = forms.BooleanField(label="Yes, delete event", required=True)
|
||||||
|
|
79
events/location.py
Normal file
79
events/location.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
import math
|
||||||
|
import pytz
|
||||||
|
import datetime
|
||||||
|
import geocoder
|
||||||
|
|
||||||
|
KM_PER_DEGREE_LAT = 110.574
|
||||||
|
KM_PER_DEGREE_LNG = 111.320 # At the equator
|
||||||
|
|
||||||
|
class TimezoneChoices():
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for tz in pytz.all_timezones:
|
||||||
|
yield (tz, tz)
|
||||||
|
|
||||||
|
def get_client_ip(request):
|
||||||
|
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||||
|
if x_forwarded_for:
|
||||||
|
ip = x_forwarded_for.split(',')[0]
|
||||||
|
else:
|
||||||
|
ip = request.META.get('REMOTE_ADDR')
|
||||||
|
return ip
|
||||||
|
|
||||||
|
def get_geoip(request):
|
||||||
|
client_ip = get_client_ip(request)
|
||||||
|
if client_ip == '127.0.0.1' or client_ip == 'localhost':
|
||||||
|
if settings.DEBUG:
|
||||||
|
client_ip = '8.8.8.8' # Try Google's server
|
||||||
|
print("Client is localhost, using 8.8.8.8 for geoip instead")
|
||||||
|
else:
|
||||||
|
raise Exception("Client is localhost")
|
||||||
|
|
||||||
|
g = geocoder.ip(client_ip)
|
||||||
|
return g
|
||||||
|
|
||||||
|
def get_bounding_box(center, radius):
|
||||||
|
minlat = center[0]-(radius/KM_PER_DEGREE_LAT)
|
||||||
|
maxlat = center[0]+(radius/KM_PER_DEGREE_LAT)
|
||||||
|
minlng = center[1]-(radius/(KM_PER_DEGREE_LNG*math.cos(math.radians(center[0]))))
|
||||||
|
maxlng = center[1]+(radius/(KM_PER_DEGREE_LNG*math.cos(math.radians(center[0]))))
|
||||||
|
return (minlat, maxlat, minlng, maxlng)
|
||||||
|
|
||||||
|
def distance(center1, center2):
|
||||||
|
avglat = (center2[0] + center1[0])/2
|
||||||
|
dlat = (center2[0] - center1[0]) * KM_PER_DEGREE_LAT
|
||||||
|
dlng = (center2[1] - center1[1]) * (KM_PER_DEGREE_LNG*math.cos(math.radians(avglat)))
|
||||||
|
dkm = math.sqrt((dlat*dlat) + (dlng*dlng))
|
||||||
|
print("Distance between %s and %s is %s" % (center1, center2, dkm))
|
||||||
|
return dkm
|
||||||
|
|
||||||
|
def city_distance_from(ll, city):
|
||||||
|
if city is not None and city.latitude is not None and city.longitude is not None:
|
||||||
|
return distance((ll), (city.latitude, city.longitude))
|
||||||
|
else:
|
||||||
|
return 99999
|
||||||
|
|
||||||
|
def team_distance_from(ll, team):
|
||||||
|
if team.city is not None:
|
||||||
|
return city_distance_from(ll, team.city)
|
||||||
|
else:
|
||||||
|
return 99999
|
||||||
|
|
||||||
|
def event_distance_from(ll, event):
|
||||||
|
if event.place is not None and event.place.latitude is not None and event.place.longitude is not None:
|
||||||
|
return distance((ll), (event.place.latitude, event.place.longitude))
|
||||||
|
if event.team is not None:
|
||||||
|
return team_distance_from(ll, event.team)
|
||||||
|
else:
|
||||||
|
return 99999
|
||||||
|
|
||||||
|
def searchable_distance_from(ll, searchable):
|
||||||
|
if searchable.latitude is not None and searchable.longitude is not None:
|
||||||
|
return distance((ll), (float(searchable.latitude), float(searchable.longitude)))
|
||||||
|
else:
|
||||||
|
return 99999
|
||||||
|
|
||||||
|
|
12
events/management/commands/recreate_searchables.py
Normal file
12
events/management/commands/recreate_searchables.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
from events.models.events import Event, update_event_searchable
|
||||||
|
import urllib
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Regenerated Searchable records from this node'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
for event in Event.objects.all():
|
||||||
|
update_event_searchable(event)
|
31
events/migrations/0021_add_account_secret.py
Normal file
31
events/migrations/0021_add_account_secret.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 2.0 on 2018-03-27 15:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
def gen_unique_keys(apps, schema_editor):
|
||||||
|
UserProfile = apps.get_model('events', 'UserProfile')
|
||||||
|
for profile in UserProfile.objects.all():
|
||||||
|
profile.secret_key = uuid.uuid4()
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('events', '0020_add_event_comments'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='userprofile',
|
||||||
|
name='secret_key',
|
||||||
|
field=models.UUIDField(default=uuid.uuid4),
|
||||||
|
),
|
||||||
|
migrations.RunPython(gen_unique_keys, reverse_code=migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventcomment',
|
||||||
|
name='event',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='events.Event'),
|
||||||
|
),
|
||||||
|
]
|
47
events/migrations/0022_localize_datetimes.py
Normal file
47
events/migrations/0022_localize_datetimes.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# Generated by Django 2.0 on 2018-04-01 15:11
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
def localize_event_datetimes(apps, schema_editor):
|
||||||
|
Event = MyModel = apps.get_model('events', 'Event')
|
||||||
|
for event in Event.objects.all():
|
||||||
|
utc_tz = pytz.timezone('UTC')
|
||||||
|
event_tz = get_event_timezone(event)
|
||||||
|
print("Converting event %s to %s" % (event.id, event_tz))
|
||||||
|
event.start_time = utc_tz.localize(timezone.make_naive(event_tz.localize(timezone.make_naive(event.start_time, pytz.utc))))
|
||||||
|
event.end_time = utc_tz.localize(timezone.make_naive(event_tz.localize(timezone.make_naive(event.end_time, pytz.utc))))
|
||||||
|
event.save()
|
||||||
|
|
||||||
|
def localize_commonevent_datetimes(apps, schema_editor):
|
||||||
|
CommonEvent = MyModel = apps.get_model('events', 'CommonEvent')
|
||||||
|
for event in CommonEvent.objects.all():
|
||||||
|
utc_tz = pytz.timezone('UTC')
|
||||||
|
event_tz = get_event_timezone(event)
|
||||||
|
print("Converting common event %s to %s" % (event.id, event_tz))
|
||||||
|
event.start_time = utc_tz.localize(timezone.make_naive(event_tz.localize(timezone.make_naive(event.start_time, pytz.utc))))
|
||||||
|
event.end_time = utc_tz.localize(timezone.make_naive(event_tz.localize(timezone.make_naive(event.end_time, pytz.utc))))
|
||||||
|
event.save()
|
||||||
|
|
||||||
|
def get_event_timezone(event):
|
||||||
|
if event.place is not None:
|
||||||
|
return pytz.timezone(event.place.tz)
|
||||||
|
elif hasattr(event, 'team') and event.team is not None:
|
||||||
|
return pytz.timezone(event.team.tz)
|
||||||
|
elif hasattr(event, 'city') and event.city is not None:
|
||||||
|
return pytz.timezone(event.city.tz)
|
||||||
|
else:
|
||||||
|
return pytz.timezone("UTC")
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('events', '0021_add_account_secret'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(localize_event_datetimes, reverse_code=migrations.RunPython.noop),
|
||||||
|
migrations.RunPython(localize_commonevent_datetimes, reverse_code=migrations.RunPython.noop),
|
||||||
|
]
|
64
events/migrations/0023_add_searchable_timezone.py
Normal file
64
events/migrations/0023_add_searchable_timezone.py
Normal file
File diff suppressed because one or more lines are too long
|
@ -3,6 +3,7 @@ from django.contrib.sites.models import Site
|
||||||
from django.contrib.auth.models import User, Group
|
from django.contrib.auth.models import User, Group
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.shortcuts import reverse
|
from django.shortcuts import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
@ -13,6 +14,7 @@ from imagekit.processors import ResizeToFill
|
||||||
from .locale import *
|
from .locale import *
|
||||||
from .profiles import *
|
from .profiles import *
|
||||||
from .search import *
|
from .search import *
|
||||||
|
from .. import location
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import pytz
|
import pytz
|
||||||
|
@ -22,14 +24,13 @@ import hashlib
|
||||||
|
|
||||||
SLUG_OK = '-_~'
|
SLUG_OK = '-_~'
|
||||||
|
|
||||||
|
|
||||||
class Place(models.Model):
|
class Place(models.Model):
|
||||||
name = models.CharField(help_text=_('Name of the Place'), max_length=150)
|
name = models.CharField(help_text=_('Name of the Place'), max_length=150)
|
||||||
city = models.ForeignKey(City, verbose_name=_('City'), on_delete=models.CASCADE)
|
city = models.ForeignKey(City, verbose_name=_('City'), on_delete=models.CASCADE)
|
||||||
address = models.CharField(help_text=_('Address with Street and Number'), max_length=150, null=True, blank=True)
|
address = models.CharField(help_text=_('Address with Street and Number'), max_length=150, null=True, blank=True)
|
||||||
longitude = models.FloatField(help_text=_('Longitude in Degrees East'), null=True, blank=True)
|
longitude = models.FloatField(help_text=_('Longitude in Degrees East'), null=True, blank=True)
|
||||||
latitude = models.FloatField(help_text=_('Latitude in Degrees North'), null=True, blank=True)
|
latitude = models.FloatField(help_text=_('Latitude in Degrees North'), null=True, blank=True)
|
||||||
tz = models.CharField(max_length=32, verbose_name=_('Timezone'), default='UTC', choices=[(tz, tz) for tz in pytz.all_timezones], blank=False, null=False)
|
tz = models.CharField(max_length=32, verbose_name=_('Timezone'), default='UTC', choices=location.TimezoneChoices(), blank=False, null=False)
|
||||||
place_url = models.URLField(help_text=_('URL for the Place Homepage'), verbose_name=_('URL of the Place'), max_length=200, blank=True, null=True)
|
place_url = models.URLField(help_text=_('URL for the Place Homepage'), verbose_name=_('URL of the Place'), max_length=200, blank=True, null=True)
|
||||||
cover_img = models.URLField(_("Place photo"), null=True, blank=True)
|
cover_img = models.URLField(_("Place photo"), null=True, blank=True)
|
||||||
|
|
||||||
|
@ -58,8 +59,8 @@ class Event(models.Model):
|
||||||
team = models.ForeignKey(Team, on_delete=models.CASCADE)
|
team = models.ForeignKey(Team, on_delete=models.CASCADE)
|
||||||
parent = models.ForeignKey('CommonEvent', related_name='participating_events', null=True, blank=True, on_delete=models.SET_NULL)
|
parent = models.ForeignKey('CommonEvent', related_name='participating_events', null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
start_time = models.DateTimeField(help_text=_('Local date and time that the event starts'), verbose_name=_('Local Start Time'), db_index=True)
|
start_time = models.DateTimeField(help_text=_('Date and time that the event starts'), verbose_name=_('Start Time'), db_index=True)
|
||||||
end_time = models.DateTimeField(help_text=_('Local date and time that the event ends'), verbose_name=_('Local End Time'), db_index=True)
|
end_time = models.DateTimeField(help_text=_('Date and time that the event ends'), verbose_name=_('End Time'), db_index=True)
|
||||||
summary = models.TextField(help_text=_('Summary of the Event'), blank=True, null=True)
|
summary = models.TextField(help_text=_('Summary of the Event'), blank=True, null=True)
|
||||||
|
|
||||||
place = models.ForeignKey(Place, blank=True, null=True, on_delete=models.CASCADE)
|
place = models.ForeignKey(Place, blank=True, null=True, on_delete=models.CASCADE)
|
||||||
|
@ -68,7 +69,7 @@ class Event(models.Model):
|
||||||
announce_url = models.URLField(verbose_name=_('Announcement'), help_text=_('URL for the announcement'), max_length=200, blank=True, null=True)
|
announce_url = models.URLField(verbose_name=_('Announcement'), help_text=_('URL for the announcement'), max_length=200, blank=True, null=True)
|
||||||
|
|
||||||
created_by = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
created_by = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
||||||
created_time = models.DateTimeField(help_text=_('the date and time when the event was created'), default=datetime.datetime.now, db_index=True)
|
created_time = models.DateTimeField(help_text=_('the date and time when the event was created'), default=timezone.now, db_index=True)
|
||||||
|
|
||||||
tags = models.CharField(verbose_name=_("Keyword Tags"), blank=True, null=True, max_length=128)
|
tags = models.CharField(verbose_name=_("Keyword Tags"), blank=True, null=True, max_length=128)
|
||||||
#image
|
#image
|
||||||
|
@ -76,6 +77,35 @@ class Event(models.Model):
|
||||||
|
|
||||||
attendees = models.ManyToManyField(UserProfile, through='Attendee', related_name="attending", blank=True)
|
attendees = models.ManyToManyField(UserProfile, through='Attendee', related_name="attending", blank=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tz(self):
|
||||||
|
if self.place is not None:
|
||||||
|
return self.place.tz
|
||||||
|
elif self.team is not None:
|
||||||
|
return self.team.tz
|
||||||
|
else:
|
||||||
|
return settings.TIME_ZONE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def local_start_time(self, val=None):
|
||||||
|
if val is not None:
|
||||||
|
self.start_time = val.astimezone(python.utc)
|
||||||
|
else:
|
||||||
|
if self.start_time is None:
|
||||||
|
return None
|
||||||
|
event_tz = pytz.timezone(self.tz)
|
||||||
|
return timezone.make_naive(self.start_time.astimezone(event_tz), event_tz)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def local_end_time(self, val=None):
|
||||||
|
if val is not None:
|
||||||
|
self.end_time = val.astimezone(python.utc)
|
||||||
|
else:
|
||||||
|
if self.end_time is None:
|
||||||
|
return None
|
||||||
|
event_tz = pytz.timezone(self.tz)
|
||||||
|
return timezone.make_naive(self.end_time.astimezone(event_tz), event_tz)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('show-event', kwargs={'event_id': self.id, 'event_slug': self.slug})
|
return reverse('show-event', kwargs={'event_id': self.id, 'event_slug': self.slug})
|
||||||
|
|
||||||
|
@ -112,7 +142,7 @@ def update_event_searchable(event):
|
||||||
searchable = Searchable(event_uri)
|
searchable = Searchable(event_uri)
|
||||||
searchable.origin_node = origin_url
|
searchable.origin_node = origin_url
|
||||||
searchable.federation_node = origin_url
|
searchable.federation_node = origin_url
|
||||||
searchable.federation_time = datetime.datetime.now()
|
searchable.federation_time = timezone.now()
|
||||||
|
|
||||||
searchable.event_url = event_url
|
searchable.event_url = event_url
|
||||||
|
|
||||||
|
@ -124,6 +154,7 @@ def update_event_searchable(event):
|
||||||
searchable.group_name = event.team.name
|
searchable.group_name = event.team.name
|
||||||
searchable.start_time = event.start_time
|
searchable.start_time = event.start_time
|
||||||
searchable.end_time = event.end_time
|
searchable.end_time = event.end_time
|
||||||
|
searchable.tz = event.tz
|
||||||
searchable.cost = 0
|
searchable.cost = 0
|
||||||
searchable.tags = event.tags
|
searchable.tags = event.tags
|
||||||
if (event.place is not None):
|
if (event.place is not None):
|
||||||
|
@ -176,7 +207,7 @@ class Attendee(models.Model):
|
||||||
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
||||||
role = models.SmallIntegerField(_("Role"), choices=ROLES, default=NORMAL, db_index=True)
|
role = models.SmallIntegerField(_("Role"), choices=ROLES, default=NORMAL, db_index=True)
|
||||||
status = models.SmallIntegerField(_("Attending?"), choices=STATUSES, default=YES, db_index=True)
|
status = models.SmallIntegerField(_("Attending?"), choices=STATUSES, default=YES, db_index=True)
|
||||||
joined_date = models.DateTimeField(default=datetime.datetime.now)
|
joined_date = models.DateTimeField(default=timezone.now)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def role_name(self):
|
def role_name(self):
|
||||||
|
@ -212,7 +243,7 @@ class EventComment(MPTTModel):
|
||||||
author = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
author = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
||||||
event = models.ForeignKey(Event, related_name='comments', on_delete=models.CASCADE)
|
event = models.ForeignKey(Event, related_name='comments', on_delete=models.CASCADE)
|
||||||
body = models.TextField()
|
body = models.TextField()
|
||||||
created_time = models.DateTimeField(default=datetime.datetime.now, db_index=True)
|
created_time = models.DateTimeField(default=timezone.now, db_index=True)
|
||||||
status = models.SmallIntegerField(choices=STATUSES, default=APPROVED, db_index=True)
|
status = models.SmallIntegerField(choices=STATUSES, default=APPROVED, db_index=True)
|
||||||
parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True, on_delete=models.SET_NULL)
|
parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
|
@ -224,8 +255,8 @@ class CommonEvent(models.Model):
|
||||||
organization = models.ForeignKey(Organization, null=True, blank=True, on_delete=models.CASCADE)
|
organization = models.ForeignKey(Organization, null=True, blank=True, on_delete=models.CASCADE)
|
||||||
parent = models.ForeignKey('CommonEvent', related_name='sub_events', null=True, blank=True, on_delete=models.SET_NULL)
|
parent = models.ForeignKey('CommonEvent', related_name='sub_events', null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
|
||||||
start_time = models.DateTimeField(help_text=_('Local date and time that the event starts'), verbose_name=_('Local Start Time'), db_index=True)
|
start_time = models.DateTimeField(help_text=_('Date and time that the event starts'), verbose_name=_('Start Time'), db_index=True)
|
||||||
end_time = models.DateTimeField(help_text=_('Local date and time that the event ends'), verbose_name=_('Local End Time'), db_index=True)
|
end_time = models.DateTimeField(help_text=_('Date and time that the event ends'), verbose_name=_('End Time'), db_index=True)
|
||||||
summary = models.TextField(help_text=_('Summary of the Event'), blank=True, null=True)
|
summary = models.TextField(help_text=_('Summary of the Event'), blank=True, null=True)
|
||||||
|
|
||||||
country = models.ForeignKey(Country, null=True, blank=True, on_delete=models.SET_NULL)
|
country = models.ForeignKey(Country, null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
@ -237,7 +268,7 @@ class CommonEvent(models.Model):
|
||||||
announce_url = models.URLField(verbose_name=_('Announcement'), help_text=_('URL for the announcement'), max_length=200, blank=True, null=True)
|
announce_url = models.URLField(verbose_name=_('Announcement'), help_text=_('URL for the announcement'), max_length=200, blank=True, null=True)
|
||||||
|
|
||||||
created_by = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
created_by = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
||||||
created_time = models.DateTimeField(help_text=_('the date and time when the event was created'), default=datetime.datetime.now, db_index=True)
|
created_time = models.DateTimeField(help_text=_('the date and time when the event was created'), default=timezone.now, db_index=True)
|
||||||
|
|
||||||
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=False, null=True)
|
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=False, null=True)
|
||||||
topics = models.ManyToManyField('Topic', blank=True)
|
topics = models.ManyToManyField('Topic', blank=True)
|
||||||
|
|
|
@ -8,7 +8,9 @@ from imagekit.models import ProcessedImageField
|
||||||
from imagekit.processors import ResizeToFill
|
from imagekit.processors import ResizeToFill
|
||||||
|
|
||||||
from .locale import *
|
from .locale import *
|
||||||
|
from .. import location
|
||||||
|
|
||||||
|
import uuid
|
||||||
import pytz
|
import pytz
|
||||||
import datetime
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -18,7 +20,7 @@ class UserProfile(models.Model):
|
||||||
|
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
realname = models.CharField(verbose_name=_("Real Name"), max_length=150, blank=True)
|
realname = models.CharField(verbose_name=_("Real Name"), max_length=150, blank=True)
|
||||||
tz = models.CharField(max_length=32, verbose_name=_('Timezone'), default='UTC', choices=[(tz, tz) for tz in pytz.all_timezones], blank=False, null=False)
|
tz = models.CharField(max_length=32, verbose_name=_('Timezone'), default='UTC', choices=location.TimezoneChoices(), blank=False, null=False)
|
||||||
avatar = ProcessedImageField(verbose_name=_("Photo Image"),
|
avatar = ProcessedImageField(verbose_name=_("Photo Image"),
|
||||||
upload_to='avatars',
|
upload_to='avatars',
|
||||||
processors=[ResizeToFill(128, 128)],
|
processors=[ResizeToFill(128, 128)],
|
||||||
|
@ -31,6 +33,8 @@ class UserProfile(models.Model):
|
||||||
|
|
||||||
send_notifications = models.BooleanField(verbose_name=_('Send notification emails'), default=True)
|
send_notifications = models.BooleanField(verbose_name=_('Send notification emails'), default=True)
|
||||||
|
|
||||||
|
secret_key = models.UUIDField(default=uuid.uuid4, editable=True)
|
||||||
|
|
||||||
categories = models.ManyToManyField('Category', blank=True)
|
categories = models.ManyToManyField('Category', blank=True)
|
||||||
topics = models.ManyToManyField('Topic', blank=True)
|
topics = models.ManyToManyField('Topic', blank=True)
|
||||||
|
|
||||||
|
@ -196,7 +200,7 @@ class Team(models.Model):
|
||||||
cover_img = models.URLField(_("Team Photo"), null=True, blank=True)
|
cover_img = models.URLField(_("Team Photo"), null=True, blank=True)
|
||||||
languages = models.ManyToManyField(Language, blank=True)
|
languages = models.ManyToManyField(Language, blank=True)
|
||||||
active = models.BooleanField(_("Active Team"), default=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.'))
|
tz = models.CharField(max_length=32, verbose_name=_('Default Timezone'), default='UTC', choices=location.TimezoneChoices(), blank=False, null=False, help_text=_('The most commonly used timezone for this Team.'))
|
||||||
|
|
||||||
members = models.ManyToManyField(UserProfile, through='Member', related_name="memberships", blank=True)
|
members = models.ManyToManyField(UserProfile, through='Member', related_name="memberships", blank=True)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .. import location
|
||||||
|
|
||||||
|
import pytz
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
# Provides a searchable index of events that may belong to this site or a federated site
|
# Provides a searchable index of events that may belong to this site or a federated site
|
||||||
|
@ -17,6 +22,7 @@ class Searchable(models.Model):
|
||||||
latitude = models.DecimalField(max_digits=12, decimal_places=8, null=True, blank=True)
|
latitude = models.DecimalField(max_digits=12, decimal_places=8, null=True, blank=True)
|
||||||
start_time = models.DateTimeField()
|
start_time = models.DateTimeField()
|
||||||
end_time = models.DateTimeField()
|
end_time = models.DateTimeField()
|
||||||
|
tz = models.CharField(max_length=32, verbose_name=_('Default Timezone'), default='UTC', choices=location.TimezoneChoices(), blank=False, null=False, help_text=_('The most commonly used timezone for this Team.'))
|
||||||
cost = models.PositiveSmallIntegerField(default=0, blank=True)
|
cost = models.PositiveSmallIntegerField(default=0, blank=True)
|
||||||
tags = models.CharField(blank=True, null=True, max_length=128)
|
tags = models.CharField(blank=True, null=True, max_length=128)
|
||||||
|
|
||||||
|
@ -27,6 +33,26 @@ class Searchable(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return u'%s' % (self.event_url)
|
return u'%s' % (self.event_url)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def local_start_time(self, val=None):
|
||||||
|
if val is not None:
|
||||||
|
self.start_time = val.astimezone(python.utc)
|
||||||
|
else:
|
||||||
|
if self.start_time is None:
|
||||||
|
return None
|
||||||
|
event_tz = pytz.timezone(self.tz)
|
||||||
|
return timezone.make_naive(self.start_time.astimezone(event_tz), event_tz)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def local_end_time(self, val=None):
|
||||||
|
if val is not None:
|
||||||
|
self.end_time = val.astimezone(python.utc)
|
||||||
|
else:
|
||||||
|
if self.end_time is None:
|
||||||
|
return None
|
||||||
|
event_tz = pytz.timezone(self.tz)
|
||||||
|
return timezone.make_naive(self.end_time.astimezone(event_tz), event_tz)
|
||||||
|
|
||||||
class SearchableSerializer(serializers.ModelSerializer):
|
class SearchableSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Searchable
|
model = Searchable
|
||||||
|
|
|
@ -139,6 +139,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
29
get_together/static/js/jquery-ui-lookup.js
vendored
29
get_together/static/js/jquery-ui-lookup.js
vendored
|
@ -28,6 +28,7 @@
|
||||||
self.options.search(this.value, function(searchText, results){
|
self.options.search(this.value, function(searchText, results){
|
||||||
if (searchText != self.searchField[0].value) return ;
|
if (searchText != self.searchField[0].value) return ;
|
||||||
|
|
||||||
|
self.current_data = results;
|
||||||
self.element.empty();
|
self.element.empty();
|
||||||
var selected = " selected"
|
var selected = " selected"
|
||||||
self.element.append('<option value="">--------</option>')
|
self.element.append('<option value="">--------</option>')
|
||||||
|
@ -43,7 +44,33 @@
|
||||||
open: function(event) {
|
open: function(event) {
|
||||||
this._super()
|
this._super()
|
||||||
this.searchField.focus()
|
this.searchField.focus()
|
||||||
}
|
},
|
||||||
|
_select: function( item, event ) {
|
||||||
|
var oldIndex = this.element[ 0 ].selectedIndex;
|
||||||
|
|
||||||
|
// Change native select element
|
||||||
|
this.element[ 0 ].selectedIndex = item.index;
|
||||||
|
this.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) );
|
||||||
|
this._setAria( item );
|
||||||
|
|
||||||
|
// Get lookup data for this item
|
||||||
|
var data = this.current_data[item.index-1]
|
||||||
|
|
||||||
|
this._trigger( "select", event, { item: item, data: data } );
|
||||||
|
|
||||||
|
if ( item.index !== oldIndex ) {
|
||||||
|
this._trigger( "change", event, { item: item } );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.close( event );
|
||||||
|
},
|
||||||
|
get_data_for: function(index) {
|
||||||
|
if (this.current_data != null && current_data.length > index) {
|
||||||
|
return this.current_data[index];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
}(jQuery));
|
}(jQuery));
|
||||||
|
|
|
@ -45,10 +45,10 @@
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
|
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
|
||||||
<ul class="navbar-nav mr-auto">
|
<ul class="navbar-nav mr-auto">
|
||||||
<li class="nav-item{% if request.resolver_match.url_name == "events" %} active{% endif %}">
|
<li class="nav-item{% if request.resolver_match.url_name == "events" or request.resolver_match.url_name == "all-events" %} active{% endif %}">
|
||||||
<a class="nav-link" href="{% url 'events' %}">Events{% if request.resolver_match.url_name == "events" %} <span class="sr-only">(current)</span>{% endif %}</a>
|
<a class="nav-link" href="{% url 'events' %}">Events{% if request.resolver_match.url_name == "events" %} <span class="sr-only">(current)</span>{% endif %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item{% if request.resolver_match.url_name == "teams" %} active{% endif %}">
|
<li class="nav-item{% if request.resolver_match.url_name == "teams" or request.resolver_match.url_name == "all-teams" %} active{% endif %}">
|
||||||
<a class="nav-link" href="{% url 'teams' %}">Teams{% if request.resolver_match.url_name == "teams" %} <span class="sr-only">(current)</span>{% endif %}</a>
|
<a class="nav-link" href="{% url 'teams' %}">Teams{% if request.resolver_match.url_name == "teams" %} <span class="sr-only">(current)</span>{% endif %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% comment %}
|
{% comment %}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<form action="{% url "create-event" team.id%}" method="post">
|
<form action="{% url "create-event" team.id%}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if event.parent %}<<input type="hidden" name="common" value="{{event.parent.id}}" />{% endif %}
|
{% if event.parent %}<input type="hidden" name="common" value="{{event.parent.id}}" />{% endif %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
{% include "events/event_form.html" %}
|
{% include "events/event_form.html" %}
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "get_together/base.html" %}
|
{% extends "get_together/base.html" %}
|
||||||
{% load static %}
|
{% load static tz %}
|
||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
<link href="{% static 'css/bootstrap-album.css' %}" rel="stylesheet"/>
|
<link href="{% static 'css/bootstrap-album.css' %}" rel="stylesheet"/>
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text"><strong><a class="card-link" href="{{event.get_absolute_url}}">{{event.name}}</a></strong></p>
|
<p class="card-text"><strong><a class="card-link" href="{{event.get_absolute_url}}">{{event.name}}</a></strong></p>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<small class="text-muted">{{ event.start_time }}</small>
|
<small class="text-muted">{{ event.local_start_time }}</small>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn btn-primary" href="{{ event.get_absolute_url }}">View</a></span>
|
<a class="btn btn-primary" href="{{ event.get_absolute_url }}">View</a></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "get_together/base.html" %}
|
{% extends "get_together/base.html" %}
|
||||||
{% load markup static %}
|
{% load markup static tz %}
|
||||||
|
|
||||||
{% block add_to_totle %} | {{event.name}}{% endblock %}
|
{% block add_to_totle %} | {{event.name}}{% endblock %}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>Time:</b></td><td>{{ event.start_time }} - {{ event.end_time }}</td>
|
<td><b>Time:</b></td><td>{{ event.local_start_time }} - {{ event.local_end_time }}</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><b>Place:</b></td><td>
|
<td><b>Place:</b></td><td>
|
||||||
{% if event.place %}
|
{% if event.place %}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if near_events.count > 0 %}
|
{% if near_events %}
|
||||||
{% for event in near_events %}
|
{% for event in near_events %}
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card mb-4 box-shadow">
|
<div class="card mb-4 box-shadow">
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text"><strong><a class="card-link" href="{{event.event_url}}">{{event.event_title}}</a></strong></p>
|
<p class="card-text"><strong><a class="card-link" href="{{event.event_url}}">{{event.event_title}}</a></strong></p>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<small class="text-muted">{{ event.start_time }}<br/>{{event.location_name}}</small>
|
<small class="text-muted">{{ event.local_start_time }}<br/>{{event.location_name}}</small>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn btn-primary" href="{{event.event_url}}">View</a></span>
|
<a class="btn btn-primary" href="{{event.event_url}}">View</a></span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if near_teams.count > 0 %}
|
{% if near_teams %}
|
||||||
{% for team in near_teams %}
|
{% for team in near_teams %}
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card mb-4 box-shadow">
|
<div class="card mb-4 box-shadow">
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text"><strong><a class="card-link" href="{{event.get_absolute_url}}">{{event.name}}</a></strong></p>
|
<p class="card-text"><strong><a class="card-link" href="{{event.get_absolute_url}}">{{event.name}}</a></strong></p>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<small class="text-muted">{{ event.start_time }}</small>
|
<small class="text-muted">{{ event.local_start_time }}</small>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="btn btn-primary" href="{{ event.get_absolute_url }}">View</a></span>
|
<a class="btn btn-primary" href="{{ event.get_absolute_url }}">View</a></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -83,7 +83,8 @@
|
||||||
selectField.append('<option value="'+ json.id +'" selected>'+ json.display + '</option>');
|
selectField.append('<option value="'+ json.id +'" selected>'+ json.display + '</option>');
|
||||||
selectField.lookup("refresh");
|
selectField.lookup("refresh");
|
||||||
|
|
||||||
$("#id_tz").val(json.tz)
|
$("#id_tz").val(json.tz);
|
||||||
|
$("#id_tz").selectmenu("refresh");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -216,6 +217,10 @@ $(document).ready(function(){
|
||||||
|
|
||||||
return callback(q, data);
|
return callback(q, data);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
select: function( event, ui ) {
|
||||||
|
$("#id_tz").val(ui.data.tz);
|
||||||
|
$("#id_tz").selectmenu("refresh");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
$("#id_tz").selectmenu();
|
$("#id_tz").selectmenu();
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h6 class="mt-2 mb-0"><a href="{{event.get_absolute_url}}">{{event.name}}</a></h6>
|
<h6 class="mt-2 mb-0"><a href="{{event.get_absolute_url}}">{{event.name}}</a></h6>
|
||||||
<small class="text-muted">{{ event.start_time }}</small>
|
<small class="text-muted">{{ event.local_start_time }}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -28,6 +28,10 @@ $(document).ready(function(){
|
||||||
|
|
||||||
return callback(q, data);
|
return callback(q, data);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
select: function( event, ui ) {
|
||||||
|
$("#id_tz").val(ui.data.tz);
|
||||||
|
$("#id_tz").selectmenu("refresh");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
$("#id_category").selectmenu();
|
$("#id_category").selectmenu();
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
$("#city_select").lookup({
|
$("#city_select").lookup({
|
||||||
search: function(searchText, callback) {
|
search: function(searchText, callback) {
|
||||||
if (searchText.length < 3) return callback(searchText, []);
|
if (searchText.length < 3) return callback(searchText, []);
|
||||||
$.getJSON("/api/cities/?q="+searchText, function(data) {
|
$.getJSON("/api/cities/?q="+searchText, function(data) {
|
||||||
var m = this.url.match(/q=([^&]+)/);
|
var m = this.url.match(/q=([^&]+)/);
|
||||||
var q = "";
|
var q = "";
|
||||||
|
@ -27,8 +27,12 @@ $(document).ready(function(){
|
||||||
|
|
||||||
return callback(q, data);
|
return callback(q, data);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
select: function( event, ui ) {
|
||||||
|
$("#id_tz").val(ui.data.tz);
|
||||||
|
$("#id_tz").selectmenu("refresh");
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
$("#id_category").selectmenu();
|
$("#id_category").selectmenu();
|
||||||
$("#id_tz").selectmenu();
|
$("#id_tz").selectmenu();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "get_together/base.html" %}
|
{% extends "get_together/base.html" %}
|
||||||
{% load markup %}
|
{% load markup tz %}
|
||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
<style>
|
<style>
|
||||||
|
@ -28,6 +28,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'join-team' team.id %}" class="btn btn-success btn-sm">Join Team</a>
|
<a href="{% url 'join-team' team.id %}" class="btn btn-success btn-sm">Join Team</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a href="{% url 'team-event-ical' team.id %}" class="btn btn-success btn-sm">iCal</a>
|
||||||
</h2><hr/>
|
</h2><hr/>
|
||||||
|
|
||||||
{% if team.description %}<p>{{ team.description|markdown }}</p><hr/>{% endif %}
|
{% if team.description %}<p>{{ team.description|markdown }}</p><hr/>{% endif %}
|
||||||
|
@ -38,7 +39,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col"><a href="{{ event.get_absolute_url }}">{{event.name}}</a></div>
|
<div class="col"><a href="{{ event.get_absolute_url }}">{{event.name}}</a></div>
|
||||||
<div class="col">{{ event.place }}</div>
|
<div class="col">{{ event.place }}</div>
|
||||||
<div class="col">{{ event.start_time }}</div>
|
<div class="col">{{ event.local_start_time }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if can_create_event %}
|
{% if can_create_event %}
|
||||||
|
@ -58,7 +59,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col"><a href="{{ event.get_absolute_url }}">{{event.name}}</a></div>
|
<div class="col"><a href="{{ event.get_absolute_url }}">{{event.name}}</a></div>
|
||||||
<div class="col">{{ event.place }}</div>
|
<div class="col">{{ event.place }}</div>
|
||||||
<div class="col">{{ event.start_time }}</div>
|
<div class="col">{{ event.local_start_time }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<img class="align-bottom" border="1" src="{{user.avatar_url}}" height="128px"/> {{user.user}}
|
<img class="align-bottom" border="1" src="{{user.avatar_url}}" height="128px"/> {{user.user}}
|
||||||
{% if user.user.id == request.user.id %}
|
{% if user.user.id == request.user.id %}
|
||||||
<a href="{% url 'edit-profile' %}" class="btn btn-secondary btn-sm">Edit Profile</a>
|
<a href="{% url 'edit-profile' %}" class="btn btn-secondary btn-sm">Edit Profile</a>
|
||||||
|
<a href="{% url 'user-event-ical' request.user.profile.secret_key %}" class="btn btn-success btn-sm">iCal</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,6 +20,7 @@ from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
from events import views as event_views
|
from events import views as event_views
|
||||||
|
from events import feeds
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -46,6 +47,7 @@ urlpatterns = [
|
||||||
path('profile/+confirm_email/<str:confirmation_key>', views.user_confirm_email, name='confirm-email'),
|
path('profile/+confirm_email/<str:confirmation_key>', views.user_confirm_email, name='confirm-email'),
|
||||||
path('profile/+confirm_notifications', views.user_confirm_notifications, name='confirm-notifications'),
|
path('profile/+confirm_notifications', views.user_confirm_notifications, name='confirm-notifications'),
|
||||||
path('profile/<int:user_id>/', views.show_profile, name='show-profile'),
|
path('profile/<int:user_id>/', views.show_profile, name='show-profile'),
|
||||||
|
path('profile/<str:account_secret>.ics', feeds.UserEventsCalendar(), name='user-event-ical'),
|
||||||
|
|
||||||
path('events/', views.events_list, name='events'),
|
path('events/', views.events_list, name='events'),
|
||||||
path('events/all/', views.events_list_all, name='all-events'),
|
path('events/all/', views.events_list_all, name='all-events'),
|
||||||
|
@ -56,6 +58,7 @@ urlpatterns = [
|
||||||
path('team/<int:team_id>/+join/', event_views.join_team, name='join-team'),
|
path('team/<int:team_id>/+join/', event_views.join_team, name='join-team'),
|
||||||
path('team/<int:team_id>/+leave/', event_views.leave_team, name='leave-team'),
|
path('team/<int:team_id>/+leave/', event_views.leave_team, name='leave-team'),
|
||||||
path('team/<int:team_id>/+delete/', views.delete_team, name='delete-team'),
|
path('team/<int:team_id>/+delete/', views.delete_team, name='delete-team'),
|
||||||
|
path('team/<int:team_id>/events.ics', feeds.TeamEventsCalendar(), name='team-event-ical'),
|
||||||
|
|
||||||
path('+create-team/', views.create_team, name='create-team'),
|
path('+create-team/', views.create_team, name='create-team'),
|
||||||
path('team/+create-event/', views.create_event_team_select, name='create-event-team-select'),
|
path('team/+create-event/', views.create_event_team_select, name='create-event-team-select'),
|
||||||
|
|
|
@ -10,6 +10,7 @@ from events.models.events import Event, Place, Attendee
|
||||||
from events.models.profiles import Team, UserProfile, Member
|
from events.models.profiles import Team, UserProfile, Member
|
||||||
from events.models.search import Searchable
|
from events.models.search import Searchable
|
||||||
from events.forms import SearchForm
|
from events.forms import SearchForm
|
||||||
|
from events import location
|
||||||
|
|
||||||
from accounts.decorators import setup_wanted
|
from accounts.decorators import setup_wanted
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -69,7 +70,7 @@ def home(request, *args, **kwards):
|
||||||
if len(nearby_cities) == 0:
|
if len(nearby_cities) == 0:
|
||||||
city_distance += 1
|
city_distance += 1
|
||||||
else:
|
else:
|
||||||
city = nearby_cities[0]
|
city = sorted(nearby_cities, key=lambda city: location.city_distance_from(ll, city))[0]
|
||||||
except:
|
except:
|
||||||
pass # City lookup failed
|
pass # City lookup failed
|
||||||
|
|
||||||
|
@ -93,10 +94,10 @@ def home(request, *args, **kwards):
|
||||||
context['maxlng'] = maxlng
|
context['maxlng'] = maxlng
|
||||||
|
|
||||||
near_events = Searchable.objects.filter(latitude__gte=minlat, latitude__lte=maxlat, longitude__gte=minlng, longitude__lte=maxlng, end_time__gte=datetime.datetime.now())
|
near_events = Searchable.objects.filter(latitude__gte=minlat, latitude__lte=maxlat, longitude__gte=minlng, longitude__lte=maxlng, end_time__gte=datetime.datetime.now())
|
||||||
context['near_events'] = near_events
|
context['near_events'] = sorted(near_events, key=lambda searchable: location.searchable_distance_from(ll, searchable))
|
||||||
|
|
||||||
near_teams = Team.objects.filter(city__latitude__gte=minlat, city__latitude__lte=maxlat, city__longitude__gte=minlng, city__longitude__lte=maxlng)
|
near_teams = Team.objects.filter(city__latitude__gte=minlat, city__latitude__lte=maxlat, city__longitude__gte=minlng, city__longitude__lte=maxlng)
|
||||||
context['near_teams'] = near_teams
|
context['near_teams'] = sorted(near_teams, key=lambda team: location.team_distance_from(ll, team))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print("Error looking up nearby teams and events", err)
|
print("Error looking up nearby teams and events", err)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
|
@ -5,11 +5,12 @@ from django.contrib.auth import logout as logout_user
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.shortcuts import render, redirect, reverse
|
from django.shortcuts import render, redirect, reverse
|
||||||
from django.http import HttpResponse, JsonResponse
|
from django.http import HttpResponse, JsonResponse
|
||||||
|
from django.utils import timezone
|
||||||
from events.models.profiles import Team, Organization, UserProfile, Member
|
|
||||||
from events.forms import TeamEventForm, NewTeamEventForm, DeleteEventForm, EventCommentForm, NewPlaceForm, UploadEventPhotoForm, NewCommonEventForm
|
|
||||||
|
|
||||||
from events.models.events import Event, CommonEvent, EventPhoto, Place, Attendee
|
from events.models.events import Event, CommonEvent, EventPhoto, Place, Attendee
|
||||||
|
from events.models.profiles import Team, Organization, UserProfile, Member
|
||||||
|
from events.forms import TeamEventForm, NewTeamEventForm, DeleteEventForm, EventCommentForm, NewPlaceForm, UploadEventPhotoForm, NewCommonEventForm
|
||||||
|
from events import location
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import simplejson
|
import simplejson
|
||||||
|
@ -18,18 +19,20 @@ import simplejson
|
||||||
def events_list(request, *args, **kwargs):
|
def events_list(request, *args, **kwargs):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return redirect('all-events')
|
return redirect('all-events')
|
||||||
events = Event.objects.filter(attendees=request.user.profile, end_time__gt=datetime.datetime.now()).order_by('start_time')
|
events = Event.objects.filter(attendees=request.user.profile, end_time__gt=timezone.now()).order_by('start_time')
|
||||||
|
geo_ip = location.get_geoip(request)
|
||||||
context = {
|
context = {
|
||||||
'active': 'my',
|
'active': 'my',
|
||||||
'events_list': events,
|
'events_list': sorted(events, key=lambda event: location.event_distance_from(geo_ip.latlng, event)),
|
||||||
}
|
}
|
||||||
return render(request, 'get_together/events/list_events.html', context)
|
return render(request, 'get_together/events/list_events.html', context)
|
||||||
|
|
||||||
def events_list_all(request, *args, **kwargs):
|
def events_list_all(request, *args, **kwargs):
|
||||||
events = Event.objects.filter(end_time__gt=datetime.datetime.now()).order_by('start_time')
|
events = Event.objects.filter(end_time__gt=timezone.now()).order_by('start_time')
|
||||||
|
geo_ip = location.get_geoip(request)
|
||||||
context = {
|
context = {
|
||||||
'active': 'all',
|
'active': 'all',
|
||||||
'events_list': events,
|
'events_list': sorted(events, key=lambda event: location.event_distance_from(geo_ip.latlng, event)),
|
||||||
}
|
}
|
||||||
return render(request, 'get_together/events/list_events.html', context)
|
return render(request, 'get_together/events/list_events.html', context)
|
||||||
|
|
||||||
|
@ -81,6 +84,7 @@ def create_event(request, team_id):
|
||||||
form = NewTeamEventForm(request.POST, instance=new_event)
|
form = NewTeamEventForm(request.POST, instance=new_event)
|
||||||
if form.is_valid:
|
if form.is_valid:
|
||||||
new_event = form.save()
|
new_event = form.save()
|
||||||
|
Attendee.objects.create(event=new_event, user=request.user.profile, role=Attendee.HOST, status=Attendee.YES)
|
||||||
return redirect('add-place', new_event.id)
|
return redirect('add-place', new_event.id)
|
||||||
else:
|
else:
|
||||||
context = {
|
context = {
|
||||||
|
|
|
@ -7,9 +7,9 @@ from django.shortcuts import render, redirect
|
||||||
from django.http import HttpResponse, JsonResponse
|
from django.http import HttpResponse, JsonResponse
|
||||||
|
|
||||||
from events.models.profiles import Organization, Team, UserProfile, Member
|
from events.models.profiles import Organization, Team, UserProfile, Member
|
||||||
from events.forms import TeamForm, NewTeamForm, DeleteTeamForm
|
|
||||||
|
|
||||||
from events.models.events import Event, CommonEvent, Place, Attendee
|
from events.models.events import Event, CommonEvent, Place, Attendee
|
||||||
|
from events.forms import TeamForm, NewTeamForm, DeleteTeamForm
|
||||||
|
from events import location
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import simplejson
|
import simplejson
|
||||||
|
@ -19,18 +19,20 @@ def teams_list(request, *args, **kwargs):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return redirect('all-teams')
|
return redirect('all-teams')
|
||||||
|
|
||||||
teams = request.user.profile.memberships.all()
|
teams = request.user.profile.memberships.all().distinct()
|
||||||
|
geo_ip = location.get_geoip(request)
|
||||||
context = {
|
context = {
|
||||||
'active': 'my',
|
'active': 'my',
|
||||||
'teams': teams,
|
'teams': sorted(teams, key=lambda team: location.team_distance_from(geo_ip.latlng, team)),
|
||||||
}
|
}
|
||||||
return render(request, 'get_together/teams/list_teams.html', context)
|
return render(request, 'get_together/teams/list_teams.html', context)
|
||||||
|
|
||||||
def teams_list_all(request, *args, **kwargs):
|
def teams_list_all(request, *args, **kwargs):
|
||||||
teams = Team.objects.all()
|
teams = Team.objects.all()
|
||||||
|
geo_ip = location.get_geoip(request)
|
||||||
context = {
|
context = {
|
||||||
'active': 'all',
|
'active': 'all',
|
||||||
'teams': teams,
|
'teams': sorted(teams, key=lambda team: location.team_distance_from(geo_ip.latlng, team)),
|
||||||
}
|
}
|
||||||
return render(request, 'get_together/teams/list_teams.html', context)
|
return render(request, 'get_together/teams/list_teams.html', context)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ defusedxml==0.5.0
|
||||||
dj-database-url==0.4.2
|
dj-database-url==0.4.2
|
||||||
Django==2.0
|
Django==2.0
|
||||||
django-appconf==1.0.2
|
django-appconf==1.0.2
|
||||||
|
django-ical==1.4
|
||||||
django-imagekit==4.0.2
|
django-imagekit==4.0.2
|
||||||
django-imagekit-cropper==1.16
|
django-imagekit-cropper==1.16
|
||||||
django-js-asset==1.0.0
|
django-js-asset==1.0.0
|
||||||
|
@ -14,6 +15,7 @@ django-settings-export==1.2.1
|
||||||
djangorestframework==3.7.7
|
djangorestframework==3.7.7
|
||||||
future==0.16.0
|
future==0.16.0
|
||||||
geocoder==1.36.0
|
geocoder==1.36.0
|
||||||
|
icalendar==4.0.1
|
||||||
idna==2.6
|
idna==2.6
|
||||||
Markdown==2.6.11
|
Markdown==2.6.11
|
||||||
model-mommy==1.5.1
|
model-mommy==1.5.1
|
||||||
|
@ -21,6 +23,7 @@ oauthlib==2.0.6
|
||||||
pilkit==2.0
|
pilkit==2.0
|
||||||
Pillow==5.0.0
|
Pillow==5.0.0
|
||||||
PyJWT==1.5.3
|
PyJWT==1.5.3
|
||||||
|
python-dateutil==2.7.2
|
||||||
python3-openid==3.1.0
|
python3-openid==3.1.0
|
||||||
pytz==2017.3
|
pytz==2017.3
|
||||||
ratelim==0.1.6
|
ratelim==0.1.6
|
||||||
|
|
Loading…
Reference in a new issue