symposion_app/symposion/proposals/models.py

218 lines
6.6 KiB
Python
Raw Normal View History

from __future__ import unicode_literals
2012-07-12 04:38:39 +00:00
import os
import uuid
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models import Q
from django.utils.encoding import python_2_unicode_compatible
2012-07-18 23:21:21 +00:00
from django.utils.translation import ugettext_lazy as _
2014-01-11 06:50:04 +00:00
from django.utils.timezone import now
2012-07-12 04:38:39 +00:00
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
2012-07-12 04:38:39 +00:00
import reversion
2012-07-12 04:38:39 +00:00
from markitup.fields import MarkupField
from model_utils.managers import InheritanceManager
from symposion.conference.models import Section
from symposion.speakers.models import Speaker
2012-07-12 04:38:39 +00:00
@python_2_unicode_compatible
2012-07-12 04:38:39 +00:00
class ProposalSection(models.Model):
"""
configuration of proposal submissions for a specific Section.
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
a section is available for proposals iff:
* it is after start (if there is one) and
* it is before end (if there is one) and
* closed is NULL or False
"""
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
section = models.OneToOneField(Section)
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
start = models.DateTimeField(null=True, blank=True)
end = models.DateTimeField(null=True, blank=True)
closed = models.NullBooleanField()
2012-08-14 20:52:23 +00:00
published = models.NullBooleanField()
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
@classmethod
def available(cls):
return cls._default_manager.filter(
2014-01-11 06:50:04 +00:00
Q(start__lt=now()) | Q(start=None),
Q(end__gt=now()) | Q(end=None),
2012-07-12 04:38:39 +00:00
Q(closed=False) | Q(closed=None),
)
2014-07-30 18:19:26 +00:00
def is_available(self):
if self.closed:
return False
2014-01-11 06:50:04 +00:00
if self.start and self.start > now():
return False
2014-01-11 06:50:04 +00:00
if self.end and self.end < now():
return False
return True
2014-07-30 18:19:26 +00:00
def __str__(self):
2012-07-12 04:38:39 +00:00
return self.section.name
@python_2_unicode_compatible
2012-07-12 04:38:39 +00:00
class ProposalKind(models.Model):
"""
e.g. talk vs panel vs tutorial vs poster
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
Note that if you have different deadlines, reviewers, etc. you'll want
to distinguish the section as well as the kind.
"""
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
section = models.ForeignKey(Section, related_name="proposal_kinds")
2014-07-30 18:19:26 +00:00
2012-07-18 23:21:21 +00:00
name = models.CharField(_("Name"), max_length=100)
2012-07-12 04:38:39 +00:00
slug = models.SlugField()
2014-07-30 18:19:26 +00:00
def __str__(self):
2012-07-12 04:38:39 +00:00
return self.name
@python_2_unicode_compatible
2012-07-12 04:38:39 +00:00
class ProposalBase(models.Model):
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
objects = InheritanceManager()
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
kind = models.ForeignKey(ProposalKind)
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
title = models.CharField(max_length=100)
description = models.TextField(
_("Brief Description"),
2012-07-12 04:38:39 +00:00
max_length=400, # @@@ need to enforce 400 in UI
2014-07-30 18:19:26 +00:00
help_text=_("If your proposal is accepted this will be made public and printed in the "
"program. Should be one paragraph, maximum 400 characters.")
2012-07-12 04:38:39 +00:00
)
abstract = MarkupField(
2012-07-18 23:21:21 +00:00
_("Detailed Abstract"),
2014-07-30 18:19:26 +00:00
help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit "
"using <a href='http://daringfireball.net/projects/markdown/basics' "
"target='_blank'>Markdown</a>.")
2012-07-12 04:38:39 +00:00
)
additional_notes = MarkupField(
blank=True,
2014-07-30 18:19:26 +00:00
help_text=_("Anything else you'd like the program committee to know when making their "
"selection: your past experience, etc. This is not made public. Edit using "
"<a href='http://daringfireball.net/projects/markdown/basics' "
"target='_blank'>Markdown</a>.")
2012-07-12 04:38:39 +00:00
)
submitted = models.DateTimeField(
2014-01-11 06:50:04 +00:00
default=now,
2012-07-12 04:38:39 +00:00
editable=False,
)
speaker = models.ForeignKey(Speaker, related_name="proposals")
def additional_speaker_validator(self, a_speaker):
if a_speaker.speaker.email == self.speaker.email:
raise ValidationError(_("%s is same as primary speaker.") % a_speaker.speaker.email)
if a_speaker in [self.additional_speakers]:
raise ValidationError(_("%s has already been in speakers.") % a_speaker.speaker.email)
additional_speakers = models.ManyToManyField(Speaker, through="AdditionalSpeaker",
blank=True, validators=[additional_speaker_validator])
2012-07-12 04:38:39 +00:00
cancelled = models.BooleanField(default=False)
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
def can_edit(self):
return True
2014-07-30 18:19:26 +00:00
2012-08-31 02:55:20 +00:00
@property
def section(self):
return self.kind.section
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
@property
def speaker_email(self):
return self.speaker.email
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
@property
def number(self):
return str(self.pk).zfill(3)
2014-07-30 18:19:26 +00:00
@property
def status(self):
try:
return self.result.status
except ObjectDoesNotExist:
return _('Undecided')
2012-07-12 04:38:39 +00:00
def speakers(self):
yield self.speaker
2014-07-30 18:19:26 +00:00
speakers = self.additional_speakers.exclude(
additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED)
for speaker in speakers:
2012-07-12 04:38:39 +00:00
yield speaker
2014-07-30 18:19:26 +00:00
2012-09-09 01:38:57 +00:00
def notification_email_context(self):
return {
"title": self.title,
"speaker": self.speaker.name,
"speakers": ', '.join([x.name for x in self.speakers()]),
2012-09-09 01:38:57 +00:00
"kind": self.kind.name,
}
2012-07-12 04:38:39 +00:00
def __str__(self):
return self.title
reversion.register(ProposalBase)
@python_2_unicode_compatible
2012-07-12 04:38:39 +00:00
class AdditionalSpeaker(models.Model):
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
SPEAKING_STATUS_PENDING = 1
SPEAKING_STATUS_ACCEPTED = 2
SPEAKING_STATUS_DECLINED = 3
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
SPEAKING_STATUS = [
2012-07-18 23:21:21 +00:00
(SPEAKING_STATUS_PENDING, _("Pending")),
(SPEAKING_STATUS_ACCEPTED, _("Accepted")),
(SPEAKING_STATUS_DECLINED, _("Declined")),
2012-07-12 04:38:39 +00:00
]
2014-07-30 18:19:26 +00:00
speaker = models.ForeignKey(Speaker)
2012-07-12 04:38:39 +00:00
proposalbase = models.ForeignKey(ProposalBase)
status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING)
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
class Meta:
unique_together = ("speaker", "proposalbase")
def __str__(self):
if self.status is self.SPEAKING_STATUS_PENDING:
return _(u"pending speaker (%s)") % self.speaker.email
elif self.status is self.SPEAKING_STATUS_DECLINED:
return _(u"declined speaker (%s)") % self.speaker.email
else:
return self.speaker.name
2012-07-12 04:38:39 +00:00
def uuid_filename(instance, filename):
ext = filename.split(".")[-1]
filename = "%s.%s" % (uuid.uuid4(), ext)
return os.path.join("document", filename)
class SupportingDocument(models.Model):
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents")
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
uploaded_by = models.ForeignKey(User)
2014-01-11 06:50:04 +00:00
created_at = models.DateTimeField(default=now)
2014-07-30 18:19:26 +00:00
2012-07-12 04:38:39 +00:00
file = models.FileField(upload_to=uuid_filename)
description = models.CharField(max_length=140)
def download_url(self):
2014-07-30 18:19:26 +00:00
return reverse("proposal_document_download",
args=[self.pk, os.path.basename(self.file.name).lower()])