221 lines
6.8 KiB
Python
221 lines
6.8 KiB
Python
from django.db import models
|
|
from django.utils.text import slugify
|
|
from django.utils import timezone
|
|
from django.conf import settings
|
|
from django.shortcuts import resolve_url
|
|
|
|
from byostorage.user import BYOStorage
|
|
|
|
import random
|
|
|
|
from urllib.parse import urlparse
|
|
|
|
import os.path
|
|
from .utils import sign_data
|
|
|
|
MEDIA_TYPES = [
|
|
('audio', "Audio"),
|
|
('video', "Video"),
|
|
('general', "General"),
|
|
]
|
|
|
|
def rough_date(d):
|
|
if not d:
|
|
return False, "sometime..."
|
|
days = (d - timezone.now()).days
|
|
in_past = days < 0
|
|
if in_past:
|
|
days = abs(days)
|
|
if days == 0:
|
|
m = int((d-timezone.now()).seconds/60)
|
|
if m > 60:
|
|
return in_past, "{0:d} hours".format(int(m / 60))
|
|
return in_past, "{0:d} minutes!".format(int(m % 60))
|
|
if days >= 14:
|
|
return in_past, "{0:d} weeks".format(int(days/7))
|
|
if days >= 7:
|
|
return in_past, "{0:d} weeks, {1:d} days".format(int(days / 7), int(days % 7))
|
|
return in_past, f"{days} days"
|
|
|
|
|
|
def generate_code(length=9):
|
|
return "".join([ random.choice('0123456789') for _ in range(length) ])
|
|
|
|
class EnsembleQuerySet(models.QuerySet):
|
|
|
|
def for_user(self, user, ensemble_keys=[], project_keys=[]):
|
|
if user.is_superuser:
|
|
return self
|
|
|
|
f = models.Q(slug__in=ensemble_keys) | models.Q(projects__in=project_keys)
|
|
|
|
if user.is_authenticated:
|
|
f |= models.Q(admins=user.pk)
|
|
|
|
return self.filter(f).distinct()
|
|
|
|
class Ensemble(models.Model):
|
|
''' A group that plays together
|
|
|
|
'''
|
|
name = models.CharField(max_length=100,
|
|
help_text="Display name")
|
|
slug = models.SlugField(max_length=100, editable=False, unique=True,
|
|
help_text="Short name for the ensemble - used for folders")
|
|
admins = models.ManyToManyField('auth.User', related_name='ensembles')
|
|
details = models.TextField(blank=True,
|
|
help_text="Description of the ensemble (markdown)")
|
|
storage = models.ForeignKey('byostorage.UserStorage', null=True, on_delete=models.SET_NULL,
|
|
help_text="Default storage for this ensemble")
|
|
nonce = models.SmallIntegerField(default=1,
|
|
help_text="Increment this to reset the authentication links")
|
|
|
|
objects = EnsembleQuerySet.as_manager()
|
|
|
|
class Meta:
|
|
ordering = ('slug', )
|
|
|
|
#def active_projects(self):
|
|
# #return self.projects.filter(active=True, event_date__gte=timezone.now())
|
|
# return self.projects.active().current()
|
|
|
|
def has_admin(self, user):
|
|
if not user.is_authenticated:
|
|
return False
|
|
if user.is_superuser:
|
|
return True
|
|
return user.pk in self.admins.values_list('pk', flat=True)
|
|
|
|
def save(self, **kwargs):
|
|
if not self.slug:
|
|
self.slug = slugify(self.name)
|
|
super(Ensemble, self).save(**kwargs)
|
|
|
|
def get_absolute_url(self):
|
|
return resolve_url('ensemble_detail', ensemble=self.slug)
|
|
|
|
def auth(self):
|
|
return sign_data(f'{self.pk}-{self.nonce}', 12)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class ProjectQuerySet(models.QuerySet):
|
|
def current(self):
|
|
return self.filter(models.Q(event_date__gte=(timezone.now()-timezone.timedelta(7))) | models.Q(event_date=None))
|
|
|
|
def active(self):
|
|
return self.filter(active=True)
|
|
|
|
def for_user(self, user, project_keys=[], ensemble_keys=[]):
|
|
if user.is_superuser:
|
|
return self
|
|
|
|
f = models.Q(pk__in=project_keys) | models.Q(ensemble__slug__in=ensemble_keys)
|
|
|
|
if user.is_authenticated:
|
|
f |= models.Q(ensemble__admins=user.pk)
|
|
|
|
return self.filter(f)
|
|
|
|
class Project(models.Model):
|
|
''' A Project linked to an ensemble
|
|
'''
|
|
name = models.CharField(max_length=100)
|
|
ensemble = models.ForeignKey(Ensemble, related_name='projects', on_delete=models.CASCADE, null=True)
|
|
description = models.TextField(blank=True,
|
|
help_text="Markdown format")
|
|
active = models.BooleanField(default=True)
|
|
event_date =models.DateTimeField(null=True, blank=True)
|
|
owner = models.CharField(max_length=255, blank=True)
|
|
nonce = models.SmallIntegerField(default=1,
|
|
help_text="Increment this to reset the authentication links")
|
|
|
|
objects = ProjectQuerySet.as_manager()
|
|
|
|
class Meta:
|
|
ordering = ['active', 'event_date']
|
|
|
|
@property
|
|
def days(self):
|
|
return (self.event_date - timezone.now().date()).days
|
|
|
|
@property
|
|
def has_happened(self):
|
|
if not self.event_date:
|
|
return False
|
|
return self.event_date < timezone.now()
|
|
|
|
@property
|
|
def rough_date(self):
|
|
if not self.event_date:
|
|
return "No timescale"
|
|
in_past, s = rough_date(self.event_date)
|
|
if in_past:
|
|
return f"{s} ago"
|
|
return f"In {s}"
|
|
|
|
@property
|
|
def folder(self):
|
|
project = slugify(self.name)
|
|
print(f"{self.ensemble.storage_id}:{self.ensemble.slug}/{project}")
|
|
return f"{self.ensemble.storage_id}:{self.ensemble.slug}/{project}"
|
|
|
|
@property
|
|
def active_modules(self):
|
|
return self.modules.values_list('name', flat=True)
|
|
|
|
def get_absolute_url(self):
|
|
return resolve_url('project_detail', project=self.pk)
|
|
|
|
def auth(self):
|
|
return sign_data(f'{self.pk}-{self.nonce}', 12)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class Module(models.Model):
|
|
''' Enable modules on a oriject
|
|
'''
|
|
name = models.SlugField(max_length=20, choices=[ (x, x.title()) for x in settings.POLYPHONIC_MODULES ])
|
|
project = models.ForeignKey(Project, related_name="modules", on_delete=models.CASCADE)
|
|
|
|
def resource_key(resource, filename):
|
|
return f'{resource.project.folder}/resources/{filename}'
|
|
|
|
class Resource(models.Model):
|
|
''' A viewable file resource attached to a project
|
|
|
|
e.g PDF instructions, MP3 backing track
|
|
'''
|
|
project = models.ForeignKey(Project, related_name='resources', on_delete=models.CASCADE)
|
|
name = models.CharField(max_length=100)
|
|
description = models.TextField(blank=True)
|
|
file = models.FileField(storage=BYOStorage(), upload_to=resource_key)
|
|
media_type = models.CharField(max_length=10, choices=MEDIA_TYPES, default='*')
|
|
visible = models.BooleanField(default=True)
|
|
|
|
class Meta:
|
|
ordering = ['-visible', '-pk']
|
|
|
|
def accept(self):
|
|
if self.media_type == 'general':
|
|
return ".*"
|
|
return f"{self.media_type}/*"
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class WikiPage(models.Model):
|
|
''' An editable wiki page for the project in markdown format
|
|
|
|
'''
|
|
project = models.ForeignKey(Project, related_name='wiki_pages', on_delete=models.CASCADE)
|
|
title = models.CharField(max_length=255)
|
|
markdown = models.TextField()
|
|
|
|
def get_absolute_url(self):
|
|
return resolve_url('wiki', project=self.project_id, pk=self.pk)
|
|
|
|
def __str__(self):
|
|
return self.title |