from django.contrib import messages
from django.conf import settings
from django.db import transaction, connection
-from django.db.models import Q, Count
+from django.db.models import Q, Count, Avg
from django.db.models.expressions import F
from django.forms import formsets
from django.forms import ValidationError
from models import Conference, ConferenceRegistration, ConferenceSession
-from models import ConferenceSessionSlides, GlobalOptOut
+from models import ConferenceSessionSlides, ConferenceSessionVote, GlobalOptOut
from models import ConferenceSessionFeedback, Speaker, Speaker_Photo
from models import ConferenceFeedbackQuestion, ConferenceFeedbackAnswer
from models import RegistrationType, PrepaidVoucher, PrepaidBatch
curs = connection.cursor()
- if request.method=='POST':
- # Record votes
- # We could probably do this with some fancy writable CTEs, but
- # this code won't run often, so we don't really care about being
- # fast, and this is easier...
- # Thus, remove existing entries and replace them with current ones.
- curs.execute("DELETE FROM confreg_conferencesessionvote WHERE voter_id=%(userid)s AND session_id IN (SELECT id FROM confreg_conferencesession WHERE conference_id=%(confid)s)", {
- 'confid': conference.id,
- 'userid': request.user.id,
- })
- curs.executemany("INSERT INTO confreg_conferencesessionvote (session_id, voter_id, vote, comment) VALUES (%(sid)s, %(vid)s, %(vote)s, %(comment)s)", [
- {
- 'sid': k[3:],
- 'vid': request.user.id,
- 'vote': int(v) > 0 and int(v) or None,
- 'comment': request.POST['tc_%s' % k[3:]],
- }
- for k,v in request.POST.items() if k.startswith("sv_") and (int(v)>0 or request.POST['tc_%s' % k[3:]])
- ])
-
- return HttpResponseRedirect(".")
-
order = ""
if request.GET.has_key("sort"):
if request.GET["sort"] == "avg":
session.status!=session.lastnotifiedstatus and 1 or 0,
), content_type='text/plain')
+@login_required
+@transaction.atomic
+def talkvote_vote(request, confname):
+ conference = get_object_or_404(Conference, urlname=confname)
+ if not conference.talkvoters.filter(pk=request.user.id):
+ return HttpResponse('You are not a talk voter for this conference!')
+ if request.method!='POST':
+ return HttpResponse('Can only use POST')
+
+ session = get_object_or_404(ConferenceSession, conference=conference, id=request.POST['sessionid'])
+ v = int(request.POST['vote'])
+ if v > 0:
+ vote,created = ConferenceSessionVote.objects.get_or_create(session=session, voter=request.user)
+ vote.vote = v
+ vote.save()
+ else:
+ ConferenceSessionVote.objects.filter(session=session, voter=request.user).delete()
+
+ # Re-calculate the average
+ avg = session.conferencesessionvote_set.all().aggregate(Avg('vote'))['vote__avg']
+ if avg is None:
+ avg = 0
+ return HttpResponse("{0:.2f}".format(avg), content_type='text/plain')
+
+@login_required
+@transaction.atomic
+def talkvote_comment(request, confname):
+ conference = get_object_or_404(Conference, urlname=confname)
+ if not conference.talkvoters.filter(pk=request.user.id):
+ return HttpResponse('You are not a talk voter for this conference!')
+ if request.method!='POST':
+ return HttpResponse('Can only use POST')
+
+ session = get_object_or_404(ConferenceSession, conference=conference, id=request.POST['sessionid'])
+ vote,created = ConferenceSessionVote.objects.get_or_create(session=session, voter=request.user)
+ vote.comment = request.POST['comment']
+ vote.save()
+
+ return HttpResponse(vote.comment, content_type='text/plain')
+
@login_required
@csrf_exempt
@transaction.atomic
url(r'^events/(?P<urlname>[^/]+)/volunteer/', include(postgresqleu.confreg.volsched)),
url(r'^events/([^/]+)/talkvote/$', postgresqleu.confreg.views.talkvote),
url(r'^events/([^/]+)/talkvote/changestatus/$', postgresqleu.confreg.views.talkvote_status),
+ url(r'^events/([^/]+)/talkvote/vote/$', postgresqleu.confreg.views.talkvote_vote),
+ url(r'^events/([^/]+)/talkvote/comment/$', postgresqleu.confreg.views.talkvote_comment),
url(r'^events/([^/]+)/sessions/$', postgresqleu.confreg.views.sessionlist),
url(r'^events/speaker/(\d+)/photo/$', postgresqleu.confreg.views.speakerphoto),
url(r'^events/([^/]+)/speakerprofile/$', postgresqleu.confreg.views.speakerprofile),
<script type="text/javascript">
$(function() {
- $('.dlg').each(function(idx, el) {
- $(el).dialog({
- autoOpen: false,
- minWidth: 400,
- minHeight: 250,
- height: 600,
- });
+ $('.talkaccd').accordion({
+ 'collapsible': true,
+ 'active': false,
+ 'heightStyle': 'content',
+ 'animate': {
+ duration: 100,
+ },
});
$('#dlgStatus').dialog({
resizable: false,
});
+ $('#dlgComment').dialog({
+ autoOpen: false,
+ modal: true,
+ resizable: false,
+ minWidth: 350,
+ });
+ $('#dlgComment').live('keyup', function(e){
+ if (e.keyCode == 13) {
+ $(':button:contains("Save")').click();
+ }
+ });
+
$('#ajaxStatus').hide();
});
buttons: buttons,
}).dialog('open');
}
+
+function castVote(sessionid) {
+ var s = $('#sv_' + sessionid);
+ var p = s.parent('td');
+ var val = s.val();
+ var avgbox = p.siblings('td.avgbox');
+
+ p.css('background-color', 'gray');
+ $.post('vote/',
+ {
+ 'csrfmiddlewaretoken': '{{csrf_token}}',
+ 'sessionid': sessionid,
+ 'vote': val,
+ },
+ function(data) {
+ p.css('background-color', (val==0)?'red':'white');
+ avgbox.html(data);
+ }
+ ).fail(function() {
+ alert('AJAX call failed');
+ });
+}
+
+function editComment(sessionid) {
+ var old = $('#owncomment_' + sessionid).text();
+ $('#dlgCommentText').val(old);
+
+ $('#dlgComment').dialog('option', {
+ 'title': 'Edit comment',
+ 'modal': true,
+ }).dialog('option', {
+ buttons: [{
+ id: 'dlgCommentSaveButton',
+ text: 'Save',
+ click: function() {
+ var dlg = $(this);
+ var txt = $('#dlgCommentText').val();
+ $('#dlgCommentSaveButton').button("disable");
+ if (txt != old) {
+ $.post('comment/', {
+ 'csrfmiddlewaretoken': '{{csrf_token}}',
+ 'sessionid': sessionid,
+ 'comment': txt,
+ },
+ function (data) {
+ dlg.dialog("close");
+ $('#owncomment_' + sessionid).text(data);
+ $('#owncommentwrap_' + sessionid).css('display', (data=='')?'none':'block');
+ }
+ ).fail(function() {
+ alert('AJAX call failed');
+ });
+ }
+ else {
+ dlg.dialog("close");
+ }
+ },
+ }],
+ }).dialog('open');
+}
</script>
<style>
td.dlgClickable {
<div id="ajaxStatus" class="alert alert-success" style="position: fixed; width: 100%;opacity: .8; text-align: center; font-weight: bold; font-size: 1.2em;">Nothing Yet</div>
-<form method="post" action=".">{% csrf_token %}
<table class="table table-bordered table-condensed">
<tr>
- <th><a href="?sort=session">Session</a> | <a href="?sort=speakers">Speakers</a></th>
+ <th class="col-md-6"><a href="?sort=session">Session</a> | <a href="?sort=speakers">Speakers</a></th>
<th>Status</th>
{%for u in users%}
<th>{{u}}</th>
{%endfor%}
<th><a href="?sort=avg">Average</a></th>
- <th>Your comments</th>
- <th>Other comments</th>
+ <th>Comments</th>
</tr>
{%for s in sessionvotes%}
<tr>
- <td onClick="showDialog({{s.id}}, '{{s.title|escape|escapejs|escape}}')" class="dlgClickable">{{s.title}} ({{s.speakers}})
- <div id="popup_{{s.id}}" class="dlg">
- <div><strong>Speakers:</strong> {{s.speakers_full}}</div>
- <div><strong>Track:</strong> {{s.track}}</div>
- <p>
+ <td>
+ <div class="talkaccd">
+ <h3>{{s.title}} ({{s.speakers}})</h3>
+ <div>
+ <div><strong>Speakers:</strong> {{s.speakers_full}}</div>
+ <div><strong>Track:</strong> {{s.track}}</div>
+ <br/>
+ <p>
{{s.abstract}}
- </p>
+ </p>
{%if s.submissionnote%}
- <hr/>
- <h3>Submission notes</h3>
- <p>
+ <hr/>
+ <h3>Submission notes</h3>
+ <p>
{{s.submissionnote}}
- </p>
+ </p>
{%endif%}
- <hr/>
- <h3>Speaker profile</h3>
- <p>
+ <hr/>
+ <h3>Speaker profile</h3>
+ <p>
{{s.speakers_long|markdown}}
- </p>
- </div>
+ </p>
+ </div>
+ </div>
</td>
{%if isadmin%}
<td{%if not s.statusid == s.laststatusid %} bgcolor="yellow"{%endif%} onClick="changeStatus({{s.id}})" class="dlgClickable" id="statusstr{{s.id}}">{{s.status}}</td>
{%endif%}
{%for u in s.users%}
<td{%if u|default_if_none:0 == 0%} style="background-color:red"{%endif%}>{%if forloop.first %}
- <select name="sv_{{s.id}}">
+ <select id="sv_{{s.id}}" onChange="castVote({{s.id}})">
{%for opt in "0123456789"|make_list%}
<option value="{{opt}}"{%if opt|add:0 == u|add:0%} SELECTED{%endif%}>{{opt}}</option>
{%endfor%}
{%endif%}
</td>
{%endfor%}
- <td>{{s.avg|default_if_none:''}}</td>
- <td><input type="text" name="tc_{{s.id}}" maxlength="200" value="{{s.owncomment}}"/></td>
- <td>{{s.comments|safe}}</td>
+ <td class="avgbox">{{s.avg|default_if_none:''}}</td>
+ <td>
+ <div style="margin-right: 0.5em; float:left;">
+ <a class="btn btn-default btn-xs" href="javascript:editComment({{s.id}})"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
+ </div>
+ <div style="display:inline-block;">
+ <span id="owncommentwrap_{{s.id}}" style="display:{%if s.owncomment%}block{%else%}none{%endif%}">{{user.username}}: <span id="owncomment_{{s.id}}">{{s.owncomment}}</span></span>{{s.comments|safe}}
+ </div>
+ </td>
</tr>
{%endfor%}
</table>
-<input type="submit" value="Save">
-</form>
+
{%if isadmin and conference.pending_session_notifications%}
<h2>Notifications</h2>
<p>
<div id="dlgStatus">
</div>
+<div id="dlgComment">
+ <input type="text" id="dlgCommentText" style="width: 300px;">
+</div>
+
{%endblock%}