diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1d932f6b --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2016-2018, BornHack +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 4494c0ea..22e9d088 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ Django project to power Bornhack. Features include news, villages, webshop, and more. -## Setup +## Development setup ### Clone the repo Clone with --recursive to include submodules: git clone --recursive https://github.com/bornhack/bornhack-website -If you already cloned the repository, you can add the submodules like this: +If you already cloned the repository without --recursive, you can change into the directory and add the submodules with: git submodule update --init --recursive @@ -20,60 +20,72 @@ $ virtualenv venv -p python3 $ source venv/bin/activate ``` +If you installed python3 using Homebrew on macOS, you will need to install virtualenv by runinng the following command first: +``` +pip3 install virtualenv +``` + ### System libraries Install system dependencies (method depends on OS): - postgresql headers (for psycopg2): - Debian: libpq-dev - FreeBSD: databases/postgresql93-client + - macOS: If using the PostgreSQL.app, the headers are included, only path needs to be added - libjpeg (for pdf generation) - Debian: libjpeg-dev - FreeBSD: graphics/jpeg-turbo + - macOS: brew install libjpeg +- libmagic (might already be installed) + - macOS: brew install libmagic - wkhtmltopdf (also for pdf generation): - Debian: wkhtmltopdf - FreeBSD: converters/wkhtmltopdf + - macOS: install from https://wkhtmltopdf.org/ - fonts - Debian: ? - FreeBSD: x11-fonts/webfonts + - macOS: ? ### Python packages Install pip packages: ``` - (venv) $ pip install -r src/requirements.txt +(venv) $ pip install -r src/requirements/dev.txt ``` +### Postgres + +You need to have a running Postgres instance (we use Postgres-specific datetime range fields). Install Postgress, and add a database `bornhack` (or whichever you like) with some way for the application to connect to it, for instance adding a user with a password. + +You can also use Unix socket connections if you know how to. It's faster, easier and perhaps more secure. + ### Configuration file -Copy environment settings file and change settings as needed: + +Copy dev environment settings file and change settings as needed: + ``` - (venv) $ cp src/bornhack/environment_settings.py.dist src/bornhack/environment_settings.py +(venv) $ cp src/bornhack/environment_settings.py.dist.dev src/bornhack/environment_settings.py ``` -Edit the configuration file, replacing all the ``{{ placeholder }}`` patterns -(intended for Ansible). +Edit the configuration file, setting up `DATABASES` matching your Postgres settings. ### Database Is this a new installation? Initialize the database: + ``` - (venv) $ src/manage.py migrate +(venv) $ src/manage.py migrate ``` Is this for local development? Bootstrap the database with dummy data and users: -``` - (venv) $ src/manage.py bootstrap-devsite -``` - -### Deploy camps+program test data - -Run this command to create a bunch of nice test data: ``` - (venv) $ src/manage.py bootstrap-devsite +(venv) $ src/manage.py bootstrap-devsite ``` ### Done Is this for local development? Start the Django devserver: ``` - (venv) $ src/manage.py runserver +(venv) $ src/manage.py runserver ``` Otherwise start uwsgi or similar to serve the application. @@ -87,7 +99,7 @@ Enjoy! Add a new camp by running: ``` - (venv) $ src/manage.py createcamp {camp-slug} +(venv) $ src/manage.py createcamp {camp-slug} ``` Then go to the admin interface to edit the camp details, adding the same slug @@ -102,8 +114,21 @@ You can also specify details like: * `{camp-slug}-logo-large.png` * `{camp-slug}-logo-small.png` -### multicamp prod migration notes - -* when villages.0008 migration fails go add camp_id to all existing villages -* go to admin interface and add bornhack 2017, and set slug for bornhack 2016 -* convert events to the new format (somehow) +## Contributors +* Alexander Færøy https://github.com/ahf +* Benjamin Bach https://github.com/benjaoming +* coral https://github.com/coral +* Henrik Kramshøj https://github.com/kramse +* Janus Troelsen https://github.com/ysangkok +* Jonty Wareing https://github.com/Jonty +* Kasper Christensen https://github.com/fALKENdk +* klarstrup https://github.com/klarstrup +* kugg https://github.com/kugg +* RadicalPet https://github.com/RadicalPet +* Reynir Björnsson https://github.com/reynir +* Ronni Elken Lindsgaard https://github.com/rlindsgaard +* Stephan Telling https://github.com/Telling +* Thomas Flummer https://github.com/flummer +* Thomas Steen Rasmusssen https://github.com/tykling +* Víðir Valberg Guðmundsson https://github.com/valberg +* Ximin Luo https://github.com/infinity0 diff --git a/schedule/src/Decoders.elm b/schedule/src/Decoders.elm index b574794c..367d9fce 100644 --- a/schedule/src/Decoders.elm +++ b/schedule/src/Decoders.elm @@ -46,8 +46,6 @@ speakerDecoder = |> 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 @@ -82,6 +80,7 @@ eventInstanceDecoder = |> required "url" string |> required "event_slug" string |> required "event_type" string + |> required "event_track" string |> required "bg-color" string |> required "fg-color" string |> required "from" dateDecoder @@ -111,6 +110,13 @@ eventTypeDecoder = |> required "light_text" bool +eventTrackDecoder : Decoder FilterType +eventTrackDecoder = + decode TrackFilter + |> required "name" string + |> required "slug" string + + initDataDecoder : Decoder (Flags -> Filter -> Location -> Route -> Bool -> Model) initDataDecoder = decode Model @@ -119,4 +125,5 @@ initDataDecoder = |> required "event_instances" (list eventInstanceDecoder) |> required "event_locations" (list eventLocationDecoder) |> required "event_types" (list eventTypeDecoder) + |> required "event_tracks" (list eventTrackDecoder) |> required "speakers" (list speakerDecoder) diff --git a/schedule/src/Main.elm b/schedule/src/Main.elm index 7b03faeb..b165e2f0 100644 --- a/schedule/src/Main.elm +++ b/schedule/src/Main.elm @@ -34,10 +34,10 @@ init flags location = parseLocation location emptyFilter = - Filter [] [] [] + 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/Models.elm b/schedule/src/Models.elm index 9f60f394..305c57fa 100644 --- a/schedule/src/Models.elm +++ b/schedule/src/Models.elm @@ -49,6 +49,7 @@ type alias Model = , eventInstances : List EventInstance , eventLocations : List FilterType , eventTypes : List FilterType + , eventTracks : List FilterType , speakers : List Speaker , flags : Flags , filter : Filter @@ -69,8 +70,6 @@ type alias Speaker = { name : String , slug : SpeakerSlug , biography : String - , largePictureUrl : Maybe String - , smallPictureUrl : Maybe String } @@ -81,6 +80,7 @@ type alias EventInstance = , url : String , eventSlug : EventSlug , eventType : String + , eventTrack : String , backgroundColor : String , forgroundColor : String , from : Date @@ -142,11 +142,13 @@ type FilterType = TypeFilter FilterName FilterSlug TypeColor TypeLightText | LocationFilter FilterName FilterSlug LocationIcon | VideoFilter FilterName FilterSlug + | TrackFilter FilterName FilterSlug type alias Filter = { eventTypes : List FilterType , eventLocations : List FilterType + , eventTracks : List FilterType , videoRecording : List FilterType } @@ -162,6 +164,9 @@ unpackFilterType filter = VideoFilter name slug -> ( name, slug ) + TrackFilter name slug -> + ( name, slug ) + getSlugFromFilterType filter = let diff --git a/schedule/src/Update.elm b/schedule/src/Update.elm index 8d2ad964..720cea93 100644 --- a/schedule/src/Update.elm +++ b/schedule/src/Update.elm @@ -96,6 +96,19 @@ update msg model = videoRecording :: model.filter.videoRecording } + TrackFilter name slug -> + let + eventTrack = + TrackFilter name slug + in + { currentFilter + | eventTracks = + if List.member eventTrack model.filter.eventTracks then + List.filter (\x -> x /= eventTrack) model.filter.videoRecording + else + eventTrack :: model.filter.eventTracks + } + query = filterToQuery newFilter diff --git a/schedule/src/Views/DayView.elm b/schedule/src/Views/DayView.elm index a93f3af2..d421c583 100644 --- a/schedule/src/Views/DayView.elm +++ b/schedule/src/Views/DayView.elm @@ -69,7 +69,7 @@ locationColumns eventInstances eventLocations offset minutes = , ( "justify-content", "space-around" ) ] , classList - [ ( "col-sm-11", True ) + [ ( "col-xs-11", True ) ] ] (List.map (\location -> locationColumn columnWidth eventInstances offset minutes location) eventLocations) @@ -269,7 +269,7 @@ gutter : List Date -> Html Msg gutter hours = div [ classList - [ ( "col-sm-1", True ) + [ ( "col-xs-1", True ) , ( "day-view-gutter", True ) ] ] diff --git a/schedule/src/Views/EventDetail.elm b/schedule/src/Views/EventDetail.elm index 93e35451..97bb69b0 100644 --- a/schedule/src/Views/EventDetail.elm +++ b/schedule/src/Views/EventDetail.elm @@ -123,14 +123,13 @@ eventDetailSidebar event model = ] (videoRecordingLink ++ [ speakerSidebar speakers - , eventMetaDataSidebar event - , eventInstancesSidebar eventInstances + , eventMetaDataSidebar event eventInstances model ] ) -eventMetaDataSidebar : Event -> Html Msg -eventMetaDataSidebar event = +eventMetaDataSidebar : Event -> List EventInstance -> Model -> Html Msg +eventMetaDataSidebar event eventInstances model = let ( showVideoRecoring, videoRecording ) = case event.videoState of @@ -142,10 +141,23 @@ eventMetaDataSidebar event = _ -> ( False, "" ) + + eventInstanceMetaData = + case eventInstances of + [ instance ] -> + eventInstanceItem instance model + + instances -> + [ h4 [] + [ text "Multiple occurences:" ] + , ul + [] + (List.map (\ei -> li [] <| eventInstanceItem ei model) instances) + ] in div [] - [ h4 [] [ text "Metadata" ] - , ul [] + ([ h4 [] [ text "Metadata" ] + , ul [] ([ li [] [ strong [] [ text "Type: " ], text event.eventType ] ] ++ (case showVideoRecoring of @@ -156,7 +168,44 @@ eventMetaDataSidebar event = [] ) ) + ] + ++ eventInstanceMetaData + ) + + +eventInstanceItem : EventInstance -> Model -> List (Html Msg) +eventInstanceItem eventInstance model = + let + toFormat = + if Date.day eventInstance.from == Date.day eventInstance.to then + "HH:mm" + else + "E HH:mm" + + ( locationName, _ ) = + model.eventLocations + |> List.map unpackFilterType + |> List.filter + (\( _, locationSlug ) -> + locationSlug == eventInstance.location + ) + |> List.head + |> Maybe.withDefault ( "Unknown", "" ) + in + [ p [] + [ strong [] [ text "When: " ] + , text + ((Date.Extra.toFormattedString "E HH:mm" eventInstance.from) + ++ " to " + ++ (Date.Extra.toFormattedString toFormat eventInstance.to) + ) ] + , p [] + [ strong [] [ text "Where: " ] + , text <| locationName ++ " " + , i [ classList [ ( "fa", True ), ( "fa-" ++ eventInstance.locationIcon, True ) ] ] [] + ] + ] speakerSidebar : List Speaker -> Html Msg @@ -175,32 +224,3 @@ speakerDetail speaker = li [] [ a [ href <| routeToString <| SpeakerRoute speaker.slug ] [ text speaker.name ] ] - - -eventInstancesSidebar : List EventInstance -> Html Msg -eventInstancesSidebar eventInstances = - div [] - [ h4 [] - [ text "This event will occur at:" ] - , ul - [] - (List.map eventInstanceItem eventInstances) - ] - - -eventInstanceItem : EventInstance -> Html Msg -eventInstanceItem eventInstance = - let - toFormat = - if Date.day eventInstance.from == Date.day eventInstance.to then - "HH:mm" - else - "E HH:mm" - in - li [] - [ text - ((Date.Extra.toFormattedString "E HH:mm" eventInstance.from) - ++ " to " - ++ (Date.Extra.toFormattedString toFormat eventInstance.to) - ) - ] diff --git a/schedule/src/Views/FilterView.elm b/schedule/src/Views/FilterView.elm index 8a641868..4ba483d3 100644 --- a/schedule/src/Views/FilterView.elm +++ b/schedule/src/Views/FilterView.elm @@ -37,6 +37,9 @@ applyFilters day model = locations = slugs model.eventLocations model.filter.eventLocations + tracks = + slugs model.eventTracks model.filter.eventTracks + videoFilters = slugs videoRecordingFilters model.filter.videoRecording @@ -47,6 +50,7 @@ applyFilters day model = && (Date.Extra.equalBy Date.Extra.Day eventInstance.from day.date) && List.member eventInstance.location locations && List.member eventInstance.eventType types + && List.member eventInstance.eventTrack tracks && List.member eventInstance.videoState videoFilters ) model.eventInstances @@ -77,6 +81,12 @@ filterSidebar model = model.filter.eventLocations model.eventInstances .location + , filterView + "Track" + model.eventTracks + model.filter.eventTracks + model.eventInstances + .eventTrack , filterView "Video" videoRecordingFilters @@ -214,10 +224,10 @@ filterChoiceView filter currentFilters eventInstances slugLike = "film" "to-be-recorded" -> - "video-camera" + "video" "not-to-be-recorded" -> - "ban" + "video-slash" _ -> "" @@ -309,11 +319,15 @@ parseFilterFromQuery query model = locations = getFilter "location" model.eventLocations query + tracks = + getFilter "tracks" model.eventTracks query + videoFilters = getFilter "video" videoRecordingFilters query in { eventTypes = types , eventLocations = locations + , eventTracks = tracks , videoRecording = videoFilters } diff --git a/schedule/src/Views/ScheduleOverview.elm b/schedule/src/Views/ScheduleOverview.elm index 8e1f7fe9..95e533f1 100644 --- a/schedule/src/Views/ScheduleOverview.elm +++ b/schedule/src/Views/ScheduleOverview.elm @@ -80,25 +80,25 @@ dayEventInstanceIcons eventInstance = case eventInstance.videoState of "has-recording" -> [ i - [ classList [ ( "fa", True ), ( "fa-film", True ), ( "pull-right", True ) ] ] + [ classList [ ( "fa", True ), ( "fa-film", True ), ( "pull-right", True ), ( "fa-fw", True ) ] ] [] ] "to-be-recorded" -> [ i - [ classList [ ( "fa", True ), ( "fa-video-camera", True ), ( "pull-right", True ) ] ] + [ classList [ ( "fa", True ), ( "fa-video", True ), ( "pull-right", True ), ( "fa-fw", True ) ] ] [] ] "not-to-be-recorded" -> [ i - [ classList [ ( "fa", True ), ( "fa-ban", True ), ( "pull-right", True ) ] ] + [ classList [ ( "fa", True ), ( "fa-video-slash", True ), ( "pull-right", True ), ( "fa-fw", True ) ] ] [] ] _ -> [] in - [ i [ classList [ ( "fa", True ), ( "fa-" ++ eventInstance.locationIcon, True ), ( "pull-right", True ) ] ] [] + [ i [ classList [ ( "fa", True ), ( "fa-" ++ eventInstance.locationIcon, True ), ( "pull-right", True ), ( "fa-fw", True ) ] ] [] ] ++ videoIcon diff --git a/schedule/src/Views/SpeakerDetail.elm b/schedule/src/Views/SpeakerDetail.elm index 4c13f2b7..c54248d9 100644 --- a/schedule/src/Views/SpeakerDetail.elm +++ b/schedule/src/Views/SpeakerDetail.elm @@ -22,33 +22,18 @@ speakerDetailView speakerSlug model = model.speakers |> List.filter (\speaker -> speaker.slug == speakerSlug) |> List.head - - image = - case speaker of - Just speaker -> - case speaker.smallPictureUrl of - Just smallPictureUrl -> - [ img [ src smallPictureUrl ] [] ] - - Nothing -> - [] - - Nothing -> - [] in case speaker of Just speaker -> div [] - ([ a [ onClick BackInHistory, classList [ ( "btn", True ), ( "btn-default", True ) ] ] + [ a [ onClick BackInHistory, classList [ ( "btn", True ), ( "btn-default", True ) ] ] [ i [ classList [ ( "fa", True ), ( "fa-chevron-left", True ) ] ] [] , text " Back" ] - , h3 [] [ text speaker.name ] - , div [] [ Markdown.toHtml [] speaker.biography ] - , speakerEvents speaker model - ] - ++ image - ) + , h3 [] [ text speaker.name ] + , div [] [ Markdown.toHtml [] speaker.biography ] + , speakerEvents speaker model + ] Nothing -> div [] [ text "Unknown speaker..." ] diff --git a/scripts/schemagif.sh b/scripts/schemagif.sh new file mode 100755 index 00000000..41407832 --- /dev/null +++ b/scripts/schemagif.sh @@ -0,0 +1,69 @@ +#!/bin/sh +################################# +# Loop over migrations in the +# BornHack website project, apply +# one by one, and run +# postgresql_autodoc for each. +# +# Use the generated .dot files +# to generate PNGs and watermark +# the PNG with the migration name. +# +# Finally use $whatever to combine +# all the PNGs to an animation and +# marvel at the ingenuity of Man. +# +# This scripts makes a million +# assumptions about the local env. +# and installed packages. Enjoy! +# +# /Tykling, April 2018 +################################# +#set -x + +# warn the user +read -p "WARNING: This scripts deletes and recreates the local pg database named bornhackdb several times. Continue? " + +# wipe database +sudo su postgres -c "dropdb bornhackdb; createdb -O bornhack bornhackdb" + +# run migrate with --fake to get list of migrations +MIGRATIONS=$(python manage.py migrate --fake | grep FAKED | cut -d " " -f 4 | cut -d "." -f 1-2) + +# wipe database again +sudo su postgres -c "dropdb bornhackdb; createdb -O bornhack bornhackdb" + +# create output folder +sudo rm -rf postgres_autodoc +mkdir postgres_autodoc +sudo chown postgres:postgres postgres_autodoc + +# loop over migrations +COUNTER=0 +for MIGRATION in $MIGRATIONS; do + COUNTER=$(( $COUNTER + 1 )) + ALFACOUNTER=$(printf "%04d" $COUNTER) + + echo "processing migration #${COUNTER}: $MIG" + APP=$(echo $MIGRATION | cut -d "." -f 1) + MIG=$(echo $MIGRATION | cut -d "." -f 2) + + echo "--- running migration: APP: $APP MIGRATION: $MIG ..." + python manage.py migrate --no-input $APP $MIG + + echo "--- running postgresql_autodoc and dot..." + cd postgres_autodoc + sudo su postgres -c "mkdir ${ALFACOUNTER}-$MIGRATION" + cd "${ALFACOUNTER}-${MIGRATION}" + # run postgresql_autodoc + sudo su postgres -c "postgresql_autodoc -d bornhackdb" + # create PNG from .dot file + sudo su postgres -c "dot -Tpng bornhackdb.dot -o bornhackdb.png" + # create watermark image with migration name as white on black text + sudo su postgres -c "convert -background none -undercolor black -fill white -font DejaVu-Sans-Mono-Bold -size 5316x4260 -pointsize 72 -gravity SouthEast label:${ALFACOUNTER}-${MIGRATION} background.png" + # combine the images + sudo su postgres -c "composite -gravity center bornhackdb.png background.png final.png" + cd .. + cd .. +done + diff --git a/src/backoffice/mixins.py b/src/backoffice/mixins.py new file mode 100644 index 00000000..17361b74 --- /dev/null +++ b/src/backoffice/mixins.py @@ -0,0 +1,30 @@ +from utils.mixins import RaisePermissionRequiredMixin + + +class OrgaTeamPermissionMixin(RaisePermissionRequiredMixin): + """ + Permission mixin for views used by Orga Team + """ + permission_required = ("camps.backoffice_permission", "camps.orgateam_permission") + + +class EconomyTeamPermissionMixin(RaisePermissionRequiredMixin): + """ + Permission mixin for views used by Economy Team + """ + permission_required = ("camps.backoffice_permission", "camps.economyteam_permission") + + +class InfoTeamPermissionMixin(RaisePermissionRequiredMixin): + """ + Permission mixin for views used by Info Team/InfoDesk + """ + permission_required = ("camps.backoffice_permission", "camps.infoteam_permission") + + +class ContentTeamPermissionMixin(RaisePermissionRequiredMixin): + """ + Permission mixin for views used by Content Team + """ + permission_required = ("camps.backoffice_permission", "program.contentteam_permission") + diff --git a/src/backoffice/templates/approve_public_credit_names.html b/src/backoffice/templates/approve_public_credit_names.html new file mode 100644 index 00000000..0ed6beb8 --- /dev/null +++ b/src/backoffice/templates/approve_public_credit_names.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} +{% load commonmark %} +{% load static from staticfiles %} +{% load imageutils %} +{% block extra_head %} + + +{% endblock extra_head %} +{% block content %} +
Username | +Public Credit Name | +Actions | +|
---|---|---|---|
{{ profile.user.username }} | +{{ profile.user.email }} | +{{ profile.public_credit_name }} | ++ Open In Admin + | +
-
Ticket UUID | +Ticket Type | +Ticket Order ID | +Order User | +Order Email | +Ticket Name | +Ticket Email | +Product | +
---|---|---|---|---|---|---|---|
{{ ticket.uuid }} | +{{ ticket.shortname }} | +{{ ticket.order.id }} | +{{ ticket.order.user }} | +{{ ticket.order.user.email }} | +{{ ticket.name }} | +{{ ticket.email }} | +{{ ticket.product }} | +
+
Manage {{ camp.title }}
+ + {% endfor %} ++
Use this view to mark products such as merchandise, cabins, fridges and so on as handed out.
+ + +Use this view to check-in tickets when participants arrive.
+ + +Use this view to mark badges as handed out.
+ + {% endif %} + + {% if perms.camps.contentteam_permission %} +Use this view to manage SpeakerProposals and EventProposals
+ + {% endif %} + + {% if perms.camps.orgateam_permission %} +Use this view to check and approve users Public Credit Names
+ + +Use this view to look at Merchandise Orders
+ + +Use this view to generate a list of merchandise that needs to be ordered
+ + +Use this view to look at Village category OrderProductRelations
+ + +Use this view to generate a list of village gear that needs to be ordered
+ + {% endif %} + + {% if perms.camps.economyteam_permission %} +Use this view to see and approve/reject expenses.
+ + +Use this view to view and create reimbursements for approved expenses.
+ + +Use this view to see and approve/reject revenues.
+ + {% endif %} +No pending SpeakerProposals found
+ {% else %} +Name | +Ticket? | +Speaker? | +Submitting User | +Action | +|
---|---|---|---|---|---|
{{ proposal.name }} | +{{ proposal.email }} | +{{ proposal.needs_oneday_ticket|truefalseicon }} | +{{ proposal.event|truefalseicon }} | +{{ proposal.user }} | +Manage | +
No pending SpeakerProposals found
+ {% else %} +Title | +Track | +Type | +Speakers | +Event? | +Submitting User | +Action | +
---|---|---|---|---|---|---|
{{ proposal.title }} | +{{ proposal.track }} | +{{ proposal.event_type }} | +{% for speaker in proposal.speakers.all %} {% endfor %} | +{{ proposal.speaker|truefalseicon }} | +{{ proposal.user }} | +Manage | +
Merchandise Type | +Quantity | +
---|---|
{{ key }} | +{{ val }} | +
Order | +User | +OPR Id | +Product | +Quantity | +|
---|---|---|---|---|---|
Order #{{ productrel.order.id }} | +{{ productrel.order.user }} | +{{ productrel.order.user.email }} | +{{ productrel.id }} | +{{ productrel.product.name }} | +{{ productrel.quantity }} | +
Order | +User | +OPR Id | +Product | +Quantity | +|
---|---|---|---|---|---|
Order #{{ productrel.order.id }} | +{{ productrel.order.user }} | +{{ productrel.order.user.email }} | +{{ productrel.id }} | +{{ productrel.product.name }} | +{{ productrel.quantity }} | +
Description | +Amount | +Invoice | +Responsible Team | +
---|---|---|---|
{{ expense.description }} | +{{ expense.amount }} | +{{ expense.invoice }} | +{{ expense.responsible_team }} Team | +
The total amount for this reimbursement will be {{ total_amount.amount__sum }} DKK
+ + +{% endblock content %} + diff --git a/src/backoffice/templates/reimbursement_create_userselect.html b/src/backoffice/templates/reimbursement_create_userselect.html new file mode 100644 index 00000000..429a5315 --- /dev/null +++ b/src/backoffice/templates/reimbursement_create_userselect.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} +{% load commonmark %} +{% load static from staticfiles %} +{% load imageutils %} +{% block content %} + +Create a reimbursement for user {{ user.username }}
+ + {% endfor %} +The total amount for this reimbursement is {{ reimbursement.amount }} DKK
+ +Really delete this reimbursement?
+ + +{% endblock content %} + diff --git a/src/backoffice/templates/reimbursement_detail_backoffice.html b/src/backoffice/templates/reimbursement_detail_backoffice.html new file mode 100644 index 00000000..6a649f6c --- /dev/null +++ b/src/backoffice/templates/reimbursement_detail_backoffice.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} +{% load bootstrap3 %} + +{% block content %} +The total amount for this reimbursement is {{ reimbursement.amount }} DKK
+ + +{% endblock content %} + diff --git a/src/backoffice/templates/reimbursement_list_backoffice.html b/src/backoffice/templates/reimbursement_list_backoffice.html new file mode 100644 index 00000000..f0519888 --- /dev/null +++ b/src/backoffice/templates/reimbursement_list_backoffice.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} +{% load staticfiles %} + +{% block extra_head %} + + +{% endblock extra_head %} + +{% block content %} +Ticket UUID | +Ticket Type | +Order ID | +Order User | +Order Email | +Ticket Name | +Ticket Email | +Product | +
---|---|---|---|---|---|---|---|
{{ ticket.uuid }} | +{{ ticket.shortname }} | +{{ ticket.order.id }} | +{{ ticket.order.user }} | +{{ ticket.order.user.email }} | +{{ ticket.name }} | +{{ ticket.email }} | +{{ ticket.product }} | +
Type | +Quantity | +
---|---|
{{ key }} | +{{ val }} | +
You are very welcome to ask questions and show your interest on our different channels:
+{% include 'includes/contact.html' %} ++ {% thumbnail 'img/bornhack-2016/fonsmark' 'FA0_1983.JPG' 'Happy organisers welcoming people at the entrance to BornHack 2016' %} + {% thumbnail 'img/bornhack-2016/fonsmark' 'FA0_1986.JPG' 'A bus full of hackers arrive at BornHack 2016' %} + {% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5126.JPG' 'Late night hacking at Baconsvin village at BornHack 2016' %} + {% thumbnail 'img/bornhack-2016/fonsmark' 'FB1_5168.JPG' '#irl_bar by night at BornHack 2016' %} + {% thumbnail 'img/bornhack-2016/esbjerg' '1600x900-B12A2452.jpg' 'Soldering the BornHack 2016 badge' %} + {% thumbnail 'img/bornhack-2016/esbjerg' '1600x900-B12A2608.jpg' 'Colored lights at night' %} + {% thumbnail 'img/bornhack-2016/fonsmark' 'FA0_1961.JPG' 'BornHack' %} + {% thumbnail 'img/bornhack-2016/esbjerg' '1600x900-B12A2485.jpg' 'Colored light in the grass' %} + {% thumbnail 'img/bornhack-2016/esbjerg' '1600x988-B12A2624.jpg' 'Working on decorations' %} + {% thumbnail 'img/bornhack-2016/esbjerg' '1600x900-B12A2604.jpg' 'Sitting around the campfire at BornHack 2016' %} +
+{% endblock content %} diff --git a/src/camps/templates/camp_list.html b/src/camps/templates/camp_list.html index 33b25296..abc8537d 100644 --- a/src/camps/templates/camp_list.html +++ b/src/camps/templates/camp_list.html @@ -24,7 +24,7 @@What | +Description | +Actions | +
---|---|---|
Expenses |
+ You have {{ expense_count }} expense{{ expense_count|pluralize }} ({{ approved_expense_count }} approved, {{ rejected_expense_count }} rejected, and {{ unapproved_expense_count }} pending approval) for {{ camp.title }}, for a total of {{ expense_total|default:"0" }} DKK. | ++ List Expenses + Create Expense + | +
Reimbursements |
+ You have {{ reimbursement_count }} reimbursement{{ reimbursement_count|pluralize }} ({{ paid_reimbursement_count }} paid, {{ unpaid_reimbursement_count }} pending payment) for {{ camp.title }}, for a total of {{ reimbursement_total }} DKK. | ++ List Reimbursements + | +
Revenue |
+ You have {{ revenue_count }} revenue{{ revenue_count|pluralize }} ({{ approved_revenue_count }} approved, {{ rejected_revenue_count }} rejected, and {{ unapproved_revenue_count }} still pending approval) for {{ camp.title }}, for a total of {{ revenue_total|default:"0" }} DKK. | ++ List Revenues + Create Revenue + | +
You don't have permission to add expenses. Please ask someone from the Economy team to add the permission if you need it.
Created By | +{{ expense.user }} | +
---|---|
Amount | +{{ expense.amount }} DKK | +
Invoice Date | +{{ expense.invoice_date }} | +
Description | +{{ expense.description }} | +
Paid by BornHack? | +This expense was paid by {% if expense.paid_by_bornhack %}BornHack{% else %}{{ expense.user }}, and will be reimbursed when approved.{% endif %} | +
Filename | +{{ expense.invoice }} | +
Approved? | +{{ expense.approval_status }} | +
Reimbursement? | ++ {% if expense.reimbursement %} + {% if request.resolver_match.app_name == "backoffice" %} + {{ expense.reimbursement.pk }} + {% else %} + {{ expense.reimbursement.pk }} + {% endif %} + {% else %} + N/A + {% endif %} + |
Invoice | +
+ + Filename: {{ expense.invoice_filename }} + |
+
Economy Team Notes | +{{ expense.notes|default:"N/A" }} | +
Created By | + {% if not reimbursement %} +Paid by | + {% endif %} +Amount | +Invoice Date | +Description | +Responsible Team | + {% if not reimbursement %} +Approved | +Reimbursement | + {% endif %} +Actions | +
---|---|---|---|---|---|---|---|---|
{{ expense.user }} | + {% if not reimbursement %} +{% if expense.paid_by_bornhack %}BornHack{% else %}{{ expense.user }}{% endif %} | + {% endif %} +{{ expense.amount }} DKK | +{{ expense.invoice_date }} | +{{ expense.description }} | +{{ expense.responsible_team.name }} Team | + + {% if not reimbursement %} +{{ expense.approval_status }} | ++ {% if expense.reimbursement and not expense.paid_by_bornhack %} + {% if request.resolver_match.app_name == "backoffice" %} + Details + {% else %} + Details + {% endif %} + {% else %} + N/A + {% endif %} + | + {% endif %} + ++ {% if request.resolver_match.app_name == "backoffice" %} + Details + {% else %} + Details + Update + Delete + {% endif %} + | +
Reimbursement User | +{{ reimbursement.reimbursement_user }} | +
---|---|
Created By Economy Team Member | +{{ reimbursement.user }} | +
Total Amount | +{{ reimbursement.amount }} DKK | +
Economy Team Notes | +{{ reimbursement.notes|default:"N/A" }} | +
Paid | +{{ reimbursement.paid }} | +
Created | +{{ reimbursement.created }} by {{ reimbursement.user }} | +
Expenses covered by this Reimbursement | ++ {% include 'includes/expense_list_panel.html' with expense_list=reimbursement.covered_expenses.all %} + | +
Camp | +Created By | +Created For | +Economy Team Notes | +Amount | +Paid | +Actions | +
---|---|---|---|---|---|---|
{{ reim.camp }} | +{{ reim.user }} | +{{ reim.reimbursement_user }} | +{{ reim.notes|default:"N/A" }} | +{{ reim.amount }} DKK | +{{ reim.paid }} | ++ {% if request.resolver_match.app_name == "backoffice" %} + Details + Update + {% if not reim.paid %} + Delete + {% endif %} + {% else %} + Details + {% endif %} + | +
Created By | +Amount | +Invoice Date | +Description | +Responsible Team | +Approved | +Actions | +
---|---|---|---|---|---|---|
{{ revenue.user }} | +{{ revenue.amount }} DKK | +{{ revenue.invoice_date }} | +{{ revenue.description }} | +{{ revenue.responsible_team.name }} Team | +{{ revenue.approval_status }} | ++ {% if request.resolver_match.app_name == "backoffice" %} + Details + {% else %} + Details + Update + Delete + {% endif %} + | +
You don't have permission to add revenue. Please ask someone from the Economy team to add the permission if you need it.
+ BornHack can always improve, but we need your feedback to know how. So write you thoughts on what was good and what wasn't, it's highly appreciated! +
+{{ item.body|commonmark }}
+{{ item.body|trustedcommonmark }}
BornHack 2016 is a 7 days outdoor technology tent camping festival that will take place from the 27th of August to the 3rd of September 2016 on the island of Bornholm in Denmark. It is first time that BornHack will take place and it is our goal to make BornHack a yearly recurring event with 100 to 350 participants.
- -We are looking for gifted, entertaining and technically enlightening speakers to host talks, lightning talks and workshops at BornHack.
- -Please reach out to us on speakers@bornhack.dk with a title, abstract, biography, an optional picture of yourself and whether it is a regular talk, lightning talk, workshop or something entirely different. Please ensure that all information is in English. The submitted information will be published both as a news entry and in the official event program on our website, if the submission is accepted.
- -We are very open to different topics. We expect that the majority of the presentation at BornHack will be on security, networking, programming, distributed systems, privacy, and how these technologies relate to society.
- -The ticket shop for BornHack 2016 is already open and available at https://bornhack.dk/shop/ - please make sure you have also read our Code of Conduct.
- -Regular talks are 45 minutes of presentation, 10 minutes of questions from the audience followed by 5 minutes of preparation for setting up the next speaker.
- -Please bring your own laptop with your presentation on; it should have an HDMI socket and we will provide the cable to the projector. We do not guarantee that audio will work, even if your laptop supports that.
- -We will provide you with a one-day entrance ticket free of charge, but due to our limited funds, you would have to pay for transportation to and from the event yourself. We also encourage you to participate for the entire week, but you would also have to pay for the ticket yourself.
- -Lightning talks are 10 minutes of presentation. A laptop will be connected to the projector at the location of the presentations.
- -A lightning talk is an excellent opportunity for inexperienced speakers to present a topic that you find interesting.
- -You MUST buy yourself an entrance ticket to host a lightning talk; we are unable to offer free tickets for everyone that gives a lightning talk.
- -We have two workshop areas that will be able to host workshops for approximately 20 people per room. Workshops can be up to 3 hours per slot and can be extended for daily workshops.
- -You MUST buy yourself an entrance ticket to host a workshop; we are unable to offer free tickets for everyone that hosts a workshop.
- -The BornHack speakers team can be contacted via speakers@bornhack.dk - for general information reach out to the info team via info@bornhack.dk
- -We are also reachable via IRC in #BornHack on irc.baconsvin.org or 6nbtgccn5nbcodn3.onion - both listening for TLS connections on port 6697.
- -For more information, please have a look at https://bornhack.dk/ or follow us on Twitter at @bornhax.
-{% endblock %} diff --git a/src/program/templates/bornhack-2017_call_for_speakers.html b/src/program/templates/bornhack-2017_call_for_speakers.html deleted file mode 100644 index 80e03280..00000000 --- a/src/program/templates/bornhack-2017_call_for_speakers.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends 'program_base.html' %} - -{% block title %} -Call for Speakers | {{ block.super }} -{% endblock %} - -{% block program_content %} - -{% if not camp.call_for_speakers_open %} -We are looking for gifted, talented, humourous, technically enlightened speakers to host talks, lightning talks, and workshops at BornHack.
- -We are very open to different topics. We expect that the majority of the presentation at BornHack will be on security, networking, programming, distributed systems, privacy, and how these technologies relate to society.
- -BornHack is trying to be an inclusive event so please make sure you have read and understood our Code of Conduct.
- -Regular talks are 45 minutes of presentation, 10 minutes of questions from the audience followed by 5 minutes of preparation for setting up the next speaker.
- -Please bring your own laptop with your presentation on; it should have an ordinary HDMI output and we will provide the cable to the projector. We do not guarantee that audio will work, even if your laptop supports it - please reach out to us early if this is a requirement.
- -We will provide speakers with a one-day entrance ticket free of charge, but due to our limited funds, you would have to pay for transportation to and from the event yourself. We also encourage speakers to participate for the entire week, but you will have to pay for the full ticket yourself.
- -Lightning talks are 10 minutes of presentation. A laptop will be connected to the projector at the location of the presentations.
- -A lightning talk is an excellent opportunity for inexperienced speakers to share an interesting idea, presentation, or maybe just a small story.
- -You must buy an entrance ticket to host a lightning talk; we are unable to offer free tickets for lightning talks.
- -We have two workshop areas that will be able to host workshops for approximately 20 people per room. Workshops can be up to 3 hours per slot and can be extended to full day workshops.
- -You must buy an entrance ticket to host a workshop; we are unable to offer free tickets for workshops.
- -Please submit content for BornHack 2017 as early as possible. You can submit content via our website:
- -We will review incoming proposals and notify you as early as possible on whether the proposal was accepted or not. Proposals submitted before 1st of July will be notified by us no later than the 16th of July. Late submissions are welcome, but we might be running low on available slots at that time.
- -The BornHack content team can be reached at content@bornhack.dk - for general questions regarding the event please reach out to the info team at info@bornhack.dk
- -We are reachable via IRC in #BornHack on irc.baconsvin.org (6nbtgccn5nbcodn3.onion) on port 6697 with TLS, you can also follow us on Twitter at @bornhax.
- -{% endblock %} diff --git a/src/program/templates/bornhack-2019_call_for_speakers.html b/src/program/templates/bornhack-2019_call_for_speakers.html deleted file mode 100644 index 4a5180a5..00000000 --- a/src/program/templates/bornhack-2019_call_for_speakers.html +++ /dev/null @@ -1 +0,0 @@ -program/templates/bornhack-2019_call_for_speakers.html \ No newline at end of file diff --git a/src/program/templates/bornhack-2018_call_for_speakers.html b/src/program/templates/bornhack-2020_call_for_speakers.html similarity index 100% rename from src/program/templates/bornhack-2018_call_for_speakers.html rename to src/program/templates/bornhack-2020_call_for_speakers.html diff --git a/src/program/templates/call_for_participation.html b/src/program/templates/call_for_participation.html new file mode 100644 index 00000000..be660949 --- /dev/null +++ b/src/program/templates/call_for_participation.html @@ -0,0 +1,22 @@ +{% extends 'program_base.html' %} +{% load commonmark %} + +{% block title %} +Call for Participation | {{ block.super }} +{% endblock %} + +{% block program_content %} + +{% if not camp.call_for_participation_open %} +This CFP has not been written yet.
+{% else %} +{{ camp.call_for_participation|trustedcommonmark }} +{% endif %} + +{% endblock %} diff --git a/src/program/templates/combined_proposal_select_person.html b/src/program/templates/combined_proposal_select_person.html new file mode 100644 index 00000000..a57fd5dd --- /dev/null +++ b/src/program/templates/combined_proposal_select_person.html @@ -0,0 +1,33 @@ +{% extends 'program_base.html' %} + +{% block title %} +Use Existing {{ eventtype.host_title }} or Add New? | {{ block.super }} +{% endblock %} + +{% block program_content %} + +Pick a {{ eventtype.host_title }} from the list below, or press the button at the bottom to add a new {{ eventtype.host_title }} for this {{ eventtype.name }}.
+ +Really add {{ speakerproposal.name }} as {{ eventproposal.event_type.host_title }} for {{ eventproposal.title }}? +
+{% endblock program_content %} + diff --git a/src/program/templates/event_proposal_remove_person.html b/src/program/templates/event_proposal_remove_person.html new file mode 100644 index 00000000..dd1ebcae --- /dev/null +++ b/src/program/templates/event_proposal_remove_person.html @@ -0,0 +1,15 @@ +{% extends 'program_base.html' %} +{% load bootstrap3 %} + +{% block program_content %} +Really remove this {{ eventproposal.event_type.host_title }} from this event?
+ + + +{% endblock program_content %} + diff --git a/src/program/templates/event_proposal_select_person.html b/src/program/templates/event_proposal_select_person.html new file mode 100644 index 00000000..3c77bde4 --- /dev/null +++ b/src/program/templates/event_proposal_select_person.html @@ -0,0 +1,33 @@ +{% extends 'program_base.html' %} + +{% block title %} +Add {{ eventproposal.event_type.host_title }} to {{ eventproposal.title }} | {{ block.super }} +{% endblock %} + +{% block program_content %} + +You are adding a new {{ eventproposal.event_type.host_title }} to {{ eventproposal.title }}. Either pick an existing {{ eventproposal.event_type.host_title }} from the list below, or press the button to create a new {{ eventproposal.event_type.host_title }}.
+ +- Back to List + Back to List + {% if camp.call_for_participation_open and not camp.read_only %} + Delete + {% endif %}
{% endblock program_content %} diff --git a/src/program/templates/eventproposal_form.html b/src/program/templates/eventproposal_form.html index 26a36643..c806a7ba 100644 --- a/src/program/templates/eventproposal_form.html +++ b/src/program/templates/eventproposal_form.html @@ -2,12 +2,15 @@ {% load bootstrap3 %} {% block program_content %} -Title | +Type | +URLs | +People | +Track | +Status | + {% if request.resolver_match.app_name == "program" %} +Available Actions | + {% endif %} +
---|---|---|---|---|---|---|
{{ eventproposal.title }} | +{{ eventproposal.event_type }} | +{% for url in eventproposal.urls.all %} + {% empty %}N/A{% endfor %} | ++ {% for person in eventproposal.speakers.all %} + {% if request.resolver_match.app_name == "program" %} + + {% else %} + + {% endif %} + {% endfor %} + | +{{ eventproposal.track.name }} | +{{ eventproposal.proposal_status }} | + {% if request.resolver_match.app_name == "program" %} ++ + Details + + | + {% endif %} +
You are submitting a new proposal for {{ speaker.name }}. Please begin by selecting the type of proposal below:
+ {% else %} +To submit content for {{ camp.title }} please begin by selecting the type of event below:
+ {% endif %} +{{ eventtype.description }}
{% endif %} + + {% endfor %} +If you have questions or experience problems submitting proposals here please let us know on IRC or by mail. You can also send an email with your proposal and the Content team will take care of creating it in the system.
+Type | +URL | + {% if not camp.read_only and request.resolver_match.app_name == "program" and eventproposal.user == request.user %} +Available Actions | + {% endif %} +
---|---|---|
{{ url.urltype.name }} | +{{ url }} | ++ {% if not camp.read_only and request.resolver_match.app_name == "program" and eventproposal.user == request.user %} + Update + Delete + {% endif %} + | +
Name | +Events | +URLs | +Status | + {% if request.resolver_match.app_name == "program" %} +Available Actions | + {% endif %} +
---|---|---|---|---|
{{ speakerproposal.name }} | ++ {% if speakerproposal.eventproposals.all %} + {% for ep in speakerproposal.eventproposals.all %} + + {% endfor %} + {% else %} + N/A + {% endif %} + | ++ {% for url in speakerproposal.urls.all %} + + {% empty %} + N/A + {% endfor %} + | +{{ speakerproposal.proposal_status }} | + {% if request.resolver_match.app_name == "program" %} ++ + Details + + {% if camp.call_for_participation_open and not camp.read_only and eventproposal and eventproposal.speakers.count > 1 %} + Remove {{ eventproposal.event_type.host_title }} + {% endif %} + | + {% endif %} +
Type | +URLs | + {% if camp.call_for_participation_open and not camp.read_only and request.resolver_match.app_name == "program" %} +Available Actions | + {% endif %} +
---|---|---|
{{ url.urltype.name }} | +{{ url }} | + {% if camp.call_for_participation_open and not camp.read_only and request.resolver_match.app_name == "program" %} ++ Update + Delete + | + {% endif %} +
diff --git a/src/program/templates/proposal_delete.html b/src/program/templates/proposal_delete.html new file mode 100644 index 00000000..89842635 --- /dev/null +++ b/src/program/templates/proposal_delete.html @@ -0,0 +1,19 @@ +{% extends 'program_base.html' %} +{% load bootstrap3 %} + +{% block program_content %} +{% if object.name %} +
Really delete this proposal? This action cannot be undone.
+ + + +{% endblock program_content %} + diff --git a/src/program/templates/proposal_list.html b/src/program/templates/proposal_list.html index 35221261..13245d8f 100644 --- a/src/program/templates/proposal_list.html +++ b/src/program/templates/proposal_list.html @@ -5,98 +5,39 @@ Proposals | {{ block.super }} {% endblock %} {% block program_content %} -To submit a talk or other event for {{ camp.title }} you need to to the following:
-If you experience problems submitting proposals here please let us know on IRC or by mail. You can also send an email with your proposal and the Content team will take care of creating it in the system.
- -Name | -Status | -Actions | -
---|---|---|
{{ speakerproposal.name }} | -{{ speakerproposal.proposal_status }} | -- Details - {% if not camp.read_only %} - Modify - {% if speakerproposal.proposal_status == "pending" or speakerproposal.proposal_status == "approved" %} - Submit - {% else %} - Submit - {% endif %} - Delete - {% endif %} - | -
-
-
Title | -Type | -Status | -Actions | -
---|---|---|---|
{{ eventproposal.title }} | -{{ eventproposal.event_type }} | -{{ eventproposal.proposal_status }} | -- Details - {% if not camp.read_only %} - Modify - {% if eventproposal.proposal_status == "pending" %} - Submit - {% else %} - Submit - {% endif %} - Delete - {% endif %} - | -
{{ url.urltype }}: {{ url.url }}
+ {% endfor %} {% else %} -{{ speaker.biography|commonmark }} +No URLs found.
{% endif %}No speakers found for {{ camp.title }}
{% endif %} - - {% endblock program_content %} diff --git a/src/program/templates/speakerproposal_delete.html b/src/program/templates/speakerproposal_delete.html new file mode 100644 index 00000000..de47ab2d --- /dev/null +++ b/src/program/templates/speakerproposal_delete.html @@ -0,0 +1,15 @@ +{% extends 'program_base.html' %} +{% load bootstrap3 %} + +{% block program_content %} +Really delete this proposal? This action cannot be undone.
+ + + +{% endblock program_content %} + diff --git a/src/program/templates/speakerproposal_detail.html b/src/program/templates/speakerproposal_detail.html index cf1ed7fb..451909e4 100644 --- a/src/program/templates/speakerproposal_detail.html +++ b/src/program/templates/speakerproposal_detail.html @@ -1,37 +1,24 @@ {% extends 'program_base.html' %} -{% load commonmark %} {% block program_content %} -- Back to List + Back to List + {% if camp.call_for_participation_open and not camp.read_only %} + {% if not speakerproposal.eventproposals.all %} + Delete Person + {% endif %} + {% endif %}
{% endblock program_content %} diff --git a/src/program/templates/speakerproposal_form.html b/src/program/templates/speakerproposal_form.html index 87723e7a..7da0bd55 100644 --- a/src/program/templates/speakerproposal_form.html +++ b/src/program/templates/speakerproposal_form.html @@ -2,11 +2,19 @@ {% load bootstrap3 %} {% block program_content %} -Really delete this URL?
+{{ url.urltype }}: {{ url.url }}
+This action cannot be undone.
+ + +{% endblock program_content %} + diff --git a/src/program/templates/url_form.html b/src/program/templates/url_form.html new file mode 100644 index 00000000..343a77b7 --- /dev/null +++ b/src/program/templates/url_form.html @@ -0,0 +1,21 @@ +{% extends 'program_base.html' %} +{% load bootstrap3 %} + +{% block program_content %} + ++ {{ object.description|untrustedcommonmark }} +
++On this page participants of {{ camp.title }} can communicate about ridesharing to and from the festival. +
+ + + + Create ride + + + ++ When + | + Location + | + Seats + | + | + +{% for ride in ride_list %} +
---|---|---|---|
+ {{ ride.when|date:"c" }} + | + {{ ride.location }} + | + {{ ride.seats }} + | + + Details + + +{% empty %} + + |
+ No rideshares yet! + +{% endfor %} + |
Remember:
diff --git a/src/shop/templates/creditnote_list.html b/src/shop/templates/creditnote_list.html
index b93497a7..5e9ec593 100644
--- a/src/shop/templates/creditnote_list.html
+++ b/src/shop/templates/creditnote_list.html
@@ -1,6 +1,7 @@
{% extends 'shop_base.html' %}
{% load bootstrap3 %}
{% load shop_tags %}
+{% load bornhack %}
{% block shop_content %}
Credit Notes
diff --git a/src/shop/templates/order_detail.html b/src/shop/templates/order_detail.html
index d242f7a4..94450ef1 100644
--- a/src/shop/templates/order_detail.html
+++ b/src/shop/templates/order_detail.html
@@ -68,9 +68,17 @@
{% if not order.open == None %}
Comment:
-
-Payment should be made by bank transfer to our account in Arbejdernes Landsbank reg. 5371 account no. 0244504 within two weeks from {{ invoice.created|date:"b jS, Y" }} please. Thank you! + Payment should be made by bank transfer to our account in {{ bank }}:
+{{ invoice.order.invoice_address|linebreaks }}
+{% else %}
diff --git a/src/shop/templatetags/shop_tags.py b/src/shop/templatetags/shop_tags.py
index 530445f5..ea4a083b 100644
--- a/src/shop/templatetags/shop_tags.py
+++ b/src/shop/templatetags/shop_tags.py
@@ -1,10 +1,8 @@
from django import template
-from django.utils.safestring import mark_safe
from decimal import Decimal
register = template.Library()
-
@register.filter
def currency(value):
try:
@@ -12,11 +10,3 @@ def currency(value):
except ValueError:
return False
-
-@register.filter()
-def truefalseicon(value):
- if value:
- return mark_safe("")
- else:
- return mark_safe("")
-
diff --git a/src/shop/tests.py b/src/shop/tests.py
index 7ce503c2..f28ec463 100644
--- a/src/shop/tests.py
+++ b/src/shop/tests.py
@@ -1,3 +1,75 @@
from django.test import TestCase
+from django.utils import timezone
-# Create your tests here.
+from psycopg2.extras import DateTimeTZRange
+
+from .factories import (
+ ProductFactory,
+ OrderProductRelationFactory,
+)
+
+
+class ProductAvailabilityTest(TestCase):
+ """ Test logic about availability of products. """
+
+ def test_product_available_by_stock(self):
+ """ If no orders have been made, the product is still available. """
+ product = ProductFactory(stock_amount=10)
+ self.assertEqual(product.left_in_stock, 10)
+ self.assertTrue(product.is_available())
+
+ def test_product_not_available_by_stock(self):
+ """ If max orders have been made, the product is NOT available. """
+ product = ProductFactory(stock_amount=2)
+
+ for i in range(2):
+ opr = OrderProductRelationFactory(product=product)
+ order = opr.order
+ order.paid = True
+ order.save()
+
+ self.assertEqual(product.left_in_stock, 0)
+ self.assertFalse(product.is_stock_available)
+ self.assertFalse(product.is_available())
+
+ def test_product_available_by_time(self):
+ """ The product is available if now is in the right timeframe. """
+ product = ProductFactory()
+ # The factory defines the timeframe as now and 31 days forward.
+ self.assertTrue(product.is_time_available)
+ self.assertTrue(product.is_available())
+
+ def test_product_not_available_by_time(self):
+ """ The product is not available if now is outside the timeframe. """
+ available_in = DateTimeTZRange(
+ lower=timezone.now() - timezone.timedelta(5),
+ upper=timezone.now() - timezone.timedelta(1)
+ )
+ product = ProductFactory(available_in=available_in)
+ # The factory defines the timeframe as now and 31 days forward.
+ self.assertFalse(product.is_time_available)
+ self.assertFalse(product.is_available())
+
+ def test_product_is_not_available_yet(self):
+ """ The product is not available because we are before lower bound. """
+ available_in = DateTimeTZRange(
+ lower=timezone.now() + timezone.timedelta(5)
+ )
+ product = ProductFactory(available_in=available_in)
+ # Make sure there is no upper - just in case.
+ self.assertEqual(product.available_in.upper, None)
+ # The factory defines the timeframe as now and 31 days forward.
+ self.assertFalse(product.is_time_available)
+ self.assertFalse(product.is_available())
+
+ def test_product_is_available_from_now_on(self):
+ """ The product is available because we are after lower bound. """
+ available_in = DateTimeTZRange(
+ lower=timezone.now() - timezone.timedelta(1)
+ )
+ product = ProductFactory(available_in=available_in)
+ # Make sure there is no upper - just in case.
+ self.assertEqual(product.available_in.upper, None)
+ # The factory defines the timeframe as now and 31 days forward.
+ self.assertTrue(product.is_time_available)
+ self.assertTrue(product.is_available())
diff --git a/src/shop/urls.py b/src/shop/urls.py
index df8f82b9..061b411c 100644
--- a/src/shop/urls.py
+++ b/src/shop/urls.py
@@ -1,29 +1,31 @@
-from django.conf.urls import url
+from django.urls import path, include
from .views import *
-from tickets.views import ShopTicketListView
+
+app_name = 'shop'
urlpatterns = [
- url(r'^$', ShopIndexView.as_view(), name='index'),
+ path('', ShopIndexView.as_view(), name='index'),
- url(r'products/(?P
- Note! This Call for Sponsors is no longer relevant. It is kept here for historic purposes.
-
-{% endif %}
-
-Becoming a BornHack 2016 Sponsor-We are looking for sponsors to help us make BornHack an unforgettable -event and allow it to grow. If you would like to sponsor us do not hesitate -to contact us at sponsors@bornhack.dk. -If you work for a company that you believe might be able and willing to -sponsor BornHack please direct the right people to this page. - -You can read more about the event and possible areas of your -sponsorship below. - -The Concept-The idea and basic concept of BornHack comes from participation -in similar camps in Germany and the Netherlands. These events -have a huge traction (several thousand participants) and we therefore -think the time is right for another one. - -The Organisers-BornHack is a technology festival put together by a group of -people from Denmark and Sweden employed in the local IT industry. -The organiser group have a burning desire to set up a forum where -people with different interests in IT and technology can come together -to share ideas and socialize. Several of the co-organisers have -previously been (or are still) involved in organising conferences such as -Open Source Days. - -Location and Format-The first BornHack will be inviting 350 paying guests for a full -week, the ambition is to grow the number of attendees over the coming -years. It will take place at Jarlsgaard -on Bornholm, Denmark, where we have a gigabit fiber connection -to the outside world. Hopefully the weather will be with us as well. - -Sponsorship-A sponsorship can be in the range of 5000 DKK and up. You get -to have a logo of your choice placed on our website in the sponsors -section, as well as mentions in written material such as programs -where sponsors will be disclosed. - -Sponsors often prefer to sponsor a certain area or event at the -camp, where we will figure out an appropriate display in cooperation -with you. Suggested sponsorships include: - -
If you have other ideas you would be interested -in sponsoring, reach out to us on -sponsors@bornhack.dk -and we can talk about it. -{% endblock %} diff --git a/src/sponsors/templates/bornhack-2017_call_for_sponsors.html b/src/sponsors/templates/bornhack-2017_call_for_sponsors.html deleted file mode 100644 index 9b0c194f..00000000 --- a/src/sponsors/templates/bornhack-2017_call_for_sponsors.html +++ /dev/null @@ -1,72 +0,0 @@ -{% extends 'base.html' %} -{% load static from staticfiles %} - -{% block title %} -Call for Sponsors | {{ block.super }} -{% endblock %} - -{% block content %} - -{% if not camp.call_for_sponsors_open %} -
- Note! This Call for Sponsors is no longer relevant. It is kept here for historic purposes.
-
-{% endif %}
-
-Becoming a {{ camp.title }} Sponsor-We are looking for sponsors to help us make the second BornHack as unforgettable -as the first one. If you would like to sponsor us do not hesitate to contact us at -sponsors@bornhack.dk. If you work for an -organisation or company that you believe might be able and willing to -sponsor {{ camp.title }} please direct the right people to this page. - -The Concept-BornHack is an outdoor tent camping festival with a focus on technology -and society, and how the two interact. The idea and basic concept of BornHack -comes from participation in similar camps in Germany and the Netherlands. These -events have huge traction (thousands of participants, sells out fast) and has -inspired us to make BornHack. - -The Organisers-BornHack is put together by a group of people from Denmark and Sweden employed -primarily in the IT industry. The organiser group share a desire to set up a forum -where people with different interests in IT and technology can come together to -share ideas and socialise. Several of the organisers have previously been (or are -still) involved in organising conferences such as -Open Source Days. - -Location and Format-For {{ camp.title }} we will be inviting up to 500 paying guests for a full -week, the ambition is to grow the number of attendees over the coming -years. It will take place at Jarlsgaard -on Bornholm, Denmark, where we have a great venue with a fiber connection to the -outside world. - -Sponsorship-A sponsorship can be in the range of 5000 DKK and up. You get -to have a logo of your choice placed on our website in the sponsors -section, and we can also display tasteful signs or banners in or -around our speakers tent. - -Sponsors often prefer to sponsor a certain area or event at the -camp, where we will figure out an appropriate display in cooperation -with you. Suggested sponsorships include: - -
If you have other ideas you would be interested in sponsoring, reach out to us on -sponsors@bornhack.dk -and we can talk about it. Cash sponsorships are also very welcome. -{% endblock %} - diff --git a/src/sponsors/templates/bornhack-2018_call_for_sponsors.html b/src/sponsors/templates/bornhack-2018_call_for_sponsors.html deleted file mode 100644 index f420e9d0..00000000 --- a/src/sponsors/templates/bornhack-2018_call_for_sponsors.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends 'base.html' %} -{% load static from staticfiles %} - -{% block title %} -Call for Sponsors | {{ block.super }} -{% endblock %} - -{% block content %} - -{% if not camp.call_for_sponsors_open %} -
- Note! This Call for Sponsors is no longer relevant. It is kept here for historic purposes.
-
-{% endif %}
-
-Becoming a {{ camp.title }} Sponsor-We are looking for sponsors to help us make the second BornHack as unforgettable -as the first one. If you would like to sponsor us do not hesitate to contact us at -sponsors@bornhack.dk. If you work for an -organisation or company that you believe might be able and willing to -sponsor {{ camp.title }} please direct the right people to this page. - -The Concept-BornHack is an outdoor tent camping festival with a focus on technology -and society, and how the two interact. The idea and basic concept of BornHack -comes from participation in similar camps in Germany and the Netherlands. These -events have huge traction (thousands of participants, sells out fast) and has -inspired us to make BornHack. - -The Organisers-BornHack is put together by a group of people from Denmark and Sweden employed -primarily in the IT industry. The organiser group share a desire to set up a forum -where people with different interests in IT and technology can come together to -share ideas and socialise. Several of the organisers have previously been (or are -still) involved in organising conferences such as -Open Source Days. - -Location and Format-For {{ camp.title }} we will be inviting up to 500 paying guests for a full -week, the ambition is to grow the number of attendees over the coming -years. It will take place at Jarlsgaard -on Bornholm, Denmark, where we have a great venue with a fiber connection to the -outside world. - -Sponsorship-A sponsorship can be in the range of 5000 DKK and up. You get -to have a logo of your choice placed on our website in the sponsors -section, and we can also display tasteful signs or banners in or -around our speakers tent. - -Sponsors often prefer to sponsor a certain area or event at the -camp, where we will figure out an appropriate display in cooperation -with you. Suggested sponsorships include: - -
If you have other ideas you would be interested in sponsoring, reach out to us on -sponsors@bornhack.dk -and we can talk about it. Cash sponsorships are also very welcome. -{% endblock %} diff --git a/src/sponsors/templates/sponsors.html b/src/sponsors/templates/sponsors.html index 21fd8739..fb506c53 100644 --- a/src/sponsors/templates/sponsors.html +++ b/src/sponsors/templates/sponsors.html @@ -1,5 +1,6 @@ {% extends 'base.html' %} {% load static from staticfiles %} +{% load commonmark %} {% block title %} Sponsors | {{ block.super }} @@ -8,6 +9,8 @@ Sponsors | {{ block.super }} {% block content %} +{% if sponsors %} +{{ view.camp.title }} Sponsors@@ -17,6 +20,7 @@ Sponsors | {{ block.super }} you, we are immensely grateful! +{% endif %} {% for sponsor in sponsors %} @@ -35,7 +39,7 @@ Sponsors | {{ block.super }} {% if sponsor.url %} {% endif %} -
+ Note! This Call for Sponsors is not open.
+
+{% endif %}
+
+{% if not camp.call_for_sponsors %}
+This CFS has not been written yet. +{% else %} +{{ camp.call_for_sponsors|trustedcommonmark }} +{% endif %} + {% endblock %} diff --git a/src/sponsors/views.py b/src/sponsors/views.py index 3c1a3644..87ecd49e 100644 --- a/src/sponsors/views.py +++ b/src/sponsors/views.py @@ -18,7 +18,3 @@ class SponsorsView(CampViewMixin, ListView): 'name', ) - -class CallForSponsorsView(CampViewMixin, TemplateView): - def get_template_names(self): - return '%s_call_for_sponsors.html' % self.camp.slug diff --git a/src/static_src/css/bornhack.css b/src/static_src/css/bornhack.css index 08dcb11c..e64e764f 100644 --- a/src/static_src/css/bornhack.css +++ b/src/static_src/css/bornhack.css @@ -1,6 +1,6 @@ body { margin-top: 85px; - margin-bottom: 35px; + margin-bottom: 105px; overflow: scroll; } @@ -12,6 +12,20 @@ a, a:active, a:focus { outline: none; } +/* Z-index */ +/* Bootstrap values +.dropdown-backdrop { z-index: 990; } +.navbar-static-top, .dropdown-menu { z-index: 1000; } +.navbar-fixed-top, .navbar-fixed-bottom { z-index: 1030; } +.modal-backdrop { z-index: 1040; } +.modal { z-index: 1050; } +.popover { z-index: 1060; } +.tooltip { z-index: 1070; } + */ +.sticky { + z-index: 980; +} + @media (max-width: 520px) { #main { width: 100%; @@ -48,7 +62,7 @@ a, a:active, a:focus { margin-top: 6px; } -.nav li a { +#top-navbar > .nav li a { padding: 30px 7px; } @@ -236,7 +250,6 @@ footer { .sticky { position: sticky; background-color: #fff; - z-index: 9999; } #daypicker { diff --git a/src/static_src/css/font-awesome.min.css b/src/static_src/css/font-awesome.min.css deleted file mode 100644 index 540440ce..00000000 --- a/src/static_src/css/font-awesome.min.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/src/static_src/css/fontawesome-all.min.css b/src/static_src/css/fontawesome-all.min.css new file mode 100644 index 00000000..8539c00d --- /dev/null +++ b/src/static_src/css/fontawesome-all.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +.fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-aws:before{content:"\f375"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blind:before{content:"\f29d"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-book:before{content:"\f02d"}.fa-book-open:before{content:"\f518"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-certificate:before{content:"\f0a3"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-deviantart:before{content:"\f1bd"}.fa-diagnoses:before{content:"\f470"}.fa-dice:before{content:"\f522"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-divide:before{content:"\f529"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-excel:before{content:"\f1c3"}.fa-file-image:before{content:"\f1c5"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-fulcrum:before{content:"\f50b"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-martini:before{content:"\f000"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hashtag:before{content:"\f292"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-meh:before{content:"\f11a"}.fa-memory:before{content:"\f538"}.fa-mercury:before{content:"\f223"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-moon:before{content:"\f186"}.fa-motorcycle:before{content:"\f21c"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-old-republic:before{content:"\f510"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-people-carry:before{content:"\f4ce"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-plane:before{content:"\f072"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poo:before{content:"\f2fe"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-r-project:before{content:"\f4f7"}.fa-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-rendact:before{content:"\f3e4"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-ribbon:before{content:"\f4d6"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-rupee-sign:before{content:"\f156"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-search:before{content:"\f002"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shower:before{content:"\f2cc"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skull:before{content:"\f54c"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spinner:before{content:"\f110"}.fa-spotify:before{content:"\f1bc"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-star:before{content:"\f005"}.fa-star-half:before{content:"\f089"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toolbox:before{content:"\f552"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-moving:before{content:"\f4df"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-glass:before{content:"\f4e3"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:Font Awesome\ 5 Brands;font-style:normal;font-weight:400;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:Font Awesome\ 5 Brands}@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:Font Awesome\ 5 Free}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/src/static_src/fonts/FontAwesome.otf b/src/static_src/fonts/FontAwesome.otf deleted file mode 100644 index 401ec0f3..00000000 Binary files a/src/static_src/fonts/FontAwesome.otf and /dev/null differ diff --git a/src/static_src/fonts/fontawesome-webfont.eot b/src/static_src/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca9..00000000 Binary files a/src/static_src/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/src/static_src/fonts/fontawesome-webfont.svg b/src/static_src/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845e..00000000 --- a/src/static_src/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - diff --git a/src/static_src/fonts/fontawesome-webfont.ttf b/src/static_src/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2f..00000000 Binary files a/src/static_src/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/src/static_src/fonts/fontawesome-webfont.woff b/src/static_src/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a4..00000000 Binary files a/src/static_src/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/src/static_src/fonts/fontawesome-webfont.woff2 b/src/static_src/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc60..00000000 Binary files a/src/static_src/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/src/static_src/images/sort_asc.png b/src/static_src/images/sort_asc.png new file mode 100644 index 00000000..e1ba61a8 Binary files /dev/null and b/src/static_src/images/sort_asc.png differ diff --git a/src/static_src/images/sort_asc_disabled.png b/src/static_src/images/sort_asc_disabled.png new file mode 100644 index 00000000..fb11dfe2 Binary files /dev/null and b/src/static_src/images/sort_asc_disabled.png differ diff --git a/src/static_src/images/sort_both.png b/src/static_src/images/sort_both.png new file mode 100644 index 00000000..af5bc7c5 Binary files /dev/null and b/src/static_src/images/sort_both.png differ diff --git a/src/static_src/images/sort_desc.png b/src/static_src/images/sort_desc.png new file mode 100644 index 00000000..0e156deb Binary files /dev/null and b/src/static_src/images/sort_desc.png differ diff --git a/src/static_src/images/sort_desc_disabled.png b/src/static_src/images/sort_desc_disabled.png new file mode 100644 index 00000000..c9fdd8a1 Binary files /dev/null and b/src/static_src/images/sort_desc_disabled.png differ diff --git a/src/static_src/img/bornhack-2018/logo/bornhack-2018-logo-l.png b/src/static_src/img/bornhack-2018/logo/bornhack-2018-logo-l.png index 4e24b972..3e2e4b7e 100644 Binary files a/src/static_src/img/bornhack-2018/logo/bornhack-2018-logo-l.png and b/src/static_src/img/bornhack-2018/logo/bornhack-2018-logo-l.png differ diff --git a/src/static_src/img/bornhack-2018/logo/bornhack-2018-logo-s.png b/src/static_src/img/bornhack-2018/logo/bornhack-2018-logo-s.png index e14e0fae..192be842 100644 Binary files a/src/static_src/img/bornhack-2018/logo/bornhack-2018-logo-s.png and b/src/static_src/img/bornhack-2018/logo/bornhack-2018-logo-s.png differ diff --git a/src/static_src/img/bornhack-2019/logo/bornhack-2019-logo-l.png b/src/static_src/img/bornhack-2019/logo/bornhack-2019-logo-l.png index 5be0fe94..dc210614 100644 Binary files a/src/static_src/img/bornhack-2019/logo/bornhack-2019-logo-l.png and b/src/static_src/img/bornhack-2019/logo/bornhack-2019-logo-l.png differ diff --git a/src/static_src/img/bornhack-2020/logo/bornhack-2020-logo-l.png b/src/static_src/img/bornhack-2020/logo/bornhack-2020-logo-l.png new file mode 100644 index 00000000..fbd91ac7 Binary files /dev/null and b/src/static_src/img/bornhack-2020/logo/bornhack-2020-logo-l.png differ diff --git a/src/static_src/img/bornhack-2020/logo/bornhack-2020-logo-s.png b/src/static_src/img/bornhack-2020/logo/bornhack-2020-logo-s.png new file mode 100644 index 00000000..e14e0fae Binary files /dev/null and b/src/static_src/img/bornhack-2020/logo/bornhack-2020-logo-s.png differ diff --git a/src/static_src/img/feed-icon-14x14.png b/src/static_src/img/feed-icon-14x14.png new file mode 100755 index 00000000..b3c949d2 Binary files /dev/null and b/src/static_src/img/feed-icon-14x14.png differ diff --git a/src/static_src/img/na.jpg b/src/static_src/img/na.jpg new file mode 100644 index 00000000..95597ba6 Binary files /dev/null and b/src/static_src/img/na.jpg differ diff --git a/src/static_src/img/pdf.png b/src/static_src/img/pdf.png new file mode 100644 index 00000000..0744dd07 Binary files /dev/null and b/src/static_src/img/pdf.png differ diff --git a/src/static_src/img/sponsors/DM_Logo_RGB.png b/src/static_src/img/sponsors/DM_Logo_RGB.png new file mode 100644 index 00000000..648699d0 Binary files /dev/null and b/src/static_src/img/sponsors/DM_Logo_RGB.png differ diff --git a/src/static_src/img/sponsors/bornfiber_logo_2018_300x300px.png b/src/static_src/img/sponsors/bornfiber_logo_2018_300x300px.png new file mode 100644 index 00000000..e000d1ff Binary files /dev/null and b/src/static_src/img/sponsors/bornfiber_logo_2018_300x300px.png differ diff --git a/src/static_src/img/sponsors/epson.png b/src/static_src/img/sponsors/epson.png new file mode 100755 index 00000000..39ecf939 Binary files /dev/null and b/src/static_src/img/sponsors/epson.png differ diff --git a/src/static_src/img/sponsors/letsgo.png b/src/static_src/img/sponsors/letsgo.png new file mode 100644 index 00000000..26f82f9d Binary files /dev/null and b/src/static_src/img/sponsors/letsgo.png differ diff --git a/src/static_src/img/sponsors/pcbway.png b/src/static_src/img/sponsors/pcbway.png new file mode 100644 index 00000000..4a942e1b Binary files /dev/null and b/src/static_src/img/sponsors/pcbway.png differ diff --git a/src/static_src/img/sponsors/saxobank.png b/src/static_src/img/sponsors/saxobank.png new file mode 100644 index 00000000..756a9b02 Binary files /dev/null and b/src/static_src/img/sponsors/saxobank.png differ diff --git a/src/static_src/img/sponsors/zibra-wireless-logo.png b/src/static_src/img/sponsors/zibra-wireless-logo.png new file mode 100644 index 00000000..fad51c49 Binary files /dev/null and b/src/static_src/img/sponsors/zibra-wireless-logo.png differ diff --git a/src/static_src/pdf/bornhack-2018_letterhead.odt b/src/static_src/pdf/bornhack-2018_letterhead.odt new file mode 100644 index 00000000..b34df314 Binary files /dev/null and b/src/static_src/pdf/bornhack-2018_letterhead.odt differ diff --git a/src/static_src/pdf/bornhack-2018_letterhead.pdf b/src/static_src/pdf/bornhack-2018_letterhead.pdf new file mode 100644 index 00000000..8436470b Binary files /dev/null and b/src/static_src/pdf/bornhack-2018_letterhead.pdf differ diff --git a/src/static_src/pdf/bornhack-2018_test_letterhead.odt b/src/static_src/pdf/bornhack-2018_test_letterhead.odt new file mode 100644 index 00000000..12484034 Binary files /dev/null and b/src/static_src/pdf/bornhack-2018_test_letterhead.odt differ diff --git a/src/static_src/pdf/bornhack-2018_test_letterhead.pdf b/src/static_src/pdf/bornhack-2018_test_letterhead.pdf new file mode 100644 index 00000000..683afb1b Binary files /dev/null and b/src/static_src/pdf/bornhack-2018_test_letterhead.pdf differ diff --git a/src/static_src/webfonts/fa-brands-400.eot b/src/static_src/webfonts/fa-brands-400.eot new file mode 100644 index 00000000..f8e48185 Binary files /dev/null and b/src/static_src/webfonts/fa-brands-400.eot differ diff --git a/src/static_src/webfonts/fa-brands-400.svg b/src/static_src/webfonts/fa-brands-400.svg new file mode 100644 index 00000000..68eb65a1 --- /dev/null +++ b/src/static_src/webfonts/fa-brands-400.svg @@ -0,0 +1,1127 @@ + + + diff --git a/src/static_src/webfonts/fa-brands-400.ttf b/src/static_src/webfonts/fa-brands-400.ttf new file mode 100644 index 00000000..2b00dae7 Binary files /dev/null and b/src/static_src/webfonts/fa-brands-400.ttf differ diff --git a/src/static_src/webfonts/fa-brands-400.woff b/src/static_src/webfonts/fa-brands-400.woff new file mode 100644 index 00000000..9e4b7e1c Binary files /dev/null and b/src/static_src/webfonts/fa-brands-400.woff differ diff --git a/src/static_src/webfonts/fa-brands-400.woff2 b/src/static_src/webfonts/fa-brands-400.woff2 new file mode 100644 index 00000000..b9e58c5e Binary files /dev/null and b/src/static_src/webfonts/fa-brands-400.woff2 differ diff --git a/src/static_src/webfonts/fa-regular-400.eot b/src/static_src/webfonts/fa-regular-400.eot new file mode 100644 index 00000000..5217c95f Binary files /dev/null and b/src/static_src/webfonts/fa-regular-400.eot differ diff --git a/src/static_src/webfonts/fa-regular-400.svg b/src/static_src/webfonts/fa-regular-400.svg new file mode 100644 index 00000000..5f495431 --- /dev/null +++ b/src/static_src/webfonts/fa-regular-400.svg @@ -0,0 +1,467 @@ + + + diff --git a/src/static_src/webfonts/fa-regular-400.ttf b/src/static_src/webfonts/fa-regular-400.ttf new file mode 100644 index 00000000..cefbd50f Binary files /dev/null and b/src/static_src/webfonts/fa-regular-400.ttf differ diff --git a/src/static_src/webfonts/fa-regular-400.woff b/src/static_src/webfonts/fa-regular-400.woff new file mode 100644 index 00000000..954b0593 Binary files /dev/null and b/src/static_src/webfonts/fa-regular-400.woff differ diff --git a/src/static_src/webfonts/fa-regular-400.woff2 b/src/static_src/webfonts/fa-regular-400.woff2 new file mode 100644 index 00000000..bd35950e Binary files /dev/null and b/src/static_src/webfonts/fa-regular-400.woff2 differ diff --git a/src/static_src/webfonts/fa-solid-900.eot b/src/static_src/webfonts/fa-solid-900.eot new file mode 100644 index 00000000..cc691d63 Binary files /dev/null and b/src/static_src/webfonts/fa-solid-900.eot differ diff --git a/src/static_src/webfonts/fa-solid-900.svg b/src/static_src/webfonts/fa-solid-900.svg new file mode 100644 index 00000000..1534b64b --- /dev/null +++ b/src/static_src/webfonts/fa-solid-900.svg @@ -0,0 +1,2231 @@ + + + diff --git a/src/static_src/webfonts/fa-solid-900.ttf b/src/static_src/webfonts/fa-solid-900.ttf new file mode 100644 index 00000000..618136ab Binary files /dev/null and b/src/static_src/webfonts/fa-solid-900.ttf differ diff --git a/src/static_src/webfonts/fa-solid-900.woff b/src/static_src/webfonts/fa-solid-900.woff new file mode 100644 index 00000000..af476578 Binary files /dev/null and b/src/static_src/webfonts/fa-solid-900.woff differ diff --git a/src/static_src/webfonts/fa-solid-900.woff2 b/src/static_src/webfonts/fa-solid-900.woff2 new file mode 100644 index 00000000..9ef566a9 Binary files /dev/null and b/src/static_src/webfonts/fa-solid-900.woff2 differ diff --git a/src/teams/admin.py b/src/teams/admin.py index 38e03f0d..9fb3b571 100644 --- a/src/teams/admin.py +++ b/src/teams/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin -from .models import Team, TeamArea, TeamMember, TeamTask +from .models import Team, TeamMember, TeamTask, TeamShift from .email import add_added_membership_email, add_removed_membership_email +from camps.utils import CampPropertyListFilter @admin.register(TeamTask) @@ -20,19 +21,29 @@ class TeamMemberInline(admin.TabularInline): @admin.register(Team) class TeamAdmin(admin.ModelAdmin): def get_responsible(self, obj): - return ", ".join([resp.get_full_name() for resp in obj.responsible]) + return ", ".join([resp.profile.public_credit_name for resp in obj.responsible_members.all()]) get_responsible.short_description = 'Responsible' list_display = [ 'name', - 'area', + 'camp', 'get_responsible', 'needs_members', + 'public_irc_channel_name', + 'public_irc_channel_bot', + 'public_irc_channel_managed', + 'private_irc_channel_name', + 'private_irc_channel_bot', + 'private_irc_channel_managed', ] list_filter = [ - 'camp', + CampPropertyListFilter, 'needs_members', + 'public_irc_channel_bot', + 'public_irc_channel_managed', + 'private_irc_channel_bot', + 'private_irc_channel_managed', ] inlines = [TeamMemberInline] @@ -40,6 +51,7 @@ class TeamAdmin(admin.ModelAdmin): @admin.register(TeamMember) class TeamMemberAdmin(admin.ModelAdmin): list_filter = [ + CampPropertyListFilter, 'team', 'approved', ] @@ -84,8 +96,9 @@ class TeamMemberAdmin(admin.ModelAdmin): remove_member.description = 'Remove a user from the team.' -@admin.register(TeamArea) -class TeamAreaAdmin(admin.ModelAdmin): + +@admin.register(TeamShift) +class TeamShiftAdmin(admin.ModelAdmin): list_filter = [ - 'camp' + 'team', ] diff --git a/src/teams/apps.py b/src/teams/apps.py index 17954d66..3dd023c4 100644 --- a/src/teams/apps.py +++ b/src/teams/apps.py @@ -1,5 +1,13 @@ from django.apps import AppConfig +from django.db.models.signals import post_save, post_delete +from .signal_handlers import teammember_saved, teammember_deleted class TeamsConfig(AppConfig): name = 'teams' + + def ready(self): + # connect the post_save signal, always including a dispatch_uid to prevent it being called multiple times in corner cases + post_save.connect(teammember_saved, sender='teams.TeamMember', dispatch_uid='teammember_save_signal') + post_delete.connect(teammember_deleted, sender='teams.TeamMember', dispatch_uid='teammember_save_signal') + diff --git a/src/teams/email.py b/src/teams/email.py index 62d997db..944f0edb 100644 --- a/src/teams/email.py +++ b/src/teams/email.py @@ -49,10 +49,11 @@ def add_new_membership_email(membership): return add_outgoing_email( text_template='emails/new_membership_email.txt', html_template='emails/new_membership_email.html', - to_recipients=[resp.email for resp in membership.team.responsible], + to_recipients=[resp.email for resp in membership.team.responsible_members.all()], formatdict=formatdict, subject='New membership request for {} at {}'.format( membership.team.name, membership.team.camp.title ) ) + diff --git a/src/teams/forms.py b/src/teams/forms.py deleted file mode 100644 index dab9796c..00000000 --- a/src/teams/forms.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.forms import ModelForm -from .models import Team - - -class ManageTeamForm(ModelForm): - class Meta: - model = Team - fields = ['description', 'needs_members'] diff --git a/src/teams/migrations/0019_auto_20180304_1019.py b/src/teams/migrations/0019_auto_20180304_1019.py new file mode 100644 index 00000000..019b3764 --- /dev/null +++ b/src/teams/migrations/0019_auto_20180304_1019.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-04 09:19 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0018_auto_20171122_2204'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='team', + unique_together=set([]), + ), + migrations.RemoveField( + model_name='team', + name='camp', + ), + ] diff --git a/src/teams/migrations/0020_auto_20180304_1233.py b/src/teams/migrations/0020_auto_20180304_1233.py new file mode 100644 index 00000000..4cfe13a0 --- /dev/null +++ b/src/teams/migrations/0020_auto_20180304_1233.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-04 11:33 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0019_auto_20180304_1019'), + ] + + operations = [ + migrations.AlterField( + model_name='teamarea', + name='camp', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='teamareas', to='camps.Camp'), + ), + ] diff --git a/src/teams/migrations/0021_auto_20180318_0906.py b/src/teams/migrations/0021_auto_20180318_0906.py new file mode 100644 index 00000000..e03f8214 --- /dev/null +++ b/src/teams/migrations/0021_auto_20180318_0906.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-18 08:06 +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 = [ + ('teams', '0020_auto_20180304_1233'), + ] + + operations = [ + migrations.AlterField( + model_name='teammember', + name='team', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='teams.Team'), + ), + migrations.AlterField( + model_name='teammember', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='teamtask', + name='team', + field=models.ForeignKey(help_text='The team this task belongs to', on_delete=django.db.models.deletion.PROTECT, related_name='tasks', to='teams.Team'), + ), + ] diff --git a/src/teams/migrations/0022_auto_20180318_1135.py b/src/teams/migrations/0022_auto_20180318_1135.py new file mode 100644 index 00000000..d2707376 --- /dev/null +++ b/src/teams/migrations/0022_auto_20180318_1135.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-18 10:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0021_auto_20180318_0906'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='irc_channel', + field=models.BooleanField(default=False, help_text='Check to make the IRC bot join the team IRC channel. Leave unchecked to disable IRC bot functionality for this team entirely.'), + ), + migrations.AddField( + model_name='team', + name='irc_channel_managed', + field=models.BooleanField(default=True, help_text='Check to make the bot manage the team IRC channel. The bot will register the channel with ChanServ if possible, and manage ACLs as needed.'), + ), + migrations.AddField( + model_name='team', + name='irc_channel_name', + field=models.TextField(blank=True, default='', help_text='Team IRC channel. Leave blank to generate channel name automatically, based on camp slug and team slug.'), + ), + migrations.AddField( + model_name='team', + name='irc_channel_private', + field=models.BooleanField(default=True, help_text='Check to make the IRC channel private for team members only, also sets +s. Leave unchecked to make the IRC channel public and open for everyone.'), + ), + ] diff --git a/src/teams/migrations/0023_auto_20180318_1256.py b/src/teams/migrations/0023_auto_20180318_1256.py new file mode 100644 index 00000000..3a8983cd --- /dev/null +++ b/src/teams/migrations/0023_auto_20180318_1256.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-18 11:56 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0022_auto_20180318_1135'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='shortslug', + field=models.SlugField(blank=True, help_text='Abbreviated version of the slug. Used in places like IRC channel names where space is limited'), + ), + migrations.AlterField( + model_name='team', + name='name', + field=models.CharField(help_text='The team name', max_length=255), + ), + migrations.AlterField( + model_name='team', + name='slug', + field=models.SlugField(blank=True, help_text='Url slug for this team. Leave blank to generate based on team name', max_length=255), + ), + ] diff --git a/src/teams/migrations/0024_populate_shortslugs.py b/src/teams/migrations/0024_populate_shortslugs.py new file mode 100644 index 00000000..4babd51e --- /dev/null +++ b/src/teams/migrations/0024_populate_shortslugs.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-18 11:45 +from __future__ import unicode_literals + +from django.db import migrations + +def populate_team_shortslugs(apps, schema_editor): + Team = apps.get_model('teams', 'Team') + for team in Team.objects.all(): + if not team.shortslug: + team.shortslug = team.slug + team.save() + +class Migration(migrations.Migration): + dependencies = [ + ('teams', '0023_auto_20180318_1256'), + ] + + operations = [ + migrations.RunPython(populate_team_shortslugs), + ] + diff --git a/src/teams/migrations/0025_auto_20180318_1318.py b/src/teams/migrations/0025_auto_20180318_1318.py new file mode 100644 index 00000000..2ad4a528 --- /dev/null +++ b/src/teams/migrations/0025_auto_20180318_1318.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-18 12:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0024_populate_shortslugs'), + ] + + operations = [ + migrations.AlterField( + model_name='team', + name='shortslug', + field=models.SlugField(help_text='Abbreviated version of the slug. Used in places like IRC channel names where space is limited'), + ), + ] diff --git a/src/teams/migrations/0026_team_camp.py b/src/teams/migrations/0026_team_camp.py new file mode 100644 index 00000000..577bdd71 --- /dev/null +++ b/src/teams/migrations/0026_team_camp.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-25 13:45 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0025_auto_20180318_1250'), + ('teams', '0025_auto_20180318_1318'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='camp', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='teams', to='camps.Camp'), + ), + ] diff --git a/src/teams/migrations/0027_fixup_teams.py b/src/teams/migrations/0027_fixup_teams.py new file mode 100644 index 00000000..879090e0 --- /dev/null +++ b/src/teams/migrations/0027_fixup_teams.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-25 13:45 +from __future__ import unicode_literals + +from django.db import migrations + +def add_team_camp(apps, schema_editor): + Team = apps.get_model('teams', 'Team') + TeamArea = apps.get_model('teams', 'TeamArea') + TeamMember = apps.get_model('teams', 'TeamMember') + + for team in Team.objects.all(): + print("camp processing team %s..." % team.name) + team.camp = team.area.camp + team.save() + print("set camp %s for team %s" % (team.camp.slug, team.name)) + +def add_missing_team_responsibles(apps, schema_editor): + Team = apps.get_model('teams', 'Team') + TeamArea = apps.get_model('teams', 'TeamArea') + TeamMember = apps.get_model('teams', 'TeamMember') + + for team in Team.objects.all(): + print("responsible processing team %s..." % team.name) + responsibles = TeamMember.objects.filter(team=team, responsible=True) + if not responsibles: + # get the area responsibles instead + responsibles = team.area.responsible.all() + for responsible in responsibles: + if isinstance(responsible, TeamMember): + # we need User objects instead of TeamMember objects + responsible = responsible.user + try: + membership = TeamMember.objects.get(team=team, user=responsible) + if not membership.responsible: + # already a member of the team, but not responsible + membership.responsible=True + membership.save() + print("%s is now marked as responsible" % membership.user.username) + except TeamMember.DoesNotExist: + # add the responsible as a member of the team + membership = TeamMember.objects.create( + team=team, + user=responsible, + responsible=True, + approved=True + ) + print("new membership has been created for team %s" % team.name) + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0026_team_camp'), + ] + + operations = [ + migrations.RunPython(add_team_camp), + migrations.RunPython(add_missing_team_responsibles), + ] + diff --git a/src/teams/migrations/0028_auto_20180331_1416.py b/src/teams/migrations/0028_auto_20180331_1416.py new file mode 100644 index 00000000..919fe0cf --- /dev/null +++ b/src/teams/migrations/0028_auto_20180331_1416.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-31 12:16 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0027_fixup_teams'), + ] + + operations = [ + migrations.AlterField( + model_name='team', + name='area', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='teams', to='teams.TeamArea'), + ), + ] diff --git a/src/teams/migrations/0029_remove_team_area.py b/src/teams/migrations/0029_remove_team_area.py new file mode 100644 index 00000000..1d86f49e --- /dev/null +++ b/src/teams/migrations/0029_remove_team_area.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-03-31 12:31 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0028_auto_20180331_1416'), + ] + + operations = [ + migrations.RemoveField( + model_name='team', + name='area', + ), + ] diff --git a/src/teams/migrations/0030_auto_20180402_1514.py b/src/teams/migrations/0030_auto_20180402_1514.py new file mode 100644 index 00000000..b7abdabd --- /dev/null +++ b/src/teams/migrations/0030_auto_20180402_1514.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-02 13:14 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0029_remove_team_area'), + ] + + operations = [ + migrations.AlterField( + model_name='team', + name='irc_channel_name', + field=models.TextField(blank=True, default='', help_text='Team IRC channel. Leave blank to generate channel name automatically, based on camp shortslug and team shortslug.'), + ), + migrations.AlterField( + model_name='team', + name='irc_channel_private', + field=models.BooleanField(default=True, help_text='Check to make the IRC channel secret and +i (private for team members only using an ACL). Leave unchecked to make the IRC channel public and open for everyone.'), + ), + migrations.AlterField( + model_name='team', + name='needs_members', + field=models.BooleanField(default=True, help_text='Check to indicate that this team needs more members'), + ), + ] diff --git a/src/teams/migrations/0031_auto_20180402_2146.py b/src/teams/migrations/0031_auto_20180402_2146.py new file mode 100644 index 00000000..22cef6cb --- /dev/null +++ b/src/teams/migrations/0031_auto_20180402_2146.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-02 19:46 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0030_auto_20180402_1514'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='mailing_list_archive_public', + field=models.BooleanField(default=False, help_text='Check if the mailing list archive is public'), + ), + migrations.AddField( + model_name='team', + name='mailing_list_nonmember_posts', + field=models.BooleanField(default=False, help_text='Check if the mailinglist allows non-list-members to post'), + ), + ] diff --git a/src/teams/migrations/0032_auto_20180402_2148.py b/src/teams/migrations/0032_auto_20180402_2148.py new file mode 100644 index 00000000..421e6683 --- /dev/null +++ b/src/teams/migrations/0032_auto_20180402_2148.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-02 19:48 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0031_auto_20180402_2146'), + ] + + operations = [ + migrations.AlterField( + model_name='team', + name='camp', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='teams', to='camps.Camp'), + ), + ] diff --git a/src/teams/migrations/0033_auto_20180402_2204.py b/src/teams/migrations/0033_auto_20180402_2204.py new file mode 100644 index 00000000..338cc17c --- /dev/null +++ b/src/teams/migrations/0033_auto_20180402_2204.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-02 20:04 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0032_auto_20180402_2148'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='teamarea', + unique_together=set([]), + ), + migrations.RemoveField( + model_name='teamarea', + name='camp', + ), + migrations.RemoveField( + model_name='teamarea', + name='responsible', + ), + migrations.DeleteModel( + name='TeamArea', + ), + ] diff --git a/src/teams/migrations/0034_auto_20180402_2334.py b/src/teams/migrations/0034_auto_20180402_2334.py new file mode 100644 index 00000000..eb4a7718 --- /dev/null +++ b/src/teams/migrations/0034_auto_20180402_2334.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-02 21:34 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0033_auto_20180402_2204'), + ] + + operations = [ + migrations.AlterModelOptions( + name='teammember', + options={'ordering': ['-responsible', 'approved']}, + ), + ] diff --git a/src/teams/migrations/0035_auto_20180402_2344.py b/src/teams/migrations/0035_auto_20180402_2344.py new file mode 100644 index 00000000..78ea2a64 --- /dev/null +++ b/src/teams/migrations/0035_auto_20180402_2344.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-02 21:44 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0034_auto_20180402_2334'), + ] + + operations = [ + migrations.AlterModelOptions( + name='teammember', + options={'ordering': ['-responsible', '-approved']}, + ), + ] diff --git a/src/teams/migrations/0036_auto_20180403_0201.py b/src/teams/migrations/0036_auto_20180403_0201.py new file mode 100644 index 00000000..ad6bede4 --- /dev/null +++ b/src/teams/migrations/0036_auto_20180403_0201.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-03 00:01 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('camps', '0025_auto_20180318_1250'), + ('teams', '0035_auto_20180402_2344'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='team', + unique_together=set([('slug', 'camp'), ('name', 'camp')]), + ), + ] diff --git a/src/teams/migrations/0037_auto_20180408_1416.py b/src/teams/migrations/0037_auto_20180408_1416.py new file mode 100644 index 00000000..6f7f30c9 --- /dev/null +++ b/src/teams/migrations/0037_auto_20180408_1416.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-08 12:16 +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 = [ + ('teams', '0036_auto_20180403_0201'), + ] + + operations = [ + migrations.AddField( + model_name='teammember', + name='irc_channel_acl_ok', + field=models.BooleanField(default=False, help_text='Maintained by the IRC bot, do not edit manually. True if the teammembers NickServ username has been added to the Team IRC channels ACL.'), + ), + migrations.AlterField( + model_name='teammember', + name='approved', + field=models.BooleanField(default=False, help_text='True if this membership is approved. False if not.'), + ), + migrations.AlterField( + model_name='teammember', + name='responsible', + field=models.BooleanField(default=False, help_text='True if this teammember is responsible for this Team. False if not.'), + ), + migrations.AlterField( + model_name='teammember', + name='team', + field=models.ForeignKey(help_text='The Team this membership relates to', on_delete=django.db.models.deletion.PROTECT, to='teams.Team'), + ), + migrations.AlterField( + model_name='teammember', + name='user', + field=models.ForeignKey(help_text='The User object this team membership relates to', on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/src/teams/migrations/0038_auto_20180412_1844.py b/src/teams/migrations/0038_auto_20180412_1844.py new file mode 100644 index 00000000..31a4c9ea --- /dev/null +++ b/src/teams/migrations/0038_auto_20180412_1844.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-12 16:44 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0037_auto_20180408_1416'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='private_irc_channel_bot', + field=models.BooleanField(default=False, help_text='Check to make the bot join the teams private IRC channel. Leave unchecked to disable the IRC bot for this channel.'), + ), + migrations.AddField( + model_name='team', + name='private_irc_channel_managed', + field=models.BooleanField(default=False, help_text='Check to make the bot manage the private IRC channel by registering it with NickServ, setting +I and maintaining the ACL.'), + ), + migrations.AddField( + model_name='team', + name='private_irc_channel_name', + field=models.CharField(blank=True, help_text='The private IRC channel for this team. Will be shown to team members on the team page. Leave empty if the team has no private IRC channel.', max_length=50, null=True, unique=True), + ), + migrations.AddField( + model_name='team', + name='public_irc_channel_bot', + field=models.BooleanField(default=False, help_text='Check to make the bot join the teams public IRC channel. Leave unchecked to disable the IRC bot for this channel.'), + ), + migrations.AddField( + model_name='team', + name='public_irc_channel_managed', + field=models.BooleanField(default=False, help_text='Check to make the bot manage the teams public IRC channel by registering it with NickServ.'), + ), + migrations.AddField( + model_name='team', + name='public_irc_channel_name', + field=models.CharField(blank=True, help_text='The public IRC channel for this team. Will be shown on the team page so people know how to reach the team. Leave empty if the team has no public IRC channel.', max_length=50, null=True, unique=True), + ), + ] diff --git a/src/teams/migrations/0039_fix_irc_channels.py b/src/teams/migrations/0039_fix_irc_channels.py new file mode 100644 index 00000000..cff67d54 --- /dev/null +++ b/src/teams/migrations/0039_fix_irc_channels.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-12 15:46 +from __future__ import unicode_literals + +from django.db import migrations + +def fix_irc_channels(apps, schema_editor): + Team = apps.get_model('teams', 'Team') + for team in Team.objects.filter(irc_channel=True): + print("fixing irc channel for team %s" % team.name) + if team.irc_channel_private: + team.private_irc_channel_name=team.irc_channel_name + if team.irc_channel_managed: + team.private_irc_channel_managed=True + team.private_irc_channel_bot=True + else: + team.public_irc_channel_name=team.irc_channel_name + if team.irc_channel_managed: + team.public_irc_channel_managed=True + team.public_irc_channel_bot=True + team.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0038_auto_20180412_1844'), + ] + + operations = [ + migrations.RunPython(fix_irc_channels), + ] + diff --git a/src/teams/migrations/0040_auto_20180412_2109.py b/src/teams/migrations/0040_auto_20180412_2109.py new file mode 100644 index 00000000..dc0db141 --- /dev/null +++ b/src/teams/migrations/0040_auto_20180412_2109.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-12 19:09 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0039_fix_irc_channels'), + ] + + operations = [ + migrations.RemoveField( + model_name='team', + name='irc_channel', + ), + migrations.RemoveField( + model_name='team', + name='irc_channel_managed', + ), + migrations.RemoveField( + model_name='team', + name='irc_channel_name', + ), + migrations.RemoveField( + model_name='team', + name='irc_channel_private', + ), + ] diff --git a/src/teams/migrations/0041_auto_20180412_2231.py b/src/teams/migrations/0041_auto_20180412_2231.py new file mode 100644 index 00000000..933f4868 --- /dev/null +++ b/src/teams/migrations/0041_auto_20180412_2231.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-12 20:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0040_auto_20180412_2109'), + ] + + operations = [ + migrations.RemoveField( + model_name='teammember', + name='irc_channel_acl_ok', + ), + migrations.AddField( + model_name='teammember', + name='irc_acl_fix_needed', + field=models.BooleanField(default=False, help_text='Maintained by the IRC bot, manual editing should not be needed. Will be set to true when a teammember sets or changes NickServ username, and back to false after the ACL has been fixed by the bot.'), + ), + ] diff --git a/src/teams/migrations/0042_auto_20180413_1933.py b/src/teams/migrations/0042_auto_20180413_1933.py new file mode 100644 index 00000000..279fda50 --- /dev/null +++ b/src/teams/migrations/0042_auto_20180413_1933.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2018-04-13 17:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0041_auto_20180412_2231'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='private_irc_channel_fix_needed', + field=models.BooleanField(default=False, help_text='Used to indicate to the IRC bot that this teams private IRC channel is in need of a permissions and ACL fix.'), + ), + migrations.AddField( + model_name='team', + name='public_irc_channel_fix_needed', + field=models.BooleanField(default=False, help_text='Used to indicate to the IRC bot that this teams public IRC channel is in need of a permissions and ACL fix.'), + ), + migrations.AlterField( + model_name='team', + name='public_irc_channel_managed', + field=models.BooleanField(default=False, help_text='Check to make the bot manage the teams public IRC channel by registering it with NickServ and setting +Oo for all teammembers.'), + ), + ] diff --git a/src/teams/migrations/0043_auto_20180702_1338.py b/src/teams/migrations/0043_auto_20180702_1338.py new file mode 100644 index 00000000..ad64e0dc --- /dev/null +++ b/src/teams/migrations/0043_auto_20180702_1338.py @@ -0,0 +1,43 @@ +# Generated by Django 2.0.4 on 2018-07-02 18:38 + +import django.contrib.postgres.fields.ranges +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0042_auto_20180413_1933'), + ] + + operations = [ + migrations.CreateModel( + name='TeamShift', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('shift_range', django.contrib.postgres.fields.ranges.DateTimeRangeField()), + ('people_required', models.IntegerField(default=1)), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='team', + name='shifts_enabled', + field=models.BooleanField(default=False, help_text='Does this team have shifts? This enables defining shifts for this team.'), + ), + migrations.AddField( + model_name='teamshift', + name='team', + field=models.ForeignKey(help_text='The team this shift belongs to', on_delete=django.db.models.deletion.PROTECT, related_name='shifts', to='teams.Team'), + ), + migrations.AddField( + model_name='teamshift', + name='team_members', + field=models.ManyToManyField(to='teams.TeamMember'), + ), + ] diff --git a/src/teams/migrations/0043_auto_20180804_1641.py b/src/teams/migrations/0043_auto_20180804_1641.py new file mode 100644 index 00000000..e2460dc1 --- /dev/null +++ b/src/teams/migrations/0043_auto_20180804_1641.py @@ -0,0 +1,24 @@ +# Generated by Django 2.0.4 on 2018-08-04 14:41 + +import django.contrib.postgres.fields.ranges +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0042_auto_20180413_1933'), + ] + + operations = [ + migrations.AddField( + model_name='teamtask', + name='completed', + field=models.BooleanField(default=False, help_text='Check to mark this task as completed.'), + ), + migrations.AddField( + model_name='teamtask', + name='when', + field=django.contrib.postgres.fields.ranges.DateTimeRangeField(blank=True, help_text='When does this task need to be started and/or finished?', null=True), + ), + ] diff --git a/src/teams/migrations/0044_auto_20180702_1507.py b/src/teams/migrations/0044_auto_20180702_1507.py new file mode 100644 index 00000000..cb19b4ac --- /dev/null +++ b/src/teams/migrations/0044_auto_20180702_1507.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.4 on 2018-07-02 20:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0043_auto_20180702_1338'), + ] + + operations = [ + migrations.AlterField( + model_name='teamshift', + name='team_members', + field=models.ManyToManyField(blank=True, to='teams.TeamMember'), + ), + ] diff --git a/src/teams/migrations/0045_merge_20180805_1131.py b/src/teams/migrations/0045_merge_20180805_1131.py new file mode 100644 index 00000000..5eb121ee --- /dev/null +++ b/src/teams/migrations/0045_merge_20180805_1131.py @@ -0,0 +1,14 @@ +# Generated by Django 2.0.4 on 2018-08-05 09:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0044_auto_20180702_1507'), + ('teams', '0043_auto_20180804_1641'), + ] + + operations = [ + ] diff --git a/src/teams/migrations/0046_auto_20180808_2154.py b/src/teams/migrations/0046_auto_20180808_2154.py new file mode 100644 index 00000000..513ca22d --- /dev/null +++ b/src/teams/migrations/0046_auto_20180808_2154.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0.4 on 2018-08-08 19:54 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0045_merge_20180805_1131'), + ] + + operations = [ + migrations.AlterModelOptions( + name='teamshift', + options={'ordering': ('shift_range',)}, + ), + ] diff --git a/src/teams/migrations/0047_taskcomment.py b/src/teams/migrations/0047_taskcomment.py new file mode 100644 index 00000000..3dc0eada --- /dev/null +++ b/src/teams/migrations/0047_taskcomment.py @@ -0,0 +1,29 @@ +# Generated by Django 2.1 on 2018-08-14 17:42 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0046_auto_20180808_2154'), + ] + + operations = [ + migrations.CreateModel( + name='TaskComment', + fields=[ + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('content', models.TextField()), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='teams.TeamMember')), + ('task', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='comments', to='teams.TeamTask')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/src/teams/migrations/0048_auto_20180814_1950.py b/src/teams/migrations/0048_auto_20180814_1950.py new file mode 100644 index 00000000..c59f9f8c --- /dev/null +++ b/src/teams/migrations/0048_auto_20180814_1950.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1 on 2018-08-14 17:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0047_taskcomment'), + ] + + operations = [ + migrations.RenameField( + model_name='taskcomment', + old_name='content', + new_name='comment', + ), + ] diff --git a/src/teams/migrations/0049_auto_20180815_1119.py b/src/teams/migrations/0049_auto_20180815_1119.py new file mode 100644 index 00000000..07325294 --- /dev/null +++ b/src/teams/migrations/0049_auto_20180815_1119.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1 on 2018-08-15 09:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0048_auto_20180814_1950'), + ] + + operations = [ + migrations.AlterModelOptions( + name='teamtask', + options={'ordering': ['completed', 'when', 'name']}, + ), + ] diff --git a/src/teams/models.py b/src/teams/models.py index ed708fd3..99b6451e 100644 --- a/src/teams/models.py +++ b/src/teams/models.py @@ -1,13 +1,16 @@ -from django.db import models -from django.db.models.signals import post_save -from django.dispatch import receiver -from django.utils.text import slugify -from utils.models import CampRelatedModel -from .email import add_new_membership_email -from django.core.exceptions import ValidationError -from django.contrib.auth.models import User -from django.core.urlresolvers import reverse_lazy import logging + +from django.db import models +from django.utils.text import slugify +from django.core.exceptions import ValidationError +from django.contrib.postgres.fields import DateTimeRangeField +from django.contrib.auth.models import User +from django.urls import reverse_lazy +from django.conf import settings +from django.contrib.postgres.fields import DateTimeRangeField + +from utils.models import CampRelatedModel, CreatedUpdatedModel, UUIDModel + logger = logging.getLogger("bornhack.%s" % __name__) @@ -35,44 +38,105 @@ TEAM_GUIDE_TEMPLATE=""" """ -class TeamArea(CampRelatedModel): - class Meta: - ordering = ['name'] - unique_together = ('name', 'camp') - - name = models.CharField(max_length=255) - description = models.TextField(default='') - camp = models.ForeignKey('camps.Camp') - responsible = models.ManyToManyField( - 'auth.User', - related_name='responsible_team_areas' - ) - - def __str__(self): - return '{} ({})'.format(self.name, self.camp) - - class Team(CampRelatedModel): - class Meta: - ordering = ['name'] - unique_together = (('name', 'camp'), ('slug', 'camp')) - - name = models.CharField(max_length=255) - slug = models.SlugField(max_length=255, blank=True) - camp = models.ForeignKey('camps.Camp', related_name="teams") - area = models.ForeignKey( - 'teams.TeamArea', - related_name='teams', - on_delete=models.PROTECT + camp = models.ForeignKey( + 'camps.Camp', + related_name="teams", + on_delete=models.PROTECT, ) + + name = models.CharField( + max_length=255, + help_text='The team name', + ) + + slug = models.SlugField( + max_length=255, + blank=True, + help_text='Url slug for this team. Leave blank to generate based on team name', + ) + + shortslug = models.SlugField( + help_text='Abbreviated version of the slug. Used in places like IRC channel names where space is limited', + ) + description = models.TextField() - needs_members = models.BooleanField(default=True) + + needs_members = models.BooleanField( + default=True, + help_text='Check to indicate that this team needs more members', + ) + members = models.ManyToManyField( 'auth.User', related_name='teams', through='teams.TeamMember' ) - mailing_list = models.EmailField(blank=True) + + # mailing list related fields + mailing_list = models.EmailField( + blank=True + ) + + mailing_list_archive_public = models.BooleanField( + default=False, + help_text='Check if the mailing list archive is public' + ) + + mailing_list_nonmember_posts = models.BooleanField( + default=False, + help_text='Check if the mailinglist allows non-list-members to post' + ) + + # IRC related fields + public_irc_channel_name = models.CharField( + blank=True, + null=True, + unique=True, + max_length=50, + help_text='The public IRC channel for this team. Will be shown on the team page so people know how to reach the team. Leave empty if the team has no public IRC channel.' + ) + public_irc_channel_bot = models.BooleanField( + default=False, + help_text='Check to make the bot join the teams public IRC channel. Leave unchecked to disable the IRC bot for this channel.' + ) + public_irc_channel_managed = models.BooleanField( + default=False, + help_text='Check to make the bot manage the teams public IRC channel by registering it with NickServ and setting +Oo for all teammembers.' + ) + public_irc_channel_fix_needed = models.BooleanField( + default=False, + help_text='Used to indicate to the IRC bot that this teams public IRC channel is in need of a permissions and ACL fix.' + ) + + private_irc_channel_name = models.CharField( + blank=True, + null=True, + unique=True, + max_length=50, + help_text='The private IRC channel for this team. Will be shown to team members on the team page. Leave empty if the team has no private IRC channel.' + ) + private_irc_channel_bot = models.BooleanField( + default=False, + help_text='Check to make the bot join the teams private IRC channel. Leave unchecked to disable the IRC bot for this channel.' + ) + private_irc_channel_managed = models.BooleanField( + default=False, + help_text='Check to make the bot manage the private IRC channel by registering it with NickServ, setting +I and maintaining the ACL.' + ) + private_irc_channel_fix_needed = models.BooleanField( + default=False, + help_text='Used to indicate to the IRC bot that this teams private IRC channel is in need of a permissions and ACL fix.' + ) + + shifts_enabled = models.BooleanField( + default=False, + help_text="Does this team have shifts? This enables defining shifts for this team." + ) + + class Meta: + ordering = ['name'] + unique_together = (('name', 'camp'), ('slug', 'camp')) guide = models.TextField( blank=True, @@ -84,75 +148,164 @@ class Team(CampRelatedModel): def __str__(self): return '{} ({})'.format(self.name, self.camp) + def get_absolute_url(self): + return reverse_lazy('teams:general', kwargs={'camp_slug': self.camp.slug, 'team_slug': self.slug}) + def save(self, **kwargs): - if ( - not self.pk or - not self.slug - ): + # generate slug if needed + if not self.pk or not self.slug: slug = slugify(self.name) self.slug = slug + # set shortslug if needed + if not self.shortslug: + self.shortslug = self.slug + super().save(**kwargs) def clean(self): - if self.camp != self.area.camp: - raise ValidationError({'camp': 'camp is different from area.camp'}) + # make sure the public irc channel name is prefixed with a # if it is set + if self.public_irc_channel_name and self.public_irc_channel_name[0] != "#": + self.public_irc_channel_name = "#%s" % self.public_irc_channel_name - def memberstatus(self, member): - if member not in self.members.all(): - return "Not member" - else: - if TeamMember.objects.get(team=self, user=member).approved: - return "Member" - else: - return "Membership Pending" + # make sure the private irc channel name is prefixed with a # if it is set + if self.private_irc_channel_name and self.private_irc_channel_name[0] != "#": + self.private_irc_channel_name = "#%s" % self.private_irc_channel_name + + # make sure the channel names are not reserved + if self.public_irc_channel_name == settings.IRCBOT_PUBLIC_CHANNEL or self.public_irc_channel_name == settings.IRCBOT_VOLUNTEER_CHANNEL: + raise ValidationError('The public IRC channel name is reserved') + if self.private_irc_channel_name == settings.IRCBOT_PUBLIC_CHANNEL or self.private_irc_channel_name == settings.IRCBOT_VOLUNTEER_CHANNEL: + raise ValidationError('The private IRC channel name is reserved') + + # make sure public_irc_channel_name is not in use as public or private irc channel for another team, case insensitive + if self.public_irc_channel_name: + if Team.objects.filter(private_irc_channel_name__iexact=self.public_irc_channel_name).exclude(pk=self.pk).exists() or Team.objects.filter(public_irc_channel_name__iexact=self.public_irc_channel_name).exclude(pk=self.pk).exists(): + raise ValidationError('The public IRC channel name is already in use on another team!') + + # make sure private_irc_channel_name is not in use as public or private irc channel for another team, case insensitive + if self.private_irc_channel_name: + if Team.objects.filter(private_irc_channel_name__iexact=self.private_irc_channel_name).exclude(pk=self.pk).exists() or Team.objects.filter(public_irc_channel_name__iexact=self.private_irc_channel_name).exclude(pk=self.pk).exists(): + raise ValidationError('The private IRC channel name is already in use on another team!') @property - def responsible(self): - if TeamMember.objects.filter(team=self, responsible=True).exists(): - return User.objects.filter( - teammember__team=self, - teammember__responsible=True - ) - else: - return self.area.responsible.all() + def memberships(self): + """ + Returns all TeamMember objects for this team. + Use self.members.all() to get User objects for all members, + or use self.memberships.all() to get TeamMember objects for all members. + """ + return TeamMember.objects.filter( + team=self + ) @property - def anoncount(self): - return self.approvedmembers.filter(user__profile__public_credit_name_approved=False).count() + def approved_members(self): + """ + Returns only approved members (returns User objects, not TeamMember objects) + """ + return self.members.filter( + teammember__approved=True + ) @property - def approvedmembers(self): - return TeamMember.objects.filter(team=self, approved=True) + def unapproved_members(self): + """ + Returns only unapproved members (returns User objects, not TeamMember objects) + """ + return self.members.filter( + teammember__approved=False + ) + + @property + def responsible_members(self): + """ + Return only approved and responsible members + Used to handle permissions for team management + """ + return self.members.filter( + teammember__approved=True, + teammember__responsible=True + ) + + @property + def regular_members(self): + """ + Return only approved and not responsible members with + an approved public_credit_name. + Used on the people pages. + """ + return self.members.filter( + teammember__approved=True, + teammember__responsible=False, + ) + + @property + def unnamed_members(self): + """ + Returns only approved and not responsible members, + without an approved public_credit_name. + """ + return self.members.filter( + teammember__approved=True, + teammember__responsible=False, + profile__public_credit_name_approved=False + ) class TeamMember(CampRelatedModel): - user = models.ForeignKey('auth.User') - team = models.ForeignKey('teams.Team') - approved = models.BooleanField(default=False) - responsible = models.BooleanField(default=False) + + user = models.ForeignKey( + 'auth.User', + on_delete=models.PROTECT, + help_text="The User object this team membership relates to", + ) + + team = models.ForeignKey( + 'teams.Team', + on_delete=models.PROTECT, + help_text="The Team this membership relates to" + ) + + approved = models.BooleanField( + default=False, + help_text="True if this membership is approved. False if not." + ) + + responsible = models.BooleanField( + default=False, + help_text="True if this teammember is responsible for this Team. False if not." + ) + + irc_acl_fix_needed = models.BooleanField( + default=False, + help_text='Maintained by the IRC bot, manual editing should not be needed. Will be set to true when a teammember sets or changes NickServ username, and back to false after the ACL has been fixed by the bot.', + ) + + class Meta: + ordering = ['-responsible', '-approved'] def __str__(self): - return '{} is {} member of team {}'.format( - self.user, '' if self.approved else 'an unapproved', self.team + return '{} is {} {} member of team {}'.format( + self.user, + '' if self.approved else 'an unapproved', + '' if not self.responsible else 'a responsible', + self.team ) @property def camp(self): + """ All CampRelatedModels must have a camp FK or a camp property """ return self.team.camp - -@receiver(post_save, sender=TeamMember) -def add_responsible_email(sender, instance, created, **kwargs): - if created: - if not add_new_membership_email(instance): - logger.error('Error adding email to outgoing queue') + camp_filter = 'team__camp' class TeamTask(CampRelatedModel): team = models.ForeignKey( 'teams.Team', related_name='tasks', + on_delete=models.PROTECT, help_text='The team this task belongs to', ) name = models.CharField( @@ -167,9 +320,18 @@ class TeamTask(CampRelatedModel): description = models.TextField( help_text='Description of the task. Markdown is supported.' ) + when = DateTimeRangeField( + blank=True, + null=True, + help_text='When does this task need to be started and/or finished?' + ) + completed = models.BooleanField( + help_text='Check to mark this task as completed.', + default=False + ) class Meta: - ordering = ['name'] + ordering = ['completed', 'when', 'name'] unique_together = (('name', 'team'), ('slug', 'team')) def get_absolute_url(self): @@ -177,14 +339,61 @@ class TeamTask(CampRelatedModel): @property def camp(self): + """ All CampRelatedModels must have a camp FK or a camp property """ return self.team.camp + camp_filter = 'team__camp' + def save(self, **kwargs): + # generate slug if needed if not self.slug: self.slug = slugify(self.name) super().save(**kwargs) - @property - def responsible(self): - return team.responsible.all() +class TaskComment(UUIDModel, CreatedUpdatedModel): + task = models.ForeignKey('teams.TeamTask', on_delete=models.PROTECT, related_name="comments") + author = models.ForeignKey('teams.TeamMember', on_delete=models.PROTECT) + comment = models.TextField() + + +class TeamShift(CampRelatedModel): + + class Meta: + ordering = ("shift_range",) + + team = models.ForeignKey( + 'teams.Team', + related_name='shifts', + on_delete=models.PROTECT, + help_text='The team this shift belongs to', + ) + + shift_range = DateTimeRangeField() + + team_members = models.ManyToManyField( + TeamMember, + blank=True, + ) + + people_required = models.IntegerField( + default=1 + ) + + @property + def camp(self): + """ All CampRelatedModels must have a camp FK or a camp property """ + return self.team.camp + + camp_filter = 'team__camp' + + def __str__(self): + return "{} team shift from {} to {}".format( + self.team.name, + self.shift_range.lower, + self.shift_range.upper + ) + + @property + def users(self): + return [member.user for member in self.team_members.all()] diff --git a/src/teams/signal_handlers.py b/src/teams/signal_handlers.py new file mode 100644 index 00000000..1a0fa6a6 --- /dev/null +++ b/src/teams/signal_handlers.py @@ -0,0 +1,30 @@ +from .email import add_new_membership_email +from ircbot.utils import add_irc_message +from django.conf import settings +import logging +logger = logging.getLogger("bornhack.%s" % __name__) + + +def teammember_saved(sender, instance, created, **kwargs): + """ + This signal handler is called whenever a TeamMember instance is saved + """ + # if this is a new unapproved teammember send a mail to team responsibles + if created and not instance.approved: + # call the mail sending function + if not add_new_membership_email(instance): + logger.error('Error adding email to outgoing queue') + + +def teammember_deleted(sender, instance, **kwargs): + """ + This signal handler is called whenever a TeamMember instance is deleted + """ + if instance.team.private_irc_channel_name and instance.team.private_irc_channel_managed: + # TODO: remove user from private channel ACL + pass + + if instance.team.public_irc_channel_name and instance.team.public_irc_channel_managed: + # TODO: remove user from public channel ACL + pass + diff --git a/src/teams/templates/fix_irc_acl.html b/src/teams/templates/fix_irc_acl.html new file mode 100644 index 00000000..8a8887e6 --- /dev/null +++ b/src/teams/templates/fix_irc_acl.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} +{% load commonmark %} + +{% block title %} +Fix IRC permissions for NickServ user {{ request.user.profile.nickserv_username }} for IRC channel {{ team.irc_channel_name }} +{% endblock %} + +{% block content %} + +Fix IRC permissions+This will make the bot re-add IRC ACL for your NickServ user {{ request.user.profile.nickserv_username }} for IRC channel {{ team.irc_channel_name }}. Use this in cases where you are unable to join the team IRC channel after entering your NickServ username in your profile. + +{% endblock %} diff --git a/src/teams/templates/task_detail.html b/src/teams/templates/task_detail.html index 5ac2b2b7..c650ebb3 100644 --- a/src/teams/templates/task_detail.html +++ b/src/teams/templates/task_detail.html @@ -1,15 +1,47 @@ -{% extends 'base.html' %} +{% extends 'team_base.html' %} {% load commonmark %} +{% load bootstrap3 %} {% block title %} {{ task.name }} {% endblock %} -{% block content %} +{% block team_content %}
-
diff --git a/src/teams/templates/task_form.html b/src/teams/templates/task_form.html
index c03e64c6..69d7e603 100644
--- a/src/teams/templates/task_form.html
+++ b/src/teams/templates/task_form.html
@@ -1,4 +1,4 @@
-{% extends 'base.html' %}
+{% extends 'team_base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
@@ -11,7 +11,7 @@ Create Task
for {{ team.name }} Team
{% endblock %}
-{% block content %}
+{% block team_content %}
Task: {{ task.name }}{{ task.description|commonmark }}
-
+ Task: {{ task.name }} ({% if not task.completed %}Not {% endif %}Completed)
+ {{ task.description|untrustedcommonmark }}
+
+ {% if user in task.team.members.all %}
+
+ +
+
+ {% endif %}
+ + @@ -30,7 +30,7 @@ for {{ team.name }} Team
+
+
+{{ team.name }} Team+
+
+
+{% endblock %}
diff --git a/src/teams/templates/team_detail.html b/src/teams/templates/team_detail.html
deleted file mode 100644
index d6e86830..00000000
--- a/src/teams/templates/team_detail.html
+++ /dev/null
@@ -1,102 +0,0 @@
-{% extends 'base.html' %}
-{% load commonmark %}
-{% load teams_tags %}
-{% load bootstrap3 %}
-
-{% block title %}
-Team: {{ team.name }} | {{ block.super }}
-{% endblock %}
-
-{% block content %}
-
-
+
+
+
+
+ + + {% if request.user.is_authenticated %} + + {% if request.user in team.members.all %} + Your membership status: {% membershipstatus user team %} + + {% if request.user in team.responsible_members.all %} + Manage Team + {% endif %} + + {% else %} + {% if team.needs_members %} + This team is looking for members! Join Team + {% endif %} + {% endif %} + + {% endif %} +
+
+ {% block team_content %}{% endblock %}
+
+
+
-
-
-{% endblock %}
diff --git a/src/teams/templates/team_general.html b/src/teams/templates/team_general.html
new file mode 100644
index 00000000..2c5bfc91
--- /dev/null
+++ b/src/teams/templates/team_general.html
@@ -0,0 +1,48 @@
+{% extends 'team_base.html' %}
+{% load commonmark %}
+{% load bootstrap3 %}
+{% load teams_tags %}
+
+
+{% block team_content %}
+
+{{ team.name }} Team
-
-
- {{ team.description|unsafecommonmark }}
-
- {% if request.user|is_team_member:team %}
- Team guide
- {% endif %}
- {% if request.user in team.responsible.all %}
- Manage team
- {% endif %}
-
-
-- - Members-The following {{ team.approvedmembers.count }} people are members of the {{ team.name }} team: -
Your membership status: {% membershipstatus request.user team %} - {% endif %} - - {% if request.user in team.members.all %} - Leave Team - {% else %} - {% if team.needs_members %} - This team is looking for members! Join Team - {% endif %} - {% endif %} - -- - Tasks-This team is responsible for the following tasks -
+
+
+{# Team communications #}
+
+
+ Description+
+ {{ team.description|untrustedcommonmark }}
+
+
+
+{% endblock %}
diff --git a/src/teams/templates/team_info_categories.html b/src/teams/templates/team_info_categories.html
new file mode 100644
index 00000000..15f0900c
--- /dev/null
+++ b/src/teams/templates/team_info_categories.html
@@ -0,0 +1,48 @@
+{% extends 'team_base.html' %}
+{% load commonmark %}
+{% load bootstrap3 %}
+{% load teams_tags %}
+
+
+{% block team_content %}
+
+
+
+ Communication Channels+
+ {{ team.camp.title }} teams primarily use mailing lists and IRC to communicate. The {{ team.name }} team can be contacted in the following ways:
+
+
+Mailing List+ {% if team.mailing_list and request.user in team.approved_members.all %} +The {{ team.name }} Team mailinglist is {{ team.mailing_list }}{% if team.mailing_list_archive_public %}, and the archives are publicly available{% endif %}. You should sign up for the list if you haven't already. + {% elif team.mailing_list and team.mailinglist_nonmember_posts %} +The {{ team.name }} Team mailinglist is {{ team.mailing_list }}{% if team.mailing_list_archive_public %}, and the archives are publicly available{% endif %}. You do not need to be a member of the list to post to it. + {% else %} +The {{ team.name }} Team does not have a public mailing list, but it can be contacted through our main email info@bornhack.dk. + {% endif %} + + IRC Channel+ {% if team.public_irc_channel_name %} +The {{ team.name }} Team public IRC channel is {{ team.public_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}. + {% else %} + The {{ team.name }} Team does not have a public IRC channel, but it can be reached through our main IRC channel {{ IRCBOT_PUBLIC_CHANNEL }} on {{ IRCBOT_SERVER_HOSTNAME }}. + {% endif %} + + {% if request.user in team.approved_members.all and team.private_irc_channel_name %} +The {{ team.name }} Team private IRC channel is {{ team.private_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}. + {% endif %} + +
+
+
+{% endblock %}
diff --git a/src/teams/templates/team_info_item_delete_confirm.html b/src/teams/templates/team_info_item_delete_confirm.html
new file mode 100644
index 00000000..18e6e28b
--- /dev/null
+++ b/src/teams/templates/team_info_item_delete_confirm.html
@@ -0,0 +1,30 @@
+{% extends 'team_base.html' %}
+{% load commonmark %}
+{% load bootstrap3 %}
+
+{% block title %}
+{% if form.instance.id %}
+Edit Info Item: {{ form.instance.headline }}
+{% else %}
+Create Info item
+{% endif %}
+in {{ form.instance.category.headline }}
+{% endblock %}
+
+{% block team_content %}
+
+
+ Info Categories+
+
+ {% for info_category in team.info_categories.all %}
+
+
+{{ info_category.headline }}+
+ + {% endfor %} +
+
+{% endblock %}
diff --git a/src/teams/templates/team_info_item_form.html b/src/teams/templates/team_info_item_form.html
new file mode 100644
index 00000000..e062ae9d
--- /dev/null
+++ b/src/teams/templates/team_info_item_form.html
@@ -0,0 +1,43 @@
+{% extends 'team_base.html' %}
+{% load commonmark %}
+{% load bootstrap3 %}
+
+{% block title %}
+{% if object %}
+Editing "{{ object.headline }}"
+in "{{ form.instance.category.headline }}"
+{% else %}
+Create Info item
+in "{{ category.headline }}"
+{% endif %}
+{% endblock %}
+
+{% block team_content %}
+
+
+ + Delete info item {{ object.name }} + in {{ form.instance.category.headline }} ++
+
+
+
+
+{% endblock %}
diff --git a/src/teams/templates/team_join.html b/src/teams/templates/team_join.html
index ac4e8fd0..f231b7a3 100644
--- a/src/teams/templates/team_join.html
+++ b/src/teams/templates/team_join.html
@@ -1,18 +1,23 @@
-{% extends 'base.html' %}
+{% extends 'team_base.html' %}
{% load commonmark %}
{% block title %}
Join Team: {{ team.name }} | {{ block.super }}
{% endblock %}
-{% block content %}
+{% block team_content %}
-
+
+ + {% if object %} + Editing "{{ object.headline }}" + in "{{ object.category.headline }}" + {% else %} + Create Info Item + in "{{ category.headline }}" + {% endif %} ++
+
+
+
+{{ team.name }} Team-Really join the {{ team.name }} team? You will receive a message when your membership has been approved. + Really join the {{ team.name }} Team for {{ team.camp.title }}? + +Your membership will need to be approved by a team responsible. You will receive an email when your membership request has been processed. + + + + {% endblock %} diff --git a/src/teams/templates/team_leave.html b/src/teams/templates/team_leave.html index 9d904f54..7e345e3d 100644 --- a/src/teams/templates/team_leave.html +++ b/src/teams/templates/team_leave.html @@ -1,18 +1,18 @@ -{% extends 'base.html' %} +{% extends 'team_base.html' %} {% load commonmark %} {% block title %} Leave Team: {{ team.name }} | {{ block.super }} {% endblock %} -{% block content %} +{% block team_content %} Leave {{ team.name }} TeamReally leave the {{ team.name }} team? {% endblock %} diff --git a/src/teams/templates/team_list.html b/src/teams/templates/team_list.html index c72df85d..99733c13 100644 --- a/src/teams/templates/team_list.html +++ b/src/teams/templates/team_list.html @@ -7,13 +7,47 @@ Teams | {{ block.super }} {% endblock %} {% block content %} - {{ camp.title }} Teams-This is a list of the teams for {{ camp.title }}. To join a team just press the Join button, but please put some info in your profile first, so the team responsible has some idea who you are. -You can also leave a team of course, but please let the team responsible know why :) -Team memberships need to be approved by a team responsible. You will receive a message when your membership has been approved. -At {{ camp.title }} all organisers and volunteers buy full tickets like everyone else. In the future our budget may allow for discounts or free tickets for volunteers, but not this year. However: Please let us know if you can't afford a ticket - we will figure something out! -We currently have {{ teams.count }} teams for {{ camp.title }}: + +
+
+
{% if teams %}
+
+
+ {% if user.is_authenticated %}
+ About teams+This is a list of the teams for {{ camp.title }}. To join a team just press the Join button, but please put some info in your profile first, so the team responsible has some idea who you are. +You can also leave a team of course, but please let the team responsible know why :) +Team memberships need to be approved by a team responsible. You will receive a message when your membership has been approved. +At {{ camp.title }} all organisers and volunteers buy full tickets like everyone else. At future events our budget may allow for discounts or free tickets for volunteers, but currently it does not. +We currently have {{ teams.count }} teams for {{ camp.title }}. +
+
+ {% endif %}
+
+
+
+ {% if user_teams %}
+ Your teams+
No members found! -{% endif %} {% endblock %} - diff --git a/src/teams/templates/team_members.html b/src/teams/templates/team_members.html new file mode 100644 index 00000000..3873993d --- /dev/null +++ b/src/teams/templates/team_members.html @@ -0,0 +1,98 @@ +{% extends 'team_base.html' %} +{% load commonmark %} +{% load bootstrap3 %} +{% load teams_tags %} + + +{% block team_content %} + +
+
+
+{% endblock %}
diff --git a/src/teams/templates/team_shift_confirm_delete.html b/src/teams/templates/team_shift_confirm_delete.html
new file mode 100644
index 00000000..16a9ece2
--- /dev/null
+++ b/src/teams/templates/team_shift_confirm_delete.html
@@ -0,0 +1,10 @@
+{% extends 'team_base.html' %}
+
+{% block team_content %}
+
+
+
+ Members+
+
+The following {{ team.approved_members.count }} people {% if team.unapproved_members.count %}(and {{ team.unapproved_members.count }} pending){% endif %} are members of the {{ team.name }} Team: +
Your membership status: {% membershipstatus user team %} + + {% if request.user in team.members.all %} + {% if team.irc_channel and team.irc_channel_managed and request.user.profile.nickserv_username %} + Fix IRC ACL + {% endif %} + Leave Team + {% else %} + {% if team.needs_members %} + This team is looking for members! Join Team + {% endif %} + {% endif %} + {% endif %} + ++ Are you sure you want to delete {{ object }}? + +
+
+{% endblock %}
diff --git a/src/teams/templates/team_user_shifts.html b/src/teams/templates/team_user_shifts.html
new file mode 100644
index 00000000..fe370b1d
--- /dev/null
+++ b/src/teams/templates/team_user_shifts.html
@@ -0,0 +1,44 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+
+
+ Tasks+
+
+The {{ team.name }} Team is responsible for the following tasks +
Your shifts+ +
Approve member {{ teammember.user.profile.name }} for the {{ teammember.team.name }} teamReally approve the user {{ teammember.user.profile.name }} for the {{ teammember.team.name }} team? The user will receive an email with a message.
Remove member {{ teammember.user.profile.name }} from the {{ teammember.team.name }} teamReally remove the user {{ teammember.user.profile.name }} from the {{ teammember.team.name }} team? The user will receive an email with a message.
|