from django.utils.safestring import mark_safe
from django.utils.html import escape
from django.utils import timezone
+import django.db.models
from postgresqleu.confsponsor.models import ScannedAttendee
from .models import Conference
from .twitter import get_all_conference_social_media
from postgresqleu.util.fields import UserModelChoiceField
from postgresqleu.util.widgets import EmailTextWidget, MonospaceTextarea
+from postgresqleu.util.widgets import CallForPapersSpeakersWidget
from postgresqleu.util.db import exec_to_list
from postgresqleu.util.magic import magicdb
from postgresqleu.util.backendlookups import GeneralAccountLookup
if 'data' in kwargs and 'speaker' in kwargs['data']:
vals.extend([int(x) for x in kwargs['data'].getlist('speaker')])
- self.fields['speaker'].queryset = Speaker.objects.defer('photo', 'photo512').filter(pk__in=vals)
- self.fields['speaker'].label_from_instance = lambda x: "{0} <{1}>".format(x.fullname, x.email)
+ self.fields['speaker'].widget = CallForPapersSpeakersWidget()
+ self.fields['speaker'].queryset = Speaker.objects.defer('photo', 'photo512').filter(pk__in=vals).annotate(
+ iscurrent=django.db.models.Case(django.db.models.When(pk=currentspeaker.pk, then=True), output_field=django.db.models.BooleanField())
+ ).order_by('iscurrent', 'fullname')
self.fields['speaker'].required = True
- self.fields['speaker'].help_text = "Type the beginning of a speakers email address to add more speakers"
if not self.instance.conference.skill_levels:
del self.fields['skill_level']
if self.instance.conference.callforpaperstags:
+ self.fields['tags'].widget = forms.CheckboxSelectMultiple()
self.fields['tags'].queryset = ConferenceSessionTag.objects.filter(conference=self.instance.conference)
self.fields['tags'].label_from_instance = lambda x: x.tag
self.fields['tags'].required = False
def join_dictkeys(list_to_join, attrname, separator=', '):
if not list_to_join:
return ''
- return separator.join(item[attrname] for item in list_to_join)
+ return separator.join(str(item[attrname]) for item in list_to_join)
try:
speaker = Speaker.objects.get(user=request.user)
- sessions = ConferenceSession.objects.filter(conference=conference, speaker=speaker).order_by('title')
+ sessions = ConferenceSession.objects.filter(conference=conference, speaker=speaker).order_by('status', 'title')
other_submissions = ConferenceSession.objects.filter(speaker=speaker).exclude(conference=conference).exists()
except Speaker.DoesNotExist:
other_submissions = False
raise Http404("No query")
conference = get_conference_or_404(confname)
- speaker = get_object_or_404(Speaker, user=request.user)
-
- # This is a lookup for speakers that's public. To avoid harvesting, we allow
- # only *prefix* matching of email addresses, and you have to type at least 6 characters
- # before you get anything.
- prefix = request.GET['query'].lower()
- if len(prefix) > 5:
- vals = [{
- 'id': s.id,
- 'value': "{0} <{1}>".format(s.fullname, s.email),
- } for s in Speaker.objects.filter(user__email__startswith=prefix).exclude(fullname='')]
- else:
- vals = []
- return HttpResponse(json.dumps({
- 'values': vals,
- }), content_type='application/json')
-
-
-@login_required
-def public_tags_lookup(request, confname):
if 'query' not in request.GET:
- raise Http404("No query")
-
- conference = get_conference_or_404(confname)
- speaker = get_object_or_404(Speaker, user=request.user)
+ raise Http404("Query missing")
+ speaker = get_object_or_404(Speaker, user__email=request.GET.get('query', '').lower())
- prefix = request.GET['query']
- vals = [{
- 'id': t.id,
- 'value': t.tag,
- } for t in ConferenceSessionTag.objects.filter(conference=conference)]
return HttpResponse(json.dumps({
- 'values': vals,
+ 'id': speaker.id,
+ 'name': speaker.fullname,
}), content_type='application/json')
url(r'^events/([^/]+)/callforpapers/(\d+)/delslides/(\d+)/$', postgresqleu.confreg.views.callforpapers_delslides),
url(r'^events/([^/]+)/callforpapers/(\d+)/speakerconfirm/$', postgresqleu.confreg.views.callforpapers_confirm),
url(r'^events/([^/]+)/callforpapers/lookups/speakers/$', postgresqleu.confreg.views.public_speaker_lookup),
- url(r'^events/([^/]+)/callforpapers/lookups/tags/$', postgresqleu.confreg.views.public_tags_lookup),
url(r'^events/callforpapers/$', postgresqleu.confreg.views.callforpaperslist),
url(r'^events/([^/]+)/register/confirm/$', postgresqleu.confreg.views.confirmreg),
url(r'^events/([^/]+)/register/policy/$', postgresqleu.confreg.views.regconfirmpolicy),
--- /dev/null
+<select name="{{ widget.name }}" style="display: none" multiple>{% for option in widget.options %}
+ <option value="{{option.value}}" selected>
+{%endfor%}
+</select>
+<ul class="pgeu-speaker-list">
+{% for option in widget.options %}<li data-id="{{option.value}}">{{option.label}}{% if not forloop.first %} (<a class="pgeu-speaker-remove" href="#">remove</a>){%endif%}</li>
+{%endfor%}
+</ul>
+<button id="{{ widget.name}}-add" class="pgeu-speaker-add">Add speaker</button>
context = super().get_context(name, value, attrs)
context['setmap'] = self.setvalues
return context
+
+
+class CallForPapersSpeakersWidget(forms.SelectMultiple):
+ template_name = 'forms/widgets/speaker_select.html'
+
+ def get_context(self, name, value, attrs):
+ context = super().get_context(name, value, attrs)
+ context['widget']['options'] = list(self.options(name, context['widget']['value'], attrs))
+ return context
</div>
</footer>
-
- {{ asset("js", "jquery3") }}
- {{ asset("js", "bootstrap4") }}
{%block pagescript%}{%endblock%}
</body>
</html>
{%extends "base.html" %}
{%block title%}Call for Papers - {{conference}}{%endblock%}
{%block pagescript%}
-{{asset("css", "selectize")}}
-{{asset("js", "selectize")}}
-
-<script language="javascript">
-$(function() {
- /* Re-enable the speaker field, and turn it into selectize */
- $('tr#tr_speaker').css({'display': 'table-row'});
- $('#id_speaker').selectize({
- plugins: ['remove_button'],
- valueField: 'id',
- labelField: 'value',
- searchField: 'value',
- load: async function(query, callback) {
- if (!query.length) return callback();
- let url = new URL('/events/{{conference.urlname}}/callforpapers/lookups/speakers/', document.location.href);
- url.searchParams.append('query', query);
- let response = await fetch(url);
- let data = await response.json();
- callback(data.values);
- },
- });
+<script language="javascript" defer>
+ async function addSpeakerClick(event) {
+ event.preventDefault();
+
+ let email = prompt('Enter the email address of the speaker to add.');
+ if (email) {
+ let res = await fetch('/events/{{conference.urlname}}/callforpapers/lookups/speakers/?' + new URLSearchParams({'query': email}));
+ if (res.status != 200) {
+ alert('Speaker not found.\n\nNote that thee speaker must have an existing profile on this site with the given email address before they can be adde to a session.\n');
+ return;
+ }
+ let speaker = await res.json();
+
+ let ul = event.target.previousElementSibling;
+ let select = ul.previousElementSibling;
+
+ if (select.querySelector('option[value="' + speaker.id + '"]')) {
+ alert('This speaker has already been added.');
+ return;
+ }
+
+ let newli = document.createElement('li');
+ newli.dataset.id = speaker.id;
+ newli.innerHTML = speaker.name + ' (<a class="pgeu-speaker-remove" href=#"#>remove</a>)';
+ ul.appendChild(newli);
+
+ let newoption = document.createElement('option');
+ newoption.value = speaker.id;
+ newoption.selected = true;
+ select.appendChild(newoption);
+ }
+
+ return false;
+ }
+
+ function removeSpeakerClick(event) {
+ if (event.target.tagName == 'A' && event.target.classList.contains('pgeu-speaker-remove')) {
+ event.preventDefault();
+
+ let idtoremove = event.target.parentNode.dataset.id;
- /* Selectize the tags field, if it exists */
- $('#id_tags').selectize({
- plugins: ['remove_button'],
- valueField: 'id',
- labelField: 'value',
- searchField: 'value',
- load: async function(query, callback) {
- if (!query.length) return callback();
- let url = new URL('/events/{{conference.urlname}}/callforpapers/lookups/tags/', document.location.href);
- url.searchParams.append('query', query);
- let response = await fetch(url);
- let data = await response.json();
- callback(data.values);
- },
+ if (!confirm('Are you sure you want to remove this speaker?')) {
+ return;
+ }
+
+ /* <a>.<li>.<ul>.<select> */
+ event.target.parentNode.parentNode.previousElementSibling.querySelector('option[value="' + idtoremove + '"]').remove();
+
+ event.target.parentNode.remove();
+
+ alert('Speaker removed. You have to also save the form to make it permanent.');
+ }
+ }
+
+
+ document.querySelectorAll("button.pgeu-speaker-add").forEach((button) => {
+ button.addEventListener('click', addSpeakerClick);
+ });
+ document.querySelectorAll("ul.pgeu-speaker-list").forEach((ul) => {
+ ul.addEventListener('click', removeSpeakerClick);
});
-});
</script>
{%endblock%}
tr.err {
background-color: #ffb6b6;
}
-
-/* Hide the speaker field for non-javascript sessions */
-tr#tr_speaker {
- display:none;
-}
</style>
{%endblock%}