Merge branch 'master' of github.com:bornhack/bornhack-website
This commit is contained in:
commit
0f9ba324cd
7
src/bornhack/asgi.py
Normal file
7
src/bornhack/asgi.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import os
|
||||||
|
from channels.asgi import get_channel_layer
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bornhack.settings")
|
||||||
|
|
||||||
|
channel_layer = get_channel_layer()
|
||||||
|
|
8
src/bornhack/routing.py
Normal file
8
src/bornhack/routing.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from program.consumers import ScheduleConsumer
|
||||||
|
|
||||||
|
|
||||||
|
channel_routing = [
|
||||||
|
ScheduleConsumer.as_route(path=r"^/schedule/"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
|
|
||||||
|
'channels',
|
||||||
|
|
||||||
'profiles',
|
'profiles',
|
||||||
'camps',
|
'camps',
|
||||||
'shop',
|
'shop',
|
||||||
|
@ -108,9 +110,27 @@ MIDDLEWARE = [
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
INSTALLED_APPS += ['debug_toolbar', ]
|
INSTALLED_APPS += [
|
||||||
|
'debug_toolbar',
|
||||||
|
'channels_panel'
|
||||||
|
]
|
||||||
MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware'] + MIDDLEWARE
|
MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware'] + MIDDLEWARE
|
||||||
INTERNAL_IPS = "127.0.0.1"
|
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 = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
|
@ -142,3 +162,9 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CHANNEL_LAYERS = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "asgiref.inmemory.ChannelLayer",
|
||||||
|
"ROUTING": "bornhack.routing.channel_routing",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Event, Speaker, EventType, EventInstance, EventLocation, SpeakerProposal, EventProposal
|
from .models import Event, Speaker, EventType, EventInstance, EventLocation, SpeakerProposal, EventProposal, Favorite
|
||||||
|
|
||||||
|
|
||||||
@admin.register(SpeakerProposal)
|
@admin.register(SpeakerProposal)
|
||||||
|
@ -43,6 +43,11 @@ class SpeakerAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Favorite)
|
||||||
|
class FavoriteAdmin(admin.ModelAdmin):
|
||||||
|
raw_id_fields = ('event_instance',)
|
||||||
|
|
||||||
|
|
||||||
class SpeakerInline(admin.StackedInline):
|
class SpeakerInline(admin.StackedInline):
|
||||||
model = Speaker.events.through
|
model = Speaker.events.through
|
||||||
|
|
||||||
|
|
43
src/program/consumers.py
Normal file
43
src/program/consumers.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
from channels.generic.websockets import JsonWebsocketConsumer
|
||||||
|
|
||||||
|
from .models import EventInstance, Favorite
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleConsumer(JsonWebsocketConsumer):
|
||||||
|
http_user = True
|
||||||
|
|
||||||
|
def connection_groups(self, **kwargs):
|
||||||
|
return ['schedule_users']
|
||||||
|
|
||||||
|
def connect(self, message, **kwargs):
|
||||||
|
self.send({"accept": True})
|
||||||
|
|
||||||
|
def raw_receive(self, message, **kwargs):
|
||||||
|
content = self.decode_json(message['text'])
|
||||||
|
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(user=message.user)
|
||||||
|
|
||||||
|
if action == 'favorite':
|
||||||
|
event_instance_id = content.get('event_instance_id')
|
||||||
|
event_instance = EventInstance.objects.get(id=event_instance_id)
|
||||||
|
Favorite.objects.create(
|
||||||
|
user=message.user,
|
||||||
|
event_instance=event_instance
|
||||||
|
)
|
||||||
|
|
||||||
|
if action == 'unfavorite':
|
||||||
|
event_instance_id = content.get('event_instance_id')
|
||||||
|
event_instance = EventInstance.objects.get(id=event_instance_id)
|
||||||
|
favorite = Favorite.objects.get(event_instance=event_instance, user=message.user)
|
||||||
|
favorite.delete()
|
||||||
|
|
||||||
|
self.send(data)
|
||||||
|
|
||||||
|
def disconnect(self, message, **kwargs):
|
||||||
|
pass
|
26
src/program/migrations/0038_favorite.py
Normal file
26
src/program/migrations/0038_favorite.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11 on 2017-04-15 23:21
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('program', '0037_eventtype_include_in_event_list'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Favorite',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('event_instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='program.EventInstance')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='favorites', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -14,6 +14,7 @@ from django.apps import apps
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
|
||||||
import icalendar
|
import icalendar
|
||||||
|
import CommonMark
|
||||||
|
|
||||||
from utils.models import CreatedUpdatedModel, CampRelatedModel
|
from utils.models import CreatedUpdatedModel, CampRelatedModel
|
||||||
|
|
||||||
|
@ -246,7 +247,7 @@ class EventProposal(UserSubmittedModel):
|
||||||
# loop through the speakerproposals linked to this eventproposal and associate any related speaker objects with this event
|
# loop through the speakerproposals linked to this eventproposal and associate any related speaker objects with this event
|
||||||
for sp in self.speakers.all():
|
for sp in self.speakers.all():
|
||||||
if sp.speaker:
|
if sp.speaker:
|
||||||
event.speaker_set.add(sp.speaker)
|
event.speakers.add(sp.speaker)
|
||||||
|
|
||||||
self.proposal_status = eventproposalmodel.PROPOSAL_APPROVED
|
self.proposal_status = eventproposalmodel.PROPOSAL_APPROVED
|
||||||
self.save()
|
self.save()
|
||||||
|
@ -375,8 +376,8 @@ class Event(CampRelatedModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speakers_list(self):
|
def speakers_list(self):
|
||||||
if self.speaker_set.exists():
|
if self.speakers.exists():
|
||||||
return ", ".join(self.speaker_set.all().values_list('name', flat=True))
|
return ", ".join(self.speakers.all().values_list('name', flat=True))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
|
@ -448,6 +449,33 @@ class EventInstance(CampRelatedModel):
|
||||||
ievent['location'] = icalendar.vText(self.location.name)
|
ievent['location'] = icalendar.vText(self.location.name)
|
||||||
return ievent
|
return ievent
|
||||||
|
|
||||||
|
def to_json(self, user=None):
|
||||||
|
parser = CommonMark.Parser()
|
||||||
|
renderer = CommonMark.HtmlRenderer()
|
||||||
|
ast = parser.parse(self.event.abstract)
|
||||||
|
abstract = renderer.render(ast)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'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,
|
||||||
|
'speakers': [
|
||||||
|
{ 'name': speaker.name
|
||||||
|
, 'url': str(speaker.get_absolute_url())
|
||||||
|
} for speaker in self.event.speakers.all()]
|
||||||
|
}
|
||||||
|
|
||||||
|
if user and user.is_authenticated:
|
||||||
|
is_favorited = user.favorites.filter(event_instance=self).exists()
|
||||||
|
data['is_favorited'] = is_favorited
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_speaker_picture_upload_path(instance, filename):
|
def get_speaker_picture_upload_path(instance, filename):
|
||||||
""" We want speaker pictures are saved as MEDIA_ROOT/public/speakers/camp-slug/speaker-slug/filename """
|
""" We want speaker pictures are saved as MEDIA_ROOT/public/speakers/camp-slug/speaker-slug/filename """
|
||||||
|
@ -501,6 +529,7 @@ class Speaker(CampRelatedModel):
|
||||||
Event,
|
Event,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text='The event(s) this speaker is anchoring',
|
help_text='The event(s) this speaker is anchoring',
|
||||||
|
related_name='speakers'
|
||||||
)
|
)
|
||||||
|
|
||||||
proposal = models.OneToOneField(
|
proposal = models.OneToOneField(
|
||||||
|
@ -526,3 +555,10 @@ class Speaker(CampRelatedModel):
|
||||||
return reverse_lazy('speaker_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
|
return reverse_lazy('speaker_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug})
|
||||||
|
|
||||||
|
|
||||||
|
class Favorite(models.Model):
|
||||||
|
user = models.ForeignKey('auth.User', related_name='favorites')
|
||||||
|
event_instance = models.ForeignKey('program.EventInstance')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ['user', 'event_instance']
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<a href="{% url 'event_detail' camp_slug=camp.slug slug=event.slug %}">{{ event.title }}</a>
|
<a href="{% url 'event_detail' camp_slug=camp.slug slug=event.slug %}">{{ event.title }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% for speaker in event.speaker_set.all %}
|
{% for speaker in event.speakers.all %}
|
||||||
<a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}">{{ speaker.name }}</a><br>
|
<a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}">{{ speaker.name }}</a><br>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
N/A
|
N/A
|
||||||
|
|
|
@ -22,10 +22,10 @@
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
{% if event.speaker_set.exists %}
|
{% if event.speakers.exists %}
|
||||||
<h4>Speakers</h4>
|
<h4>Speakers</h4>
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
{% for speaker in event.speaker_set.all %}
|
{% for speaker in event.speakers.all %}
|
||||||
<h4><a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}" class="list-group-item">{{ speaker.name }}</a></h4>
|
<h4><a href="{% url 'speaker_detail' camp_slug=camp.slug slug=speaker.slug %}" class="list-group-item">{{ speaker.name }}</a></h4>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
{% extends 'schedule_base.html' %}
|
{% extends 'schedule_base.html' %}
|
||||||
|
|
||||||
{% load commonmark %}
|
{% load commonmark %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block extra_head %}
|
||||||
|
<script src="{% static "channels/js/websocketbridge.js" %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block schedule_content %}
|
{% block schedule_content %}
|
||||||
{% if eventinstances %}
|
{% if eventinstances %}
|
||||||
|
@ -12,9 +17,8 @@
|
||||||
<a class="event"
|
<a class="event"
|
||||||
href="{% url 'event_detail' camp_slug=camp.slug slug=eventinstance.event.slug %}"
|
href="{% url 'event_detail' camp_slug=camp.slug slug=eventinstance.event.slug %}"
|
||||||
style="background-color: {{ eventinstance.event.event_type.color }}; color: {% if eventinstance.event.event_type.light_text %}white{% else %}black{% endif %};"
|
style="background-color: {{ eventinstance.event.event_type.color }}; color: {% if eventinstance.event.event_type.light_text %}white{% else %}black{% endif %};"
|
||||||
data-toggle="modal"
|
data-eventinstance-id="{{ eventinstance.id }}"
|
||||||
data-target="#{{ eventinstance.event.slug }}"
|
>
|
||||||
data-remote=false>
|
|
||||||
<small>{{ eventinstance.when.lower|date:"H:i" }} - {{ eventinstance.when.upper|date:"H:i" }}</small>
|
<small>{{ eventinstance.when.lower|date:"H:i" }} - {{ eventinstance.when.upper|date:"H:i" }}</small>
|
||||||
<span class="pull-right" style="font-family: 'FontAwesome';">&#x{{ eventinstance.location.icon }};</span>
|
<span class="pull-right" style="font-family: 'FontAwesome';">&#x{{ eventinstance.location.icon }};</span>
|
||||||
<br />
|
<br />
|
||||||
|
@ -23,20 +27,24 @@
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="modal" id="{{ eventinstance.event.slug }}" tabindex="-1" role="dialog" aria-labelledby="{{ eventinstance.event.slug }}-label">
|
<div class="modal" id="event-template" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
<h4 class="modal-title" id="{{ eventinstance.event.slug }}-label">{{ eventinstance.event.title }}</h4>
|
<h4 class="modal-title"></h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body modal-body-content">
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
{{ eventinstance.event.abstract|commonmark}}
|
<h4>Speaker(s):</h4>
|
||||||
|
<ul class="speakers">
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default pull-left" data-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-default pull-left" data-dismiss="modal">Close</button>
|
||||||
<a class="btn btn-success"><i class="fa fa-star"></i> Favorite</a>
|
<a class="favorite-button btn btn-success"><i class="fa fa-star"></i> Favorite</a>
|
||||||
<a class="btn btn-info" href="{% url 'event_detail' camp_slug=camp.slug slug=eventinstance.event.slug %}"><i class="fa fa-info"></i> More</a>
|
<a class="more-button btn btn-info" href=""><i class="fa fa-info"></i> More</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,4 +58,104 @@
|
||||||
<h2>No scheduled events for {{ camp.title }} yet!</h2>
|
<h2>No scheduled events for {{ camp.title }} yet!</h2>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const webSocketBridge = new channels.WebSocketBridge();
|
||||||
|
|
||||||
|
var event_elements = document.getElementsByClassName("event");
|
||||||
|
var modals = {};
|
||||||
|
|
||||||
|
function toggleFavoriteButton(button) {
|
||||||
|
if(button.getAttribute('data-state') == 'true') {
|
||||||
|
favorite_button.classList.remove('btn-success');
|
||||||
|
favorite_button.classList.add('btn-danger');
|
||||||
|
favorite_button.innerHTML = '<i class="fa fa-minus"></i> 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 = '<i class="fa fa-star"></i> 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() {
|
||||||
|
webSocketBridge.send({action: 'init', camp_slug: '{{ camp.slug }}'});
|
||||||
|
});
|
||||||
|
webSocketBridge.listen(function(payload, stream) {
|
||||||
|
if(payload['action'] == 'event_instance') {
|
||||||
|
event_instance_id = payload['event_instance']['id'];
|
||||||
|
modal = modals[event_instance_id];
|
||||||
|
modal_title = modal.getElementsByClassName('modal-title')[0];
|
||||||
|
modal_title.innerHTML = payload['event_instance']['title']
|
||||||
|
modal_body_content = modal.getElementsByClassName('modal-body-content')[0];
|
||||||
|
modal_body_content.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];
|
||||||
|
if(payload['event_instance']['is_favorited'] !== undefined) {
|
||||||
|
favorite_button.setAttribute('data-state', payload['event_instance']['is_favorited'])
|
||||||
|
toggleFavoriteButton(favorite_button);
|
||||||
|
} else {
|
||||||
|
favorite_button.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
speakers_div = modal.getElementsByClassName('speakers')[0];
|
||||||
|
speakers = payload['event_instance']['speakers'];
|
||||||
|
for(speaker_id in speakers) {
|
||||||
|
var speaker = speakers[speaker_id];
|
||||||
|
var speaker_li = document.createElement('li');
|
||||||
|
var speaker_a = document.createElement('a');
|
||||||
|
speaker_a.setAttribute('href', speaker['url']);
|
||||||
|
speaker_a.appendChild(document.createTextNode(speaker['name']));
|
||||||
|
speaker_li.appendChild(speaker_a);
|
||||||
|
speakers_div.appendChild(speaker_li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function openModal(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
|
||||||
|
if(modal == undefined) {
|
||||||
|
template = document.getElementById('event-template');
|
||||||
|
modal = template.cloneNode(true);
|
||||||
|
body = document.getElementsByTagName('body')[0];
|
||||||
|
body.appendChild(modal);
|
||||||
|
modal.setAttribute('id', 'event-modal-' + event_instance_id)
|
||||||
|
modals[event_instance_id] = modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#event-modal-' + event_instance_id).modal();
|
||||||
|
webSocketBridge.send({action: 'get_event_instance', event_instance_id: event_instance_id})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var event_id in event_elements) {
|
||||||
|
event_element = event_elements.item(event_id);
|
||||||
|
event_element.onclick = openModal
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endblock schedule_content %}
|
{% endblock schedule_content %}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
CommonMark==0.7.3
|
CommonMark==0.7.3
|
||||||
Django==1.10.5
|
Django==1.10.5
|
||||||
|
channels==1.1.3
|
||||||
Pillow==4.0.0
|
Pillow==4.0.0
|
||||||
PyPDF2==1.26.0
|
PyPDF2==1.26.0
|
||||||
Unidecode==0.04.20
|
Unidecode==0.04.20
|
||||||
|
@ -9,8 +10,9 @@ bleach==1.5.0
|
||||||
defusedxml==0.4.1
|
defusedxml==0.4.1
|
||||||
django-allauth==0.30.0
|
django-allauth==0.30.0
|
||||||
django-bleach==0.3.0
|
django-bleach==0.3.0
|
||||||
django-bootstrap3==8.1.0
|
django-bootstrap3==8.2.2
|
||||||
django-debug-toolbar==1.6
|
django-debug-toolbar==1.6
|
||||||
|
django-channels-panel==0.0.5
|
||||||
django-extensions==1.7.7
|
django-extensions==1.7.7
|
||||||
django-wkhtmltopdf==3.1.0
|
django-wkhtmltopdf==3.1.0
|
||||||
docopt==0.6.2
|
docopt==0.6.2
|
||||||
|
|
|
@ -71,7 +71,7 @@ a, a:active, a:focus {
|
||||||
/* Footer */
|
/* Footer */
|
||||||
footer {
|
footer {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 700px;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
|
|
|
@ -106,7 +106,8 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% bootstrap_messages %}
|
{% bootstrap_messages %}
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
<footer class="row">
|
</div>
|
||||||
|
<footer>
|
||||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
<a href="{% url 'general-terms' %}">General Terms & Conditions</a> |
|
<a href="{% url 'general-terms' %}">General Terms & Conditions</a> |
|
||||||
|
@ -116,7 +117,6 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue