Files @ ff281c17ee63
Branch filter:

Location: symposion_app/vendor/symposion/schedule/views.py - annotation

Joel Addison
Add speaker bio to JSON API
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
51709c6eaf39
d6ac7edc5d4a
51709c6eaf39
b48d66fd9de6
db908372fffb
252697b842c0
b48d66fd9de6
d6ac7edc5d4a
ef420b2d4300
0a4e626dfe9b
252697b842c0
4d1e9cf78e31
51709c6eaf39
d6a59f2e4f3b
a37d620afb13
a37d620afb13
d5986de87043
11f697d13757
4d1e9cf78e31
a41fb8bd3542
7601791e8cd0
4e22717639a7
adcfa4596731
0a4e626dfe9b
252697b842c0
252697b842c0
298b162be697
1b2cdeffb0e9
0a4e626dfe9b
36ab6d599ffc
bec6903ca1c9
1b3ef8d4247d
1b3ef8d4247d
0a4e626dfe9b
0a4e626dfe9b
0a4e626dfe9b
0a4e626dfe9b
1b2cdeffb0e9
36ab6d599ffc
1b2cdeffb0e9
1b2cdeffb0e9
1b2cdeffb0e9
ef420b2d4300
2a68242a5482
36ab6d599ffc
251f9ea2809a
251f9ea2809a
251f9ea2809a
251f9ea2809a
36ab6d599ffc
2a68242a5482
2a68242a5482
2a68242a5482
2a68242a5482
2a68242a5482
2a68242a5482
2a68242a5482
2a68242a5482
36ab6d599ffc
2a68242a5482
2a68242a5482
2a68242a5482
11f697d13757
2a68242a5482
2a68242a5482
1b2cdeffb0e9
36ab6d599ffc
1b2cdeffb0e9
c7592bc33e26
c7592bc33e26
36ab6d599ffc
6a1e59812adf
6a1e59812adf
36ab6d599ffc
0a4e626dfe9b
0a4e626dfe9b
6a1e59812adf
0a4e626dfe9b
11f697d13757
0a4e626dfe9b
0a4e626dfe9b
1b2cdeffb0e9
1b2cdeffb0e9
6b41b5c4773c
6b41b5c4773c
36ab6d599ffc
1b2cdeffb0e9
d98f9b82a85b
36ab6d599ffc
f42766beefc6
f42766beefc6
f42766beefc6
13bc9ffacb95
c47907b29ee5
13bc9ffacb95
13bc9ffacb95
11f697d13757
13bc9ffacb95
13bc9ffacb95
b48d66fd9de6
b48d66fd9de6
6b41b5c4773c
6b41b5c4773c
36ab6d599ffc
b48d66fd9de6
f42766beefc6
f42766beefc6
f42766beefc6
f42766beefc6
e9c97a9586f2
36ab6d599ffc
b48d66fd9de6
b48d66fd9de6
b48d66fd9de6
b48d66fd9de6
b48d66fd9de6
36ab6d599ffc
11f697d13757
b48d66fd9de6
36ab6d599ffc
b48d66fd9de6
b48d66fd9de6
b48d66fd9de6
b48d66fd9de6
d6a59f2e4f3b
0a4e626dfe9b
36ab6d599ffc
d6a59f2e4f3b
d6a59f2e4f3b
36ab6d599ffc
1b2cdeffb0e9
36ab6d599ffc
4d1e9cf78e31
4d1e9cf78e31
b154d90eed40
4d1e9cf78e31
4d1e9cf78e31
4d1e9cf78e31
4d1e9cf78e31
4d1e9cf78e31
4d1e9cf78e31
4d1e9cf78e31
4d1e9cf78e31
4d1e9cf78e31
f3e9cc9a5db3
f3e9cc9a5db3
0a4e626dfe9b
0a4e626dfe9b
f3e9cc9a5db3
4d1e9cf78e31
0a4e626dfe9b
11f697d13757
db908372fffb
db908372fffb
d6a59f2e4f3b
1b2cdeffb0e9
36ab6d599ffc
d6a59f2e4f3b
d6a59f2e4f3b
36ab6d599ffc
1b2cdeffb0e9
36ab6d599ffc
7c102aefa3a5
19d826ad00de
7c102aefa3a5
19d826ad00de
19d826ad00de
19d826ad00de
19d826ad00de
19d826ad00de
19d826ad00de
19d826ad00de
19d826ad00de
19d826ad00de
19d826ad00de
19d826ad00de
19d826ad00de
4461c2f5102f
7c102aefa3a5
19d826ad00de
7c102aefa3a5
1b2cdeffb0e9
7c102aefa3a5
7c102aefa3a5
7c102aefa3a5
11f697d13757
1b3ef8d4247d
1b3ef8d4247d
ef420b2d4300
1b3ef8d4247d
36ab6d599ffc
1b3ef8d4247d
970e002157dd
bc0f49f391c3
97e1086b1d74
970e002157dd
97e1086b1d74
97e1086b1d74
970e002157dd
bc0f49f391c3
bc0f49f391c3
bc0f49f391c3
970e002157dd
970e002157dd
970e002157dd
970e002157dd
970e002157dd
36ab6d599ffc
1b3ef8d4247d
1b3ef8d4247d
97e1086b1d74
1b3ef8d4247d
11f697d13757
c4db94b7e5fd
c4db94b7e5fd
adcfa4596731
adcfa4596731
adcfa4596731
adcfa4596731
adcfa4596731
adcfa4596731
adcfa4596731
adcfa4596731
adcfa4596731
72b5c3ff556d
72b5c3ff556d
ff281c17ee63
adcfa4596731
adcfa4596731
c4db94b7e5fd
0ebcc2f1247a
0ebcc2f1247a
0ebcc2f1247a
0ebcc2f1247a
51709c6eaf39
51709c6eaf39
c4db94b7e5fd
c4db94b7e5fd
135c5d2da328
51709c6eaf39
135c5d2da328
135c5d2da328
2b91a7296ccf
2b91a7296ccf
51709c6eaf39
51709c6eaf39
51709c6eaf39
135c5d2da328
135c5d2da328
2c97ec710662
2c97ec710662
2c97ec710662
2c97ec710662
2c97ec710662
e1e86a7a3958
2c97ec710662
37dd7dd15ba6
2c97ec710662
51709c6eaf39
51709c6eaf39
5e372be5f6ba
f42766beefc6
f42766beefc6
135c5d2da328
135c5d2da328
135c5d2da328
135c5d2da328
135c5d2da328
135c5d2da328
51709c6eaf39
c4db94b7e5fd
adcfa4596731
5e372be5f6ba
2c97ec710662
51709c6eaf39
c4db94b7e5fd
c4db94b7e5fd
c4db94b7e5fd
2b91a7296ccf
135c5d2da328
135c5d2da328
51709c6eaf39
8cf4bf349039
8cf4bf349039
c4db94b7e5fd
51709c6eaf39
5e372be5f6ba
51709c6eaf39
c4db94b7e5fd
c4db94b7e5fd
c4db94b7e5fd
dbb4ebbb7044
c4db94b7e5fd
c4db94b7e5fd
a41fb8bd3542
298b162be697
a37d620afb13
a37d620afb13
a37d620afb13
d6ac7edc5d4a
a37d620afb13
298b162be697
155f841afa5d
155f841afa5d
710d377016c1
a37d620afb13
a37d620afb13
a37d620afb13
a37d620afb13
46ca912f7ce0
46ca912f7ce0
710d377016c1
710d377016c1
a37d620afb13
a37d620afb13
298b162be697
a37d620afb13
3d626e842067
3d626e842067
710d377016c1
a37d620afb13
a37d620afb13
710d377016c1
710d377016c1
a37d620afb13
3d626e842067
3d626e842067
710d377016c1
a37d620afb13
d6ac7edc5d4a
710d377016c1
a37d620afb13
d6ac7edc5d4a
710d377016c1
a37d620afb13
a37d620afb13
710d377016c1
298b162be697
a37d620afb13
298b162be697
298b162be697
298b162be697
a37d620afb13
a37d620afb13
298b162be697
46ca912f7ce0
46ca912f7ce0
a41fb8bd3542
298b162be697
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
11f697d13757
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
252697b842c0
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
252697b842c0
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
252697b842c0
b783901e98d9
b783901e98d9
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
11f697d13757
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
a41fb8bd3542
import json
import pytz

