diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 3fb405d3..8a7db88b 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -284,6 +284,24 @@ class Comment(models.Model): commented_at = models.DateTimeField(default=datetime.now) +class NotificationTemplate(models.Model): + + label = models.CharField(max_length=100) + subject = models.CharField(max_length=100) + body = models.TextField() + + +class ResultNotification(models.Model): + + proposal = models.ForeignKey("proposals.ProposalBase", related_name="notifications") + template = models.ForeignKey(NotificationTemplate, null=True, blank=True, on_delete=models.SET_NULL) + timestamp = models.DateTimeField(default=datetime.now) + to_address = models.EmailField() + from_address = models.EmailField() + subject = models.CharField(max_length=100) + body = models.TextField() + + def promote_proposal(proposal): if hasattr(proposal, "presentation") and proposal.presentation: diff --git a/symposion/reviews/urls.py b/symposion/reviews/urls.py index 6770cb88..8336dc04 100644 --- a/symposion/reviews/urls.py +++ b/symposion/reviews/urls.py @@ -5,10 +5,11 @@ urlpatterns = patterns("symposion.reviews.views", url(r"^section/(?P<section_slug>[\w\-]+)/$", "review_section", name="review_section"), url(r"^section/(?P<section_slug>[\w\-]+)/assignments/$", "review_section", {"assigned": True}, name="review_section_assignments"), url(r"^section/(?P<section_slug>[\w\-]+)/status/$", "review_status", name="review_status"), - url(r"^section/(?P<section_slug>[\w\-]+)/status/(?P<key>[\w]+)/$", "review_status", name="review_status"), + url(r"^section/(?P<section_slug>[\w\-]+)/status/(?P<key>\w+)/$", "review_status", name="review_status"), url(r"^section/(?P<section_slug>[\w\-]+)/list/(?P<user_pk>\d+)/$", "review_list", name="review_list_user"), url(r"^section/(?P<section_slug>[\w\-]+)/admin/$", "review_admin", name="review_admin"), url(r"^section/(?P<section_slug>[\w\-]+)/admin/accept/$", "review_bulk_accept", name="review_bulk_accept"), + url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/$", "result_notification", name="result_notification"), url(r"^review/(?P<pk>\d+)/$", "review_detail", name="review_detail"), diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 71cac478..ef90c2b0 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -369,3 +369,17 @@ def review_bulk_accept(request, section_slug): return render(request, "reviews/review_bulk_accept.html", { "form": form, }) + + +@login_required +def result_notification(request, section_slug, status): + if not request.user.has_perm("reviews.can_manage_%s" % section_slug): + return access_not_permitted(request) + + proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, result__status=status).select_related("speaker__user", "result").select_subclasses() + + ctx = { + "section_slug": section_slug, + "proposals": proposals, + } + return render(request, "reviews/result_notification.html", ctx) diff --git a/symposion/templates/reviews/result_notification.html b/symposion/templates/reviews/result_notification.html new file mode 100644 index 00000000..f16993c3 --- /dev/null +++ b/symposion/templates/reviews/result_notification.html @@ -0,0 +1,132 @@ +{% extends "reviews/base.html" %} + +{% load i18n %} + +{% block extra_style %} + <style type="text/css"> + .table-striped tbody tr.selected td { + background-color: #F7F4E6; + } + </style> +{% endblock %} + +{% block body %} + <h1>Result Notification</h1> + + <span class="action-counter">0</span> selected + + <table class="table table-striped table-bordered"> + <thead> + <th><input type="checkbox" id="action-toggle"></th> + <th>#</th> + <th>{% trans "Speaker / Title" %}</th> + <th>{% trans "Category" %}</th> + <th>{% trans "Status" %}</th> + <th>{% trans "Notified?" %}</th> + </thead> + + <tbody> + {% for proposal in proposals %} + <tr> + <td><input class="action-select" type="checkbox" name="_selected_action" value="{{ proposal.pk }}"></td> + <td>{{ proposal.number }}</td> + <td> + <a href="{% url review_detail proposal.pk %}"> + <small><strong>{{ proposal.speaker }}</strong></small> + <br /> + {{ proposal.title }} + </a> + </td> + <td>{{ proposal.track }}</td> + <td> + {% with proposal.result.status as status %} + <div class="{{ status }}"> + {% if status != "undecided" %} + <span>{{ status }}</span> + {% endif %} + </div> + {% endwith %} + </td> + <td> + </td> + </tr> + {% endfor %} + </tbody> + </table> +{% endblock %} + +{% block extra_script %} + <script type="text/javascript"> + (function($) { + $.fn.actions = function(opts) { + var options = $.extend({}, $.fn.actions.defaults, opts); + var actionCheckboxes = $(this); + checker = function(checked) { + if (checked) { + } else { + reset(); + } + $(actionCheckboxes).attr("checked", checked) + .parent().parent().toggleClass(options.selectedClass, checked); + } + updateCounter = function() { + var sel = $(actionCheckboxes).filter(":checked").length; + $(options.counterContainer).html(sel); + $(options.allToggle).attr("checked", function() { + if (sel == actionCheckboxes.length) { + value = true; + } else { + value = false; + } + return value; + }); + } + reset = function() { + $(options.counterContainer).show(); + } + // Show counter by default + $(options.counterContainer).show(); + // Check state of checkboxes and reinit state if needed + $(this).filter(":checked").each(function(i) { + $(this).parent().parent().toggleClass(options.selectedClass); + updateCounter(); + }); + $(options.allToggle).show().click(function() { + checker($(this).attr("checked")); + updateCounter(); + }); + lastChecked = null; + $(actionCheckboxes).click(function(event) { + if (!event) { var event = window.event; } + var target = event.target ? event.target : event.srcElement; + if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey == true) { + var inrange = false; + $(lastChecked).attr("checked", target.checked) + .parent().parent().toggleClass(options.selectedClass, target.checked); + $(actionCheckboxes).each(function() { + if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) { + inrange = (inrange) ? false : true; + } + if (inrange) { + $(this).attr("checked", target.checked) + .parent().parent().toggleClass(options.selectedClass, target.checked); + } + }); + } + $(target).parent().parent().toggleClass(options.selectedClass, target.checked); + lastChecked = target; + updateCounter(); + }); + } + /* Setup plugin defaults */ + $.fn.actions.defaults = { + counterContainer: "span.action-counter", + allToggle: "#action-toggle", + selectedClass: "selected" + } + })($); + $(function() { + $("tr input.action-select").actions(); + }); + </script> +{% endblock %}