From 8000896f3de9b25713421ae926ba1ceaa33ac055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Sat, 15 Apr 2017 19:35:18 +0200 Subject: [PATCH 1/7] Adding channels with the schedule as first app using them. --- src/bornhack/routing.py | 8 +++ src/bornhack/settings.py | 28 ++++++++- src/program/consumers.py | 28 +++++++++ src/program/models.py | 17 ++++++ src/program/templates/schedule_overview.html | 61 +++++++++++++++++--- src/requirements.txt | 3 +- 6 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 src/bornhack/routing.py create mode 100644 src/program/consumers.py diff --git a/src/bornhack/routing.py b/src/bornhack/routing.py new file mode 100644 index 00000000..7776c0e7 --- /dev/null +++ b/src/bornhack/routing.py @@ -0,0 +1,8 @@ +from program.consumers import ScheduleConsumer + + +channel_routing = [ + ScheduleConsumer.as_route(path=r"^/schedule/"), +] + + diff --git a/src/bornhack/settings.py b/src/bornhack/settings.py index 4006ef4e..aa0d6823 100644 --- a/src/bornhack/settings.py +++ b/src/bornhack/settings.py @@ -28,6 +28,8 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'django.contrib.sites', + 'channels', + 'profiles', 'camps', 'shop', @@ -108,9 +110,27 @@ MIDDLEWARE = [ if DEBUG: EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' - INSTALLED_APPS += ['debug_toolbar', ] + INSTALLED_APPS += [ + 'debug_toolbar', + 'channels_panel' + ] MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware'] + MIDDLEWARE INTERNAL_IPS = "127.0.0.1" + DEBUG_TOOLBAR_PANELS = [ + 'debug_toolbar.panels.versions.VersionsPanel', + 'debug_toolbar.panels.timer.TimerPanel', + 'debug_toolbar.panels.settings.SettingsPanel', + 'debug_toolbar.panels.headers.HeadersPanel', + 'debug_toolbar.panels.request.RequestPanel', + 'debug_toolbar.panels.sql.SQLPanel', + 'debug_toolbar.panels.staticfiles.StaticFilesPanel', + 'debug_toolbar.panels.templates.TemplatesPanel', + 'debug_toolbar.panels.cache.CachePanel', + 'debug_toolbar.panels.signals.SignalsPanel', + 'debug_toolbar.panels.logging.LoggingPanel', + 'debug_toolbar.panels.redirects.RedirectsPanel', + 'channels_panel.panel.ChannelsDebugPanel', + ] LOGGING = { 'version': 1, @@ -142,3 +162,9 @@ LOGGING = { }, } +CHANNEL_LAYERS = { + "default": { + "BACKEND": "asgiref.inmemory.ChannelLayer", + "ROUTING": "bornhack.routing.channel_routing", + }, +} diff --git a/src/program/consumers.py b/src/program/consumers.py new file mode 100644 index 00000000..3b814314 --- /dev/null +++ b/src/program/consumers.py @@ -0,0 +1,28 @@ +from channels.generic.websockets import JsonWebsocketConsumer + +from .models import EventInstance + + +class ScheduleConsumer(JsonWebsocketConsumer): + http_user = True + + def connection_groups(self, **kwargs): + return ['schedule_users'] + + def connect(self, message, **kwargs): + self.send({"accept": True}) + + def receive(self, content, **kwargs): + action = content.get('action') + data = {} + + if action == 'get_event_instance': + event_instance_id = content.get('event_instance_id') + event_instance = EventInstance.objects.get(id=event_instance_id) + data['action'] = 'event_instance' + data['event_instance'] = event_instance.to_json() + + self.send(data) + + def disconnect(self, message, **kwargs): + pass diff --git a/src/program/models.py b/src/program/models.py index e8c1c98a..a71b4278 100644 --- a/src/program/models.py +++ b/src/program/models.py @@ -14,6 +14,7 @@ from django.apps import apps from django.core.files.base import ContentFile import icalendar +import CommonMark from utils.models import CreatedUpdatedModel, CampRelatedModel @@ -448,6 +449,22 @@ class EventInstance(CampRelatedModel): ievent['location'] = icalendar.vText(self.location.name) return ievent + def to_json(self): + parser = CommonMark.Parser() + renderer = CommonMark.HtmlRenderer() + ast = parser.parse(self.event.abstract) + abstract = renderer.render(ast) + return { + 'title': self.event.title, + 'event_slug': self.event.slug, + 'abstract': abstract, + 'from': self.when.lower.isoformat(), + 'to': self.when.lower.isoformat(), + 'url': str(self.event.get_absolute_url()), + 'id': self.id, + } + + def get_speaker_picture_upload_path(instance, filename): """ We want speaker pictures are saved as MEDIA_ROOT/public/speakers/camp-slug/speaker-slug/filename """ diff --git a/src/program/templates/schedule_overview.html b/src/program/templates/schedule_overview.html index 6cc55748..1b752aa8 100644 --- a/src/program/templates/schedule_overview.html +++ b/src/program/templates/schedule_overview.html @@ -1,6 +1,11 @@ {% extends 'schedule_base.html' %} {% load commonmark %} +{% load staticfiles %} + +{% block extra_head %} + +{% endblock %} {% block schedule_content %} {% if eventinstances %} @@ -12,9 +17,8 @@ + data-eventinstance-id="{{ eventinstance.id }}" + > {{ eventinstance.when.lower|date:"H:i" }} - {{ eventinstance.when.upper|date:"H:i" }} &#x{{ eventinstance.location.icon }};
@@ -23,20 +27,19 @@
{% endif %} - @@ -58,7 +58,31 @@ var event_elements = document.getElementsByClassName("event"); var modals = {}; - var events = {}; + + function toggleFavoriteButton(button) { + if(button.getAttribute('data-state') == 'true') { + favorite_button.classList.remove('btn-success'); + favorite_button.classList.add('btn-danger'); + favorite_button.innerHTML = ' Remove favorite'; + + favorite_button.onclick = function(e) { + button.setAttribute('data-state', 'false') + webSocketBridge.send({action: 'unfavorite', event_instance_id: event_instance_id}); + toggleFavoriteButton(button) + } + } else { + favorite_button.classList.remove('btn-danger'); + favorite_button.classList.add('btn-success'); + favorite_button.innerHTML = ' Favorite'; + + favorite_button.onclick = function(e) { + button.setAttribute('data-state', 'true') + webSocketBridge.send({action: 'favorite', event_instance_id: event_instance_id}); + toggleFavoriteButton(button) + } + + } + } webSocketBridge.connect('/schedule/'); webSocketBridge.socket.addEventListener('open', function() { @@ -74,12 +98,22 @@ modal_body.innerHTML = payload['event_instance']['abstract']; more_button = modal.getElementsByClassName('more-button')[0]; more_button.setAttribute('href', payload['event_instance']['url']); + favorite_button = modal.getElementsByClassName('favorite-button')[0]; + favorite_button.setAttribute('data-state', payload['event_instance']['is_favorited']) + toggleFavoriteButton(favorite_button) } }); function openModal(e) { e.preventDefault(); - event_instance_id = e.target.dataset['eventinstanceId']; + + // Avoid that clicking the text in the event will bring up an empty modal + target = e.target; + if (e.target !== this) { + target = e.target.parentElement + } + + event_instance_id = target.dataset['eventinstanceId']; modal = modals[event_instance_id]; From d511df399d5af8d2961cd96fcf220ac3ee7c8edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Sun, 16 Apr 2017 02:20:59 +0200 Subject: [PATCH 6/7] Fixing footer. --- src/static_src/css/bornhack.css | 2 +- src/templates/base.html | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/static_src/css/bornhack.css b/src/static_src/css/bornhack.css index 547745cc..caf337d7 100644 --- a/src/static_src/css/bornhack.css +++ b/src/static_src/css/bornhack.css @@ -71,7 +71,7 @@ a, a:active, a:focus { /* Footer */ footer { position: fixed; - width: 700px; + width: 100%; text-align: center; background-color: white; bottom: 0px; diff --git a/src/templates/base.html b/src/templates/base.html index 85b1094b..b66c74aa 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -106,17 +106,17 @@ {% endif %} {% bootstrap_messages %} {% block content %}{% endblock %} - + From f96c8b6db5b7aab78e965c6a4798463f3bdd0fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=AD=C3=B0ir=20Valberg=20Gu=C3=B0mundsson?= Date: Mon, 17 Apr 2017 20:29:59 +0200 Subject: [PATCH 7/7] Show speakers in the modal. Also removed favorite action when anonymous. --- src/program/models.py | 13 ++++++--- src/program/templates/event_list.html | 2 +- .../templates/schedule_event_detail.html | 4 +-- src/program/templates/schedule_overview.html | 29 ++++++++++++++++--- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/program/models.py b/src/program/models.py index d64892e3..33f98139 100644 --- a/src/program/models.py +++ b/src/program/models.py @@ -247,7 +247,7 @@ class EventProposal(UserSubmittedModel): # loop through the speakerproposals linked to this eventproposal and associate any related speaker objects with this event for sp in self.speakers.all(): if sp.speaker: - event.speaker_set.add(sp.speaker) + event.speakers.add(sp.speaker) self.proposal_status = eventproposalmodel.PROPOSAL_APPROVED self.save() @@ -376,8 +376,8 @@ class Event(CampRelatedModel): @property def speakers_list(self): - if self.speaker_set.exists(): - return ", ".join(self.speaker_set.all().values_list('name', flat=True)) + if self.speakers.exists(): + return ", ".join(self.speakers.all().values_list('name', flat=True)) return False def get_absolute_url(self): @@ -463,9 +463,13 @@ class EventInstance(CampRelatedModel): 'to': self.when.lower.isoformat(), 'url': str(self.event.get_absolute_url()), 'id': self.id, + 'speakers': [ + { 'name': speaker.name + , 'url': str(speaker.get_absolute_url()) + } for speaker in self.event.speakers.all()] } - if user: + if user and user.is_authenticated: is_favorited = user.favorites.filter(event_instance=self).exists() data['is_favorited'] = is_favorited @@ -525,6 +529,7 @@ class Speaker(CampRelatedModel): Event, blank=True, help_text='The event(s) this speaker is anchoring', + related_name='speakers' ) proposal = models.OneToOneField( diff --git a/src/program/templates/event_list.html b/src/program/templates/event_list.html index 216aa945..c3ffac7a 100644 --- a/src/program/templates/event_list.html +++ b/src/program/templates/event_list.html @@ -28,7 +28,7 @@ {{ event.title }} - {% for speaker in event.speaker_set.all %} + {% for speaker in event.speakers.all %} {{ speaker.name }}
{% empty %} N/A diff --git a/src/program/templates/schedule_event_detail.html b/src/program/templates/schedule_event_detail.html index c53a6028..61b95a70 100644 --- a/src/program/templates/schedule_event_detail.html +++ b/src/program/templates/schedule_event_detail.html @@ -22,10 +22,10 @@
- {% if event.speaker_set.exists %} + {% if event.speakers.exists %}

Speakers

- {% for speaker in event.speaker_set.all %} + {% for speaker in event.speakers.all %}

{{ speaker.name }}

{% endfor %}
diff --git a/src/program/templates/schedule_overview.html b/src/program/templates/schedule_overview.html index 23f8cc7e..f95f3ece 100644 --- a/src/program/templates/schedule_overview.html +++ b/src/program/templates/schedule_overview.html @@ -34,7 +34,12 @@ +