from django.http import Http404, HttpResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.urls import reverse
from django.template import loader, Context
from django.conf import settings
from django.views.decorators.clickjacking import xframe_options_exempt

from django.contrib.auth import get_user_model
from django.contrib import messages
from django.contrib.sites.models import Site

from django_ical.views import ICalFeed

from django.contrib.auth.decorators import login_required

from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm
from symposion.schedule.models import Schedule, Day, Slot, Presentation, Session, SessionRole
from symposion.schedule.timetable import TimeTable
from symposion.conference.models import Conference
from pinaxcon.templatetags.lca2018_tags import speaker_photo

User = get_user_model()


def fetch_schedule(slug):
    qs = Schedule.objects.all()

    if slug is None:
        if qs.count() > 1:
            raise Http404()
        schedule = next(iter(qs), None)
        if schedule is None:
            raise Http404()
    else:
        schedule = get_object_or_404(qs, section__slug=slug)

    return schedule


@xframe_options_exempt
def schedule_conference(request):

    if request.user.is_staff:
        schedules = Schedule.objects.filter(hidden=False)
    else:
        schedules = Schedule.objects.filter(published=True, hidden=False)

    sections = []
    for schedule in schedules:
        days_qs = Day.objects.filter(schedule=schedule)
        days = [TimeTable(day) for day in days_qs]
        sections.append({
            "schedule": schedule,
            "days": days,
        })

    ctx = {
        "sections": sections,
    }
    return render(request, "symposion/schedule/schedule_conference.html", ctx)


