diff --git a/pyconbalkan/core/static/css/components/person.css b/pyconbalkan/core/static/css/components/person.css index a99ad207..24606e32 100644 --- a/pyconbalkan/core/static/css/components/person.css +++ b/pyconbalkan/core/static/css/components/person.css @@ -32,12 +32,31 @@ .presentation__talk { color: #343C3E; + } .presentation__workshop { color: #666; } +.presentation__keynote, +.presentation__talk, +.presentation__workshop { + padding-left: 1em; +} + +.cat-session { + border: 1px solid; + border-radius: 2px; + width: 3em; + text-align: center; + color: #3aaee2; + font-size: 14px; + font-weight: 700; + float:left; + margin-top: 1.3em; +} + .speaker, .organizer, .sponsor { diff --git a/pyconbalkan/core/static/css/components/timetable.css b/pyconbalkan/core/static/css/components/timetable.css index 6cba71ca..44d4c5e5 100644 --- a/pyconbalkan/core/static/css/components/timetable.css +++ b/pyconbalkan/core/static/css/components/timetable.css @@ -1,3 +1,59 @@ +#dates-wrapper { + text-align: center; +} + +.day-select { + border: 1px solid #343C3E; + border-radius: 6px; + background: white; + font-size: 16px; + padding: 6px; + text-decoration: none; + margin: 1em; +} + +.day-select:focus {outline:0;} + +.day-select:active, +.day-select:hover, +.selected-day { + background: #27aae1; + color: white; + border: 1px solid transparent; +} + +/*.room-timeline {*/ +/* width: 1800px !important;*/ +/* overflow: hidden !important;*/ +/*}*/ + +.timetable .time-entry { + background-color: #27aae1 !important; + border: 1px solid #efefef !important; + text-decoration: none; + font-size: 11px; +} + +.timetable .time-entry:hover { + background-color: #343c3e !important; +} + +.timetable>section>header li { + width: 115px !important; +} + +.timetable ul.room-timeline li:before { + background-image: unset !important; +} + +.timetable ul.room-timeline li:after { + background-size: 112px auto !important +} + +.timetable ul.room-timeline li .time-entry { + height: 140px; +} + .presentation { display: flex; margin: 15px 0; diff --git a/pyconbalkan/core/static/css/timetablejs.css b/pyconbalkan/core/static/css/timetablejs.css new file mode 100644 index 00000000..d76f3617 --- /dev/null +++ b/pyconbalkan/core/static/css/timetablejs.css @@ -0,0 +1 @@ +.timetable{display:flex;align-items:stretch;width:100%;max-width:100%;box-sizing:border-box}.timetable *{box-sizing:inherit}.timetable li,.timetable ul{list-style-type:none;margin:0}.timetable li,.timetable ul{padding:0}.timetable>aside{flex:none;max-width:30%;padding:0!important;margin-top:46px;border-right:5px solid transparent;position:relative}.timetable>aside li{padding:0 15px;background-color:#efefef;line-height:46px}.timetable>aside li:not(:last-of-type){border-bottom:1px solid #fff}.timetable>aside .row-heading{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;word-wrap:normal}.timetable>aside:before{content:"";display:block;background-color:#fff;height:46px;position:relative;margin-top:-46px;top:0}.timetable aside li,.timetable time li{height:140px}.timetable>section{flex:3 0 auto;width:0;padding:0!important}.timetable>section time{display:block;width:100%;overflow-x:scroll;-webkit-overflow-scrolling:touch}.timetable>section>header{position:relative;top:0;z-index:3;width:100%;overflow:hidden;background-color:#fff;transform-style:preserve-3d}.timetable>section>header ul{display:flex;height:46px;align-items:center}.timetable>section>header li{flex:none;display:block;position:relative;width:96px}.timetable>section>header li .time-label{display:block;position:absolute;left:0}.timetable>section>header li:not(:first-of-type) .time-label{transform:translateX(-50%)}.timetable>section>header li:last-of-type{width:0}.timetable>section>header li:last-of-type .time-label{transform:translateX(-100%)}.timetable ul.room-timeline{border-left:none;display:flex;flex-direction:column;align-items:stretch}.timetable ul.room-timeline li{position:relative;background-color:#f4f4f4;height:140px}.timetable ul.room-timeline li:nth-of-type(odd){background-color:#fdfdfd}.timetable ul.room-timeline li:first-of-type{border-top:1px solid #e5e5e5}.timetable ul.room-timeline li:last-of-type{border-bottom:1px solid #e5e5e5}.timetable ul.room-timeline li:not(:last-of-type){border-bottom:none}.timetable ul.room-timeline li:first-child .time-entry{height:140px}.timetable ul.room-timeline li:after,.timetable ul.room-timeline li:before{content:"";position:absolute;z-index:1;left:0;top:0;right:0;bottom:0}.timetable ul.room-timeline li:before{background-image:linear-gradient(90deg,#e5e5e5 1px,transparent 0);background-size:24px auto}.timetable ul.room-timeline li:after{background-image:linear-gradient(90deg,#e5e5e5,#e5e5e5 1px,#f4f4f4 0,#f4f4f4 2px,#e5e5e5 0,#e5e5e5 3px,transparent 0,transparent);background-size:96px auto;background-position:-2px 0}.timetable .time-entry{background-color:#ec6a5e;transition:background-color .2s;height:45px;display:block;position:absolute;z-index:2;padding:0 10px;white-space:normal;overflow:hidden;color:#fff;border:1px solid #e32c1b;transform-style:preserve-3d}.timetable .time-entry small{position:relative;top:50%;transform:translateY(-50%);display:block}.timetable .time-entry:hover{background-color:#e74030} \ No newline at end of file diff --git a/pyconbalkan/core/static/js/timetable.js b/pyconbalkan/core/static/js/timetable.js index 26645467..4e45c775 100644 --- a/pyconbalkan/core/static/js/timetable.js +++ b/pyconbalkan/core/static/js/timetable.js @@ -1,7 +1 @@ -$(".btn-room").click(function() { - let roomNumber = this.innerHTML; - $(".btn-room").removeClass('button--yellow'); - $(".presentations").addClass('hidden'); - $("." + roomNumber).removeClass('hidden'); - $(".btn-" + roomNumber).addClass('button--yellow'); -}); \ No newline at end of file +!function(e,t){"function"==typeof define&&define.amd?define(["exports"],t):t("undefined"!=typeof exports?exports:e.syncscroll={})}(this,function(e){var t="Width",n="Height",o="Top",r="Left",i="scroll",a="client",s=[],c=function(){var e,c,l,u,d,f=document.getElementsByClassName("syncscroll");for(d in s)if(s.hasOwnProperty(d))for(e=0;e=0&&e<24}function r(e,t){return t.indexOf(e)!==-1}function i(e,t){var n=e instanceof Date&&t instanceof Date,o=e=e?t-e:24+t-e}function s(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function c(e,t){var n;if(t){var o=e>=12?"PM":"AM";n=(e+11)%12+1+":00"+o}else{var r=e<10?"0":"";n=r+e+":00"}return n}Timetable.prototype={setScope:function(t,n){if(!e(t,n))throw new RangeError("Timetable scope should consist of (start, end) in whole hours from 0 to 23");return this.scope.hourStart=t,this.scope.hourEnd=n,this},useTwelveHour:function(){this.usingTwelveHour=!0},addLocations:function(e){function t(){return e instanceof Array}var n=this.locations;if(!t())throw new Error("Tried to add locations in wrong format");return e.forEach(function(e){if(r(e,n))throw new Error("Location already exists");n.push(e)}),this},addEvent:function(e,t,n,o,a){if(!r(t,this.locations))throw new Error("Unknown location");if(!i(n,o))throw new Error("Invalid time range: "+JSON.stringify([n,o]));var s="[object Object]"===Object.prototype.toString.call(a);return this.events.push({name:e,location:t,startDate:n,endDate:o,options:s?a:void 0}),this}},Timetable.Renderer.prototype={draw:function(e){function t(e){if(null===e)throw new Error("Timetable container not found")}function n(e){var t=e.appendChild(document.createElement("aside")),n=t.appendChild(document.createElement("ul"));o(n)}function o(e){for(var t=0;t {{ speaker.job }} {% if speaker.company %}- {{ speaker

PyCon Balkan {{ conference }} Talks

{% for presentation in presentations %}
- -

{{ presentation.title }}

-
+
+

{{ presentation.get_type_short }} + PyCon {{ presentation.get_type_display }} +

+ +

{{ presentation.title }}

+
+

{{ presentation.description|markdownify }}

diff --git a/pyconbalkan/timetable/management/commands/load_timetable.py b/pyconbalkan/timetable/management/commands/load_timetable.py index 08edebf2..35a79d0d 100644 --- a/pyconbalkan/timetable/management/commands/load_timetable.py +++ b/pyconbalkan/timetable/management/commands/load_timetable.py @@ -10,11 +10,13 @@ from pyconbalkan.speaker.models import Speaker from pyconbalkan.timetable.models import Presentation, Room, Slot +from django.core.exceptions import ObjectDoesNotExist + ALL_DATA_URL = "https://sessionize.com/api/v2/fwv5aino/view/All" class Command(BaseCommand): - help = 'Closes the specified poll for voting' + help = 'Import data from sessionize' def add_arguments(self, parser): pass @@ -24,20 +26,25 @@ def add_arguments(self, parser): def handle(self, *args, **options): conference = Conference.objects.get(year=options['year']) + _cant_find = [] data = requests.get(options['data_url']).json() questions = data['questions'] categories = data['categories'] rooms = data['rooms'] + sessions = data['sessions'] + speakers = data['speakers'] - # Sync rooms + types = categories[0]['items'] + types = {x['id']:x['name'] for x in types} + + Presentation.objects.filter(conference=conference).delete() Room.objects.filter(conference=conference).delete() Slot.objects.filter(conference=conference).delete() - all_presentations_this_year = list(Presentation.objects.filter(conference=conference).values_list("id", "title")) for room in rooms: - Room.objects.create( + Room.objects.get_or_create( pk=room['id'], conference=conference, name=room['name'], @@ -45,58 +52,48 @@ def handle(self, *args, **options): ) - # Make speaker dict - speakers = {} - for _ in data['speakers']: - first_name = _.pop('firstName') - last_name = _.pop('lastName') - speakers[f"{first_name} {last_name}"] = _ - - for speaker in speakers.values(): - for i, session in enumerate(speaker['sessions']): - speaker['sessions'][i] = list(filter(lambda _: int(_['id']) == session, data['sessions']))[0] + for session in sessions: + speaker_id = session['speakers'][0] + speaker = self.get_speaker(speaker_id, speakers) - # Attach Speaker obj from DB - speakers_qs = Speaker.objects.all().prefetch_related( - Prefetch( - "presentations", - queryset=Presentation.objects.filter(active=True, conference=conference).order_by("type") + if speaker == None: + print(speaker_id, 'Not found') + else: + speaker = speaker.id + + presentation, created = Presentation.objects.get_or_create( + pk=session['id'], + active=False, + title=session['title'], + description=session['description'], + type=Presentation.PRESENTATION_TYPE_REVERSE[types[session['categoryItems'][0]]], + speaker_id=speaker, + conference_id=conference.id, ) - ) - - _cant_find = [] - for speaker in speakers_qs: - if not any(speaker.presentations.all()): - continue - r = list(fuzzyfinder(speaker.full_name, speakers.keys())) - if not any(r): - _cant_find.append(speaker) - continue - speakers[r[0]]["model_speaker"] = speaker - speaker = speakers[r[0]] - - for _ in speaker['sessions']: - r = list(fuzzyfinder(_['title'], list(zip(*all_presentations_this_year))[1])) - if not any(r): - _cant_find.append(_) - - - try: - presentation = Presentation.objects.get(title__icontains=_['title']) - except Presentation.DoesNotExist: - _cant_find.append(_) - # TODO NEXT YEAR MAYBE, GET TZ FROM CONFERENCE OBJECT ?! - Slot.objects.create( - from_date=timezone.make_aware(datetime.strptime(_['startsAt'], "%Y-%m-%dT%H:%M:%S")), - to_date=timezone.make_aware(datetime.strptime(_['endsAt'], "%Y-%m-%dT%H:%M:%S")), - talk=presentation, - room=Room.objects.get(pk=_['roomId']), - conference=conference - ) + start = timezone.make_aware(datetime.strptime(session['startsAt'], "%Y-%m-%dT%H:%M:%S")) + end = timezone.make_aware(datetime.strptime(session['endsAt'], "%Y-%m-%dT%H:%M:%S")) + Slot.objects.get_or_create( + active=False, + from_date=start, + to_date=end, + room_id=session['roomId'], + talk_id=presentation.id, + conference_id=conference.id, + ) if any(_cant_find): if options['force']: print(_cant_find) else: raise Exception("Had problems with the following", _cant_find) + + def get_speaker(self, speaker_id, speakers): + for speaker in speakers: + if speaker['id'] == speaker_id: + full_name = " ".join(speaker['fullName'].split()) + try: + return Speaker.objects.get(full_name=full_name) + except ObjectDoesNotExist: + return None + return None \ No newline at end of file diff --git a/pyconbalkan/timetable/models.py b/pyconbalkan/timetable/models.py index 4626ceb6..0210eb5c 100644 --- a/pyconbalkan/timetable/models.py +++ b/pyconbalkan/timetable/models.py @@ -19,6 +19,12 @@ class Presentation(AbstractConference, ActiveModel): (KEYNOTE, 'Keynote'), ) + PRESENTATION_TYPE_REVERSE = { + 'Talk': TALK, + 'Workshop': WORKSHOP, + 'Keynote': KEYNOTE + } + title = models.CharField(null=True, blank=True, max_length=100) description = MarkdownxField(null=True, blank=True) type = models.IntegerField(choices=PRESENTATION_TYPE, default=TALK) diff --git a/pyconbalkan/timetable/templates/timetable.html b/pyconbalkan/timetable/templates/timetable.html index d72b1595..96f5fda4 100644 --- a/pyconbalkan/timetable/templates/timetable.html +++ b/pyconbalkan/timetable/templates/timetable.html @@ -2,80 +2,71 @@ {% load static %} {% block main_content %} - {# Timetable #} -

Timetable

-
- {% if rooms %} -
    - {% for room in rooms %} -
  • {{ room.name }}
  • - {% endfor %} -
- {% endif %} -
-
- {% if slots %} - {% for slot in slots %} -
-
-
-
    - {% for key, value in DAYS.items %} - {% if key == slot.from_date.date %} -
  • {{ value }}, {{ slot.from_date.time }} to {{ slot.to_date.time }}
  • - {% endif %} - {% endfor %} -
- {% if slot.talk.speaker.name %} -
-

{{ slot.talk.speaker.name }}

- {% if slot.talk.speaker.job %} - {{ slot.talk.speaker.job }} - {% endif %} -
- {% endif %} -

{% if slot.talk.speaker %} {% if slot.talk.type == 0 %} Talk: {% elif slot.talk.type == 1 %} Workshop: {{ slot.talk.description }} {% elif slot.talk.type == 2 %} Keynote: {% endif %} {% endif %} {{ slot.talk.description }}

-
-
- {% endfor %} - {% else %} -

- Coming Soon -

- {% endif %} -
- {% if slots_by_rooms %} - {% for room, slots in slots_by_rooms.items %} -