GetTogether/events/models/events.py

474 lines
18 KiB
Python

from django.db import models
from django.contrib.sites.models import Site
from django.contrib.auth.models import User, Group
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import reverse
from django.utils import timezone
from django.conf import settings
from rest_framework import serializers
from mptt.models import MPTTModel, TreeForeignKey
from recurrence.fields import RecurrenceField
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
from ..utils import slugify
from .locale import *
from .profiles import *
from .search import *
from .. import location
import re
import pytz
import datetime
import hashlib
class Place(models.Model):
name = models.CharField(help_text=_('Name of the Place'), max_length=150)
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)
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)
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)
cover_img = models.URLField(_("Place photo"), null=True, blank=True)
def get_absolute_url(self):
return reverse('show-place', kwargs={'place_id': self.id})
def __str__(self):
return u'%s, %s' % (self.name, self.city.name)
class PlaceSerializer(serializers.ModelSerializer):
city = serializers.CharField(read_only=True)
class Meta:
model = Place
fields = (
'id',
'name',
'city',
'address',
'longitude',
'latitude',
'tz',
'place_url',
'cover_img'
)
class Event(models.Model):
CANCELED = -1
PLANNING = 0
CONFIRMED = 1
STATUSES = [
(CANCELED, _("Canceled")),
(PLANNING, _("Planning")),
(CONFIRMED, _("Confirmed")),
]
name = models.CharField(max_length=150, verbose_name=_('Event Name'))
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)
series = models.ForeignKey('EventSeries',related_name='instances', null=True, blank=True, on_delete=models.SET_NULL)
status = models.SmallIntegerField(choices=STATUSES, default=CONFIRMED, db_index=True)
start_time = models.DateTimeField(verbose_name=_('Start Time'), db_index=True)
end_time = models.DateTimeField(verbose_name=_('End Time'), db_index=True)
summary = models.TextField(help_text=_('Markdown formatting supported'), blank=True, null=True)
place = models.ForeignKey(Place, blank=True, null=True, on_delete=models.CASCADE)
web_url = models.URLField(verbose_name=_('Website URL'), max_length=200, blank=True, null=True)
announce_url = models.URLField(verbose_name=_('Announcement URL'), max_length=200, blank=True, null=True)
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=timezone.now, db_index=True)
tags = models.CharField(verbose_name=_("Keyword Tags"), blank=True, null=True, max_length=128)
#image
#replies
attendees = models.ManyToManyField(UserProfile, through='Attendee', related_name="attending", blank=True)
sponsors = models.ManyToManyField('Sponsor', related_name='events', blank=True)
enable_comments = models.BooleanField(verbose_name=_('Comments'), default=True)
enable_photos = models.BooleanField(verbose_name=_('Photos'), default=True)
enable_presentations = models.BooleanField(verbose_name=_('Presentations'), default=False)
@property
def is_over(self):
return self.end_time <= timezone.now()
@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
def localize_datetime(self, val):
if val is not None:
event_tz = pytz.timezone(self.tz)
return timezone.make_naive(val.astimezone(event_tz), event_tz)
else:
return None
@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):
return reverse('show-event', kwargs={'event_id': self.id, 'event_slug': self.slug})
def get_full_url(self):
site = Site.objects.get(id=1)
return "https://%s%s" % (site.domain, self.get_absolute_url())
@property
def slug(self):
return slugify(self.name)
def __str__(self):
return u'%s by %s at %s' % (self.name, self.team.name, self.start_time)
def save(self, *args, **kwargs):
super().save(*args, **kwargs) # Call the "real" save() method.
if self.status > self.CANCELED:
update_event_searchable(self)
else:
delete_event_searchable(self)
def update_event_searchable(event):
site = Site.objects.get(id=1)
if settings.DEBUG:
schema = 'http'
else:
schema = 'https'
event_url = "%s://%s%s" % (schema, site.domain, event.get_absolute_url())
origin_url = "%s://%s%s" % (schema, site.domain, reverse('searchables'))
md5 = hashlib.md5()
federation_url = event_url.split('/')
federation_node = '/'.join(federation_url[:3])
federation_id = '/'.join(federation_url[:5])
md5.update(bytes(federation_id, 'utf8'))
event_uri = federation_node + '/' + md5.hexdigest()
try:
searchable = Searchable.objects.get(event_uri=event_uri)
except:
searchable = Searchable(event_uri)
searchable.origin_node = origin_url
searchable.federation_node = origin_url
searchable.federation_time = timezone.now()
searchable.event_url = event_url
if event.team.card_img_url.startswith('http:') or event.team.card_img_url.startswith('https:'):
searchable.img_url = event.team.card_img_url
else:
searchable.img_url = "%s://%s%s" % (schema, site.domain, event.team.card_img_url)
searchable.event_title = event.name
searchable.group_name = event.team.name
searchable.start_time = event.start_time
searchable.end_time = event.end_time
searchable.tz = event.tz
searchable.cost = 0
searchable.tags = event.tags
if (event.place is not None):
searchable.location_name = str(event.place.city)
searchable.venue_name = event.place.name
if event.place.longitude is not None and event.place.latitude is not None:
searchable.longitude = event.place.longitude
searchable.latitude = event.place.latitude
elif event.place.city is not None:
searchable.longitude = event.place.city.longitude
searchable.latitude = event.place.city.latitude
else:
searchable.location_name = event.team.location_name
if event.team.city is not None and (searchable.longitude is None or searchable.latitude is None):
searchable.longitude = event.team.city.longitude
searchable.latitude = event.team.city.latitude
searchable.save()
def delete_event_searchable(event):
site = Site.objects.get(id=1)
if settings.DEBUG:
schema = 'http'
else:
schema = 'https'
event_url = "%s://%s%s" % (schema, site.domain, event.get_absolute_url())
origin_url = "%s://%s%s" % (schema, site.domain, reverse('searchables'))
md5 = hashlib.md5()
federation_url = event_url.split('/')
federation_node = '/'.join(federation_url[:3])
federation_id = '/'.join(federation_url[:5])
md5.update(bytes(federation_id, 'utf8'))
event_uri = federation_node + '/' + md5.hexdigest()
try:
searchable = Searchable.objects.get(event_uri=event_uri)
searchable.delete()
except:
pass
class Attendee(models.Model):
NORMAL=0
CREW=1
HOST=2
ROLES = [
(NORMAL, _("Normal")),
(CREW, _("Crew")),
(HOST, _("Host"))
]
NO=-1
MAYBE=0
YES=1
STATUSES = [
(NO, _("No")),
(MAYBE, _("Maybe")),
(YES, _("Yes")),
]
event = models.ForeignKey(Event, on_delete=models.CASCADE)
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
role = models.SmallIntegerField(_("Role"), choices=ROLES, default=NORMAL, db_index=True)
status = models.SmallIntegerField(_("Attending"), choices=STATUSES, default=YES, db_index=True)
actual = models.SmallIntegerField(_("Attended"), choices=STATUSES, default=MAYBE, db_index=True)
joined_date = models.DateTimeField(default=timezone.now)
last_reminded = models.DateTimeField(null=True, blank=True)
@property
def role_name(self):
return Attendee.ROLES[self.role][1]
@property
def status_name(self):
return Attendee.STATUSES[self.status+1][1]
@property
def actual_name(self):
return Attendee.STATUSES[self.actual+1][1]
def __str__(self):
return "%s at %s" % (self.user, self.event)
class EventPhoto(models.Model):
event = models.ForeignKey(Event, related_name='photos', on_delete=models.CASCADE)
title = models.CharField(max_length=256)
caption = models.TextField(null=True, blank=True)
src = models.ImageField(verbose_name=_('Photo'), upload_to='event_photos')
thumbnail = ImageSpecField(source='src',
processors=[ResizeToFill(250, 187)],
format='JPEG',
options={'quality': 60})
class EventComment(MPTTModel):
REMOVED=-1
PENDING=0
APPROVED=1
STATUSES = [
(REMOVED, _("Removed")),
(PENDING, _("Pending")),
(APPROVED, _("Approved")),
]
author = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
event = models.ForeignKey(Event, related_name='comments', on_delete=models.CASCADE)
body = models.TextField(help_text=_('Markdown formatting supported'))
created_time = models.DateTimeField(default=timezone.now, 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)
@property
def local_created_time(self):
return self.event.localize_datetime(self.created_time)
def __str__(self):
return '%s at %s' % (self.author, self.created_time)
class CommonEvent(models.Model):
name = models.CharField(max_length=150, verbose_name=_('Event Name'))
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)
start_time = models.DateTimeField(verbose_name=_('Start Time'), db_index=True)
end_time = models.DateTimeField(verbose_name=_('End Time'), db_index=True)
summary = models.TextField(help_text=_('Markdown formatting supported'), blank=True, null=True)
continent = models.ForeignKey(Continent, null=True, blank=True, on_delete=models.SET_NULL)
country = models.ForeignKey(Country, null=True, blank=True, on_delete=models.SET_NULL)
spr = models.ForeignKey(SPR, null=True, blank=True, on_delete=models.SET_NULL)
city = models.ForeignKey(City, null=True, blank=True, on_delete=models.SET_NULL)
place = models.ForeignKey(Place, blank=True, null=True, on_delete=models.SET_NULL)
web_url = models.URLField(verbose_name=_('Website URL'), max_length=200, blank=True, null=True)
announce_url = models.URLField(verbose_name=_('Announcement URL'), max_length=200, blank=True, null=True)
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=timezone.now, db_index=True)
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=False, null=True)
topics = models.ManyToManyField('Topic', blank=True)
tags = models.CharField(verbose_name=_("Keyword Tags"), blank=True, null=True, max_length=128)
def get_absolute_url(self):
return reverse('show-common-event', kwargs={'event_id': self.id, 'event_slug': self.slug})
def get_full_url(self):
site = self.organization.site
if settings.DEBUG:
schema = 'http'
else:
schema = 'https'
return "%s://%s%s" % (schema, site.domain, self.get_absolute_url())
@property
def full_img_url(self):
if self.organization.tile_img is not None and self.organization.tile_img .name is not None:
if self.organization.tile_img .url.startswith('http'):
return self.organization.tile_img .url
else:
site = self.organization.site
if settings.DEBUG:
schema = 'http'
else:
schema = 'https'
return "%s://%s%s" % (schema, site.domain, self.organization.tile_img .url)
else:
return self.category.img_url
def location(self):
if self.city:
return self.city
elif self.spr:
return self.spr
elif self.country:
return self.country
elif self.continent:
return self.continent
else:
return _('Global')
@property
def slug(self):
return slugify(self.name)
def __str__(self):
return self.name
class EventSeries(models.Model):
class Meta:
verbose_name_plural = 'Event series'
name = models.CharField(max_length=150, verbose_name=_('Event Name'))
team = models.ForeignKey(Team, on_delete=models.CASCADE)
parent = models.ForeignKey('CommonEvent', related_name='planned_events', null=True, blank=True, on_delete=models.SET_NULL)
recurrences = RecurrenceField(null=True)
last_time = models.DateTimeField(help_text=_('Date and time of the last created instance in this series'), default=timezone.now, db_index=True)
start_time = models.TimeField(help_text=_('Local time that the event starts'), verbose_name=_('Start Time'), db_index=True)
end_time = models.TimeField(help_text=_('Local 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)
place = models.ForeignKey(Place, blank=True, null=True, 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=timezone.now, db_index=True)
tags = models.CharField(verbose_name=_("Keyword Tags"), blank=True, null=True, max_length=128)
@classmethod
def from_event(klass, event, recurrences):
new_series = EventSeries(
team=event.team,
parent=event.parent,
name=event.name,
start_time=event.local_start_time.time(),
end_time=event.local_end_time.time(),
last_time=event.start_time,
summary=event.summary,
place=event.place,
created_by=event.created_by,
recurrences=recurrences,
)
return new_series
def create_next_in_series(self):
next_date = self.recurrences.after(self.last_time, dtstart=self.last_time)
if next_date is None:
return None
event_tz = pytz.timezone(self.tz)
next_start = pytz.utc.localize(timezone.make_naive(event_tz.localize(datetime.datetime.combine(next_date.date(), self.start_time))))
next_end = pytz.utc.localize(timezone.make_naive(event_tz.localize(datetime.datetime.combine(next_date.date(), self.end_time))))
next_event = Event(
series=self,
team=self.team,
name=self.name,
start_time=next_start,
end_time=next_end,
summary=self.summary,
place=self.place,
created_by=self.created_by,
)
next_event.save()
Attendee.objects.create(event=next_event, user=self.created_by, role=Attendee.HOST, status=Attendee.YES)
self.last_time = next_event.start_time
self.save()
return next_event
def get_absolute_url(self):
return reverse('show-series', kwargs={'series_id': self.id, 'series_slug': self.slug})
def get_full_url(self):
site = Site.objects.get(id=1)
if settings.DEBUG:
schema = 'http'
else:
schema = 'https'
return "%s://%s%s" % (schema, site.domain, self.get_absolute_url())
@property
def slug(self):
return slugify(self.name)
@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
def __str__(self):
return u'%s by %s at %s' % (self.name, self.team.name, self.start_time)