def schedule_detail(request, slug=None):

    schedule = fetch_schedule(slug)
    if not schedule.published and not request.user.is_staff:
        raise Http404()

    days_qs = Day.objects.filter(schedule=schedule)
    days = [TimeTable(day) for day in days_qs]

    ctx = {
        "schedule": schedule,
        "days": days,
    }
    return render(request, "symposion/schedule/schedule_detail.html", ctx)


def schedule_list(request, slug=None):
    schedule = fetch_schedule(slug)
    if not schedule.published and not request.user.is_staff:
        raise Http404()

    presentations = Presentation.objects.filter(section=schedule.section)
    presentations = presentations.exclude(cancelled=True)

    if not request.user.is_staff:
        presentations = presentations.exclude(unpublish=True)

    ctx = {
        "schedule": schedule,
        "presentations": presentations,
    }
    return render(request, "symposion/schedule/schedule_list.html", ctx)


def schedule_list_csv(request, slug=None):
    schedule = fetch_schedule(slug)
    if not schedule.published and not request.user.is_staff:
        raise Http404()

    presentations = Presentation.objects.filter(section=schedule.section)
    presentations = presentations.exclude(cancelled=True)
    if not request.user.is_staff:
        presentations = presentations.exclude(unpublish=True)
    presentations = presentations.order_by("id")
    response = HttpResponse(content_type="text/csv")

    if slug:
        file_slug = slug
    else:
        file_slug = "presentations"
    response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug

    response.write(loader.get_template("symposion/schedule/schedule_list.csv").render(Context({
        "presentations": presentations,

    })))
    return response


@login_required
def schedule_edit(request, slug=None):

    if not request.user.is_staff:
        raise Http404()

    schedule = fetch_schedule(slug)

    if request.method == "POST":
        form = ScheduleSectionForm(
            request.POST, request.FILES, schedule=schedule, encoding=request.encoding
        )
        if form.is_valid():
            if 'submit' in form.data:
                msg = form.build_schedule()
            elif 'delete' in form.data:
                msg = form.delete_schedule()
            messages.add_message(request, msg[0], msg[1])
    else:
        form = ScheduleSectionForm(schedule=schedule)
    days_qs = Day.objects.filter(schedule=schedule)
    days = [TimeTable(day) for day in days_qs]
    ctx = {
        "schedule": schedule,
        "days": days,
        "form": form
    }
    return render(request, "symposion/schedule/schedule_edit.html", ctx)


