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, })