484 lines
17 KiB
Python
484 lines
17 KiB
Python
from django.db import models
|
|
from django.contrib.sites.models import Site
|
|
from django.contrib.auth.models import User, Group, AnonymousUser
|
|
from django.contrib.staticfiles.templatetags.staticfiles import static
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.shortcuts import reverse
|
|
from django.utils import timezone
|
|
from django.conf import settings
|
|
|
|
from imagekit.models import ProcessedImageField, ImageSpecField
|
|
from imagekit.processors import ResizeToFill, ResizeToFit, Adjust, ColorOverlay
|
|
|
|
from rest_framework import serializers
|
|
|
|
from .locale import *
|
|
from .. import location
|
|
from ..utils import slugify
|
|
|
|
import uuid
|
|
import pytz
|
|
import datetime
|
|
import hashlib
|
|
|
|
class UserProfile(models.Model):
|
|
" Store profile information about a user "
|
|
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
|
realname = models.CharField(verbose_name=_("Real Name"), max_length=150, blank=True)
|
|
tz = models.CharField(max_length=32, verbose_name=_('Timezone'), default='UTC', choices=location.TimezoneChoices(), blank=False, null=False)
|
|
avatar = ProcessedImageField(verbose_name=_("Photo Image"),
|
|
upload_to='avatars',
|
|
processors=[ResizeToFill(128, 128)],
|
|
format='PNG',
|
|
blank=True)
|
|
city = models.ForeignKey(City, verbose_name=_('Home city'), null=True, blank=True, on_delete=models.CASCADE)
|
|
|
|
web_url = models.URLField(verbose_name=_('Website URL'), blank=True, null=True)
|
|
twitter = models.CharField(verbose_name=_('Twitter Name'), max_length=32, blank=True, null=True)
|
|
facebook = models.URLField(verbose_name=_('Facebook URL'), max_length=32, blank=True, null=True)
|
|
|
|
send_notifications = models.BooleanField(verbose_name=_('Send notification emails'), default=True)
|
|
do_not_track = models.BooleanField(verbose_name=_("Do not track"), default=False)
|
|
|
|
secret_key = models.UUIDField(default=uuid.uuid4, editable=True)
|
|
|
|
categories = models.ManyToManyField('Category', blank=True)
|
|
topics = models.ManyToManyField('Topic', blank=True)
|
|
|
|
class Meta:
|
|
ordering = ('user__username',)
|
|
|
|
def __str__(self):
|
|
try:
|
|
if self.realname:
|
|
return self.realname
|
|
return "%s" % self.user.username
|
|
except:
|
|
return "Unknown Profile"
|
|
|
|
@property
|
|
def personal_team(self):
|
|
teams = Team.objects.filter(access=Team.PERSONAL, owner_profile=self)
|
|
if teams.count() > 0:
|
|
return teams[0]
|
|
else:
|
|
return Team.objects.create(name=str(self), access=Team.PERSONAL, owner_profile=self, city=self.city)
|
|
|
|
def avatar_url(self):
|
|
try:
|
|
if self.avatar is None or self.avatar.name is None or self.avatar.name == '':
|
|
return settings.STATIC_URL + 'img/avatar_placeholder.png'
|
|
elif self.avatar.name.startswith('http'):
|
|
return self.avatar.name
|
|
else:
|
|
return self.avatar.url
|
|
except:
|
|
return settings.STATIC_URL + 'img/avatar_placeholder.png'
|
|
|
|
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)
|
|
|
|
|
|
@property
|
|
def is_a_team_admin(self):
|
|
return Member.objects.filter(user=self, role=Member.ADMIN, team__access__in=(Team.PUBLIC, Team.PRIVATE)).count() > 0
|
|
|
|
@property
|
|
def administering(self):
|
|
return [member.team for member in Member.objects.filter(user=self, role=Member.ADMIN, team__access__in=(Team.PUBLIC, Team.PRIVATE)).order_by('team__name')]
|
|
|
|
@property
|
|
def is_a_team_moderator(self):
|
|
return Member.objects.filter(user=self, role__in=(Member.ADMIN, Member.MODERATOR), team__access__in=(Team.PUBLIC, Team.PRIVATE)).count() > 0
|
|
|
|
@property
|
|
def moderating(self):
|
|
return [member.team for member in Member.objects.filter(user=self, role__in=(Member.ADMIN, Member.MODERATOR), team__access__in=(Team.PUBLIC, Team.PRIVATE)).order_by('team__name')]
|
|
|
|
def can_create_event(self, team):
|
|
try:
|
|
if self.user.is_superuser:
|
|
return True
|
|
except:
|
|
return False
|
|
if not self.user_id:
|
|
return False
|
|
if team.owner_profile == self:
|
|
return True
|
|
if self in team.moderators:
|
|
return True
|
|
return False
|
|
|
|
def can_edit_series(self, series):
|
|
try:
|
|
if self.user.is_superuser:
|
|
return True
|
|
except:
|
|
return False
|
|
if series.created_by == self:
|
|
return True
|
|
if series.team.owner_profile == self:
|
|
return True
|
|
if self in series.team.moderators:
|
|
return True
|
|
return False
|
|
|
|
def can_edit_event(self, event):
|
|
try:
|
|
if self.user.is_superuser:
|
|
return True
|
|
except:
|
|
return False
|
|
if event.created_by == self:
|
|
return True
|
|
if event.team.owner_profile == self:
|
|
return True
|
|
if self in event.team.moderators:
|
|
return True
|
|
return False
|
|
|
|
def can_edit_org(self, org):
|
|
try:
|
|
if self.user.is_superuser:
|
|
return True
|
|
except:
|
|
return False
|
|
if not self.user_id:
|
|
return False
|
|
if org.owner_profile == self:
|
|
return True
|
|
return False
|
|
|
|
def can_create_common_event(self, org):
|
|
try:
|
|
if self.user.is_superuser:
|
|
return True
|
|
except:
|
|
return False
|
|
if not self.user_id:
|
|
return False
|
|
if org.owner_profile == self:
|
|
return True
|
|
return False
|
|
|
|
def can_edit_team(self, team):
|
|
try:
|
|
if self.user.is_superuser:
|
|
return True
|
|
except:
|
|
return False
|
|
if team.owner_profile == self:
|
|
return True
|
|
if self in team.administrators:
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_user_timezone(username):
|
|
# TODO: find a smarter way to get timezone
|
|
return 'UTC'
|
|
|
|
def _getUserProfile(self):
|
|
if not self.is_authenticated:
|
|
return UserProfile()
|
|
|
|
profile, created = UserProfile.objects.get_or_create(user=self)
|
|
|
|
if created:
|
|
profile.tz = get_user_timezone(self.username)
|
|
if self.first_name:
|
|
if self.last_name:
|
|
profile.realname = '%s %s' % (self.first_name, self.last_name)
|
|
else:
|
|
profile.realname = self.first_name
|
|
|
|
if self.email:
|
|
h = hashlib.md5()
|
|
h.update(bytearray(profile.user.email, 'utf8'))
|
|
profile.avatar = 'https://www.gravatar.com/avatar/%s.jpg?d=mm' % h.hexdigest()
|
|
|
|
profile.save()
|
|
|
|
return profile
|
|
|
|
def _getAnonProfile(self):
|
|
return UserProfile()
|
|
|
|
User.profile = property(_getUserProfile)
|
|
AnonymousUser.profile = property(_getAnonProfile)
|
|
|
|
class Organization(models.Model):
|
|
name = models.CharField(max_length=256, null=False, blank=False)
|
|
slug = models.CharField(max_length=256, null=False, blank=False)
|
|
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
|
|
|
owner_profile = models.ForeignKey(UserProfile, related_name='owned_orgs', blank=False, null=True, on_delete=models.SET_NULL)
|
|
|
|
cover_img = models.ImageField(verbose_name=_('Cover Image'), upload_to='org_covers', null=True, blank=True)
|
|
tile_img = ImageSpecField(source='cover_img',
|
|
processors=[
|
|
Adjust(contrast=0.8, color=1),
|
|
ResizeToFill(338, 200),
|
|
],
|
|
format='PNG')
|
|
|
|
banner_img = ImageSpecField(source='cover_img',
|
|
processors=[
|
|
Adjust(contrast=0.8, color=1),
|
|
ResizeToFill(825, 200),
|
|
],
|
|
format='PNG')
|
|
|
|
description = models.TextField(blank=True, null=True)
|
|
|
|
def save(self, *args, **kwargs):
|
|
new_slug = slugify(self.name)
|
|
slug_matches = list(Organization.objects.filter(slug=new_slug))
|
|
if len(slug_matches) == 0 or (len(slug_matches) == 1 and slug_matches[0].id == self.id):
|
|
self.slug = new_slug
|
|
else:
|
|
self.slug = '%s-%s' % (new_slug, self.id)
|
|
super().save(*args, **kwargs) # Call the "real" save() method.
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('show-org', kwargs={'org_slug': self.slug})
|
|
|
|
def __str__(self):
|
|
return u'%s' % (self.name)
|
|
|
|
class OrgTeamRequest(models.Model):
|
|
ORG=0
|
|
TEAM=1
|
|
ORIGINS = [
|
|
(ORG, _("Organization")),
|
|
(TEAM, _("Team")),
|
|
]
|
|
|
|
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
|
|
team = models.ForeignKey('Team', on_delete=models.CASCADE)
|
|
request_origin = models.SmallIntegerField(_("Request from"), choices=ORIGINS, default=ORG, db_index=True)
|
|
request_key = models.UUIDField(default=uuid.uuid4, editable=True)
|
|
|
|
requested_by = models.ForeignKey(UserProfile, related_name='requested_org_memberships', on_delete=models.SET_NULL, null=True, blank=False)
|
|
requested_date = models.DateTimeField(default=datetime.datetime.now)
|
|
accepted_by = models.ForeignKey(UserProfile, related_name='accepted_org_memberships', on_delete=models.SET_NULL, null=True, blank=True)
|
|
joined_date = models.DateTimeField(null=True, blank=True)
|
|
|
|
@property
|
|
def origin_name(self):
|
|
return OrgTeamRequest.ORIGINS[self.request_origin][1]
|
|
|
|
def __str__(self):
|
|
return '%s in %s' % (self.team, self.organization)
|
|
|
|
|
|
class Sponsor(models.Model):
|
|
name = models.CharField(_("Sponsor Name"), max_length=256, null=False, blank=False)
|
|
description = models.TextField(blank=True, null=True)
|
|
web_url = models.URLField(_("Website"), null=True, blank=True)
|
|
logo = ProcessedImageField(verbose_name=_("Logo"), help_text=_("Will be scaled and cropped to max 250x200 px."),
|
|
upload_to='sponsors',
|
|
processors=[ResizeToFit(250, 200)],
|
|
format='PNG',
|
|
blank=False)
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class SponsorSerializer(serializers.ModelSerializer):
|
|
display = serializers.CharField(source='__str__', read_only=True)
|
|
class Meta:
|
|
model = Sponsor
|
|
fields = (
|
|
'id',
|
|
'name',
|
|
'logo',
|
|
'web_url',
|
|
)
|
|
|
|
|
|
class PublicTeamsManager(models.Manager):
|
|
def get_queryset(self):
|
|
return super().get_queryset().filter(access=Team.PUBLIC)
|
|
|
|
class Team(models.Model):
|
|
PUBLIC=0
|
|
PERSONAL=1
|
|
PRIVATE=2
|
|
TYPES = [
|
|
(PUBLIC, _("Public")),
|
|
(PERSONAL, _("Personal")),
|
|
(PRIVATE, _("Private")),
|
|
]
|
|
name = models.CharField(_("Team Name"), max_length=256, null=False, blank=False)
|
|
slug = models.CharField(max_length=256, null=False, blank=False, unique=True)
|
|
organization = models.ForeignKey(Organization, related_name='teams', null=True, blank=True, on_delete=models.CASCADE)
|
|
access = models.SmallIntegerField(verbose_name=_("Access"), choices=TYPES, default=PUBLIC)
|
|
|
|
cover_img = models.ImageField(verbose_name=_('Cover Image'), upload_to='team_covers', null=True, blank=True)
|
|
tile_img = ImageSpecField(source='cover_img',
|
|
processors=[
|
|
Adjust(contrast=0.8, color=1),
|
|
ResizeToFill(338, 200),
|
|
],
|
|
format='PNG')
|
|
|
|
banner_img = ImageSpecField(source='cover_img',
|
|
processors=[
|
|
Adjust(contrast=0.8, color=1),
|
|
ResizeToFill(825, 200),
|
|
],
|
|
format='PNG')
|
|
|
|
description = models.TextField(blank=True, null=True)
|
|
|
|
about_page = models.TextField(blank=True, null=True)
|
|
|
|
country = models.ForeignKey(Country, null=True, blank=True, on_delete=models.CASCADE)
|
|
spr = models.ForeignKey(SPR, null=True, blank=True, on_delete=models.CASCADE)
|
|
city = models.ForeignKey(City, null=True, blank=True, on_delete=models.CASCADE)
|
|
|
|
web_url = models.URLField(_("Website"), null=True, blank=True)
|
|
email = models.EmailField(_("Email Address"), null=True, blank=True)
|
|
|
|
created_date = models.DateField(_("Date Created"), default=timezone.now, null=True, blank=True)
|
|
|
|
owner_profile = models.ForeignKey(UserProfile, related_name='owned_teams', null=True, on_delete=models.CASCADE)
|
|
admin_profiles = models.ManyToManyField(UserProfile, related_name='admins', blank=True)
|
|
contact_profiles = models.ManyToManyField(UserProfile, related_name='contacts', blank=True)
|
|
|
|
languages = models.ManyToManyField(Language, blank=True)
|
|
active = models.BooleanField(_("Active Team"), default=True)
|
|
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)
|
|
|
|
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=False, null=True)
|
|
topics = models.ManyToManyField('Topic', blank=True)
|
|
|
|
sponsors = models.ManyToManyField('Sponsor', related_name='teams', blank=True)
|
|
|
|
objects = models.Manager()
|
|
public_objects = PublicTeamsManager()
|
|
|
|
@property
|
|
def card_img_url(self):
|
|
if self.tile_img is not None and self.tile_img.name is not None:
|
|
return self.tile_img.url
|
|
elif self.organization and self.organization.tile_img and self.organization.tile_img.url is not None:
|
|
return self.organization.tile_img.url
|
|
elif self.category is not None:
|
|
return self.category.img_url
|
|
else:
|
|
return static('img/team_placeholder.png')
|
|
|
|
@property
|
|
def full_img_url(self):
|
|
if self.card_img_url.startswith('http'):
|
|
return self.card_img_url
|
|
else:
|
|
site = Site.objects.get(id=1)
|
|
return "https://%s%s" % (site.domain, self.card_img_url)
|
|
|
|
@property
|
|
def location_name(self):
|
|
if self.city:
|
|
return str(self.city)
|
|
elif self.spr:
|
|
return str(self.spr)
|
|
elif self.country:
|
|
return str(self.country)
|
|
else:
|
|
return ''
|
|
|
|
@property
|
|
def administrators(self):
|
|
return [member.user for member in Member.objects.filter(team=self, role=Member.ADMIN)]
|
|
|
|
@property
|
|
def moderators(self):
|
|
return [member.user for member in Member.objects.filter(team=self, role__in=(Member.ADMIN, Member.MODERATOR))]
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('show-team', kwargs={'team_id': self.id})
|
|
|
|
def __str__(self):
|
|
return u'%s' % (self.name)
|
|
|
|
def save(self, *args, **kwargs):
|
|
if self.city is not None:
|
|
self.spr = self.city.spr
|
|
self.country = self.spr.country
|
|
new_slug = slugify(self.name)
|
|
slug_matches = list(Team.objects.filter(slug=new_slug))
|
|
if len(slug_matches) == 0 or (len(slug_matches) == 1 and slug_matches[0].id == self.id):
|
|
self.slug = new_slug
|
|
else:
|
|
self.slug = '%s-%s' % (new_slug, self.id)
|
|
super().save(*args, **kwargs) # Call the "real" save() method.
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('show-team-by-slug', kwargs={'team_slug': self.slug})
|
|
|
|
|
|
class Member(models.Model):
|
|
NORMAL=0
|
|
MODERATOR=1
|
|
ADMIN=2
|
|
ROLES = [
|
|
(NORMAL, _("Normal")),
|
|
(MODERATOR, _("Moderator")),
|
|
(ADMIN, _("Administrator"))
|
|
]
|
|
team = models.ForeignKey(Team, on_delete=models.CASCADE)
|
|
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
|
|
role = models.SmallIntegerField(_("Member Role"), choices=ROLES, default=NORMAL, db_index=True)
|
|
joined_date = models.DateTimeField(default=datetime.datetime.now)
|
|
|
|
@property
|
|
def role_name(self):
|
|
return Member.ROLES[self.role][1]
|
|
|
|
def __str__(self):
|
|
return '%s in %s' % (self.user, self.team)
|
|
|
|
class Category(models.Model):
|
|
name = models.CharField(max_length=256)
|
|
description = models.TextField()
|
|
slug = models.CharField(max_length=256, blank=True)
|
|
img_url = models.URLField(blank=False, null=False)
|
|
|
|
class Meta:
|
|
verbose_name_plural = 'Categories'
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.slug = slugify(self.name)
|
|
super().save(*args, **kwargs)
|
|
|
|
class Topic(models.Model):
|
|
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=False, blank=False)
|
|
name = models.CharField(max_length=256)
|
|
slug = models.CharField(max_length=256, blank=True)
|
|
description = models.TextField(blank=True)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.slug = slugify(self.name)
|
|
super().save(*args, **kwargs)
|
|
|