Progress, basic calendar works

parent 90965d1c
......@@ -24,15 +24,15 @@ class CalenderJSSerializer(serializers.ModelSerializer):
"""
class Meta:
fields = (
'start', 'end', 'all_day', 'is_birthday',
'start', 'end', 'allDay', 'isBirthday',
'url', 'title', 'description',
'backgroundColor', 'textColor', 'blank'
)
start = serializers.SerializerMethodField('_start')
end = serializers.SerializerMethodField('_end')
all_day = serializers.SerializerMethodField('_all_day')
is_birthday = serializers.SerializerMethodField('_is_birthday')
allDay = serializers.SerializerMethodField('_all_day')
isBirthday = serializers.SerializerMethodField('_is_birthday')
url = serializers.SerializerMethodField('_url')
title = serializers.SerializerMethodField('_title')
description = serializers.SerializerMethodField('_description')
......
/*!
FullCalendar Bootstrap Plugin v4.2.0
Docs & License: https://fullcalendar.io/
(c) 2019 Adam Shaw
*/
.fc.fc-bootstrap a {
text-decoration: none;
}
.fc.fc-bootstrap a[data-goto]:hover {
text-decoration: underline;
}
.fc-bootstrap hr.fc-divider {
border-color: inherit;
}
.fc-bootstrap .fc-today.alert {
border-radius: 0;
}
.fc-bootstrap a.fc-event:not([href]):not([tabindex]) {
color: #fff;
}
.fc-bootstrap .fc-popover.card {
position: absolute;
}
/* Popover
--------------------------------------------------------------------------------------------------*/
.fc-bootstrap .fc-popover .card-body {
padding: 0;
}
/* TimeGrid Slats (lines that run horizontally)
--------------------------------------------------------------------------------------------------*/
.fc-bootstrap .fc-time-grid .fc-slats table {
/* some themes have background color. see through to slats */
background: none;
}
......@@ -41,10 +41,23 @@
}
#events-index {
.fc-bootstrap4 .fc-today {
.fc-bootstrap .fc-today {
background: #fff9fc;
}
.fc-now-indicator {
border-color: $dark-grey;
&.fc-now-indicator-arrow {
border-top-color: transparent;
border-bottom-color: transparent;
}
}
.fc-day-top {
padding: 5px;
}
.fc-list-view {
border: none;
......@@ -67,14 +80,15 @@
background-color: #616161;
border-radius: 0;
border: none;
overflow: hidden;
.fc-content {
padding: 5px;
transition: all 300ms ease-in-out 0s;
-moz-transition: all 300ms ease-in-out 0s;
-webkit-transition: all 300ms ease-in-out 0s;
-o-transition: all 300ms ease-in-out 0s;
transition: opacity 300ms ease-in-out 0s;
-moz-transition: opacity 300ms ease-in-out 0s;
-webkit-transition: opacity 300ms ease-in-out 0s;
-o-transition: opacity 300ms ease-in-out 0s;
}
.fc-bg {
......@@ -85,21 +99,8 @@
.fc-content {
opacity: 0.8;
}
}
}
}
@media(max-width: 575px) {
#events-index {
.fc-right {
> div {
margin-bottom: 0.5rem;
float: right;
}
> button {
clear: both;
float: right;
}
color: $white;
}
}
}
......
......@@ -5,6 +5,10 @@ var ListView; // our subclass
ListView = View.extend({
title: gettext("Upcoming Events"),
initialize: function () {
this.title = gettext("Upcoming Events");
}
computeTitle: function (d) {
return this.title;
},
......
/*!
FullCalendar Bootstrap Plugin v4.2.0
Docs & License: https://fullcalendar.io/
(c) 2019 Adam Shaw
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@fullcalendar/core')) :
typeof define === 'function' && define.amd ? define(['exports', '@fullcalendar/core'], factory) :
(global = global || self, factory(global.FullCalendarBootstrap = {}, global.FullCalendar));
}(this, function (exports, core) { 'use strict';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var BootstrapTheme = /** @class */ (function (_super) {
__extends(BootstrapTheme, _super);
function BootstrapTheme() {
return _super !== null && _super.apply(this, arguments) || this;
}
return BootstrapTheme;
}(core.Theme));
BootstrapTheme.prototype.classes = {
widget: 'fc-bootstrap',
tableGrid: 'table-bordered',
tableList: 'table',
tableListHeading: 'table-active',
buttonGroup: 'btn-group',
button: 'btn btn-primary',
buttonActive: 'active',
today: 'alert alert-info',
popover: 'card card-primary',
popoverHeader: 'card-header',
popoverContent: 'card-body',
// day grid
// for left/right border color when border is inset from edges (all-day in timeGrid view)
// avoid `table` class b/c don't want margins/padding/structure. only border color.
headerRow: 'table-bordered',
dayRow: 'table-bordered',
// list view
listView: 'card card-primary'
};
BootstrapTheme.prototype.baseIconClass = 'fa';
BootstrapTheme.prototype.iconClasses = {
close: 'fa-times',
prev: 'fa-chevron-left',
next: 'fa-chevron-right',
prevYear: 'fa-angle-double-left',
nextYear: 'fa-angle-double-right'
};
BootstrapTheme.prototype.iconOverrideOption = 'bootstrapFontAwesome';
BootstrapTheme.prototype.iconOverrideCustomButtonOption = 'bootstrapFontAwesome';
BootstrapTheme.prototype.iconOverridePrefix = 'fa-';
var main = core.createPlugin({
themeClasses: {
bootstrap: BootstrapTheme
}
});
exports.BootstrapTheme = BootstrapTheme;
exports.default = main;
Object.defineProperty(exports, '__esModule', { value: true });
}));
......@@ -25,14 +25,6 @@
noEventsMessage: "Geen evenementen om te laten zien"
};
var enGb = {
code: "en-gb",
week: {
dow: 1,
doy: 4 // The week that contains Jan 4th is the first week of the year.
}
};
return [nl, enGb];
return nl;
}));
class ListView extends FullCalendar.View {
renderSkeleton() {
// responsible for displaying the skeleton of the view within the already-defined
// this.el, an HTML element
console.log(this);
}
unrenderSkeleton() {
// should undo what renderSkeleton did
}
renderDates(dateProfile) {
// responsible for rendering the given dates
}
unrenderDates() {
// should undo whatever renderDates does
}
renderEvents(eventStore, eventUiHash) {
const events = Object.values(eventStore.instances);
var root = $("<div>").addClass("accordion bordered");
events.sort(function (a, b) {
return a.range.start < b.range.start ? -1 :
a.range.start > b.range.start ? 1 : 0;
});
if (events.length === 0) {
var alertEl = document.createElement('div');
alertEl.id = 'fc-no-events';
alertEl.classList.add('alert', 'alert-info');
var text = document.createTextNode(gettext('No events planned in the selected period.'));
alertEl.appendChild(text);
this.el.appendChild(alertEl);
}
for (let i = 0; i < events.length; i++) {
const instance = events[i];
const def = eventStore.defs[instance.defId];
console.log(instance, def);
console.log(def.extendedProps);
if (def.extendedProps.isBirthday) {
break;
}
// var date = def.range.start.toLocaleString();
// console.log(def.range.start);
// var eventCard = $("<div>").addClass("card mb-0");
//
// var eventIndicator = $("<div>")
// .addClass("event-indication")
// .attr("style", "background-color: " + e.backgroundColor);
// var cardHead = $("<div>").addClass("card-header collapsed")
// .attr("data-toggle", "collapse")
// .attr("data-target", "#event-content-" + i);
//
// cardHead.append(eventIndicator);
// cardHead.append("<div class=\"title\">" + e.title + " " +
// "(<span class=\"date\">" + date + "</span>)</div>");
//
// var cardContent = $("<div>")
// .addClass("collapse")
// .attr("id", "event-content-" + i);
//
// var url = $("<a>")
// .addClass("btn btn-primary")
// .attr("href", e.url)
// .attr("target", e.blank ? "_blank" : "_self")
// .html(gettext("Go to event"));
//
// var cardBody = $("<div>")
// .addClass("card-body")
// .html("<p>" + e.description + "</p>");
// cardBody.append(url);
//
// cardContent.append(cardBody);
// eventCard.append(cardHead);
// eventCard.append(cardContent);
//
// root.append(eventCard);
// this.el.appendChild(root);
}
}
unrenderEvents() {
const noEventsEl = document.getElementById('fc-no-events');
if (noEventsEl) {
noEventsEl.remove();
}
// should undo whatever renderEvents does
}
}
const listViewPlugin = FullCalendar.createPlugin({
views: {
list: ListView
}
});
var BIRTHDAYS_COOKIE = 'showbirthdays';
var VIEW_COOKIE = 'agendaview';
var SOURCES = {
events: "/api/v1/events/calendarjs",
birthdays: "/api/v1/members/birthdays",
......@@ -47,8 +48,8 @@ $(function () {
eventSources.push(SOURCES.birthdays);
}
var tmpView = ($(window).width() < 979) ? 'list' : 'agendaWeek';
if (Cookies.get('agendaview') !== undefined) {
tmpView = Cookies.get('agendaview');
if (Cookies.get(VIEW_COOKIE) !== undefined) {
tmpView = Cookies.get(VIEW_COOKIE);
}
// History idea and code parts from
......@@ -117,7 +118,7 @@ $(function () {
element.attr('title', event.description);
},
viewRender: function (view) {
var prevView = Cookies.get('agendaview');
var prevView = Cookies.get(VIEW_COOKIE);
var moment = calendarElement.fullCalendar('getDate');
if (moment && moment.isValid()) {
window.location.hash = 'year=' + moment.format('YYYY') + '&month=' + (moment.format('M')) + '&day=' + moment.format('DD') + '&view=' + view.name;
......@@ -125,14 +126,14 @@ $(function () {
if (view.name !== prevView) {
var windowWidth = $(window).width();
Cookies.set('agendaview', view.name);
Cookies.set(VIEW_COOKIE, view.name);
checkResponsiveState(calendarElement, windowWidth, view);
}
}
,
windowResize: function () {
var windowWidth = $(window).width();
var view = (windowWidth <= 768) ? 'list' : Cookies.get('agendaview');
var view = (windowWidth <= 768) ? 'list' : Cookies.get(VIEW_COOKIE);
var currentView = $('#calendar').fullCalendar('getView');
if (view !== currentView.name) {
calendarElement.fullCalendar('changeView', view);
......
const BIRTHDAYS_COOKIE = 'showbirthdays';
const VIEW_COOKIE = 'calendarview';
const SOURCES = {
events: "/api/v1/events/calendarjs/",
birthdays: "/api/v1/members/birthdays/",
partners: "/api/v1/partners/calendarjs/",
unpublishedEvents: "/api/v1/events/unpublished/"
events: {
id: 'event',
url: '/api/v1/events/calendarjs/',
},
birthdays: {
id: 'birthdays',
url: '/api/v1/members/birthdays/',
},
partners: {
id: 'partners',
url: '/api/v1/partners/calendarjs/',
},
unpublished: {
id: 'unpublished',
url: '/api/v1/events/unpublished/',
},
};
function checkResponsiveState(calendar, windowWidth, view) {
var buttonText = gettext('show birthdays');
if (calendar.getEventSourceById(SOURCES.birthdays.id)) {
calendar.getEventSourceById(SOURCES.birthdays.id).remove();
}
if (windowWidth <= 768) {
calendar.setOption('header', {
right: ''
});
} else {
if (view.type === 'list') {
calendar.setOption('header', {
right: 'list,timeGridWeek,dayGridMonth'
});
} else {
if (Cookies.get(BIRTHDAYS_COOKIE)) {
calendar.addEventSource(SOURCES.birthdays);
buttonText = gettext('hide birthdays');
}
calendar.setOption('header', {
right: 'showBirthdays, list,timeGridWeek,dayGridMonth prev,next today'
});
}
}
$('.fc-showBirthdays-button').html(buttonText);
}
document.addEventListener('DOMContentLoaded', function () {
const calendarEl = document.getElementById('calendar');
const showUnpublished = calendarEl.dataset['show-unpublished'];
const defaultDate = calendarEl.dataset['default-date'];
let defaultDate = calendarEl.dataset['default-date'];
const isAuthenticated = calendarEl.dataset.authenticated;
const language = calendarEl.dataset.language;
const eventSources = [SOURCES.events, SOURCES.partners];
if (showUnpublished) {
eventSources.push(SOURCES.unpublishedEvents);
eventSources.push(SOURCES.unpublished);
}
if (Cookies.get(BIRTHDAYS_COOKIE)) {
eventSources.push(SOURCES.birthdays);
}
let tmpView = ($(window).width() < 979) ? 'list' : 'timeGridWeek';
if (Cookies.get(VIEW_COOKIE) !== undefined) {
tmpView = Cookies.get(VIEW_COOKIE);
}
if (window.location.hash.indexOf('date') > -1) {
defaultDate = window.location.hash.substr(window.location.hash.indexOf('date') + 5, 24);
}
const calendar = new FullCalendar.Calendar(calendarEl, {
timeZone: 'UTC',
plugins: ['timeGrid', 'dayGrid'],
defaultView: 'timeGridWeek',
plugins: ['timeGrid', 'dayGrid', 'bootstrap', listViewPlugin],
aspectRatio: 1.8,
themeSystem: 'bootstrap',
defaultView: tmpView,
defaultDate: defaultDate,
eventSources: eventSources,
firstDay: 1,
scrollTime: '14:00:00',
timeFormat: 'HH:mm',
eventTimeFormat: {
hour: '2-digit',
minute: '2-digit',
hour12: false,
},
eventLimit: true,
locale: language,
nowIndicator: true,
views: {
list: {
buttonText: gettext('list'),
duration: { years: 5 },
type: 'list',
titleFormat: function() { return gettext("Upcoming Events") },
}
},
customButtons:
isAuthenticated ? {
showBirthdays: {
text: Cookies.get(BIRTHDAYS_COOKIE) ? gettext('hide birthdays') : gettext('show birthdays'),
click: function (e) {
if (Cookies.get(BIRTHDAYS_COOKIE)) {
e.target.innerHTML = gettext('show birthdays');
Cookies.remove(BIRTHDAYS_COOKIE);
calendar.getEventSourceById(SOURCES.birthdays.id).remove();
} else {
e.target.innerHTML = gettext('hide birthdays');
Cookies.set(BIRTHDAYS_COOKIE, 1);
calendar.addEventSource(SOURCES.birthdays);
}
}
}
} : {},
header: {
right: 'showBirthdays, list,timeGridWeek,dayGridMonth prev,next today'
},
eventClick: function (event) {
if (event.url && event.blank) {
window.open(event.url, '_blank');
return false;
} else if (event.url) {
window.replace(event.url);
return false;
eventClick: function (info) {
console.log(info);
// if (event.url && event.blank) {
// // window.open(event.url, '_blank');
// return false;
// } else if (event.url) {
// // window.replace(event.url);
// return false;
// }
},
eventRender: function (info) {
info.el.setAttribute(
'title', info.event.extendedProps.description);
},
viewSkeletonRender: function (info) {
const view = info.view;
const prevView = Cookies.get(VIEW_COOKIE);
const date = calendar.getDate();
window.location.hash = 'date=' + date.toISOString() + '&view=' + view.type;
if (view.type !== prevView) {
const windowWidth = $(window).width();
Cookies.set(VIEW_COOKIE, view.type);
checkResponsiveState(calendar, windowWidth, view);
}
},
eventRender: function (event, element) {
element.attr('title', event.description);
datesRender: function (info) {
const date = calendar.getDate();
window.location.hash = 'date=' + date.toISOString() + '&view=' + info.view.type;
},
windowResize: function () {
const windowWidth = $(window).width();
const view = (windowWidth <= 768) ? 'list' : Cookies.get(VIEW_COOKIE);
const currentView = calendar.view;
if (view !== currentView.type) {
calendar.changeView(view);
} else {
checkResponsiveState(calendar, windowWidth, currentView);
}
}
});
calendar.render();
if (calendar.view.type !== tmpView) {
calendar.changeView(tmpView);
} else {
var windowWidth = $(window).width();
checkResponsiveState(calendar, windowWidth, calendar.view);
}
});
......@@ -14,6 +14,8 @@
rel="stylesheet" type="text/css">
<link href="{% static "events/css/fullcalendar.timegrid.css" %}"
rel="stylesheet" type="text/css">
<link href="{% static "events/css/fullcalendar.bootstrap.css" %}"
rel="stylesheet" type="text/css">
{% endcompress %}
{% endblock %}
......@@ -63,16 +65,18 @@
{% block js_body %}
{{ block.super }}
{% compress js %}
<script type="text/javascript"
src="{% static "events/js/fullcalendar.locale.js" %}"></script>
<script type="text/javascript"
src="{% static "events/js/fullcalendar.core.js" %}"></script>
<script type="text/javascript"
src="{% static "events/js/fullcalendar.bootstrap.js" %}"></script>
<script type="text/javascript"
src="{% static "events/js/fullcalendar.daygrid.js" %}"></script>
<script type="text/javascript"
src="{% static "events/js/fullcalendar.timegrid.js" %}"></script>
<script type="text/javascript"
src="{% static "events/js/fullcalendar.locale.js" %}"></script>
{# <script type="text/javascript"#}
{# src="{% static "events/js/calendarlistview.js" %}"></script>#}
src="{% static "events/js/listview.js" %}"></script>
<script type="text/javascript"
src="{% static "events/js/new.js" %}"></script>
{% endcompress %}
......
......@@ -6,7 +6,7 @@ from datetime import datetime
from django.conf import settings
from django.template.defaultfilters import urlencode
from django.utils import timezone
from django.utils import timezone, dateparse
from pytz import InvalidTimeError
from rest_framework.exceptions import ParseError
......@@ -60,10 +60,9 @@ def _extract_date(param):
if param is None:
return None
try:
return timezone.make_aware(
datetime.strptime(param, '%Y-%m-%dT%H:%M:%SZ'))
return dateparse.parse_datetime(param)
except ValueError:
return timezone.make_aware(datetime.strptime(param, '%Y-%m-%d'))
return dateparse.parse_date(param)
def extract_date_range(request, allow_empty=False):
......@@ -73,14 +72,21 @@ def extract_date_range(request, allow_empty=False):
if allow_empty:
default_value = None
try:
start = _extract_date(request.query_params.get('start', default_value))
except (ValueError, InvalidTimeError) as e:
raise ParseError(detail='start query parameter invalid') from e
start = request.query_params.get('start', default_value)
print(start)
start = dateparse.parse_datetime(start)
if not timezone.is_aware(start):
start = timezone.make_aware(start)
try:
end = _extract_date(request.query_params.get('end', default_value))
except (ValueError, InvalidTimeError) as e:
raise ParseError(detail='end query parameter invalid') from e
if not start and not allow_empty:
raise ParseError(detail='start query parameter invalid')