@login_required
def schedule_slot_edit(request, slug, slot_pk):

    if not request.user.is_staff:
        raise Http404()

    slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk)

    if request.method == "POST":
        form = SlotEditForm(request.POST, slot=slot)
        if form.is_valid():
            save = False
            if "content_override" in form.cleaned_data:
                slot.content_override = form.cleaned_data["content_override"]
                save = True
            if "presentation" in form.cleaned_data:
                presentation = form.cleaned_data["presentation"]
                if presentation is None:
                    slot.unassign()
                else:
                    slot.assign(presentation)
            if save:
                slot.save()
        return redirect("schedule_edit", slug)
    else:
        form = SlotEditForm(slot=slot)
        ctx = {
            "slug": slug,
            "form": form,
            "slot": slot,
        }
        return render(request, "symposion/schedule/_slot_edit.html", ctx)


@xframe_options_exempt
def schedule_presentation_detail(request, pk):

    presentation = get_object_or_404(Presentation, pk=pk)

    schedule = None
    if presentation.slot:
        # 1) Schedule from presentation's slot
        schedule = presentation.slot.day.schedule
    else:
        # 2) Fall back to the schedule for this proposal
        section = presentation.proposal.kind.section
        if hasattr(section, 'schedule'):
            schedule = presentation.proposal.kind.section.schedule

    if not request.user.is_staff:
        # 3) Is proposal unpublished?
        if presentation.unpublish or not (schedule and schedule.published):
            raise Http404()

    ctx = {
        "presentation": presentation,
        "schedule": schedule,
    }
    return render(request, "symposion/schedule/presentation_detail.html", ctx)


def has_contact_perm(user):
    return user.has_perm('symposion_speakers.can_view_contact_details') or user.is_staff


def make_speaker_dict(user, speaker):
    return {
        'name': speaker.name,
        'twitter': speaker.twitter_username,
        'contact': speaker.email if has_contact_perm(user) else 'redacted',
        'picture_url': speaker_photo(None, speaker, 120),
        'code': speaker.code,
        'biography': speaker.biography,
    }

def schedule_json(request):
    slots = Slot.objects.filter(
        day__schedule__published=True,
        day__schedule__hidden=False
    ).order_by("start")

    protocol = request.META.get('HTTP_X_FORWARDED_PROTO', 'http')
    data = []
    for slot in slots:
        rooms = list(slot.rooms)
        slot_data = {
            "room": ", ".join(room.name for room in rooms),
            "rooms": [room.name for room in rooms],
            "start": slot.start_datetime.isoformat(),
            "end": slot.end_datetime.isoformat(),
            "duration": slot.length_in_minutes,
            "kind": slot.kind.label,
            "section": slot.day.schedule.section.slug,
            "section_name": slot.day.schedule.section.name,
            "track": None,
            "conf_key": slot.pk,
            # TODO: models should be changed.
            # these are model features from other conferences that have forked symposion
            # these have been used almost everywhere and are good candidates for
            # base proposals
            "license": "CC-BY-SA",
            "tags": "",
            "released": False,
            "contact": [],
        }
        if hasattr(slot.content, "proposal"):
            if slot.content.unpublish and not request.user.is_staff:
                continue

            track_name = None
            if len(rooms) == 1:
                track = rooms[0].track_set.filter(day=slot.day).first()
                if track:
                    track_name = track.name

            slot_data.update({
                "name": slot.content.title,
                "authors": [make_speaker_dict(request.user, s) for s in slot.content.speakers()],
                "abstract": slot.content.abstract,
                "conf_url": "%s://%s%s" % (
                    protocol,
                    Site.objects.get_current().domain,
                    reverse("schedule_presentation_detail", args=[slot.content.pk])
                ),
                "cancelled": slot.content.cancelled,
                "released": slot.content.proposal.recording_release,
                "track": track_name,
            })
            if not slot.content.speaker.twitter_username == '':
                slot_data["twitter_id"] = slot.content.speaker.twitter_username
        else:
            slot_data.update({
                "name": slot.content_override if slot.content_override else "Slot",
            })
        data.append(slot_data)

    return HttpResponse(
        json.dumps({"schedule": data}, indent=2),
        content_type="application/json"
    )


