2012-08-14 07:54:45 +00:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from decimal import Decimal
|
|
|
|
|
|
2016-06-18 07:07:21 +00:00
|
|
|
|
from django.core.exceptions import ValidationError
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
from django.db import models
|
2015-10-16 17:36:58 +00:00
|
|
|
|
from django.db.models import Q, F
|
2016-06-18 07:01:56 +00:00
|
|
|
|
from django.db.models import Case, When, Value
|
2016-06-19 08:37:54 +00:00
|
|
|
|
from django.db.models import Count
|
2012-08-14 07:54:45 +00:00
|
|
|
|
from django.db.models.signals import post_save
|
|
|
|
|
|
2020-11-22 12:21:54 +00:00
|
|
|
|
from django.contrib.auth import get_user_model
|
2015-06-26 03:46:09 +00:00
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2012-08-14 07:54:45 +00:00
|
|
|
|
|
2017-05-07 03:22:28 +00:00
|
|
|
|
from symposion import constants
|
|
|
|
|
from symposion.text_parser import parse
|
2012-08-14 07:54:45 +00:00
|
|
|
|
from symposion.proposals.models import ProposalBase
|
2012-08-31 04:30:53 +00:00
|
|
|
|
from symposion.schedule.models import Presentation
|
2012-08-14 07:54:45 +00:00
|
|
|
|
|
2020-11-22 12:21:54 +00:00
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
|
|
|
|
|
class Votes(object):
|
2016-06-18 01:40:18 +00:00
|
|
|
|
ABSTAIN = "0"
|
2016-06-10 05:29:09 +00:00
|
|
|
|
PLUS_TWO = "+2"
|
2012-08-14 07:54:45 +00:00
|
|
|
|
PLUS_ONE = "+1"
|
2016-08-02 00:53:53 +00:00
|
|
|
|
MINUS_ONE = "-1"
|
|
|
|
|
MINUS_TWO = "-2"
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
CHOICES = [
|
2016-06-10 05:29:09 +00:00
|
|
|
|
(PLUS_TWO, _("+2 — Good proposal and I will argue for it to be accepted.")),
|
|
|
|
|
(PLUS_ONE, _("+1 — OK proposal, but I will not argue for it to be accepted.")),
|
|
|
|
|
(MINUS_ONE, _("−1 — Weak proposal, but I will not argue strongly against acceptance.")),
|
|
|
|
|
(MINUS_TWO, _("−2 — Serious issues and I will argue to reject this proposal.")),
|
2016-06-18 01:40:18 +00:00
|
|
|
|
(ABSTAIN, _("Abstain - I do not want to review this proposal and I do not want to see it again.")),
|
2012-08-14 07:54:45 +00:00
|
|
|
|
]
|
2017-04-17 12:51:48 +00:00
|
|
|
|
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
VOTES = Votes()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReviewAssignment(models.Model):
|
|
|
|
|
AUTO_ASSIGNED_INITIAL = 0
|
|
|
|
|
OPT_IN = 1
|
|
|
|
|
AUTO_ASSIGNED_LATER = 2
|
2013-06-22 02:50:16 +00:00
|
|
|
|
|
|
|
|
|
NUM_REVIEWERS = 3
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
ORIGIN_CHOICES = [
|
2015-06-26 03:46:09 +00:00
|
|
|
|
(AUTO_ASSIGNED_INITIAL, _("auto-assigned, initial")),
|
|
|
|
|
(OPT_IN, _("opted-in")),
|
|
|
|
|
(AUTO_ASSIGNED_LATER, _("auto-assigned, later")),
|
2012-08-14 07:54:45 +00:00
|
|
|
|
]
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2020-11-22 12:21:54 +00:00
|
|
|
|
proposal = models.ForeignKey(
|
|
|
|
|
ProposalBase,
|
|
|
|
|
verbose_name=_("Proposal"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
|
|
|
|
user = models.ForeignKey(
|
|
|
|
|
User,
|
|
|
|
|
verbose_name=_("User"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2015-06-26 03:46:09 +00:00
|
|
|
|
origin = models.IntegerField(choices=ORIGIN_CHOICES, verbose_name=_("Origin"))
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2015-06-26 03:46:09 +00:00
|
|
|
|
assigned_at = models.DateTimeField(default=datetime.now, verbose_name=_("Assigned at"))
|
|
|
|
|
opted_out = models.BooleanField(default=False, verbose_name=_("Opted out"))
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
@classmethod
|
|
|
|
|
def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL):
|
|
|
|
|
speakers = [proposal.speaker] + list(proposal.additional_speakers.all())
|
|
|
|
|
reviewers = User.objects.exclude(
|
|
|
|
|
pk__in=[
|
|
|
|
|
speaker.user_id
|
|
|
|
|
for speaker in speakers
|
|
|
|
|
if speaker.user_id is not None
|
2013-06-22 02:50:16 +00:00
|
|
|
|
] + [
|
|
|
|
|
assignment.user_id
|
|
|
|
|
for assignment in ReviewAssignment.objects.filter(
|
|
|
|
|
proposal_id=proposal.id)]
|
2012-08-14 07:54:45 +00:00
|
|
|
|
).filter(
|
|
|
|
|
groups__name="reviewers",
|
|
|
|
|
).filter(
|
|
|
|
|
Q(reviewassignment__opted_out=False) | Q(reviewassignment=None)
|
|
|
|
|
).annotate(
|
|
|
|
|
num_assignments=models.Count("reviewassignment")
|
|
|
|
|
).order_by(
|
2013-06-22 02:50:16 +00:00
|
|
|
|
"num_assignments", "?",
|
2012-08-14 07:54:45 +00:00
|
|
|
|
)
|
2013-06-22 02:50:16 +00:00
|
|
|
|
num_assigned_reviewers = ReviewAssignment.objects.filter(
|
|
|
|
|
proposal_id=proposal.id, opted_out=0).count()
|
|
|
|
|
for reviewer in reviewers[:max(0, cls.NUM_REVIEWERS - num_assigned_reviewers)]:
|
2012-08-14 07:54:45 +00:00
|
|
|
|
cls._default_manager.create(
|
|
|
|
|
proposal=proposal,
|
|
|
|
|
user=reviewer,
|
|
|
|
|
origin=origin,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProposalMessage(models.Model):
|
2020-11-22 12:21:54 +00:00
|
|
|
|
proposal = models.ForeignKey(
|
|
|
|
|
ProposalBase,
|
|
|
|
|
related_name="messages",
|
|
|
|
|
verbose_name=_("Proposal"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
|
|
|
|
user = models.ForeignKey(
|
|
|
|
|
User,
|
|
|
|
|
verbose_name=_("User"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2015-10-16 17:36:58 +00:00
|
|
|
|
message = models.TextField(verbose_name=_("Message"))
|
|
|
|
|
message_html = models.TextField(blank=True)
|
2015-06-26 03:46:09 +00:00
|
|
|
|
submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at"))
|
2012-08-14 07:54:45 +00:00
|
|
|
|
|
2015-10-16 17:36:58 +00:00
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
|
|
self.message_html = parse(self.message)
|
|
|
|
|
return super(ProposalMessage, self).save(*args, **kwargs)
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
class Meta:
|
|
|
|
|
ordering = ["submitted_at"]
|
2015-06-26 03:46:09 +00:00
|
|
|
|
verbose_name = _("proposal message")
|
|
|
|
|
verbose_name_plural = _("proposal messages")
|
2012-08-14 07:54:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Review(models.Model):
|
|
|
|
|
VOTES = VOTES
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2020-11-22 12:21:54 +00:00
|
|
|
|
proposal = models.ForeignKey(
|
|
|
|
|
ProposalBase,
|
|
|
|
|
related_name="reviews",
|
|
|
|
|
verbose_name=_("Proposal"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
|
|
|
|
user = models.ForeignKey(
|
|
|
|
|
User,
|
|
|
|
|
verbose_name=_("User"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
# No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
|
|
|
|
|
# like some complicated encoding system.
|
2015-06-26 03:46:09 +00:00
|
|
|
|
vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES, verbose_name=_("Vote"))
|
2016-06-18 07:07:21 +00:00
|
|
|
|
comment = models.TextField(
|
|
|
|
|
blank=True,
|
|
|
|
|
verbose_name=_("Comment")
|
|
|
|
|
)
|
2015-10-16 17:36:58 +00:00
|
|
|
|
comment_html = models.TextField(blank=True)
|
2015-06-26 03:46:09 +00:00
|
|
|
|
submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at"))
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2016-06-18 07:07:21 +00:00
|
|
|
|
def clean(self):
|
|
|
|
|
err = {}
|
|
|
|
|
if self.vote != VOTES.ABSTAIN and not self.comment.strip():
|
|
|
|
|
err["comment"] = ValidationError(_("You must provide a comment"))
|
|
|
|
|
|
|
|
|
|
if err:
|
|
|
|
|
raise ValidationError(err)
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
def save(self, **kwargs):
|
2015-10-16 17:36:58 +00:00
|
|
|
|
self.comment_html = parse(self.comment)
|
2012-08-14 07:54:45 +00:00
|
|
|
|
if self.vote:
|
|
|
|
|
vote, created = LatestVote.objects.get_or_create(
|
2014-07-30 18:19:26 +00:00
|
|
|
|
proposal=self.proposal,
|
|
|
|
|
user=self.user,
|
|
|
|
|
defaults=dict(
|
|
|
|
|
vote=self.vote,
|
|
|
|
|
submitted_at=self.submitted_at,
|
2012-08-14 07:54:45 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
if not created:
|
|
|
|
|
LatestVote.objects.filter(pk=vote.pk).update(vote=self.vote)
|
|
|
|
|
self.proposal.result.update_vote(self.vote, previous=vote.vote)
|
|
|
|
|
else:
|
|
|
|
|
self.proposal.result.update_vote(self.vote)
|
|
|
|
|
super(Review, self).save(**kwargs)
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
def delete(self):
|
|
|
|
|
model = self.__class__
|
|
|
|
|
user_reviews = model._default_manager.filter(
|
|
|
|
|
proposal=self.proposal,
|
|
|
|
|
user=self.user,
|
|
|
|
|
)
|
|
|
|
|
try:
|
2012-10-03 18:17:03 +00:00
|
|
|
|
# find the latest review
|
|
|
|
|
latest = user_reviews.exclude(pk=self.pk).order_by("-submitted_at")[0]
|
2012-08-14 07:54:45 +00:00
|
|
|
|
except IndexError:
|
2012-10-03 18:17:03 +00:00
|
|
|
|
# did not find a latest which means this must be the only one.
|
2012-08-14 07:54:45 +00:00
|
|
|
|
# treat it as a last, but delete the latest vote.
|
|
|
|
|
self.proposal.result.update_vote(self.vote, removal=True)
|
|
|
|
|
lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
|
|
|
|
|
lv.delete()
|
|
|
|
|
else:
|
2012-10-03 18:17:03 +00:00
|
|
|
|
# handle that we've found a latest vote
|
|
|
|
|
# check if self is the lastest vote
|
|
|
|
|
if self == latest:
|
|
|
|
|
# self is the latest review; revert the latest vote to the
|
|
|
|
|
# previous vote
|
2014-07-30 18:19:26 +00:00
|
|
|
|
previous = user_reviews.filter(submitted_at__lt=self.submitted_at)\
|
|
|
|
|
.order_by("-submitted_at")[0]
|
2012-08-14 07:54:45 +00:00
|
|
|
|
self.proposal.result.update_vote(self.vote, previous=previous.vote, removal=True)
|
|
|
|
|
lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
|
|
|
|
|
lv.update(
|
|
|
|
|
vote=previous.vote,
|
|
|
|
|
submitted_at=previous.submitted_at,
|
|
|
|
|
)
|
|
|
|
|
else:
|
2012-10-03 18:17:03 +00:00
|
|
|
|
# self is not the latest review so we just need to decrement
|
|
|
|
|
# the comment count
|
2017-08-08 06:17:12 +00:00
|
|
|
|
self.proposal.result.comment_count = F("comment_count") - 1
|
2012-08-14 07:54:45 +00:00
|
|
|
|
self.proposal.result.save()
|
|
|
|
|
# in all cases we need to delete the review; let's do it!
|
|
|
|
|
super(Review, self).delete()
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
def css_class(self):
|
|
|
|
|
return {
|
2016-06-18 01:40:18 +00:00
|
|
|
|
self.VOTES.ABSTAIN: "abstain",
|
2016-06-13 10:46:13 +00:00
|
|
|
|
self.VOTES.PLUS_TWO: "plus-two",
|
2012-08-14 07:54:45 +00:00
|
|
|
|
self.VOTES.PLUS_ONE: "plus-one",
|
|
|
|
|
self.VOTES.MINUS_ONE: "minus-one",
|
2016-06-13 10:46:13 +00:00
|
|
|
|
self.VOTES.MINUS_TWO: "minus-two",
|
2012-08-14 07:54:45 +00:00
|
|
|
|
}[self.vote]
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
@property
|
|
|
|
|
def section(self):
|
|
|
|
|
return self.proposal.kind.section.slug
|
|
|
|
|
|
2015-06-26 03:46:09 +00:00
|
|
|
|
class Meta:
|
|
|
|
|
verbose_name = _("review")
|
|
|
|
|
verbose_name_plural = _("reviews")
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
|
|
|
|
|
class LatestVote(models.Model):
|
|
|
|
|
VOTES = VOTES
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2020-11-22 12:21:54 +00:00
|
|
|
|
proposal = models.ForeignKey(
|
|
|
|
|
ProposalBase,
|
|
|
|
|
related_name="votes",
|
|
|
|
|
verbose_name=_("Proposal"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
|
|
|
|
user = models.ForeignKey(
|
|
|
|
|
User,
|
|
|
|
|
verbose_name=_("User"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
# No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
|
|
|
|
|
# like some complicated encoding system.
|
2015-06-26 03:46:09 +00:00
|
|
|
|
vote = models.CharField(max_length=2, choices=VOTES.CHOICES, verbose_name=_("Vote"))
|
|
|
|
|
submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at"))
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
class Meta:
|
|
|
|
|
unique_together = [("proposal", "user")]
|
2015-06-26 03:46:09 +00:00
|
|
|
|
verbose_name = _("latest vote")
|
|
|
|
|
verbose_name_plural = _("latest votes")
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
def css_class(self):
|
|
|
|
|
return {
|
2016-06-18 01:40:18 +00:00
|
|
|
|
self.VOTES.ABSTAIN: "abstain",
|
2016-06-13 10:46:13 +00:00
|
|
|
|
self.VOTES.PLUS_TWO: "plus-two",
|
2012-08-14 07:54:45 +00:00
|
|
|
|
self.VOTES.PLUS_ONE: "plus-one",
|
|
|
|
|
self.VOTES.MINUS_ONE: "minus-one",
|
2016-06-13 10:46:13 +00:00
|
|
|
|
self.VOTES.MINUS_TWO: "minus-two",
|
2012-08-14 07:54:45 +00:00
|
|
|
|
}[self.vote]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProposalResult(models.Model):
|
2020-11-22 12:21:54 +00:00
|
|
|
|
proposal = models.OneToOneField(
|
|
|
|
|
ProposalBase,
|
|
|
|
|
related_name="result",
|
|
|
|
|
verbose_name=_("Proposal"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
2015-06-26 03:46:09 +00:00
|
|
|
|
score = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"), verbose_name=_("Score"))
|
|
|
|
|
comment_count = models.PositiveIntegerField(default=0, verbose_name=_("Comment count"))
|
2016-06-19 08:39:07 +00:00
|
|
|
|
# vote_count only counts non-abstain votes.
|
2015-06-26 03:46:09 +00:00
|
|
|
|
vote_count = models.PositiveIntegerField(default=0, verbose_name=_("Vote count"))
|
2016-06-18 01:40:18 +00:00
|
|
|
|
abstain = models.PositiveIntegerField(default=0, verbose_name=_("Abstain"))
|
2016-06-10 05:29:09 +00:00
|
|
|
|
plus_two = models.PositiveIntegerField(default=0, verbose_name=_("Plus two"))
|
2015-06-26 03:46:09 +00:00
|
|
|
|
plus_one = models.PositiveIntegerField(default=0, verbose_name=_("Plus one"))
|
|
|
|
|
minus_one = models.PositiveIntegerField(default=0, verbose_name=_("Minus one"))
|
2016-06-10 05:29:09 +00:00
|
|
|
|
minus_two = models.PositiveIntegerField(default=0, verbose_name=_("Minus two"))
|
2012-08-14 07:54:45 +00:00
|
|
|
|
accepted = models.NullBooleanField(choices=[
|
|
|
|
|
(True, "accepted"),
|
|
|
|
|
(False, "rejected"),
|
|
|
|
|
(None, "undecided"),
|
2015-06-26 03:46:09 +00:00
|
|
|
|
], default=None, verbose_name=_("Accepted"))
|
2012-09-02 18:45:41 +00:00
|
|
|
|
status = models.CharField(max_length=20, choices=[
|
2015-06-26 03:46:09 +00:00
|
|
|
|
("accepted", _("accepted")),
|
|
|
|
|
("rejected", _("rejected")),
|
|
|
|
|
("undecided", _("undecided")),
|
|
|
|
|
("standby", _("standby")),
|
|
|
|
|
], default="undecided", verbose_name=_("Status"))
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
@classmethod
|
|
|
|
|
def full_calculate(cls):
|
|
|
|
|
for proposal in ProposalBase.objects.all():
|
|
|
|
|
result, created = cls._default_manager.get_or_create(proposal=proposal)
|
2016-06-19 08:37:54 +00:00
|
|
|
|
result.update_vote()
|
|
|
|
|
|
2017-08-08 06:17:12 +00:00
|
|
|
|
def calculate_score(self):
|
|
|
|
|
if self.vote_count == 0:
|
|
|
|
|
return 0
|
|
|
|
|
else:
|
|
|
|
|
return ((2 * self.plus_two + self.plus_one) - (2 * self.minus_two + self.minus_one)) / self.vote_count
|
|
|
|
|
|
2016-06-19 08:37:54 +00:00
|
|
|
|
def update_vote(self, *a, **k):
|
|
|
|
|
proposal = self.proposal
|
|
|
|
|
self.comment_count = Review.objects.filter(proposal=proposal).count()
|
|
|
|
|
agg = LatestVote.objects.filter(proposal=proposal).values(
|
|
|
|
|
"vote"
|
|
|
|
|
).annotate(
|
|
|
|
|
count=Count("vote")
|
|
|
|
|
)
|
|
|
|
|
vote_count = {}
|
|
|
|
|
# Set the defaults
|
|
|
|
|
for option in VOTES.CHOICES:
|
|
|
|
|
vote_count[option[0]] = 0
|
|
|
|
|
# Set the actual values if present
|
|
|
|
|
for d in agg:
|
|
|
|
|
vote_count[d["vote"]] = d["count"]
|
|
|
|
|
|
|
|
|
|
self.abstain = vote_count[VOTES.ABSTAIN]
|
|
|
|
|
self.plus_two = vote_count[VOTES.PLUS_TWO]
|
|
|
|
|
self.plus_one = vote_count[VOTES.PLUS_ONE]
|
|
|
|
|
self.minus_one = vote_count[VOTES.MINUS_ONE]
|
|
|
|
|
self.minus_two = vote_count[VOTES.MINUS_TWO]
|
2016-06-19 08:39:07 +00:00
|
|
|
|
self.vote_count = sum(i[1] for i in vote_count.items()) - self.abstain
|
2017-08-08 06:17:12 +00:00
|
|
|
|
self.score = self.calculate_score()
|
2012-08-14 07:54:45 +00:00
|
|
|
|
self.save()
|
|
|
|
|
|
2015-06-26 03:46:09 +00:00
|
|
|
|
class Meta:
|
|
|
|
|
verbose_name = _("proposal_result")
|
|
|
|
|
verbose_name_plural = _("proposal_results")
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
|
|
|
|
|
class Comment(models.Model):
|
2020-11-22 12:21:54 +00:00
|
|
|
|
proposal = models.ForeignKey(
|
|
|
|
|
ProposalBase,
|
|
|
|
|
related_name="comments",
|
|
|
|
|
verbose_name=_("Proposal"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
|
|
|
|
commenter = models.ForeignKey(
|
|
|
|
|
User,
|
|
|
|
|
verbose_name=_("Commenter"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
2015-10-16 17:36:58 +00:00
|
|
|
|
text = models.TextField(verbose_name=_("Text"))
|
|
|
|
|
text_html = models.TextField(blank=True)
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
# Or perhaps more accurately, can the user see this comment.
|
2015-06-26 03:46:09 +00:00
|
|
|
|
public = models.BooleanField(choices=[(True, _("public")), (False, _("private"))], default=False, verbose_name=_("Public"))
|
|
|
|
|
commented_at = models.DateTimeField(default=datetime.now, verbose_name=_("Commented at"))
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
verbose_name = _("comment")
|
|
|
|
|
verbose_name_plural = _("comments")
|
2012-08-14 07:54:45 +00:00
|
|
|
|
|
2015-10-16 17:36:58 +00:00
|
|
|
|
def save(self, *args, **kwargs):
|
2015-10-17 00:54:11 +00:00
|
|
|
|
self.text_html = parse(self.text)
|
2015-10-16 17:36:58 +00:00
|
|
|
|
return super(Comment, self).save(*args, **kwargs)
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
|
2012-09-07 02:36:40 +00:00
|
|
|
|
class NotificationTemplate(models.Model):
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2015-06-26 03:46:09 +00:00
|
|
|
|
label = models.CharField(max_length=100, verbose_name=_("Label"))
|
|
|
|
|
from_address = models.EmailField(verbose_name=_("From address"))
|
|
|
|
|
subject = models.CharField(max_length=100, verbose_name=_("Subject"))
|
|
|
|
|
body = models.TextField(verbose_name=_("Body"))
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
verbose_name = _("notification template")
|
|
|
|
|
verbose_name_plural = _("notification templates")
|
2012-09-07 02:36:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ResultNotification(models.Model):
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2020-11-22 12:21:54 +00:00
|
|
|
|
proposal = models.ForeignKey(
|
|
|
|
|
ProposalBase,
|
|
|
|
|
related_name="notifications",
|
|
|
|
|
verbose_name=_("Proposal"),
|
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
|
)
|
|
|
|
|
template = models.ForeignKey(
|
|
|
|
|
NotificationTemplate,
|
|
|
|
|
null=True,
|
|
|
|
|
blank=True,
|
|
|
|
|
on_delete=models.SET_NULL,
|
|
|
|
|
verbose_name=_("Template")
|
|
|
|
|
)
|
2015-06-26 03:46:09 +00:00
|
|
|
|
timestamp = models.DateTimeField(default=datetime.now, verbose_name=_("Timestamp"))
|
|
|
|
|
to_address = models.EmailField(verbose_name=_("To address"))
|
|
|
|
|
from_address = models.EmailField(verbose_name=_("From address"))
|
2016-09-17 05:53:47 +00:00
|
|
|
|
subject = models.CharField(max_length=255, verbose_name=_("Subject"))
|
2015-06-26 03:46:09 +00:00
|
|
|
|
body = models.TextField(verbose_name=_("Body"))
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2015-06-11 23:04:22 +00:00
|
|
|
|
def recipients(self):
|
|
|
|
|
for speaker in self.proposal.speakers():
|
|
|
|
|
yield speaker.email
|
|
|
|
|
|
2016-09-03 03:16:05 +00:00
|
|
|
|
def __unicode__(self):
|
|
|
|
|
return self.proposal.title + ' ' + self.timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
|
|
2012-09-08 23:02:51 +00:00
|
|
|
|
@property
|
|
|
|
|
def email_args(self):
|
2015-06-11 23:04:22 +00:00
|
|
|
|
return (self.subject, self.body, self.from_address, self.recipients())
|
2012-09-07 02:36:40 +00:00
|
|
|
|
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
def promote_proposal(proposal):
|
2012-08-31 04:30:53 +00:00
|
|
|
|
if hasattr(proposal, "presentation") and proposal.presentation:
|
|
|
|
|
# already promoted
|
|
|
|
|
presentation = proposal.presentation
|
2016-09-18 05:46:53 +00:00
|
|
|
|
presentation.title = proposal.title
|
|
|
|
|
presentation.abstract = proposal.abstract
|
|
|
|
|
presentation.speaker = proposal.speaker
|
|
|
|
|
presentation.proposal_base = proposal
|
|
|
|
|
presentation.save()
|
|
|
|
|
presentation.additional_speakers.clear()
|
2012-08-31 04:30:53 +00:00
|
|
|
|
else:
|
|
|
|
|
presentation = Presentation(
|
2014-07-30 18:19:26 +00:00
|
|
|
|
title=proposal.title,
|
|
|
|
|
abstract=proposal.abstract,
|
|
|
|
|
speaker=proposal.speaker,
|
|
|
|
|
section=proposal.section,
|
|
|
|
|
proposal_base=proposal,
|
2012-08-31 04:30:53 +00:00
|
|
|
|
)
|
|
|
|
|
presentation.save()
|
2016-09-18 05:46:53 +00:00
|
|
|
|
for speaker in proposal.additional_speakers.all():
|
|
|
|
|
presentation.additional_speakers.add(speaker)
|
|
|
|
|
presentation.save()
|
2014-07-30 18:19:26 +00:00
|
|
|
|
|
2012-08-31 04:30:53 +00:00
|
|
|
|
return presentation
|
2012-08-14 07:54:45 +00:00
|
|
|
|
|
|
|
|
|
|
2012-08-31 04:41:12 +00:00
|
|
|
|
def unpromote_proposal(proposal):
|
|
|
|
|
if hasattr(proposal, "presentation") and proposal.presentation:
|
|
|
|
|
proposal.presentation.delete()
|
|
|
|
|
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
def accepted_proposal(sender, instance=None, **kwargs):
|
2012-09-21 01:07:08 +00:00
|
|
|
|
if instance is None:
|
2012-08-14 07:54:45 +00:00
|
|
|
|
return
|
2012-09-02 18:45:41 +00:00
|
|
|
|
if instance.status == "accepted":
|
2012-08-14 07:54:45 +00:00
|
|
|
|
promote_proposal(instance.proposal)
|
2012-08-31 04:41:12 +00:00
|
|
|
|
else:
|
|
|
|
|
unpromote_proposal(instance.proposal)
|
2017-04-17 12:51:48 +00:00
|
|
|
|
|
|
|
|
|
|
2012-08-14 07:54:45 +00:00
|
|
|
|
post_save.connect(accepted_proposal, sender=ProposalResult)
|