From d4d7fad43941c07a6ff142b87dfd65a8826b9e97 Mon Sep 17 00:00:00 2001 From: Vidir Valberg Gudmundsson Date: Wed, 2 Aug 2017 22:20:38 +0200 Subject: [PATCH] Cleanups. Adding Speaker detail. --- schedule/src/Decoders.elm | 7 ++- schedule/src/Main.elm | 2 +- schedule/src/Messages.elm | 4 +- schedule/src/Models.elm | 50 +++++++++++++++----- schedule/src/Routing.elm | 35 ++++++++++++-- schedule/src/Views.elm | 4 ++ schedule/src/Views/DayPicker.elm | 5 +- schedule/src/Views/DayView.elm | 5 +- schedule/src/Views/EventDetail.elm | 62 +++++++++++++++++++++---- schedule/src/Views/FilterView.elm | 13 +++--- schedule/src/Views/ScheduleOverview.elm | 5 +- src/program/consumers.py | 6 ++- src/program/models.py | 23 ++++++++- 13 files changed, 177 insertions(+), 44 deletions(-) diff --git a/schedule/src/Decoders.elm b/schedule/src/Decoders.elm index 09ae2452..44d469ac 100644 --- a/schedule/src/Decoders.elm +++ b/schedule/src/Decoders.elm @@ -44,6 +44,10 @@ speakerDecoder : Decoder Speaker speakerDecoder = decode Speaker |> required "name" string + |> required "slug" string + |> required "biography" string + |> optional "large_picture_url" (nullable string) Nothing + |> optional "small_picture_url" (nullable string) Nothing eventDecoder : Decoder Event @@ -52,7 +56,7 @@ eventDecoder = |> required "title" string |> required "slug" string |> required "abstract" string - |> required "speakers" (list speakerDecoder) + |> required "speaker_slugs" (list string) |> required "video_recording" bool |> optional "video_url" string "" |> required "event_type" string @@ -115,3 +119,4 @@ initDataDecoder = |> required "event_instances" (list eventInstanceDecoder) |> required "event_locations" (list eventLocationDecoder) |> required "event_types" (list eventTypeDecoder) + |> required "speakers" (list speakerDecoder) diff --git a/schedule/src/Main.elm b/schedule/src/Main.elm index 67d7af90..7b03faeb 100644 --- a/schedule/src/Main.elm +++ b/schedule/src/Main.elm @@ -37,7 +37,7 @@ init flags location = Filter [] [] [] model = - Model [] [] [] [] [] flags emptyFilter location currentRoute False + Model [] [] [] [] [] [] flags emptyFilter location currentRoute False in model ! [ sendInitMessage flags.camp_slug flags.websocket_server ] diff --git a/schedule/src/Messages.elm b/schedule/src/Messages.elm index 502f29cd..db48160e 100644 --- a/schedule/src/Messages.elm +++ b/schedule/src/Messages.elm @@ -2,7 +2,7 @@ module Messages exposing (Msg(..)) -- Local modules -import Models exposing (Day, EventType, EventLocation, EventInstance) +import Models exposing (Day, EventType, EventLocation, EventInstance, VideoRecordingFilter) -- External modules @@ -15,6 +15,6 @@ type Msg | WebSocketPayload String | ToggleEventTypeFilter EventType | ToggleEventLocationFilter EventLocation - | ToggleVideoRecordingFilter { name : String, slug : String, filter : EventInstance -> Bool } + | ToggleVideoRecordingFilter VideoRecordingFilter | OnLocationChange Location | BackInHistory diff --git a/schedule/src/Models.elm b/schedule/src/Models.elm index b8f09f10..53be6e49 100644 --- a/schedule/src/Models.elm +++ b/schedule/src/Models.elm @@ -10,11 +10,36 @@ import Date exposing (Date, now) import Navigation exposing (Location) +type alias EventSlug = + String + + +type alias EventInstanceSlug = + String + + +type alias SpeakerSlug = + String + + +type alias DaySlug = + String + + +type alias FilterQuery = + String + + + +-- Route is defined here rather than in Routing.elm due to it being used in Model. If it were in Routing.elm we would have a circular dependency. + + type Route = OverviewRoute - | OverviewFilteredRoute String - | DayRoute String + | OverviewFilteredRoute FilterQuery + | DayRoute DaySlug | EventRoute EventSlug + | SpeakerRoute SpeakerSlug | NotFoundRoute @@ -24,6 +49,7 @@ type alias Model = , eventInstances : List EventInstance , eventLocations : List EventLocation , eventTypes : List EventType + , speakers : List Speaker , flags : Flags , filter : Filter , location : Location @@ -35,10 +61,14 @@ type alias Model = type alias Filter = { eventTypes : List EventType , eventLocations : List EventLocation - , videoRecording : List { name : String, slug : String, filter : EventInstance -> Bool } + , videoRecording : List VideoRecordingFilter } +type alias VideoRecordingFilter = + { name : String, slug : String, filter : EventInstance -> Bool } + + type alias Day = { day_name : String , date : Date @@ -48,17 +78,13 @@ type alias Day = type alias Speaker = { name : String + , slug : SpeakerSlug + , biography : String + , largePictureUrl : Maybe String + , smallPictureUrl : Maybe String } -type alias EventSlug = - String - - -type alias EventInstanceSlug = - String - - type alias EventInstance = { title : String , slug : EventInstanceSlug @@ -83,7 +109,7 @@ type alias Event = { title : String , slug : EventSlug , abstract : String - , speakers : List Speaker + , speakerSlugs : List SpeakerSlug , videoRecording : Bool , videoUrl : String , eventType : String diff --git a/schedule/src/Routing.elm b/schedule/src/Routing.elm index 0bbe9642..653edde5 100644 --- a/schedule/src/Routing.elm +++ b/schedule/src/Routing.elm @@ -14,16 +14,16 @@ import UrlParser exposing (Parser, (), oneOf, map, top, s, string, parseHash) {-- URLs to support: -- # +- #/ This show the overview of the schedule -- #?type={types},location={locations},video={no,yes,link} +- #/?type={types},location={locations},video={not-to-be-recorded,to-be-recorded,has-recording} This is the overview, just with filters enable -- #day/{year}-{month}-{day} +- #/day/{year}-{month}-{day} Show a particular day -- #event/{slug} +- #/event/{slug} Show a particular event --} @@ -36,6 +36,7 @@ matchers = , map OverviewFilteredRoute (top string) , map DayRoute (s "day" string) , map EventRoute (s "event" string) + , map SpeakerRoute (s "speaker" string) ] @@ -43,3 +44,29 @@ parseLocation : Location -> Route parseLocation location = parseHash matchers location |> Maybe.withDefault NotFoundRoute + + +routeToString : Route -> String +routeToString route = + let + parts = + case route of + OverviewRoute -> + [] + + OverviewFilteredRoute query -> + [ query ] + + DayRoute iso -> + [ "day", iso ] + + EventRoute slug -> + [ "event", slug ] + + SpeakerRoute slug -> + [ "speaker", slug ] + + NotFoundRoute -> + [] + in + "#/" ++ String.join "/" parts diff --git a/schedule/src/Views.elm b/schedule/src/Views.elm index 84d66332..2da55990 100644 --- a/schedule/src/Views.elm +++ b/schedule/src/Views.elm @@ -7,6 +7,7 @@ import Messages exposing (Msg(..)) import Views.DayPicker exposing (dayPicker) import Views.DayView exposing (dayView) import Views.EventDetail exposing (eventDetailView) +import Views.SpeakerDetail exposing (speakerDetailView) import Views.ScheduleOverview exposing (scheduleOverviewView) @@ -48,6 +49,9 @@ view model = EventRoute eventSlug -> eventDetailView eventSlug model + SpeakerRoute speakerSlug -> + speakerDetailView speakerSlug model + NotFoundRoute -> div [] [ text "Not found!" ] ] diff --git a/schedule/src/Views/DayPicker.elm b/schedule/src/Views/DayPicker.elm index 4a6c0a88..d7a8b5f5 100644 --- a/schedule/src/Views/DayPicker.elm +++ b/schedule/src/Views/DayPicker.elm @@ -4,6 +4,7 @@ module Views.DayPicker exposing (..) import Models exposing (..) import Messages exposing (Msg(..)) +import Routing exposing (routeToString) -- Core modules @@ -51,7 +52,7 @@ dayPicker model = , ( "btn-default", not isAllDaysActive ) , ( "btn-primary", isAllDaysActive ) ] - , href ("#") + , href <| routeToString OverviewRoute ] [ text "All Days" ] @@ -78,7 +79,7 @@ dayButton day activeDate = , ( "btn-default", not isActive ) , ( "btn-primary", isActive ) ] - , href ("#day/" ++ (Date.Extra.toFormattedString "y-MM-dd" day.date)) + , href <| routeToString <| DayRoute <| Date.Extra.toFormattedString "y-MM-dd" day.date ] [ text day.day_name ] diff --git a/schedule/src/Views/DayView.elm b/schedule/src/Views/DayView.elm index 4c659fa1..9745c40d 100644 --- a/schedule/src/Views/DayView.elm +++ b/schedule/src/Views/DayView.elm @@ -3,7 +3,8 @@ module Views.DayView exposing (dayView) -- Local modules import Messages exposing (Msg(..)) -import Models exposing (Model, Day, EventInstance, EventLocation) +import Models exposing (Model, Day, EventInstance, EventLocation, Route(EventRoute)) +import Routing exposing (routeToString) -- Core modules @@ -236,7 +237,7 @@ eventInstanceBlock offset numberInGroup ( eventInstance, lefts ) = , ( "background-color", eventInstance.backgroundColor ) , ( "color", eventInstance.forgroundColor ) ] - , href ("#event/" ++ eventInstance.eventSlug) + , href <| routeToString <| EventRoute eventInstance.eventSlug ] [ p [] [ text ((Date.Extra.toFormattedString "HH:mm" eventInstance.from) ++ " " ++ eventInstance.title) ] ] diff --git a/schedule/src/Views/EventDetail.elm b/schedule/src/Views/EventDetail.elm index a098f5a3..aae9f710 100644 --- a/schedule/src/Views/EventDetail.elm +++ b/schedule/src/Views/EventDetail.elm @@ -4,6 +4,7 @@ module Views.EventDetail exposing (eventDetailView) import Messages exposing (Msg(..)) import Models exposing (..) +import Routing exposing (routeToString) -- Core modules @@ -27,15 +28,12 @@ eventDetailView eventSlug model = model.events |> List.filter (\e -> e.slug == eventSlug) |> List.head - - eventInstances = - List.filter (\instance -> instance.eventSlug == eventSlug) model.eventInstances in case event of Just event -> div [ class "row" ] [ eventDetailContent event - , eventDetailSidebar event eventInstances + , eventDetailSidebar event model ] Nothing -> @@ -53,12 +51,52 @@ eventDetailContent event = , text " Back" ] , h3 [] [ text event.title ] - , p [] [ Markdown.toHtml [] event.abstract ] + , div [] [ Markdown.toHtml [] event.abstract ] ] -eventDetailSidebar : Event -> List EventInstance -> Html Msg -eventDetailSidebar event eventInstances = +getSpeakersFromSlugs : List Speaker -> List SpeakerSlug -> List Speaker -> List Speaker +getSpeakersFromSlugs speakers slugs collectedSpeakers = + case speakers of + [] -> + collectedSpeakers + + speaker :: rest -> + let + foundSlug = + slugs + |> List.filter (\slug -> slug == speaker.slug) + |> List.head + + foundSpeaker = + case foundSlug of + Just slug -> + [ speaker ] + + Nothing -> + [] + + newSlugs = + case foundSlug of + Just slug -> + List.filter (\x -> x /= slug) slugs + + Nothing -> + slugs + + newCollectedSpeakers = + collectedSpeakers ++ foundSpeaker + in + case slugs of + [] -> + collectedSpeakers + + _ -> + getSpeakersFromSlugs rest newSlugs newCollectedSpeakers + + +eventDetailSidebar : Event -> Model -> Html Msg +eventDetailSidebar event model = let videoRecordingLink = case event.videoUrl of @@ -71,6 +109,12 @@ eventDetailSidebar event eventInstances = , text " Watch recording here!" ] ] + + eventInstances = + List.filter (\instance -> instance.eventSlug == event.slug) model.eventInstances + + speakers = + getSpeakersFromSlugs model.speakers event.speakerSlugs [] in div [ classList @@ -80,7 +124,7 @@ eventDetailSidebar event eventInstances = ] ] (videoRecordingLink - ++ [ speakerSidebar event.speakers + ++ [ speakerSidebar speakers , eventMetaDataSidebar event , eventInstancesSidebar eventInstances ] @@ -121,7 +165,7 @@ speakerSidebar speakers = speakerDetail : Speaker -> Html Msg speakerDetail speaker = li [] - [ text speaker.name + [ a [ href <| routeToString <| SpeakerRoute speaker.slug ] [ text speaker.name ] ] diff --git a/schedule/src/Views/FilterView.elm b/schedule/src/Views/FilterView.elm index 2718b418..a6e4700d 100644 --- a/schedule/src/Views/FilterView.elm +++ b/schedule/src/Views/FilterView.elm @@ -3,7 +3,8 @@ module Views.FilterView exposing (filterSidebar, applyFilters, parseFilterFromQu -- Local modules import Messages exposing (Msg(..)) -import Models exposing (Model, EventInstance, Filter, Day) +import Models exposing (Model, EventInstance, Filter, Day, FilterQuery, Route(OverviewFilteredRoute), VideoRecordingFilter) +import Routing exposing (routeToString) -- Core modules @@ -14,7 +15,7 @@ import Regex -- External modules import Html exposing (Html, text, div, ul, li, span, i, h4) -import Html.Attributes exposing (class, classList, href) +import Html.Attributes exposing (class, classList) import Html.Events exposing (onClick) import Date.Extra exposing (Interval(..), equalBy) @@ -95,7 +96,7 @@ hasRecordingFilter eventInstance = eventInstance.videoUrl /= "" -videoRecordingFilters : List { name : String, slug : String, filter : EventInstance -> Bool } +videoRecordingFilters : List VideoRecordingFilter videoRecordingFilters = [ { name = "Will not be recorded", slug = "not-to-be-recorded", filter = notRecordedFilter } , { name = "Will recorded", slug = "to-be-recorded", filter = recordedFilter } @@ -177,7 +178,7 @@ getFilter filterType modelItems query = List.filterMap (\x -> findFilter modelItems x) filterSlugs -parseFilterFromQuery : String -> Model -> Filter +parseFilterFromQuery : FilterQuery -> Model -> Filter parseFilterFromQuery query model = let types = @@ -195,7 +196,7 @@ parseFilterFromQuery query model = } -filterToQuery : Filter -> String +filterToQuery : Filter -> FilterQuery filterToQuery filter = let typePart = @@ -225,4 +226,4 @@ filterToQuery filter = result = String.join "&" (List.filter (\x -> x /= "") [ typePart, locationPart, videoPart ]) in - "#" ++ result + routeToString <| OverviewFilteredRoute result diff --git a/schedule/src/Views/ScheduleOverview.elm b/schedule/src/Views/ScheduleOverview.elm index 04c4a332..e99a55c7 100644 --- a/schedule/src/Views/ScheduleOverview.elm +++ b/schedule/src/Views/ScheduleOverview.elm @@ -3,8 +3,9 @@ module Views.ScheduleOverview exposing (scheduleOverviewView) -- Local modules import Messages exposing (Msg(..)) -import Models exposing (Model, Day, EventInstance, Filter) +import Models exposing (Model, Day, EventInstance, Filter, Route(EventRoute)) import Views.FilterView exposing (filterSidebar, applyFilters, parseFilterFromQuery) +import Routing exposing (routeToString) -- External modules @@ -50,7 +51,7 @@ dayEventInstanceView eventInstance = [ ( "event", True ) , ( "event-in-overview", True ) ] - , href ("#event/" ++ eventInstance.eventSlug) + , href <| routeToString <| EventRoute eventInstance.eventSlug , style [ ( "background-color", eventInstance.backgroundColor ) , ( "color", eventInstance.forgroundColor ) diff --git a/src/program/consumers.py b/src/program/consumers.py index 84a6e7fd..4c659066 100644 --- a/src/program/consumers.py +++ b/src/program/consumers.py @@ -1,7 +1,7 @@ from channels.generic.websockets import JsonWebsocketConsumer from camps.models import Camp -from .models import Event, EventInstance, Favorite, EventLocation, EventType +from .models import Event, EventInstance, Favorite, EventLocation, EventType, Speaker class ScheduleConsumer(JsonWebsocketConsumer): @@ -41,12 +41,16 @@ class ScheduleConsumer(JsonWebsocketConsumer): event_types_query_set = EventType.objects.filter() event_types = list([x.serialize() for x in event_types_query_set]) + speakers_query_set = Speaker.objects.filter(camp=camp) + speakers = list([x.serialize() for x in speakers_query_set]) + data = { "action": "init", "events": events, "event_instances": event_instances, "event_locations": event_locations, "event_types": event_types, + "speakers": speakers, "days": days, } except Camp.DoesNotExist: diff --git a/src/program/models.py b/src/program/models.py index 2bdea6b1..b5e8187b 100644 --- a/src/program/models.py +++ b/src/program/models.py @@ -14,6 +14,8 @@ from django.dispatch import receiver from django.utils.text import slugify from django.conf import settings from django.core.urlresolvers import reverse_lazy +from django.core.exceptions import ValidationError +from django.core.urlresolvers import reverse_lazy, reverse from django.core.files.storage import FileSystemStorage from django.urls import reverse from django.apps import apps @@ -433,8 +435,8 @@ class Event(CampRelatedModel): 'title': self.title, 'slug': self.slug, 'abstract': self.abstract, - 'speakers': [ - speaker.serialize() + 'speaker_slugs': [ + speaker.slug for speaker in self.speakers.all() ], 'video_recording': self.video_recording, @@ -613,10 +615,27 @@ class Speaker(CampRelatedModel): def get_absolute_url(self): return reverse_lazy('speaker_detail', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug}) + def get_picture_url(self, size): + return reverse('speaker_picture', kwargs={'camp_slug': self.camp.slug, 'slug': self.slug, 'picture': size}) + + def get_small_picture_url(self): + return self.get_picture_url('thumbnail') + + def get_large_picture_url(self): + return self.get_picture_url('large') + + def serialize(self): data = { 'name': self.name, + 'slug': self.slug, + 'biography': self.biography, } + + if self.picture_small and self.picture_large: + data['large_picture_url'] = self.get_large_picture_url() + data['small_picture_url'] = self.get_small_picture_url() + return data