class EventFeed(ICalFeed):

    product_id = '-//linux.conf.au/schedule//EN'
    timezone = settings.TIME_ZONE
    filename = 'conference.ics'

    def description(self):
        return Conference.objects.all().first().title

    def items(self):
        return Slot.objects.filter(
            day__schedule__published=True,
            day__schedule__hidden=False
        ).exclude(
            kind__label='shortbreak'
        ).order_by("start")

    def item_title(self, item):
        if hasattr(item.content, 'proposal'):
            title = item.content.title
        else:
            title = item.kind if item.kind else "Slot"
        return title

    def item_description(self, item):
        if hasattr(item.content, 'proposal'):
            description = "Speaker: %s\n%s" % (
                item.content.speaker, item.content.abstract)
        else:
            description = item.content_override if item.content_override else "No description"
        return description

    def item_start_datetime(self, item):
        return pytz.timezone(settings.TIME_ZONE).localize(item.start_datetime)

    def item_end_datetime(self, item):
        return pytz.timezone(settings.TIME_ZONE).localize(item.end_datetime)

    def item_location(self, item):
        return ", ".join(room["name"] for room in item.rooms.values())

    def item_link(self, item) -> str:
        if hasattr(item.content, 'proposal'):
            return (
                'http://%s%s' % (Site.objects.get_current().domain,
                                 reverse('schedule_presentation_detail', args=[item.content.pk])))
        else:
            return 'http://%s' % Site.objects.get_current().domain

    def item_guid(self, item):
        return '%d@%s' % (item.pk, Site.objects.get_current().domain)


def session_list(request):
    sessions = Session.objects.all().order_by('pk')

    return render(request, "symposion/schedule/session_list.html", {
        "sessions": sessions,
    })


@login_required
def session_staff_email(request):

    if not request.user.is_staff:
        return redirect("schedule_session_list")

    data = "\n".join(user.email for user in User.objects.filter(sessionrole__isnull=False).distinct())

    return HttpResponse(data, content_type="text/plain;charset=UTF-8")


def session_detail(request, session_id):

    session = get_object_or_404(Session, id=session_id)

    chair = None
    chair_denied = False
    chairs = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_CHAIR).exclude(status=False)
    if chairs:
        chair = chairs[0].user
    else:
        if request.user.is_authenticated:
            # did the current user previously try to apply and got rejected?
            if SessionRole.objects.filter(session=session, user=request.user, role=SessionRole.SESSION_ROLE_CHAIR, status=False):
                chair_denied = True

    runner = None
    runner_denied = False
    runners = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_RUNNER).exclude(status=False)
    if runners:
        runner = runners[0].user
    else:
        if request.user.is_authenticated:
            # did the current user previously try to apply and got rejected?
            if SessionRole.objects.filter(session=session, user=request.user, role=SessionRole.SESSION_ROLE_RUNNER, status=False):
                runner_denied = True

    if request.method == "POST" and request.user.is_authenticated:
        if not hasattr(request.user, "attendee") or not request.user.attendee.completed_registration:
            response = redirect("guided_registration")
            response["Location"] += "?next=%s" % request.path
            return response

        role = request.POST.get("role")
        if role == "chair":
            if chair is None and not chair_denied:
                SessionRole(session=session, role=SessionRole.SESSION_ROLE_CHAIR, user=request.user).save()
        elif role == "runner":
            if runner is None and not runner_denied:
                SessionRole(session=session, role=SessionRole.SESSION_ROLE_RUNNER, user=request.user).save()
        elif role == "un-chair":
            if chair == request.user:
                session_role = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_CHAIR, user=request.user)
                if session_role:
                    session_role[0].delete()
        elif role == "un-runner":
            if runner == request.user:
                session_role = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_RUNNER, user=request.user)
                if session_role:
                    session_role[0].delete()

        return redirect("schedule_session_detail", session_id)

    return render(request, "symposion/schedule/session_detail.html", {
        "session": session,
        "chair": chair,
        "chair_denied": chair_denied,
        "runner": runner,
        "runner_denied": runner_denied,
    })