From 3ec66786ddd177289de2ab1ea123b3e94f63a9f8 Mon Sep 17 00:00:00 2001 From: Luuk Scholten Date: Thu, 1 Sep 2016 10:18:46 +0200 Subject: [PATCH 01/18] Add calendar page --- .../events/locale/nl/LC_MESSAGES/django.mo | Bin 4077 -> 4178 bytes .../events/locale/nl/LC_MESSAGES/django.po | 107 +- .../events/static/events/css/fullcalendar.css | 1069 +++++++++++++++++ .../static/events/css/fullcalendar.print.css | 202 ++++ .../events/css/override_fullcalendar.css | 98 ++ .../events/css/registration_changed.css | 3 + .../static/events/js/calendarcardview.js | 69 ++ .../static/events/js/calendarlistview.js | 61 + website/events/static/events/js/countdown.js | 34 + .../static/events/js/fullcalendar-nl.js | 1 + .../static/events/js/fullcalendar.min.js | 9 + website/events/static/events/js/js.cookie.js | 139 +++ website/events/static/events/js/moment.js | 7 + website/events/templates/events/index.html | 100 ++ website/events/urls.py | 1 + website/events/views.py | 11 + website/thaliawebsite/menus.py | 2 +- 17 files changed, 1867 insertions(+), 46 deletions(-) create mode 100644 website/events/static/events/css/fullcalendar.css create mode 100644 website/events/static/events/css/fullcalendar.print.css create mode 100644 website/events/static/events/css/override_fullcalendar.css create mode 100644 website/events/static/events/css/registration_changed.css create mode 100644 website/events/static/events/js/calendarcardview.js create mode 100644 website/events/static/events/js/calendarlistview.js create mode 100644 website/events/static/events/js/countdown.js create mode 100755 website/events/static/events/js/fullcalendar-nl.js create mode 100644 website/events/static/events/js/fullcalendar.min.js create mode 100644 website/events/static/events/js/js.cookie.js create mode 100644 website/events/static/events/js/moment.js create mode 100644 website/events/templates/events/index.html diff --git a/website/events/locale/nl/LC_MESSAGES/django.mo b/website/events/locale/nl/LC_MESSAGES/django.mo index f378fe70b2067e65d5da80159364a01d3eaee2d3..04e92d27232cb3d8fa050bcbc66dcce736ce565c 100644 GIT binary patch delta 1571 zcmY+^TS!zv9LMol*V~$|WmnDen5Cw=BFF81S?SO zx8W9JV&)u`R&G4R73j$@W-%7vG7R8y+>Ld34At>fRQtQAxjw~QoWw==7IW|m(zW@C zT7U{o)R#17jGxK^ZY)NAriel>lw%fFyEdVp>)ogh+ED`>#%%0y??;iJ8K%&R&*N$w z#dSE2djB1!F~0dgMWOuUKKPDI*8ETd{OvwZrIA+VM}^cv4N!xMSSxC)j$j_1MosVn zYT_fP1&z7h!I%aZr=kHL;XaQs6IjA^9$`@=8n6mmP?0)Gu^oqyrse^z#K)+WPP^B$ zs4dJVJo_<-`!I$b_$-_K*B(|eO?A+UT45(@0#VdqxsIFgHR_Q1S&jy-M6Ilzg0ajl z*8@n<%u!bdwU7{M!2_r*JL4z*YIuno>R<$QebeRHIsEcyju>9jmi167B7_hx_B@nQOd#{n5mp%mPn#Z+Ph6qj-lel+j=X8=Gny<1c;1 zbM?Al{FiUayD3l;f1XvEzN1G&C1$c3QxYvX&7LKPoJgk=>9Gc#$O+qyblWtD5BW>H Xd;3l#M*X#(y!LQ7Wc~kqGI#A?5H+94 delta 1462 zcmYk+OGs2v9LMon6&G-ir_rSf}ui zF>!N^lVWbX!vg$%ojBnQM6Jx%kiwD1)7t(o49r7?23mnT)D{er2*pGSGjr^G-9JJz-SdLMw!ON(A z50Ipo$C$(TX2E&z3>n+Ja9((&7r0(Qtz;GLl%kzd9x7u+s1h||KK7y}Hh`LV*l`Rs z@C<64IgB5r-t(MPb7P6OcjHe~CaO8~Vl(oJIfvVE7PYb)&h=AN$(FDmKjSeBl9oZ7 zN0sOks{aORL8Wf;ubDJ4T^3~msEPETHdh#fcpJ4j-=Ze+13kEbjAh(h$P(0dY8?Hj z5(ZH#-;a!K`kdz{Jmg;;&p08K9xlEQ`<>sV3kEBQPaw_0ow>=rJhjhAT*gy zUC{LVpnam&m?Ccf80fr$*h?r2l+|u&9BT+=aJyDQb`R}|dciHMl tcwM#09#=5UdoU6UhsTCZC_FwBnH`NLr`+9E;<=|P`Nh*>C7Qj}e*hhMf4~3$ diff --git a/website/events/locale/nl/LC_MESSAGES/django.po b/website/events/locale/nl/LC_MESSAGES/django.po index 067b563f..f68e657d 100644 --- a/website/events/locale/nl/LC_MESSAGES/django.po +++ b/website/events/locale/nl/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-14 21:48+0200\n" -"PO-Revision-Date: 2016-09-14 21:45+0200\n" +"POT-Creation-Date: 2016-09-16 08:38+0200\n" +"PO-Revision-Date: 2016-09-16 08:40+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: nl\n" @@ -16,178 +16,178 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 1.8.8\n" +"X-Generator: Poedit 1.8.7.1\n" -#: admin.py:36 +#: admin.py:37 msgid "order" msgstr "volgorde" -#: admin.py:71 +#: admin.py:72 msgid "Edit" msgstr "Aanpassen" -#: admin.py:81 +#: admin.py:82 msgid "Number of participants" msgstr "Aantal deelnemers" -#: admin.py:85 +#: admin.py:86 msgid "Publish selected events" msgstr "Publiceer geselecteerde evenementen" -#: admin.py:89 +#: admin.py:90 msgid "Unpublish selected events" msgstr "Publicatie van geselecteerde evenementen ongedaan maken" -#: models.py:12 +#: models.py:13 msgid "No registration required" msgstr "Geen registratie vereist" -#: models.py:14 templates/events/admin/details.html:47 +#: models.py:17 templates/events/admin/details.html:47 msgid "title" msgstr "titel" -#: models.py:16 models.py:157 +#: models.py:21 models.py:167 msgid "description" msgstr "beschrijving" -#: models.py:18 +#: models.py:23 msgid "start time" msgstr "starttijd" -#: models.py:20 +#: models.py:25 msgid "end time" msgstr "eindtijd" -#: models.py:26 templates/events/admin/details.html:51 +#: models.py:31 templates/events/admin/details.html:51 msgid "organiser" msgstr "organisator" -#: models.py:30 +#: models.py:35 msgid "registration start" msgstr "start registratie" -#: models.py:36 +#: models.py:41 msgid "registration end" msgstr "einde registratie" -#: models.py:42 +#: models.py:47 msgid "cancel deadline" msgstr "afmelddeadline" -#: models.py:47 templates/events/admin/details.html:55 +#: models.py:54 templates/events/admin/details.html:55 msgid "location" msgstr "locatie" -#: models.py:50 +#: models.py:59 msgid "location for minimap" msgstr "locatie voor minimap" -#: models.py:52 +#: models.py:61 msgid "Location of Huygens: Heyendaalseweg 135, Nijmegen. Not shown as text!!" msgstr "" "Locatie van ’t Huygens: Heyendaalseweg 135, Nijmegen. Dit veld wordt niet " "getoond als tekst!!" -#: models.py:57 templates/events/admin/details.html:57 +#: models.py:66 templates/events/admin/details.html:57 msgid "price" msgstr "prijs" -#: models.py:65 templates/events/admin/details.html:59 +#: models.py:74 templates/events/admin/details.html:59 msgid "cost" msgstr "kosten" -#: models.py:69 +#: models.py:78 msgid "Actual cost of event." msgstr "werkelijke kosten van het evenement" -#: models.py:74 +#: models.py:83 msgid "maximum number of participants" msgstr "maximum aantal deelnemers" -#: models.py:80 +#: models.py:90 msgid "message when there is no registration" msgstr "bericht dat getoond wordt wanneer er geen registratie nodig is" -#: models.py:84 +#: models.py:94 msgid "Default: " msgstr "Standaard: " -#: models.py:88 +#: models.py:98 msgid "published" msgstr "gepubliceerd" -#: models.py:98 +#: models.py:108 msgid "Can't have an event travel back in time" msgstr "Een evenement kan niet terugreizen in de tijd" -#: models.py:103 +#: models.py:113 msgid "Doesn't make sense to have this if you require registrations." msgstr "Het is niet logisch om dit te hebben als je registratie vereist." -#: models.py:108 +#: models.py:118 msgid "If registration is required, you need a start of registration" msgstr "" "Als registratie vereist is, dan heb je een starttijd voor de registratie " "nodig" -#: models.py:113 +#: models.py:123 msgid "If registration is required, you need an end of registration" msgstr "" "Als registratie vereist is, dan heb je een eindtijd voor de registratie nodig" -#: models.py:117 +#: models.py:127 msgid "Registration start should be before registration end" msgstr "De starttijd voor de registratie moet voor de eindtijd liggen" -#: models.py:139 +#: models.py:149 msgid "checkbox" msgstr "checkbox" -#: models.py:140 +#: models.py:150 msgid "text field" msgstr "tekstveld" -#: models.py:141 +#: models.py:151 msgid "integer field" msgstr "integerveld" -#: models.py:146 +#: models.py:156 msgid "field type" msgstr "veldtype" -#: models.py:152 +#: models.py:162 msgid "field name" msgstr "veldnaam" -#: models.py:194 templates/events/admin/registrations_table.html:6 +#: models.py:204 templates/events/admin/registrations_table.html:6 msgid "name" msgstr "naam" -#: models.py:196 +#: models.py:206 msgid "Use this for non-members" msgstr "Gebruikt dit voor niet-leden" -#: models.py:201 +#: models.py:211 msgid "registration date" msgstr "registratiedatum" -#: models.py:202 +#: models.py:212 msgid "cancellation date" msgstr "afmelddatum" -#: models.py:207 templates/events/admin/registrations_table.html:11 +#: models.py:217 templates/events/admin/registrations_table.html:11 msgid "present" msgstr "aanwezig" -#: models.py:211 templates/events/admin/registrations_table.html:12 +#: models.py:221 templates/events/admin/registrations_table.html:12 msgid "paid" msgstr "betaald" -#: models.py:224 models.py:225 +#: models.py:234 models.py:235 msgid "Either specify a member or a name" msgstr "Geef een lid of een naam op" -#: models.py:247 +#: models.py:257 msgid "last changed" msgstr "laatst aangepast" @@ -255,6 +255,23 @@ msgstr "toevoegen" msgid "Nobody %(verb)s yet" msgstr "Niemand heeft zich %(verb)s" +#: templates/events/index.html:13 +msgid "Calendar" +msgstr "Agenda" + +#: templates/events/index.html:44 templates/events/index.html:46 +#: templates/events/index.html:52 +msgid "hide birthdays" +msgstr "Verberg verjaardagen" + +#: templates/events/index.html:44 templates/events/index.html:47 +msgid "show birthdays" +msgstr "Toon verjaardagen" + +#: templates/events/index.html:64 +msgid "list" +msgstr "Lijst" + #~ msgid "Present" #~ msgstr "Aanwezig" diff --git a/website/events/static/events/css/fullcalendar.css b/website/events/static/events/css/fullcalendar.css new file mode 100644 index 00000000..3d0474d8 --- /dev/null +++ b/website/events/static/events/css/fullcalendar.css @@ -0,0 +1,1069 @@ +/*! + * FullCalendar v2.4.0 Stylesheet + * Docs & License: http://fullcalendar.io/ + * (c) 2015 Adam Shaw + */ + + +.fc { + direction: ltr; + text-align: left; +} + +.fc-rtl { + text-align: right; +} + +body .fc { /* extra precedence to overcome jqui */ + font-size: 1em; +} + + +/* Colors +--------------------------------------------------------------------------------------------------*/ + +.fc-unthemed th, +.fc-unthemed td, +.fc-unthemed thead, +.fc-unthemed tbody, +.fc-unthemed .fc-divider, +.fc-unthemed .fc-row, +.fc-unthemed .fc-popover { + border-color: #ddd; +} + +.fc-unthemed .fc-popover { + background-color: #fff; +} + +.fc-unthemed .fc-divider, +.fc-unthemed .fc-popover .fc-header { + background: #eee; +} + +.fc-unthemed .fc-popover .fc-header .fc-close { + color: #666; +} + +.fc-unthemed .fc-today { + background: #fcf8e3; +} + +.fc-highlight { /* when user is selecting cells */ + background: #bce8f1; + opacity: .3; + filter: alpha(opacity=30); /* for IE */ +} + +.fc-bgevent { /* default look for background events */ + background: rgb(143, 223, 130); + opacity: .3; + filter: alpha(opacity=30); /* for IE */ +} + +.fc-nonbusiness { /* default look for non-business-hours areas */ + /* will inherit .fc-bgevent's styles */ + background: #d7d7d7; +} + + +/* Icons (inline elements with styled text that mock arrow icons) +--------------------------------------------------------------------------------------------------*/ + +.fc-icon { + display: inline-block; + width: 1em; + height: 1em; + line-height: 1em; + font-size: 1em; + text-align: center; + overflow: hidden; + font-family: "Courier New", Courier, monospace; + + /* don't allow browser text-selection */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + +/* +Acceptable font-family overrides for individual icons: + "Arial", sans-serif + "Times New Roman", serif + +NOTE: use percentage font sizes or else old IE chokes +*/ + +.fc-icon:after { + position: relative; + margin: 0 -1em; /* ensures character will be centered, regardless of width */ +} + +.fc-icon-left-single-arrow:after { + content: "\02039"; + font-weight: bold; + font-size: 200%; + top: -7%; + left: 3%; +} + +.fc-icon-right-single-arrow:after { + content: "\0203A"; + font-weight: bold; + font-size: 200%; + top: -7%; + left: -3%; +} + +.fc-icon-left-double-arrow:after { + content: "\000AB"; + font-size: 160%; + top: -7%; +} + +.fc-icon-right-double-arrow:after { + content: "\000BB"; + font-size: 160%; + top: -7%; +} + +.fc-icon-left-triangle:after { + content: "\25C4"; + font-size: 125%; + top: 3%; + left: -2%; +} + +.fc-icon-right-triangle:after { + content: "\25BA"; + font-size: 125%; + top: 3%; + left: 2%; +} + +.fc-icon-down-triangle:after { + content: "\25BC"; + font-size: 125%; + top: 2%; +} + +.fc-icon-x:after { + content: "\000D7"; + font-size: 200%; + top: 6%; +} + + +/* Buttons (styled ").click(function(a){s.hasClass(n+"-state-disabled")||(j(a),(s.hasClass(n+"-state-active")||s.hasClass(n+"-state-disabled"))&&s.removeClass(n+"-state-hover"))}).mousedown(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-down")}).mouseup(function(){s.removeClass(n+"-state-down")}).hover(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-hover")},function(){s.removeClass(n+"-state-hover").removeClass(n+"-state-down")}),g=g.add(s)))}),h&&g.first().addClass(n+"-corner-left").end().last().addClass(n+"-corner-right").end(),g.length>1?(f=a("
"),h&&f.addClass("fc-button-group"),f.append(g),e.append(f)):e.append(g)}),e}function g(a){o.find("h2").text(a)}function h(a){o.find(".fc-"+a+"-button").addClass(n+"-state-active")}function i(a){o.find(".fc-"+a+"-button").removeClass(n+"-state-active")}function j(a){o.find(".fc-"+a+"-button").attr("disabled","disabled").addClass(n+"-state-disabled")}function k(a){o.find(".fc-"+a+"-button").removeAttr("disabled").removeClass(n+"-state-disabled")}function l(){return p}var m=this;m.render=d,m.removeElement=e,m.updateTitle=g,m.activateButton=h,m.deactivateButton=i,m.disableButton=j,m.enableButton=k,m.getViewsWithButtons=l;var n,o=a(),p=[]}function Ka(c){function d(a,b){return!M||a.clone().stripZone()N.clone().stripZone()}function e(a,b){M=a,N=b,U=[];var c=++S,d=R.length;T=d;for(var e=0;d>e;e++)f(R[e],c)}function f(b,c){g(b,function(d){var e,f,g,h=a.isArray(b.events);if(c==S){if(d)for(e=0;e=c&&b.end<=d}function K(a,b){var c=a.start.clone().stripZone(),d=L.getEventEnd(a).stripZone();return b.startc}var L=this;L.isFetchNeeded=d,L.fetchEvents=e,L.addEventSource=h,L.removeEventSource=j,L.updateEvent=m,L.renderEvent=p,L.removeEvents=q,L.clientEvents=r,L.mutateEvent=y,L.normalizeEventRange=u,L.normalizeEventRangeTimes=v,L.ensureVisibleEventRange=w;var M,N,O=L.reportEvents,Q={events:[]},R=[Q],S=0,T=0,U=[];a.each((c.events?[c.events]:[]).concat(c.eventSources||[]),function(a,b){var c=i(b);c&&R.push(c)}),L.getBusinessHoursEvents=A,L.isEventRangeAllowed=B,L.isSelectionRangeAllowed=C,L.isExternalDropRangeAllowed=D,L.getEventCache=function(){return U}}function La(a){a._allDay=a.allDay,a._start=a.start.clone(),a._end=a.end?a.end.clone():null}var Ma=a.fullCalendar={version:"2.4.0"},Na=Ma.views={};a.fn.fullCalendar=function(b){var c=Array.prototype.slice.call(arguments,1),d=this;return this.each(function(e,f){var g,h=a(f),i=h.data("fullCalendar");"string"==typeof b?i&&a.isFunction(i[b])&&(g=i[b].apply(i,c),e||(d=g),"destroy"===b&&h.removeData("fullCalendar")):i||(i=new nb(h,b),h.data("fullCalendar",i),i.render())}),d};var Oa=["header","buttonText","buttonIcons","themeButtonIcons"];Ma.intersectionToSeg=E,Ma.applyAll=W,Ma.debounce=da,Ma.isInt=ba,Ma.htmlEscape=Y,Ma.cssToStr=$,Ma.proxy=ca,Ma.capitaliseFirstLetter=_,Ma.getClientRect=p,Ma.getContentRect=q,Ma.getScrollbarWidths=r;var Pa=null;Ma.intersectRects=w,Ma.parseFieldSpecs=A,Ma.compareByFieldSpecs=B,Ma.compareByFieldSpec=C,Ma.flexibleCompare=D,Ma.computeIntervalUnit=I,Ma.divideRangeByDuration=K,Ma.divideDurationByDuration=L,Ma.multiplyDuration=M,Ma.durationHasTime=N;var Qa=["sun","mon","tue","wed","thu","fri","sat"],Ra=["year","month","week","day","hour","minute","second","millisecond"];Ma.log=function(){var a=window.console;return a&&a.log?a.log.apply(a,arguments):void 0},Ma.warn=function(){var a=window.console;return a&&a.warn?a.warn.apply(a,arguments):Ma.log.apply(Ma,arguments)};var Sa,Ta,Ua,Va={}.hasOwnProperty,Wa=/^\s*\d{4}-\d\d$/,Xa=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/,Ya=b.fn,Za=a.extend({},Ya);Ma.moment=function(){return ea(arguments)},Ma.moment.utc=function(){var a=ea(arguments,!0);return a.hasTime()&&a.utc(),a},Ma.moment.parseZone=function(){return ea(arguments,!0,!0)},Ya.clone=function(){var a=Za.clone.apply(this,arguments);return ga(this,a),this._fullCalendar&&(a._fullCalendar=!0),a},Ya.week=Ya.weeks=function(a){var b=(this._locale||this._lang)._fullCalendar_weekCalc;return null==a&&"function"==typeof b?b(this):"ISO"===b?Za.isoWeek.apply(this,arguments):Za.week.apply(this,arguments)},Ya.time=function(a){if(!this._fullCalendar)return Za.time.apply(this,arguments);if(null==a)return b.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});this._ambigTime=!1,b.isDuration(a)||b.isMoment(a)||(a=b.duration(a));var c=0;return b.isDuration(a)&&(c=24*Math.floor(a.asDays())),this.hours(c+a.hours()).minutes(a.minutes()).seconds(a.seconds()).milliseconds(a.milliseconds())},Ya.stripTime=function(){var a;return this._ambigTime||(a=this.toArray(),this.utc(),Ta(this,a.slice(0,3)),this._ambigTime=!0,this._ambigZone=!0),this},Ya.hasTime=function(){return!this._ambigTime},Ya.stripZone=function(){var a,b;return this._ambigZone||(a=this.toArray(),b=this._ambigTime,this.utc(),Ta(this,a),this._ambigTime=b||!1,this._ambigZone=!0),this},Ya.hasZone=function(){return!this._ambigZone},Ya.local=function(){var a=this.toArray(),b=this._ambigZone;return Za.local.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,b&&Ua(this,a),this},Ya.utc=function(){return Za.utc.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,this},a.each(["zone","utcOffset"],function(a,b){Za[b]&&(Ya[b]=function(a){return null!=a&&(this._ambigTime=!1,this._ambigZone=!1),Za[b].apply(this,arguments)})}),Ya.format=function(){return this._fullCalendar&&arguments[0]?ja(this,arguments[0]):this._ambigTime?ia(this,"YYYY-MM-DD"):this._ambigZone?ia(this,"YYYY-MM-DD[T]HH:mm:ss"):Za.format.apply(this,arguments)},Ya.toISOString=function(){return this._ambigTime?ia(this,"YYYY-MM-DD"):this._ambigZone?ia(this,"YYYY-MM-DD[T]HH:mm:ss"):Za.toISOString.apply(this,arguments)},Ya.isWithin=function(a,b){var c=fa([this,a,b]);return c[0]>=c[1]&&c[0]').addClass(c.className||"").css({top:0,left:0}).append(c.content).appendTo(c.parentEl),this.el.on("click",".fc-close",function(){b.hide()}),c.autoHide&&a(document).on("mousedown",this.documentMousedownProxy=ca(this,"documentMousedown"))},documentMousedown:function(b){this.el&&!a(b.target).closest(this.el).length&&this.hide()},removeElement:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),a(document).off("mousedown",this.documentMousedownProxy)},position:function(){var b,c,d,e,f,g=this.options,h=this.el.offsetParent().offset(),i=this.el.outerWidth(),j=this.el.outerHeight(),k=a(window),l=n(this.el);e=g.top||0,f=void 0!==g.left?g.left:void 0!==g.right?g.right-i:0,l.is(window)||l.is(document)?(l=k,b=0,c=0):(d=l.offset(),b=d.top,c=d.left),b+=k.scrollTop(),c+=k.scrollLeft(),g.viewportConstrain!==!1&&(e=Math.min(e,b+l.outerHeight()-j-this.margin),e=Math.max(e,b+this.margin),f=Math.min(f,c+l.outerWidth()-i-this.margin),f=Math.max(f,c+this.margin)),this.el.css({top:e-h.top,left:f-h.left})},trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1)); +}}),db=ra.extend({grid:null,rowCoords:null,colCoords:null,containerEl:null,bounds:null,constructor:function(a){this.grid=a},build:function(){this.grid.build(),this.rowCoords=this.grid.computeRowCoords(),this.colCoords=this.grid.computeColCoords(),this.computeBounds()},clear:function(){this.grid.clear(),this.rowCoords=null,this.colCoords=null},getCell:function(b,c){var d,e,f,g=this.rowCoords,h=g.length,i=this.colCoords,j=i.length,k=null,l=null;if(this.inBounds(b,c)){for(d=0;h>d;d++)if(e=g[d],c>=e.top&&cd;d++)if(e=i[d],b>=e.left&&b=c.left&&a=c.top&&b=b*b&&this.startDrag(a)),this.isDragging&&this.drag(d,e,a)},startDrag:function(a){this.isListening||this.startListening(),this.isDragging||(this.isDragging=!0,this.dragStart(a))},dragStart:function(a){var b=this.subjectEl;this.trigger("dragStart",a),(this.subjectHref=b?b.attr("href"):null)&&b.removeAttr("href")},drag:function(a,b,c){this.trigger("drag",a,b,c),this.updateScroll(c)},mouseup:function(a){this.stopListening(a)},stopDrag:function(a){this.isDragging&&(this.stopScrolling(),this.dragStop(a),this.isDragging=!1)},dragStop:function(a){var b=this;this.trigger("dragStop",a),setTimeout(function(){b.subjectHref&&b.subjectEl.attr("href",b.subjectHref)},0)},stopListening:function(b){this.stopDrag(b),this.isListening&&(this.scrollEl&&(this.scrollEl.off("scroll",this.scrollHandlerProxy),this.scrollHandlerProxy=null),a(document).off("mousemove",this.mousemoveProxy).off("mouseup",this.mouseupProxy).off("selectstart",this.preventDefault),this.mousemoveProxy=null,this.mouseupProxy=null,this.isListening=!1,this.listenStop(b))},listenStop:function(a){this.trigger("listenStop",a)},trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1))},preventDefault:function(a){a.preventDefault()},computeScrollBounds:function(){var a=this.scrollEl;this.scrollBounds=a?o(a):null},updateScroll:function(a){var b,c,d,e,f=this.scrollSensitivity,g=this.scrollBounds,h=0,i=0;g&&(b=(f-(a.pageY-g.top))/f,c=(f-(g.bottom-a.pageY))/f,d=(f-(a.pageX-g.left))/f,e=(f-(g.right-a.pageX))/f,b>=0&&1>=b?h=b*this.scrollSpeed*-1:c>=0&&1>=c&&(h=c*this.scrollSpeed),d>=0&&1>=d?i=d*this.scrollSpeed*-1:e>=0&&1>=e&&(i=e*this.scrollSpeed)),this.setScrollVel(h,i)},setScrollVel:function(a,b){this.scrollTopVel=a,this.scrollLeftVel=b,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(ca(this,"scrollIntervalFunc"),this.scrollIntervalMs))},constrainScrollVel:function(){var a=this.scrollEl;this.scrollTopVel<0?a.scrollTop()<=0&&(this.scrollTopVel=0):this.scrollTopVel>0&&a.scrollTop()+a[0].clientHeight>=a[0].scrollHeight&&(this.scrollTopVel=0),this.scrollLeftVel<0?a.scrollLeft()<=0&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&a.scrollLeft()+a[0].clientWidth>=a[0].scrollWidth&&(this.scrollLeftVel=0)},scrollIntervalFunc:function(){var a=this.scrollEl,b=this.scrollIntervalMs/1e3;this.scrollTopVel&&a.scrollTop(a.scrollTop()+this.scrollTopVel*b),this.scrollLeftVel&&a.scrollLeft(a.scrollLeft()+this.scrollLeftVel*b),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.stopScrolling()},stopScrolling:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.scrollStop())},scrollHandler:function(){this.scrollIntervalId||this.scrollStop()},scrollStop:function(){}}),gb=fb.extend({coordMap:null,origCell:null,cell:null,coordAdjust:null,constructor:function(a,b){fb.prototype.constructor.call(this,b),this.coordMap=a},listenStart:function(a){var b,c,d,e=this.subjectEl;fb.prototype.listenStart.apply(this,arguments),this.computeCoords(),a?(c={left:a.pageX,top:a.pageY},d=c,e&&(b=o(e),d=x(d,b)),this.origCell=this.getCell(d.left,d.top),e&&this.options.subjectCenter&&(this.origCell&&(b=w(this.origCell,b)||b),d=y(b)),this.coordAdjust=z(d,c)):(this.origCell=null,this.coordAdjust=null)},computeCoords:function(){this.coordMap.build(),this.computeScrollBounds()},dragStart:function(a){var b;fb.prototype.dragStart.apply(this,arguments),b=this.getCell(a.pageX,a.pageY),b&&this.cellOver(b)},drag:function(a,b,c){var d;fb.prototype.drag.apply(this,arguments),d=this.getCell(c.pageX,c.pageY),sa(d,this.cell)||(this.cell&&this.cellOut(),d&&this.cellOver(d))},dragStop:function(){this.cellDone(),fb.prototype.dragStop.apply(this,arguments)},cellOver:function(a){this.cell=a,this.trigger("cellOver",a,sa(a,this.origCell),this.origCell)},cellOut:function(){this.cell&&(this.trigger("cellOut",this.cell),this.cellDone(),this.cell=null)},cellDone:function(){this.cell&&this.trigger("cellDone",this.cell)},listenStop:function(){fb.prototype.listenStop.apply(this,arguments),this.origCell=this.cell=null,this.coordMap.clear()},scrollStop:function(){fb.prototype.scrollStop.apply(this,arguments),this.computeCoords()},getCell:function(a,b){return this.coordAdjust&&(a+=this.coordAdjust.left,b+=this.coordAdjust.top),this.coordMap.getCell(a,b)}}),hb=ra.extend({options:null,sourceEl:null,el:null,parentEl:null,top0:null,left0:null,mouseY0:null,mouseX0:null,topDelta:null,leftDelta:null,mousemoveProxy:null,isFollowing:!1,isHidden:!1,isAnimating:!1,constructor:function(b,c){this.options=c=c||{},this.sourceEl=b,this.parentEl=c.parentEl?a(c.parentEl):b.parent()},start:function(b){this.isFollowing||(this.isFollowing=!0,this.mouseY0=b.pageY,this.mouseX0=b.pageX,this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),a(document).on("mousemove",this.mousemoveProxy=ca(this,"mousemove")))},stop:function(b,c){function d(){this.isAnimating=!1,e.removeElement(),this.top0=this.left0=null,c&&c()}var e=this,f=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(this.isFollowing=!1,a(document).off("mousemove",this.mousemoveProxy),b&&f&&!this.isHidden?(this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:f,complete:d})):d())},getEl:function(){var a=this.el;return a||(this.sourceEl.width(),a=this.el=this.sourceEl.clone().css({position:"absolute",visibility:"",display:this.isHidden?"none":"",margin:0,right:"auto",bottom:"auto",width:this.sourceEl.width(),height:this.sourceEl.height(),opacity:this.options.opacity||"",zIndex:this.options.zIndex}).appendTo(this.parentEl)),a},removeElement:function(){this.el&&(this.el.remove(),this.el=null)},updatePosition:function(){var a,b;this.getEl(),null===this.top0&&(this.sourceEl.width(),a=this.sourceEl.offset(),b=this.el.offsetParent().offset(),this.top0=a.top-b.top,this.left0=a.left-b.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})},mousemove:function(a){this.topDelta=a.pageY-this.mouseY0,this.leftDelta=a.pageX-this.mouseX0,this.isHidden||this.updatePosition()},hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())},show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}}),ib=ra.extend({view:null,isRTL:null,cellHtml:"",constructor:function(a){this.view=a,this.isRTL=a.opt("isRTL")},rowHtml:function(a,b){var c,d,e=this.getHtmlRenderer("cell",a),f="";for(b=b||0,c=0;c"+f+""},bookendCells:function(a,b,c){var d=this.getHtmlRenderer("intro",b)(c||0),e=this.getHtmlRenderer("outro",b)(c||0),f=this.isRTL?e:d,g=this.isRTL?d:e;return"string"==typeof a?f+a+g:a.prepend(f).append(g)},getHtmlRenderer:function(a,b){var c,d,e,f,g=this.view;return c=a+"Html",b&&(d=b+_(a)+"Html"),d&&(f=g[d])?e=g:d&&(f=this[d])?e=this:(f=g[c])?e=g:(f=this[c])&&(e=this),"function"==typeof f?function(){return f.apply(e,arguments)||""}:function(){return f||""}}}),jb=Ma.Grid=ib.extend({start:null,end:null,rowCnt:0,colCnt:0,el:null,coordMap:null,elsByFill:null,externalDragStartProxy:null,colHeadFormat:null,eventTimeFormat:null,displayEventTime:null,displayEventEnd:null,cellDuration:null,largeUnit:null,constructor:function(){ib.apply(this,arguments),this.coordMap=new db(this),this.elsByFill={},this.externalDragStartProxy=ca(this,"externalDragStart")},computeColHeadFormat:function(){},computeEventTimeFormat:function(){return this.view.opt("smallTimeFormat")},computeDisplayEventTime:function(){return!0},computeDisplayEventEnd:function(){return!0},setRange:function(a){this.start=a.start.clone(),this.end=a.end.clone(),this.rangeUpdated(),this.processRangeOptions()},rangeUpdated:function(){},processRangeOptions:function(){var a,b,c=this.view;this.colHeadFormat=c.opt("columnFormat")||this.computeColHeadFormat(),this.eventTimeFormat=c.opt("eventTimeFormat")||c.opt("timeFormat")||this.computeEventTimeFormat(),a=c.opt("displayEventTime"),null==a&&(a=this.computeDisplayEventTime()),b=c.opt("displayEventEnd"),null==b&&(b=this.computeDisplayEventEnd()),this.displayEventTime=a,this.displayEventEnd=b},build:function(){},clear:function(){},rangeToSegs:function(a){},diffDates:function(a,b){return this.largeUnit?H(a,b,this.largeUnit):F(a,b)},getCell:function(b,c){var d;return null==c&&("number"==typeof b?(c=b%this.colCnt,b=Math.floor(b/this.colCnt)):(c=b.col,b=b.row)),d={row:b,col:c},a.extend(d,this.getRowData(b),this.getColData(c)),a.extend(d,this.computeCellRange(d)),d},computeCellRange:function(a){var b=this.computeCellDate(a);return{start:b,end:b.clone().add(this.cellDuration)}},computeCellDate:function(a){},getRowData:function(a){return{}},getColData:function(a){return{}},getRowEl:function(a){},getColEl:function(a){},getCellDayEl:function(a){return this.getColEl(a.col)||this.getRowEl(a.row)},computeRowCoords:function(){var a,b,c,d=[];for(a=0;a"},headHtml:function(){return'
'+this.rowHtml("head")+"
"},headCellHtml:function(a){var b=this.view,c=a.start;return''+Y(c.format(this.colHeadFormat))+""},bgCellHtml:function(a){var b=this.view,c=a.start,d=this.getDayClasses(c);return d.unshift("fc-day",b.widgetContentClass),''},getDayClasses:function(a){var b=this.view,c=b.calendar.getNow().stripTime(),d=["fc-"+Qa[a.day()]];return 1==b.intervalDuration.as("months")&&a.month()!=b.intervalStart.month()&&d.push("fc-other-month"),a.isSame(c,"day")?d.push("fc-today",b.highlightStateClass):c>a?d.push("fc-past"):d.push("fc-future"),d}});jb.mixin({mousedOverSeg:null,isDraggingSeg:!1,isResizingSeg:!1,isDraggingExternal:!1,segs:null,renderEvents:function(a){var b,c,d=this.eventsToSegs(a),e=[],f=[];for(b=0;b *",function(c){var e=a(this).data("fc-seg");return!e||b.isDraggingSeg||b.isResizingSeg?void 0:d.call(this,e,c)})})},triggerSegMouseover:function(a,b){this.mousedOverSeg||(this.mousedOverSeg=a,this.view.trigger("eventMouseover",a.el[0],a.event,b))},triggerSegMouseout:function(a,b){b=b||{},this.mousedOverSeg&&(a=a||this.mousedOverSeg,this.mousedOverSeg=null,this.view.trigger("eventMouseout",a.el[0],a.event,b))},segDragMousedown:function(a,b){var c,d=this,e=this.view,f=e.calendar,i=a.el,j=a.event,k=new hb(a.el,{parentEl:e.el,opacity:e.opt("dragOpacity"),revertDuration:e.opt("dragRevertDuration"),zIndex:2}),l=new gb(e.coordMap,{distance:5,scroll:e.opt("dragScroll"),subjectEl:i,subjectCenter:!0,listenStart:function(a){k.hide(),k.start(a)},dragStart:function(b){d.triggerSegMouseout(a,b),d.segDragStart(a,b),e.hideEvent(j)},cellOver:function(b,h,i){a.cell&&(i=a.cell),c=d.computeEventDrop(i,b,j),c&&!f.isEventRangeAllowed(c,j)&&(g(),c=null),c&&e.renderDrag(c,a)?k.hide():k.show(),h&&(c=null)},cellOut:function(){e.unrenderDrag(),k.show(),c=null},cellDone:function(){h()},dragStop:function(b){k.stop(!c,function(){e.unrenderDrag(),e.showEvent(j),d.segDragStop(a,b),c&&e.reportEventDrop(j,c,this.largeUnit,i,b)})},listenStop:function(){k.stop()}});l.mousedown(b)},segDragStart:function(a,b){this.isDraggingSeg=!0,this.view.trigger("eventDragStart",a.el[0],a.event,b,{})},segDragStop:function(a,b){this.isDraggingSeg=!1,this.view.trigger("eventDragStop",a.el[0],a.event,b,{})},computeEventDrop:function(a,b,c){var d,e,f=this.view.calendar,g=a.start,h=b.start;return g.hasTime()===h.hasTime()?(d=this.diffDates(h,g),c.allDay&&N(d)?(e={start:c.start.clone(),end:f.getEventEnd(c),allDay:!1},f.normalizeEventRangeTimes(e)):e={start:c.start.clone(),end:c.end?c.end.clone():null,allDay:c.allDay},e.start.add(d),e.end&&e.end.add(d)):e={start:h.clone(),end:null,allDay:!h.hasTime()},e},applyDragOpacity:function(a){var b=this.view.opt("dragOpacity");null!=b&&a.each(function(a,c){c.style.opacity=b})},externalDragStart:function(b,c){var d,e,f=this.view;f.opt("droppable")&&(d=a((c?c.item:null)||b.target),e=f.opt("dropAccept"),(a.isFunction(e)?e.call(d[0],d):d.is(e))&&(this.isDraggingExternal||this.listenToExternalDrag(d,b,c)))},listenToExternalDrag:function(a,b,c){var d,e,f=this,i=ya(a);d=new gb(this.coordMap,{listenStart:function(){f.isDraggingExternal=!0},cellOver:function(a){e=f.computeExternalDrop(a,i),e?f.renderDrag(e):g()},cellOut:function(){e=null,f.unrenderDrag(),h()},dragStop:function(){f.unrenderDrag(),h(),e&&f.view.reportExternalDrop(i,e,a,b,c)},listenStop:function(){f.isDraggingExternal=!1}}),d.startDrag(b)},computeExternalDrop:function(a,b){var c={start:a.start.clone(),end:null};return b.startTime&&!c.start.hasTime()&&c.start.time(b.startTime),b.duration&&(c.end=c.start.clone().add(b.duration)),this.view.calendar.isExternalDropRangeAllowed(c,b.eventProps)?c:null},renderDrag:function(a,b){},unrenderDrag:function(){},segResizeMousedown:function(a,b,c){var d,e,f=this,i=this.view,j=i.calendar,k=a.el,l=a.event,m=j.getEventEnd(l);d=new gb(this.coordMap,{distance:5,scroll:i.opt("dragScroll"),subjectEl:k,dragStart:function(b){f.triggerSegMouseout(a,b),f.segResizeStart(a,b)},cellOver:function(b,d,h){e=c?f.computeEventStartResize(h,b,l):f.computeEventEndResize(h,b,l),e&&(j.isEventRangeAllowed(e,l)?e.start.isSame(l.start)&&e.end.isSame(m)&&(e=null):(g(),e=null)),e&&(i.hideEvent(l),f.renderEventResize(e,a))},cellOut:function(){e=null},cellDone:function(){f.unrenderEventResize(),i.showEvent(l),h()},dragStop:function(b){f.segResizeStop(a,b),e&&i.reportEventResize(l,e,this.largeUnit,k,b)}}),d.mousedown(b)},segResizeStart:function(a,b){this.isResizingSeg=!0,this.view.trigger("eventResizeStart",a.el[0],a.event,b,{})},segResizeStop:function(a,b){this.isResizingSeg=!1,this.view.trigger("eventResizeStop",a.el[0],a.event,b,{})},computeEventStartResize:function(a,b,c){return this.computeEventResize("start",a,b,c)},computeEventEndResize:function(a,b,c){return this.computeEventResize("end",a,b,c)},computeEventResize:function(a,b,c,d){var e,f,g=this.view.calendar,h=this.diffDates(c[a],b[a]);return e={start:d.start.clone(),end:g.getEventEnd(d),allDay:d.allDay},e.allDay&&N(h)&&(e.allDay=!1,g.normalizeEventRangeTimes(e)),e[a].add(h),e.start.isBefore(e.end)||(f=d.allDay?g.defaultAllDayEventDuration:g.defaultTimedEventDuration,this.cellDuration&&this.cellDurationj&&h.push({event:i,start:j,end:c.start}),j=c.end;return f>j&&h.push({event:i,start:j,end:f}),h},eventRangeToSegs:function(a,b){var c,d,e;for(a=this.view.calendar.ensureVisibleEventRange(a),c=b?b(a):this.rangeToSegs(a),d=0;db;b++)i+=this.dayRowHtml(b,a);for(this.el.html(i),this.rowEls=this.el.find(".fc-row"),this.dayEls=this.el.find(".fc-day"),c=0;h>c;c++)d=this.getCell(c),e.trigger("dayRender",null,d.start,this.dayEls.eq(c))},unrenderDates:function(){this.removeSegPopover()},renderBusinessHours:function(){var a=this.view.calendar.getBusinessHoursEvents(!0),b=this.eventsToSegs(a);this.renderFill("businessHours",b,"bgevent")},dayRowHtml:function(a,b){var c=this.view,d=["fc-row","fc-week",c.widgetContentClass];return b&&d.push("fc-rigid"),'
'+this.rowHtml("day",a)+'
'+(this.numbersVisible?""+this.rowHtml("number",a)+"":"")+"
"},dayCellHtml:function(a){return this.bgCellHtml(a)},computeColHeadFormat:function(){return this.rowCnt>1?"ddd":this.colCnt>1?this.view.opt("dayOfMonthFormat"):"dddd"},computeEventTimeFormat:function(){return this.view.opt("extraSmallTimeFormat")},computeDisplayEventEnd:function(){return 1==this.colCnt},rangeUpdated:function(){var a,b,c,d;if(this.updateCellDates(),a=this.cellDates,this.breakOnWeeks){for(b=a[0].day(),d=1;dd;d++)e=d*n,f=e+n-1,i=Math.max(e,b),j=Math.min(f,c),i=Math.ceil(i),j=Math.floor(j),j>=i&&(g=i===b,h=j===c,i-=e,j-=e,k={row:d,isStart:g,isEnd:h},l?(k.leftCol=n-j-1,k.rightCol=n-i-1):(k.leftCol=i,k.rightCol=j),o.push(k));return o},dateToCellOffset:function(a){var b=this.dayToCellOffsets,c=a.diff(this.start,"days");return 0>c?b[0]-1:c>=b.length?b[b.length-1]+1:b[c]},renderDrag:function(a,b){return this.renderHighlight(this.eventRangeToSegs(a)),b&&!b.el.closest(this.el).length?(this.renderRangeHelper(a,b),this.applyDragOpacity(this.helperEls),!0):void 0},unrenderDrag:function(){this.unrenderHighlight(),this.unrenderHelper()},renderEventResize:function(a,b){this.renderHighlight(this.eventRangeToSegs(a)),this.renderRangeHelper(a,b)},unrenderEventResize:function(){this.unrenderHighlight(),this.unrenderHelper()},renderHelper:function(b,c){var d,e=[],f=this.eventsToSegs([b]);f=this.renderFgSegEls(f),d=this.renderSegRows(f),this.rowEls.each(function(b,f){var g,h=a(f),i=a('
');g=c&&c.row===b?c.el.position().top:h.find(".fc-content-skeleton tbody").position().top,i.css("top",g).find("table").append(d[b].tbodyEl),h.append(i),e.push(i[0])}),this.helperEls=a(e)},unrenderHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},fillSegTag:"td",renderFill:function(b,c,d){var e,f,g,h=[];for(c=this.renderFillSegEls(b,c),e=0;e
'),f=e.find("tr"),h>0&&f.append(''),f.append(c.el.attr("colspan",i-h)),g>i&&f.append(''),this.bookendCells(f,b),e}});kb.mixin({rowStructs:null,unrenderEvents:function(){this.removeSegPopover(),jb.prototype.unrenderEvents.apply(this,arguments)},getEventSegs:function(){return jb.prototype.getEventSegs.call(this).concat(this.popoverSegs||[])},renderBgSegs:function(b){var c=a.grep(b,function(a){return a.event.allDay});return jb.prototype.renderBgSegs.call(this,c)},renderFgSegs:function(b){var c;return b=this.renderFgSegEls(b),c=this.rowStructs=this.renderSegRows(b),this.rowEls.each(function(b,d){a(d).find(".fc-content-skeleton > table").append(c[b].tbodyEl)}),b},unrenderFgSegs:function(){for(var a,b=this.rowStructs||[];a=b.pop();)a.tbodyEl.remove();this.rowStructs=null},renderSegRows:function(a){var b,c,d=[];for(b=this.groupSegRows(a),c=0;c'+Y(c)+"")),d=''+(Y(f.title||"")||" ")+"",'
'+(this.isRTL?d+" "+l:l+" "+d)+"
"+(h?'
':"")+(i?'
':"")+""},renderSegRow:function(b,c){function d(b){for(;b>g;)k=(r[e-1]||[])[g],k?k.attr("rowspan",parseInt(k.attr("rowspan")||1,10)+1):(k=a(""),h.append(k)),q[e][g]=k,r[e][g]=k,g++}var e,f,g,h,i,j,k,l=this.colCnt,m=this.buildSegLevels(c),n=Math.max(1,m.length),o=a(""),p=[],q=[],r=[];for(e=0;n>e;e++){if(f=m[e],g=0,h=a(""),p.push([]),q.push([]),r.push([]),f)for(i=0;i').append(j.el),j.leftCol!=j.rightCol?k.attr("colspan",j.rightCol-j.leftCol+1):r[e][g]=k;g<=j.rightCol;)q[e][g]=k,p[e][g]=j,g++;h.append(k)}d(l),this.bookendCells(h,"eventSkeleton"),o.append(h)}return{row:b,tbodyEl:o,cellMatrix:q,segMatrix:p,segLevels:m,segs:c}},buildSegLevels:function(a){var b,c,d,e=[];for(this.sortSegs(a),b=0;b td > :first-child").each(c),e.position().top+f>h)return d;return!1},limitRow:function(b,c){function d(d){for(;d>x;)e=u.getCell(b,x),k=u.getCellSegs(e,c),k.length&&(n=g[c-1][x],t=u.renderMoreLink(e,k),s=a("
").append(t),n.append(s),w.push(s[0])),x++}var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u=this,v=this.rowStructs[b],w=[],x=0;if(c&&c').attr("rowspan",o),k=m[q],e=this.getCell(b,j.leftCol+q),t=this.renderMoreLink(e,[j].concat(k)),s=a("
").append(t),r.append(s),p.push(r[0]),w.push(r[0]);n.addClass("fc-limited").after(a(p)),h.push(n[0])}}d(this.colCnt),v.moreEls=a(w),v.limitedEls=a(h)}}, +unlimitRow:function(a){var b=this.rowStructs[a];b.moreEls&&(b.moreEls.remove(),b.moreEls=null),b.limitedEls&&(b.limitedEls.removeClass("fc-limited"),b.limitedEls=null)},renderMoreLink:function(b,c){var d=this,e=this.view;return a('').text(this.getMoreLinkText(c.length)).on("click",function(f){var g=e.opt("eventLimitClick"),h=b.start,i=a(this),j=d.getCellDayEl(b),k=d.getCellSegs(b),l=d.resliceDaySegs(k,h),m=d.resliceDaySegs(c,h);"function"==typeof g&&(g=e.trigger("eventLimitClick",null,{date:h,dayEl:j,moreEl:i,segs:l,hiddenSegs:m},f)),"popover"===g?d.showSegPopover(b,i,l):"string"==typeof g&&e.calendar.zoomTo(h,g)})},showSegPopover:function(a,b,c){var d,e,f=this,g=this.view,h=b.parent();d=1==this.rowCnt?g.el:this.rowEls.eq(a.row),e={className:"fc-more-popover",content:this.renderSegPopoverContent(a,c),parentEl:this.el,top:d.offset().top,autoHide:!0,viewportConstrain:g.opt("popoverViewportConstrain"),hide:function(){f.segPopover.removeElement(),f.segPopover=null,f.popoverSegs=null}},this.isRTL?e.right=h.offset().left+h.outerWidth()+1:e.left=h.offset().left-1,this.segPopover=new cb(e),this.segPopover.show()},renderSegPopoverContent:function(b,c){var d,e=this.view,f=e.opt("theme"),g=b.start.format(e.opt("dayPopoverFormat")),h=a('
'+Y(g)+'
'),i=h.find(".fc-event-container");for(c=this.renderFgSegEls(c,!0),this.popoverSegs=c,d=0;d'+this.rowHtml("slotBg")+'
'+this.slatRowHtml()+"
"},slotBgCellHtml:function(a){return this.bgCellHtml(a)},slatRowHtml:function(){for(var a,c,d,e=this.view,f=this.isRTL,g="",h=b.duration(+this.minTime);h"+(c?""+Y(a.format(this.labelFormat))+"":"")+"",g+=""+(f?"":d)+''+(f?d:"")+"",h.add(this.slotDuration);return g},processOptions:function(){var c,d=this.view,e=d.opt("slotDuration"),f=d.opt("snapDuration");e=b.duration(e),f=f?b.duration(f):e,this.slotDuration=e,this.snapDuration=f,this.cellDuration=f,this.minTime=b.duration(d.opt("minTime")),this.maxTime=b.duration(d.opt("maxTime")),c=d.opt("slotLabelFormat"),a.isArray(c)&&(c=c[c.length-1]),this.labelFormat=c||d.opt("axisFormat")||d.opt("smallTimeFormat"),c=d.opt("slotLabelInterval"),this.labelInterval=c?b.duration(c):this.computeLabelInterval(e)},computeLabelInterval:function(a){var c,d,e;for(c=yb.length-1;c>=0;c--)if(d=b.duration(yb[c]),e=L(d,a),ba(e)&&e>1)return d;return b.duration(a)},computeColHeadFormat:function(){return this.colCnt>1?this.view.opt("dayOfMonthFormat"):"dddd"},computeEventTimeFormat:function(){return this.view.opt("noMeridiemTimeFormat")},computeDisplayEventEnd:function(){return!0},rangeUpdated:function(){var a,b=this.view,c=[];for(a=this.start.clone();a.isBefore(this.end);)c.push(a.clone()),a.add(1,"day"),a=b.skipHiddenDays(a);this.isRTL&&c.reverse(),this.colDates=c,this.colCnt=c.length,this.rowCnt=Math.ceil((this.maxTime-this.minTime)/this.snapDuration)},computeCellDate:function(a){var b=this.colDates[a.col],c=this.computeSnapTime(a.row);return b=this.view.calendar.rezoneDate(b),b.time(c),b},getColEl:function(a){return this.dayEls.eq(a)},computeSnapTime:function(a){return b.duration(this.minTime+this.snapDuration*a)},rangeToSegs:function(a){var b,c,d,e,f=this.colCnt,g=[];for(a={start:a.start.clone().stripZone(),end:a.end.clone().stripZone()},c=0;f>c;c++)d=this.colDates[c],e={start:d.clone().time(this.minTime),end:d.clone().time(this.maxTime)},b=E(a,e),b&&(b.col=c,g.push(b));return g},updateSize:function(a){this.computeSlatTops(),a&&this.updateSegVerticals()},computeRowCoords:function(){var a,b,c=this.el.offset().top,d=[];for(a=0;a0&&(d[a-1].bottom=b.top),d.push(b);return b.bottom=b.top+this.computeTimeTop(this.computeSnapTime(a)),d},computeDateTop:function(a,c){return this.computeTimeTop(b.duration(a.clone().stripZone()-c.clone().stripTime()))},computeTimeTop:function(a){var b,c,d,e,f=(a-this.minTime)/this.slotDuration;return f=Math.max(0,f),f=Math.min(this.slatEls.length,f),b=Math.floor(f),c=f-b,d=this.slatTops[b],c?(e=this.slatTops[b+1],d+(e-d)*c):d},computeSlatTops:function(){var b,c=[];this.slatEls.each(function(d,e){b=a(e).position().top,c.push(b)}),c.push(b+this.slatEls.last().outerHeight()),this.slatTops=c},renderDrag:function(a,b){return b?(this.renderRangeHelper(a,b),this.applyDragOpacity(this.helperEl),!0):void this.renderHighlight(this.eventRangeToSegs(a))},unrenderDrag:function(){this.unrenderHelper(),this.unrenderHighlight()},renderEventResize:function(a,b){this.renderRangeHelper(a,b)},unrenderEventResize:function(){this.unrenderHelper()},renderHelper:function(b,c){var d,e,f,g,h=this.eventsToSegs([b]);for(h=this.renderFgSegEls(h),d=this.renderSegTable(h),e=0;e').append(d).appendTo(this.el)},unrenderHelper:function(){this.helperEl&&(this.helperEl.remove(),this.helperEl=null)},renderSelection:function(a){this.view.opt("selectHelper")?this.renderRangeHelper(a):this.renderHighlight(this.selectionRangeToSegs(a))},unrenderSelection:function(){this.unrenderHelper(),this.unrenderHighlight()},renderFill:function(b,c,d){var e,f,g,h,i,j,k,l,m,n;if(c.length){for(c=this.renderFillSegEls(b,c),e=this.groupSegCols(c),d=d||b.toLowerCase(),f=a('
'),g=f.find("tr"),h=0;h").appendTo(g),i.length)for(k=a('
').appendTo(j),l=this.colDates[h],m=0;m').append(this.renderSegTable(b))),b},unrenderFgSegs:function(a){this.eventSkeletonEl&&(this.eventSkeletonEl.remove(),this.eventSkeletonEl=null)},renderSegTable:function(b){var c,d,e,f,g,h,i=a("
"),j=i.find("tr");for(c=this.groupSegCols(b),this.computeSegVerticals(b),f=0;f'),d=0;d").append(h))}return this.bookendCells(j,"eventSkeleton"),i},placeSlotSegs:function(a){var b,c,d;if(this.sortSegs(a),b=Ba(a),Ca(b),c=b[0]){for(d=0;d
'+(c?'
'+Y(c)+"
":"")+(g.title?'
'+Y(g.title)+"
":"")+'
'+(j?'
':"")+""},generateSegPositionCss:function(a){var b,c,d=this.view.opt("slotEventOverlap"),e=a.backwardCoord,f=a.forwardCoord,g=this.generateSegVerticalCss(a);return d&&(f=Math.min(1,e+2*(f-e))),this.isRTL?(b=1-f,c=e):(b=e,c=1-f),g.zIndex=a.level+1,g.left=100*b+"%",g.right=100*c+"%",d&&a.forwardPressure&&(g[this.isRTL?"marginLeft":"marginRight"]=20),g},generateSegVerticalCss:function(a){return{top:a.top,bottom:-a.bottom}},groupSegCols:function(a){var b,c=[];for(b=0;b1?"ll":"LL"},formatRange:function(a,b,c){var d=a.end;return d.hasTime()||(d=d.clone().subtract(1)),ma(a.start,d,b,c,this.opt("isRTL"))},setElement:function(a){this.el=a,this.bindGlobalHandlers()},removeElement:function(){this.clear(),this.isSkeletonRendered&&(this.unrenderSkeleton(),this.isSkeletonRendered=!1),this.unbindGlobalHandlers(),this.el.remove()},display:function(b){var c=this,d=null;return this.displaying&&(d=this.queryScroll()),this.clear().then(function(){return c.displaying=a.when(c.displayView(b)).then(function(){c.forceScroll(c.computeInitialScroll(d)),c.triggerRender()})})},clear:function(){var b=this,c=this.displaying;return c?c.then(function(){return b.displaying=null,b.clearEvents(),b.clearView()}):a.when()},displayView:function(a){this.isSkeletonRendered||(this.renderSkeleton(),this.isSkeletonRendered=!0),this.setDate(a),this.render&&this.render(),this.renderDates(),this.updateSize(),this.renderBusinessHours()},clearView:function(){this.unselect(),this.triggerUnrender(),this.unrenderBusinessHours(),this.unrenderDates(),this.destroy&&this.destroy()},renderSkeleton:function(){},unrenderSkeleton:function(){},renderDates:function(){},unrenderDates:function(){},renderBusinessHours:function(){},unrenderBusinessHours:function(){},triggerRender:function(){this.trigger("viewRender",this,this,this.el)},triggerUnrender:function(){this.trigger("viewDestroy",this,this,this.el)},bindGlobalHandlers:function(){a(document).on("mousedown",this.documentMousedownProxy)},unbindGlobalHandlers:function(){a(document).off("mousedown",this.documentMousedownProxy)},initThemingProps:function(){var a=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=a+"-widget-header",this.widgetContentClass=a+"-widget-content",this.highlightStateClass=a+"-state-highlight"},updateSize:function(a){var b;a&&(b=this.queryScroll()),this.updateHeight(a),this.updateWidth(a),a&&this.setScroll(b)},updateWidth:function(a){},updateHeight:function(a){var b=this.calendar;this.setHeight(b.getSuggestedViewHeight(),b.isHeightAuto())},setHeight:function(a,b){},computeScrollerHeight:function(a){var b,c,d=this.scrollerEl;return b=this.el.add(d),b.css({position:"relative",left:-1}),c=this.el.outerHeight()-d.height(),b.css({position:"",left:""}),a-c},computeInitialScroll:function(a){return 0},queryScroll:function(){return this.scrollerEl?this.scrollerEl.scrollTop():void 0},setScroll:function(a){return this.scrollerEl?this.scrollerEl.scrollTop(a):void 0},forceScroll:function(a){var b=this;this.setScroll(a),setTimeout(function(){b.setScroll(a)},0)},displayEvents:function(a){var b=this.queryScroll();this.clearEvents(),this.renderEvents(a),this.isEventsRendered=!0,this.setScroll(b),this.triggerEventRender()},clearEvents:function(){this.isEventsRendered&&(this.triggerEventUnrender(),this.destroyEvents&&this.destroyEvents(),this.unrenderEvents(),this.isEventsRendered=!1)},renderEvents:function(a){},unrenderEvents:function(){},triggerEventRender:function(){this.renderedEventSegEach(function(a){this.trigger("eventAfterRender",a.event,a.event,a.el)}),this.trigger("eventAfterAllRender")},triggerEventUnrender:function(){this.renderedEventSegEach(function(a){this.trigger("eventDestroy",a.event,a.event,a.el)})},resolveEventEl:function(b,c){var d=this.trigger("eventRender",b,b,c);return d===!1?c=null:d&&d!==!0&&(c=a(d)),c},showEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","")},a)},hideEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","hidden")},a)},renderedEventSegEach:function(a,b){var c,d=this.getEventSegs();for(c=0;cb;b++)(d[b]=-1!==a.inArray(b,c))||e++;if(!e)throw"invalid hiddenDays";this.isHiddenDayHash=d},isHiddenDay:function(a){return b.isMoment(a)&&(a=a.day()),this.isHiddenDayHash[a]},skipHiddenDays:function(a,b,c){var d=a.clone();for(b=b||1;this.isHiddenDayHash[(d.day()+(c?b:0)+7)%7];)d.add(b,"days");return d},computeDayRange:function(a){var b,c=a.start.clone().stripTime(),d=a.end,e=null;return d&&(e=d.clone().stripTime(),b=+d.time(),b&&b>=this.nextDayThreshold&&e.add(1,"days")),(!d||c>=e)&&(e=c.clone().add(1,"days")),{start:c,end:e}},isMultiDayEvent:function(a){var b=this.computeDayRange(a);return b.end.diff(b.start,"days")>1}}),nb=Ma.Calendar=ra.extend({dirDefaults:null,langDefaults:null,overrides:null,options:null,viewSpecCache:null,view:null,header:null,loadingLevel:0,constructor:Ga,initialize:function(){},initOptions:function(a){var b,e,f,g;a=d(a),b=a.lang,e=ob[b],e||(b=nb.defaults.lang,e=ob[b]||{}),f=X(a.isRTL,e.isRTL,nb.defaults.isRTL),g=f?nb.rtlDefaults:{},this.dirDefaults=g,this.langDefaults=e,this.overrides=a,this.options=c([nb.defaults,g,e,a]),Ha(this.options),this.viewSpecCache={}},getViewSpec:function(a){var b=this.viewSpecCache;return b[a]||(b[a]=this.buildViewSpec(a))},getUnitViewSpec:function(b){var c,d,e;if(-1!=a.inArray(b,Ra))for(c=this.header.getViewsWithButtons(),a.each(Ma.views,function(a){c.push(a)}),d=0;d1,this.weekNumbersVisible=this.opt("weekNumbers"),this.dayGrid.numbersVisible=this.dayNumbersVisible||this.weekNumbersVisible,this.el.addClass("fc-basic-view").html(this.renderHtml()),this.headRowEl=this.el.find("thead .fc-row"),this.scrollerEl=this.el.find(".fc-day-grid-container"),this.dayGrid.coordMap.containerEl=this.scrollerEl,this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.renderDates(this.hasRigidRows())},unrenderDates:function(){this.dayGrid.unrenderDates(),this.dayGrid.removeElement()},renderBusinessHours:function(){this.dayGrid.renderBusinessHours()},renderHtml:function(){return'
'+this.dayGrid.headHtml()+'
'},headIntroHtml:function(){return this.weekNumbersVisible?'"+Y(this.opt("weekNumberTitle"))+"":void 0},numberIntroHtml:function(a){return this.weekNumbersVisible?'"+this.dayGrid.getCell(a,0).start.format("w")+"":void 0},dayIntroHtml:function(){return this.weekNumbersVisible?'":void 0},introHtml:function(){return this.weekNumbersVisible?'":void 0},numberCellHtml:function(a){var b,c=a.start;return this.dayNumbersVisible?(b=this.dayGrid.getDayClasses(c),b.unshift("fc-day-number"),''+c.date()+""):""},weekNumberStyleAttr:function(){return null!==this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},hasRigidRows:function(){var a=this.opt("eventLimit");return a&&"number"!=typeof a},updateWidth:function(){this.weekNumbersVisible&&(this.weekNumberWidth=k(this.el.find(".fc-week-number")))},setHeight:function(a,b){var c,d=this.opt("eventLimit");m(this.scrollerEl),f(this.headRowEl),this.dayGrid.removeSegPopover(),d&&"number"==typeof d&&this.dayGrid.limitRows(d),c=this.computeScrollerHeight(a),this.setGridHeight(c,b),d&&"number"!=typeof d&&this.dayGrid.limitRows(d),!b&&l(this.scrollerEl,c)&&(e(this.headRowEl,r(this.scrollerEl)),c=this.computeScrollerHeight(a),this.scrollerEl.height(c))},setGridHeight:function(a,b){b?j(this.dayGrid.rowEls):i(this.dayGrid.rowEls,a,!0)},renderEvents:function(a){this.dayGrid.renderEvents(a),this.updateHeight()},getEventSegs:function(){return this.dayGrid.getEventSegs()},unrenderEvents:function(){this.dayGrid.unrenderEvents()},renderDrag:function(a,b){return this.dayGrid.renderDrag(a,b)},unrenderDrag:function(){this.dayGrid.unrenderDrag()},renderSelection:function(a){this.dayGrid.renderSelection(a)},unrenderSelection:function(){this.dayGrid.unrenderSelection()}}),vb=ub.extend({computeRange:function(a){var b,c=ub.prototype.computeRange.call(this,a);return this.isFixedWeeks()&&(b=Math.ceil(c.end.diff(c.start,"weeks",!0)),c.end.add(6-b,"weeks")),c},setGridHeight:function(a,b){b=b||"variable"===this.opt("weekMode"),b&&(a*=this.rowCnt/6),i(this.dayGrid.rowEls,a,!b)},isFixedWeeks:function(){var a=this.opt("weekMode");return a?"fixed"===a:this.opt("fixedWeekCount")}});Na.basic={"class":ub},Na.basicDay={type:"basic",duration:{days:1}},Na.basicWeek={type:"basic",duration:{weeks:1}},Na.month={"class":vb,duration:{months:1},defaults:{fixedWeekCount:!0}};var wb=mb.extend({timeGrid:null,dayGrid:null,axisWidth:null,noScrollRowEls:null,bottomRuleEl:null,bottomRuleHeight:null,initialize:function(){this.timeGrid=new lb(this),this.opt("allDaySlot")?(this.dayGrid=new kb(this),this.coordMap=new eb([this.dayGrid.coordMap,this.timeGrid.coordMap])):this.coordMap=this.timeGrid.coordMap},setRange:function(a){mb.prototype.setRange.call(this,a),this.timeGrid.setRange(a),this.dayGrid&&this.dayGrid.setRange(a)},renderDates:function(){this.el.addClass("fc-agenda-view").html(this.renderHtml()),this.scrollerEl=this.el.find(".fc-time-grid-container"),this.timeGrid.coordMap.containerEl=this.scrollerEl,this.timeGrid.setElement(this.el.find(".fc-time-grid")),this.timeGrid.renderDates(),this.bottomRuleEl=a('
').appendTo(this.timeGrid.el),this.dayGrid&&(this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.renderDates(),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight()),this.noScrollRowEls=this.el.find(".fc-row:not(.fc-scroller *)")},unrenderDates:function(){this.timeGrid.unrenderDates(),this.timeGrid.removeElement(),this.dayGrid&&(this.dayGrid.unrenderDates(),this.dayGrid.removeElement())},renderBusinessHours:function(){this.timeGrid.renderBusinessHours(),this.dayGrid&&this.dayGrid.renderBusinessHours()},renderHtml:function(){return'
'+this.timeGrid.headHtml()+'
'+(this.dayGrid?'

':"")+'
'},headIntroHtml:function(){var a,b;return this.opt("weekNumbers")?(a=this.timeGrid.getCell(0).start,b=a.format(this.opt("smallWeekFormat")),'"+Y(b)+""):'"},dayIntroHtml:function(){return'"+(this.opt("allDayHtml")||Y(this.opt("allDayText")))+""},slotBgIntroHtml:function(){return'"; +},introHtml:function(){return'"},axisStyleAttr:function(){return null!==this.axisWidth?'style="width:'+this.axisWidth+'px"':""},updateSize:function(a){this.timeGrid.updateSize(a),mb.prototype.updateSize.call(this,a)},updateWidth:function(){this.axisWidth=k(this.el.find(".fc-axis"))},setHeight:function(a,b){var c,d;null===this.bottomRuleHeight&&(this.bottomRuleHeight=this.bottomRuleEl.outerHeight()),this.bottomRuleEl.hide(),this.scrollerEl.css("overflow",""),m(this.scrollerEl),f(this.noScrollRowEls),this.dayGrid&&(this.dayGrid.removeSegPopover(),c=this.opt("eventLimit"),c&&"number"!=typeof c&&(c=xb),c&&this.dayGrid.limitRows(c)),b||(d=this.computeScrollerHeight(a),l(this.scrollerEl,d)?(e(this.noScrollRowEls,r(this.scrollerEl)),d=this.computeScrollerHeight(a),this.scrollerEl.height(d)):(this.scrollerEl.height(d).css("overflow","hidden"),this.bottomRuleEl.show()))},computeInitialScroll:function(){var a=b.duration(this.opt("scrollTime")),c=this.timeGrid.computeTimeTop(a);return c=Math.ceil(c),c&&c++,c},renderEvents:function(a){var b,c,d=[],e=[],f=[];for(c=0;c 1) { + attributes = extend({ + path: '/' + }, api.defaults, attributes); + + if (typeof attributes.expires === 'number') { + var expires = new Date(); + expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); + attributes.expires = expires; + } + + try { + result = JSON.stringify(value); + if (/^[\{\[]/.test(result)) { + value = result; + } + } catch (e) {} + + value = encodeURIComponent(String(value)); + value = value.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); + + key = encodeURIComponent(String(key)); + key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); + key = key.replace(/[\(\)]/g, escape); + + return (document.cookie = [ + key, '=', value, + attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE + attributes.path && '; path=' + attributes.path, + attributes.domain && '; domain=' + attributes.domain, + attributes.secure ? '; secure' : '' + ].join('')); + } + + // Read + + if (!key) { + result = {}; + } + + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. Also prevents odd result when + // calling "get()" + var cookies = document.cookie ? document.cookie.split('; ') : []; + var rdecode = /(%[0-9A-Z]{2})+/g; + var i = 0; + + for (; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var name = parts[0].replace(rdecode, decodeURIComponent); + var cookie = parts.slice(1).join('='); + + if (cookie.charAt(0) === '"') { + cookie = cookie.slice(1, -1); + } + + try { + cookie = converter && converter(cookie, name) || cookie.replace(rdecode, decodeURIComponent); + + if (this.json) { + try { + cookie = JSON.parse(cookie); + } catch (e) {} + } + + if (key === name) { + result = cookie; + break; + } + + if (!key) { + result[name] = cookie; + } + } catch (e) {} + } + + return result; + } + + api.get = api.set = api; + api.getJSON = function () { + return api.apply({ + json: true + }, [].slice.call(arguments)); + }; + api.defaults = {}; + + api.remove = function (key, attributes) { + api(key, '', extend(attributes, { + expires: -1 + })); + }; + + api.withConverter = init; + + return api; + } + + return init(); +})); diff --git a/website/events/static/events/js/moment.js b/website/events/static/events/js/moment.js new file mode 100644 index 00000000..c10f5dd4 --- /dev/null +++ b/website/events/static/events/js/moment.js @@ -0,0 +1,7 @@ +//! moment.js +//! version : 2.10.2 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Ac.apply(null,arguments)}function b(a){Ac=a}function c(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function d(a){return"[object Array]"===Object.prototype.toString.call(a)}function e(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function f(a,b){var c,d=[];for(c=0;c0)for(c in Cc)d=Cc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function m(b){l(this,b),this._d=new Date(+b._d),Dc===!1&&(Dc=!0,a.updateOffset(this),Dc=!1)}function n(a){return a instanceof m||null!=a&&g(a,"_isAMomentObject")}function o(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function p(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&o(a[d])!==o(b[d]))&&g++;return g+f}function q(){}function r(a){return a?a.toLowerCase().replace("_","-"):a}function s(a){for(var b,c,d,e,f=0;f0;){if(d=t(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&p(e,c,!0)>=b-1)break;b--}f++}return null}function t(a){var b=null;if(!Ec[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Bc._abbr,require("./locale/"+a),u(b)}catch(c){}return Ec[a]}function u(a,b){var c;return a&&(c="undefined"==typeof b?w(a):v(a,b),c&&(Bc=c)),Bc._abbr}function v(a,b){return null!==b?(b.abbr=a,Ec[a]||(Ec[a]=new q),Ec[a].set(b),u(a),Ec[a]):(delete Ec[a],null)}function w(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Bc;if(!d(a)){if(b=t(a))return b;a=[a]}return s(a)}function x(a,b){var c=a.toLowerCase();Fc[c]=Fc[c+"s"]=Fc[b]=a}function y(a){return"string"==typeof a?Fc[a]||Fc[a.toLowerCase()]:void 0}function z(a){var b,c,d={};for(c in a)g(a,c)&&(b=y(c),b&&(d[b]=a[c]));return d}function A(b,c){return function(d){return null!=d?(C(this,b,d),a.updateOffset(this,c),this):B(this,b)}}function B(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function C(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function D(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=y(a),"function"==typeof this[a])return this[a](b);return this}function E(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.lengthb;b++)d[b]=Jc[d[b]]?Jc[d[b]]:G(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function I(a,b){return a.isValid()?(b=J(b,a.localeData()),Ic[b]||(Ic[b]=H(b)),Ic[b](a)):a.localeData().invalidDate()}function J(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Hc.lastIndex=0;d>=0&&Hc.test(a);)a=a.replace(Hc,c),Hc.lastIndex=0,d-=1;return a}function K(a,b,c){Yc[a]="function"==typeof b?b:function(a){return a&&c?c:b}}function L(a,b){return g(Yc,a)?Yc[a](b._strict,b._locale):new RegExp(M(a))}function M(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function N(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=o(a)}),c=0;cd;d++){if(e=i([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function U(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),Q(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function V(b){return null!=b?(U(this,b),a.updateOffset(this,!0),this):B(this,"Month")}function W(){return Q(this.year(),this.month())}function X(a){var b,c=a._a;return c&&-2===a._pf.overflow&&(b=c[_c]<0||c[_c]>11?_c:c[ad]<1||c[ad]>Q(c[$c],c[_c])?ad:c[bd]<0||c[bd]>24||24===c[bd]&&(0!==c[cd]||0!==c[dd]||0!==c[ed])?bd:c[cd]<0||c[cd]>59?cd:c[dd]<0||c[dd]>59?dd:c[ed]<0||c[ed]>999?ed:-1,a._pf._overflowDayOfYear&&($c>b||b>ad)&&(b=ad),a._pf.overflow=b),a}function Y(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function Z(a,b){var c=!0;return h(function(){return c&&(Y(a),c=!1),b.apply(this,arguments)},b)}function $(a,b){hd[a]||(Y(b),hd[a]=!0)}function _(a){var b,c,d=a._i,e=id.exec(d);if(e){for(a._pf.iso=!0,b=0,c=jd.length;c>b;b++)if(jd[b][1].exec(d)){a._f=jd[b][0]+(e[6]||" ");break}for(b=0,c=kd.length;c>b;b++)if(kd[b][1].exec(d)){a._f+=kd[b][0];break}d.match(Vc)&&(a._f+="Z"),sa(a)}else a._isValid=!1}function aa(b){var c=ld.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(_(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ba(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function ca(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function da(a){return ea(a)?366:365}function ea(a){return a%4===0&&a%100!==0||a%400===0}function fa(){return ea(this.year())}function ga(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=za(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ha(a){return ga(a,this._week.dow,this._week.doy).week}function ia(){return this._week.dow}function ja(){return this._week.doy}function ka(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function la(a){var b=ga(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function ma(a,b,c,d,e){var f,g,h=ca(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:da(a-1)+g}}function na(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function oa(a,b,c){return null!=a?a:null!=b?b:c}function pa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function qa(a){var b,c,d,e,f=[];if(!a._d){for(d=pa(a),a._w&&null==a._a[ad]&&null==a._a[_c]&&ra(a),a._dayOfYear&&(e=oa(a._a[$c],d[$c]),a._dayOfYear>da(e)&&(a._pf._overflowDayOfYear=!0),c=ca(e,0,a._dayOfYear),a._a[_c]=c.getUTCMonth(),a._a[ad]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[bd]&&0===a._a[cd]&&0===a._a[dd]&&0===a._a[ed]&&(a._nextDay=!0,a._a[bd]=0),a._d=(a._useUTC?ca:ba).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[bd]=24)}}function ra(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=oa(b.GG,a._a[$c],ga(za(),1,4).year),d=oa(b.W,1),e=oa(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=oa(b.gg,a._a[$c],ga(za(),f,g).year),d=oa(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=ma(c,d,e,g,f),a._a[$c]=h.year,a._dayOfYear=h.dayOfYear}function sa(b){if(b._f===a.ISO_8601)return void _(b);b._a=[],b._pf.empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=J(b._f,b._locale).match(Gc)||[],c=0;c0&&b._pf.unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),Jc[f]?(d?b._pf.empty=!1:b._pf.unusedTokens.push(f),P(f,d,b)):b._strict&&!d&&b._pf.unusedTokens.push(f);b._pf.charsLeftOver=i-j,h.length>0&&b._pf.unusedInput.push(h),b._pf.bigHour===!0&&b._a[bd]<=12&&(b._pf.bigHour=void 0),b._a[bd]=ta(b._locale,b._a[bd],b._meridiem),qa(b),X(b)}function ta(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function ua(a){var b,d,e,f,g;if(0===a._f.length)return a._pf.invalidFormat=!0,void(a._d=new Date(0/0));for(f=0;fg)&&(e=g,d=b));h(a,d||b)}function va(a){if(!a._d){var b=z(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],qa(a)}}function wa(a){var b,c=a._i,e=a._f;return a._locale=a._locale||w(a._l),null===c||void 0===e&&""===c?k({nullInput:!0}):("string"==typeof c&&(a._i=c=a._locale.preparse(c)),n(c)?new m(X(c)):(d(e)?ua(a):e?sa(a):xa(a),b=new m(X(a)),b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b))}function xa(b){var c=b._i;void 0===c?b._d=new Date:e(c)?b._d=new Date(+c):"string"==typeof c?aa(b):d(c)?(b._a=f(c.slice(0),function(a){return parseInt(a,10)}),qa(b)):"object"==typeof c?va(b):"number"==typeof c?b._d=new Date(c):a.createFromInputFallback(b)}function ya(a,b,d,e,f){var g={};return"boolean"==typeof d&&(e=d,d=void 0),g._isAMomentObject=!0,g._useUTC=g._isUTC=f,g._l=d,g._i=a,g._f=b,g._strict=e,g._pf=c(),wa(g)}function za(a,b,c,d){return ya(a,b,c,d,!1)}function Aa(a,b){var c,e;if(1===b.length&&d(b[0])&&(b=b[0]),!b.length)return za();for(c=b[0],e=1;ea&&(a=-a,c="-"),c+E(~~(a/60),2)+b+E(~~a%60,2)})}function Ga(a){var b=(a||"").match(Vc)||[],c=b[b.length-1]||[],d=(c+"").match(qd)||["-",0,0],e=+(60*d[1])+o(d[2]);return"+"===d[0]?e:-e}function Ha(b,c){var d,f;return c._isUTC?(d=c.clone(),f=(n(b)||e(b)?+b:+za(b))-+d,d._d.setTime(+d._d+f),a.updateOffset(d,!1),d):za(b).local();return c._isUTC?za(b).zone(c._offset||0):za(b).local()}function Ia(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ja(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ga(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ia(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?Za(this,Ua(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ia(this)}function Ka(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function La(a){return this.utcOffset(0,a)}function Ma(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ia(this),"m")),this}function Na(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ga(this._i)),this}function Oa(a){return a=a?za(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Pa(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Qa(){if(this._a){var a=this._isUTC?i(this._a):za(this._a);return this.isValid()&&p(this._a,a.toArray())>0}return!1}function Ra(){return!this._isUTC}function Sa(){return this._isUTC}function Ta(){return this._isUTC&&0===this._offset}function Ua(a,b){var c,d,e,f=a,h=null;return Ea(a)?f={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(f={},b?f[b]=a:f.milliseconds=a):(h=rd.exec(a))?(c="-"===h[1]?-1:1,f={y:0,d:o(h[ad])*c,h:o(h[bd])*c,m:o(h[cd])*c,s:o(h[dd])*c,ms:o(h[ed])*c}):(h=sd.exec(a))?(c="-"===h[1]?-1:1,f={y:Va(h[2],c),M:Va(h[3],c),d:Va(h[4],c),h:Va(h[5],c),m:Va(h[6],c),s:Va(h[7],c),w:Va(h[8],c)}):null==f?f={}:"object"==typeof f&&("from"in f||"to"in f)&&(e=Xa(za(f.from),za(f.to)),f={},f.ms=e.milliseconds,f.M=e.months),d=new Da(f),Ea(a)&&g(a,"_locale")&&(d._locale=a._locale),d}function Va(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function Wa(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Xa(a,b){var c;return b=Ha(b,a),a.isBefore(b)?c=Wa(a,b):(c=Wa(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function Ya(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||($(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ua(c,d),Za(this,e,a),this}}function Za(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&C(b,"Date",B(b,"Date")+g*d),h&&U(b,B(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function $a(a){var b=a||za(),c=Ha(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,za(b)))}function _a(){return new m(this)}function ab(a,b){var c;return b=y("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=n(a)?a:za(a),+this>+a):(c=n(a)?+a:+za(a),c<+this.clone().startOf(b))}function bb(a,b){var c;return b=y("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=n(a)?a:za(a),+a>+this):(c=n(a)?+a:+za(a),+this.clone().endOf(b)a?Math.ceil(a):Math.floor(a)}function fb(a,b,c){var d,e,f=Ha(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=y(b),"year"===b||"month"===b||"quarter"===b?(e=gb(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:eb(e)}function gb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function hb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function ib(){var a=this.clone().utc();return 0b;b++)if(this._weekdaysParse[b]||(c=za([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Jb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Eb(a,this.localeData()),this.add(a-b,"d")):b}function Kb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Lb(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Mb(a,b){F(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Nb(a,b){return b._meridiemParse}function Ob(a){return"p"===(a+"").toLowerCase().charAt(0)}function Pb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Qb(a){F(0,[a,3],0,"millisecond")}function Rb(){return this._isUTC?"UTC":""}function Sb(){return this._isUTC?"Coordinated Universal Time":""}function Tb(a){return za(1e3*a)}function Ub(){return za.apply(null,arguments).parseZone()}function Vb(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function Wb(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b}function Xb(){return this._invalidDate}function Yb(a){return this._ordinal.replace("%d",a)}function Zb(a){return a}function $b(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function _b(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function ac(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function bc(a,b,c,d){var e=w(),f=i().set(d,b);return e[c](f,a)}function cc(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return bc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=bc(a,f,c,e);return g}function dc(a,b){return cc(a,b,"months",12,"month")}function ec(a,b){return cc(a,b,"monthsShort",12,"month")}function fc(a,b){return cc(a,b,"weekdays",7,"day")}function gc(a,b){return cc(a,b,"weekdaysShort",7,"day")}function hc(a,b){return cc(a,b,"weekdaysMin",7,"day")}function ic(){var a=this._data;return this._milliseconds=Od(this._milliseconds),this._days=Od(this._days),this._months=Od(this._months),a.milliseconds=Od(a.milliseconds),a.seconds=Od(a.seconds),a.minutes=Od(a.minutes),a.hours=Od(a.hours),a.months=Od(a.months),a.years=Od(a.years),this}function jc(a,b,c,d){var e=Ua(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function kc(a,b){return jc(this,a,b,1)}function lc(a,b){return jc(this,a,b,-1)}function mc(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;return g.milliseconds=d%1e3,a=eb(d/1e3),g.seconds=a%60,b=eb(a/60),g.minutes=b%60,c=eb(b/60),g.hours=c%24,e+=eb(c/24),h=eb(nc(e)),e-=eb(oc(h)),f+=eb(e/30),e%=30,h+=eb(f/12),f%=12,g.days=e,g.months=f,g.years=h,this}function nc(a){return 400*a/146097}function oc(a){return 146097*a/400}function pc(a){var b,c,d=this._milliseconds;if(a=y(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+12*nc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(oc(this._months/12)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 24*b*60+d/6e4;case"second":return 24*b*60*60+d/1e3;case"millisecond":return Math.floor(24*b*60*60*1e3)+d;default:throw new Error("Unknown unit "+a)}}function qc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*o(this._months/12)}function rc(a){return function(){return this.as(a)}}function sc(a){return a=y(a),this[a+"s"]()}function tc(a){return function(){return this._data[a]}}function uc(){return eb(this.days()/7)}function vc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function wc(a,b,c){var d=Ua(a).abs(),e=ce(d.as("s")),f=ce(d.as("m")),g=ce(d.as("h")),h=ce(d.as("d")),i=ce(d.as("M")),j=ce(d.as("y")),k=e0,k[4]=c,vc.apply(null,k)}function xc(a,b){return void 0===de[a]?!1:void 0===b?de[a]:(de[a]=b,!0)}function yc(a){var b=this.localeData(),c=wc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function zc(){var a=ee(this.years()),b=ee(this.months()),c=ee(this.days()),d=ee(this.hours()),e=ee(this.minutes()),f=ee(this.seconds()+this.milliseconds()/1e3),g=this.asSeconds();return g?(0>g?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"}var Ac,Bc,Cc=a.momentProperties=[],Dc=!1,Ec={},Fc={},Gc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g,Hc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Ic={},Jc={},Kc=/\d/,Lc=/\d\d/,Mc=/\d{3}/,Nc=/\d{4}/,Oc=/[+-]?\d{6}/,Pc=/\d\d?/,Qc=/\d{1,3}/,Rc=/\d{1,4}/,Sc=/[+-]?\d{1,6}/,Tc=/\d+/,Uc=/[+-]?\d+/,Vc=/Z|[+-]\d\d:?\d\d/gi,Wc=/[+-]?\d+(\.\d{1,3})?/,Xc=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Yc={},Zc={},$c=0,_c=1,ad=2,bd=3,cd=4,dd=5,ed=6;F("M",["MM",2],"Mo",function(){return this.month()+1}),F("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),F("MMMM",0,0,function(a){return this.localeData().months(this,a)}),x("month","M"),K("M",Pc),K("MM",Pc,Lc),K("MMM",Xc),K("MMMM",Xc),N(["M","MM"],function(a,b){b[_c]=o(a)-1}),N(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[_c]=e:c._pf.invalidMonth=a});var fd="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),gd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),hd={};a.suppressDeprecationWarnings=!1;var id=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,jd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],kd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ld=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=Z("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),F(0,["YY",2],0,function(){return this.year()%100}),F(0,["YYYY",4],0,"year"),F(0,["YYYYY",5],0,"year"),F(0,["YYYYYY",6,!0],0,"year"),x("year","y"),K("Y",Uc),K("YY",Pc,Lc),K("YYYY",Rc,Nc),K("YYYYY",Sc,Oc),K("YYYYYY",Sc,Oc),N(["YYYY","YYYYY","YYYYYY"],$c),N("YY",function(b,c){c[$c]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return o(a)+(o(a)>68?1900:2e3)};var md=A("FullYear",!1);F("w",["ww",2],"wo","week"),F("W",["WW",2],"Wo","isoWeek"),x("week","w"),x("isoWeek","W"),K("w",Pc),K("ww",Pc,Lc),K("W",Pc),K("WW",Pc,Lc),O(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=o(a)});var nd={dow:0,doy:6};F("DDD",["DDDD",3],"DDDo","dayOfYear"),x("dayOfYear","DDD"),K("DDD",Qc),K("DDDD",Mc),N(["DDD","DDDD"],function(a,b,c){c._dayOfYear=o(a)}),a.ISO_8601=function(){};var od=Z("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=za.apply(null,arguments);return this>a?this:a}),pd=Z("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=za.apply(null,arguments);return a>this?this:a});Fa("Z",":"),Fa("ZZ",""),K("Z",Vc),K("ZZ",Vc),N(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ga(a)});var qd=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var rd=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,sd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Ua.fn=Da.prototype;var td=Ya(1,"add"),ud=Ya(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var vd=Z("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});F(0,["gg",2],0,function(){return this.weekYear()%100}),F(0,["GG",2],0,function(){return this.isoWeekYear()%100}),xb("gggg","weekYear"),xb("ggggg","weekYear"),xb("GGGG","isoWeekYear"),xb("GGGGG","isoWeekYear"),x("weekYear","gg"),x("isoWeekYear","GG"),K("G",Uc),K("g",Uc),K("GG",Pc,Lc),K("gg",Pc,Lc),K("GGGG",Rc,Nc),K("gggg",Rc,Nc),K("GGGGG",Sc,Oc),K("ggggg",Sc,Oc),O(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=o(a)}),O(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),F("Q",0,0,"quarter"),x("quarter","Q"),K("Q",Kc),N("Q",function(a,b){b[_c]=3*(o(a)-1)}),F("D",["DD",2],"Do","date"),x("date","D"),K("D",Pc),K("DD",Pc,Lc),K("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),N(["D","DD"],ad),N("Do",function(a,b){b[ad]=o(a.match(Pc)[0],10)});var wd=A("Date",!0);F("d",0,"do","day"),F("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),F("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),F("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),F("e",0,0,"weekday"),F("E",0,0,"isoWeekday"),x("day","d"),x("weekday","e"),x("isoWeekday","E"),K("d",Pc),K("e",Pc),K("E",Pc),K("dd",Xc),K("ddd",Xc),K("dddd",Xc),O(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:c._pf.invalidWeekday=a}),O(["d","e","E"],function(a,b,c,d){b[d]=o(a)});var xd="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),yd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),zd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");F("H",["HH",2],0,"hour"),F("h",["hh",2],0,function(){return this.hours()%12||12}),Mb("a",!0),Mb("A",!1),x("hour","h"),K("a",Nb),K("A",Nb),K("H",Pc),K("h",Pc),K("HH",Pc,Lc),K("hh",Pc,Lc),N(["H","HH"],bd),N(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),N(["h","hh"],function(a,b,c){b[bd]=o(a),c._pf.bigHour=!0});var Ad=/[ap]\.?m?\.?/i,Bd=A("Hours",!0);F("m",["mm",2],0,"minute"),x("minute","m"),K("m",Pc),K("mm",Pc,Lc),N(["m","mm"],cd);var Cd=A("Minutes",!1);F("s",["ss",2],0,"second"),x("second","s"),K("s",Pc),K("ss",Pc,Lc),N(["s","ss"],dd);var Dd=A("Seconds",!1);F("S",0,0,function(){return~~(this.millisecond()/100)}),F(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),Qb("SSS"),Qb("SSSS"),x("millisecond","ms"),K("S",Qc,Kc),K("SS",Qc,Lc),K("SSS",Qc,Mc),K("SSSS",Tc),N(["S","SS","SSS","SSSS"],function(a,b){b[ed]=o(1e3*("0."+a))});var Ed=A("Milliseconds",!1);F("z",0,0,"zoneAbbr"),F("zz",0,0,"zoneName");var Fd=m.prototype;Fd.add=td,Fd.calendar=$a,Fd.clone=_a,Fd.diff=fb,Fd.endOf=pb,Fd.format=jb,Fd.from=kb,Fd.fromNow=lb,Fd.get=D,Fd.invalidAt=wb,Fd.isAfter=ab,Fd.isBefore=bb,Fd.isBetween=cb,Fd.isSame=db,Fd.isValid=ub,Fd.lang=vd,Fd.locale=mb,Fd.localeData=nb,Fd.max=pd,Fd.min=od,Fd.parsingFlags=vb,Fd.set=D,Fd.startOf=ob,Fd.subtract=ud,Fd.toArray=tb,Fd.toDate=sb,Fd.toISOString=ib,Fd.toJSON=ib,Fd.toString=hb,Fd.unix=rb,Fd.valueOf=qb,Fd.year=md,Fd.isLeapYear=fa,Fd.weekYear=zb,Fd.isoWeekYear=Ab,Fd.quarter=Fd.quarters=Db,Fd.month=V,Fd.daysInMonth=W,Fd.week=Fd.weeks=ka,Fd.isoWeek=Fd.isoWeeks=la,Fd.weeksInYear=Cb,Fd.isoWeeksInYear=Bb,Fd.date=wd,Fd.day=Fd.days=Jb,Fd.weekday=Kb,Fd.isoWeekday=Lb,Fd.dayOfYear=na,Fd.hour=Fd.hours=Bd,Fd.minute=Fd.minutes=Cd,Fd.second=Fd.seconds=Dd,Fd.millisecond=Fd.milliseconds=Ed,Fd.utcOffset=Ja,Fd.utc=La,Fd.local=Ma,Fd.parseZone=Na,Fd.hasAlignedHourOffset=Oa,Fd.isDST=Pa,Fd.isDSTShifted=Qa,Fd.isLocal=Ra,Fd.isUtcOffset=Sa,Fd.isUtc=Ta,Fd.isUTC=Ta,Fd.zoneAbbr=Rb,Fd.zoneName=Sb,Fd.dates=Z("dates accessor is deprecated. Use date instead.",wd),Fd.months=Z("months accessor is deprecated. Use month instead",V),Fd.years=Z("years accessor is deprecated. Use year instead",md),Fd.zone=Z("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Ka);var Gd=Fd,Hd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Id={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},Jd="Invalid date",Kd="%d",Ld=/\d{1,2}/,Md={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Nd=q.prototype;Nd._calendar=Hd,Nd.calendar=Vb,Nd._longDateFormat=Id,Nd.longDateFormat=Wb,Nd._invalidDate=Jd,Nd.invalidDate=Xb,Nd._ordinal=Kd,Nd.ordinal=Yb,Nd._ordinalParse=Ld, + Nd.preparse=Zb,Nd.postformat=Zb,Nd._relativeTime=Md,Nd.relativeTime=$b,Nd.pastFuture=_b,Nd.set=ac,Nd.months=R,Nd._months=fd,Nd.monthsShort=S,Nd._monthsShort=gd,Nd.monthsParse=T,Nd.week=ha,Nd._week=nd,Nd.firstDayOfYear=ja,Nd.firstDayOfWeek=ia,Nd.weekdays=Fb,Nd._weekdays=xd,Nd.weekdaysMin=Hb,Nd._weekdaysMin=zd,Nd.weekdaysShort=Gb,Nd._weekdaysShort=yd,Nd.weekdaysParse=Ib,Nd.isPM=Ob,Nd._meridiemParse=Ad,Nd.meridiem=Pb,u("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===o(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=Z("moment.lang is deprecated. Use moment.locale instead.",u),a.langData=Z("moment.langData is deprecated. Use moment.localeData instead.",w);var Od=Math.abs,Pd=rc("ms"),Qd=rc("s"),Rd=rc("m"),Sd=rc("h"),Td=rc("d"),Ud=rc("w"),Vd=rc("M"),Wd=rc("y"),Xd=tc("milliseconds"),Yd=tc("seconds"),Zd=tc("minutes"),$d=tc("hours"),_d=tc("days"),ae=tc("months"),be=tc("years"),ce=Math.round,de={s:45,m:45,h:22,d:26,M:11},ee=Math.abs,fe=Da.prototype;fe.abs=ic,fe.add=kc,fe.subtract=lc,fe.as=pc,fe.asMilliseconds=Pd,fe.asSeconds=Qd,fe.asMinutes=Rd,fe.asHours=Sd,fe.asDays=Td,fe.asWeeks=Ud,fe.asMonths=Vd,fe.asYears=Wd,fe.valueOf=qc,fe._bubble=mc,fe.get=sc,fe.milliseconds=Xd,fe.seconds=Yd,fe.minutes=Zd,fe.hours=$d,fe.days=_d,fe.weeks=uc,fe.months=ae,fe.years=be,fe.humanize=yc,fe.toISOString=zc,fe.toString=zc,fe.toJSON=zc,fe.locale=mb,fe.localeData=nb,fe.toIsoString=Z("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",zc),fe.lang=vd,F("X",0,0,"unix"),F("x",0,0,"valueOf"),K("x",Uc),K("X",Wc),N("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),N("x",function(a,b,c){c._d=new Date(o(a))}),a.version="2.10.2",b(za),a.fn=Gd,a.min=Ba,a.max=Ca,a.utc=i,a.unix=Tb,a.months=dc,a.isDate=e,a.locale=u,a.invalid=k,a.duration=Ua,a.isMoment=n,a.weekdays=fc,a.parseZone=Ub,a.localeData=w,a.isDuration=Ea,a.monthsShort=ec,a.weekdaysMin=hc,a.defineLocale=v,a.weekdaysShort=gc,a.normalizeUnits=y,a.relativeTimeThreshold=xc;var ge=a;return ge}); \ No newline at end of file diff --git a/website/events/templates/events/index.html b/website/events/templates/events/index.html new file mode 100644 index 00000000..023b5508 --- /dev/null +++ b/website/events/templates/events/index.html @@ -0,0 +1,100 @@ +{% extends "base.html" %} +{% load i18n static %}} +{% get_current_language as LANGUAGE_CODE %} + +{% block css_head %} + {{ block.super }} + + +{% endblock %} + +{% block body %} +

+ {% trans "Calendar" %} + + + + + + + +

+ +
+ + +{% endblock body %} + +{% block js_body %} + {{ block.super }} + + + + + +{% endblock js_body %} \ No newline at end of file diff --git a/website/events/urls.py b/website/events/urls.py index 100e0c90..3a9f0663 100644 --- a/website/events/urls.py +++ b/website/events/urls.py @@ -9,4 +9,5 @@ from . import views urlpatterns = [ url(r'admin/(?P\d+)/$', views.admin_details, name='admin-details'), url(r'admin/(?P\d+)/export/$', views.export, name='export'), + url(r'^$', views.index, name='index'), ] diff --git a/website/events/views.py b/website/events/views.py index dc8cafd4..2794a7ec 100644 --- a/website/events/views.py +++ b/website/events/views.py @@ -85,3 +85,14 @@ def export(request, event_id): response['Content-Disposition'] = ( 'attachment; filename="{}.csv"'.format(slugify(event.title))) return response + + +def index(request): + upcoming_activity = Event.objects.filter( + published=True, + end__gte=timezone.now() + ).order_by('end').first() + + return render(request, 'events/index.html', { + 'upcoming_activity': upcoming_activity + }) diff --git a/website/thaliawebsite/menus.py b/website/thaliawebsite/menus.py index 2e878155..8a127a33 100644 --- a/website/thaliawebsite/menus.py +++ b/website/thaliawebsite/menus.py @@ -19,7 +19,7 @@ main = [ {'title': _('Become Active'), 'name': 'become-active'}, {'title': _('Wiki'), 'url': '/wiki/'}, ]}, - {'title': _('Calendar'), 'name': '#'}, + {'title': _('Calendar'), 'name': 'events:index'}, {'title': _('Career'), 'name': 'partners:index', 'submenu': [ {'title': _('Partners'), 'name': 'partners:index'}, {'title': _('Vacancies'), 'name': 'partners:vacancies'}, -- GitLab From db37c6506d7719dead91f37dd14eb7bb37716c0a Mon Sep 17 00:00:00 2001 From: Luuk Scholten Date: Fri, 16 Sep 2016 08:25:57 +0200 Subject: [PATCH 02/18] Implement first version of event view --- website/events/api/serializers.py | 2 +- .../events/locale/nl/LC_MESSAGES/django.mo | Bin 4178 -> 4799 bytes .../events/locale/nl/LC_MESSAGES/django.po | 66 ++++- website/events/models.py | 3 + website/events/templates/events/event.html | 255 ++++++++++++++++++ website/events/urls.py | 1 + website/events/views.py | 31 ++- 7 files changed, 352 insertions(+), 6 deletions(-) create mode 100644 website/events/templates/events/event.html diff --git a/website/events/api/serializers.py b/website/events/api/serializers.py index 247dee07..de45c93b 100644 --- a/website/events/api/serializers.py +++ b/website/events/api/serializers.py @@ -60,4 +60,4 @@ class EventSerializer(CalenderJSSerializer): model = Event def _url(self, instance): - return reverse('#') + return reverse('events:event', kwargs={'event_id': instance.id}) diff --git a/website/events/locale/nl/LC_MESSAGES/django.mo b/website/events/locale/nl/LC_MESSAGES/django.mo index 04e92d27232cb3d8fa050bcbc66dcce736ce565c..318eb812cbd8397e56268bf20fbf53b55081e41a 100644 GIT binary patch delta 2035 zcmZ|POKenC9LMovAI$VMLum!6(%u5KP@%P@RojBpTA=|8E|h=}n@i_*I%RsV%-oJh zG?O$jAz{Z=qmjBGV2n|tkVpb1KH_7cNsWoog=s)HMt8=@2BY6!d#YvO$^7SY&V8K6 z|D2g`osU|I^Q+2UGL&KBF5*auF)p4hP@G<&FQ=fWx6g`eXcSj{MP*n+FE7uVop*o_mYj%QKNU&cQC z21~JuStT)vwb+DOKquDVZd}3m<_RjiV~#9bFvdfzAZtC2RkRDJ=Vp*anbTN_XRNcR z_uj+=zGK@Tq6Yi~t8va={}JPiZ?02ON`6Jn{BLZ;QhIN}^{9@AQ5hJ)a`cc#%`tm_ z8cCixY46Y2`=?MDID^W-dDK>1!J-EKii%Qw%|7r4uA*JZvUPnOY6YFBmF+|xG5f5C zP!md7Cs7l58b@w3<^*=o{(|Liz&}wLY^WjsTd6eDJI|Q?IDm&yD?5vK;v1-pd}`a* zQD@>W+=undY6Qn{6yHakiCPYT-fu%?U>9lv2T_Ohxg`0&pUM?3==A@NI_*jFqX`Tm zeVIp56Fh9&Y1=N~!kMt1w!Vm3;VddM=TUqAmc4%&)!)ZOd*N&Acc?QkkJ_4_Psq0@>MSsidh%Llu315#8 zIy^e*OGSI9qAk%+ZKE13o5q4^KlE~&^G?o7XYygr4KsoNzc=!ihRdHX={pomI}>if zbHc#M25BebJ4e0oAm=&Ha%ar-ZEs%A;i?l(Wb%$z@cghn`Yg6Bnv1QDZp7-#CW2fT z#p655#&f|`G!#D&y&T`W#-E-#%8SAHEmO)z*Wx3wl;@_h89!>QI39EIp&L%;qnV1n zau$)vMpr8ilyvt-b(I^+JG=UNx}yHd?>m;S(RKYPFPqBv>04I#-(}CgR9n?h+O@}X zrUEZyVJt80`A#7Saw#`-COtkUz3jMij4>VirsuauZHX;$J3TMjpJ<9tx^6D(y6I>t zF<4q~{pj7qQ&BuQTE|PFn_YUB$#{M=p4^+Xo&DR%d&$und(AF2v9iA=3_|maJ2nw| He#-m{Ei+A9U9ahu-Av2Kh|GsBCRw5oh!2KD%0l+gOOOpw z1cFb!R74395d>w354{9gK?(IxL_q{01X+E5$K$a7^Oo&kGQP!i_yNQC4}%y^H6{~FQ0;f% zHe=#um`XDj9%B|JEHfqpvvC!c;c9HcYV1IDd=1t9E^4eVFb!ug9pB*!{DyRGexoL! zg9hqxj2jc6;^RUF@-ulH^guCsvD`I=0nVFH9UMWu;3)dB$Gtv){LFa{n(;-<#T!_N zlc?w4V+!v#AE_vnU)&o%k-?gIHNd~_{bU+xW&u=6^HDFTKxM2MwN%G2g#D-ij-UoU ziki^4>m7{i1(Q_t!YA08V9XR2a2_Hp%0w+jumP2+vmAEfAkx%4!Zr8|HPbowd;ztD zVbXIDqu7dZ?8KLT)?aHFVVLTm88yQ$)Bpxho8=~M!CBNM4KN+OxD++B8V~TGW zB+Z;~?M6+c4>jR)s3p4)ApdH3nG5P*6m>r4dK;DE`=~XWLbaPgEx~J4ho4ay{Elk> z3$;l->_~lC1*qq{Q2qCy`st5T(St+ojZ3Hwub@U~Q7;-tW#$2DZJ)c&qSpSadw&7- zTpAClT@ET!<*xOp{u@yPiMLQ;$(^DH_3dipDjOGZjumcQ4{H+@5m7>eSJ5Y0Ol%-n zB~wReNmcYBWu=PHU^fvlqF&#>)`dR;r{r_g4pu2$#7Up3+E^;i7qD10{8u@j)Oz=P zi99M1LVHFVu!K-qOKAU863p9_nZu-pN;W~}oXx1TDP{Uo*-R{z8Y;C!wR=J9sWnvD zN@&Asvre?74kuclr_#20GHstHX7730ZK`*#?e-q9)7~!I;EULPUwdLzot^d-q(tjt z)lvJy_hw?=A5OBngQa#Tm}SR;yX>c6hiyzhZXc%axBk#cn;Y`kkx;p3@YLzvf$qLD F=0AkVe_#Lr diff --git a/website/events/locale/nl/LC_MESSAGES/django.po b/website/events/locale/nl/LC_MESSAGES/django.po index f68e657d..adb600fd 100644 --- a/website/events/locale/nl/LC_MESSAGES/django.po +++ b/website/events/locale/nl/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-16 08:38+0200\n" -"PO-Revision-Date: 2016-09-16 08:40+0200\n" +"POT-Creation-Date: 2016-09-16 08:49+0200\n" +"PO-Revision-Date: 2016-09-16 12:52+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: nl\n" @@ -75,6 +75,7 @@ msgid "cancel deadline" msgstr "afmelddeadline" #: models.py:54 templates/events/admin/details.html:55 +#: templates/events/event.html:21 msgid "location" msgstr "locatie" @@ -89,6 +90,7 @@ msgstr "" "getoond als tekst!!" #: models.py:66 templates/events/admin/details.html:57 +#: templates/events/event.html:26 msgid "price" msgstr "prijs" @@ -183,11 +185,11 @@ msgstr "aanwezig" msgid "paid" msgstr "betaald" -#: models.py:234 models.py:235 +#: models.py:237 models.py:238 msgid "Either specify a member or a name" msgstr "Geef een lid of een naam op" -#: models.py:257 +#: models.py:260 msgid "last changed" msgstr "laatst aangepast" @@ -255,6 +257,49 @@ msgstr "toevoegen" msgid "Nobody %(verb)s yet" msgstr "Niemand heeft zich %(verb)s" +#: templates/events/event.html:13 +msgid "from" +msgstr "van" + +#: templates/events/event.html:17 +msgid "until" +msgstr "tot" + +#: templates/events/event.html:32 +msgid "registration deadline" +msgstr "aanmelddeadline" + +#: templates/events/event.html:36 +msgid "cancellation deadline" +msgstr "afmelddeadline" + +#: templates/events/event.html:40 +msgid "number of registrations" +msgstr "aantal aanmeldingen" + +#: templates/events/event.html:42 +#, python-format +msgid "%(counter)s registration" +msgid_plural "%(counter)s registrations" +msgstr[0] "%(counter)s aanmelding" +msgstr[1] "%(counter)s aanmeldingen" + +#: templates/events/event.html:48 +msgid "max" +msgstr "max" + +#: templates/events/event.html:60 +msgid "registration status" +msgstr "aanmeldstatus" + +#: templates/events/event.html:157 +msgid "You have to log in before you can register for this event." +msgstr "Je moet inloggen voordat je jezelf kunt aanmelden." + +#: templates/events/event.html:241 +msgid "cohort" +msgstr "jaarlaag" + #: templates/events/index.html:13 msgid "Calendar" msgstr "Agenda" @@ -272,6 +317,19 @@ msgstr "Toon verjaardagen" msgid "list" msgstr "Lijst" +#~ msgid "From" +#~ msgstr "Vanaf" + +#, fuzzy +#~| msgid "location" +#~ msgid "Location" +#~ msgstr "locatie" + +#, fuzzy +#~| msgid "price" +#~ msgid "Price" +#~ msgstr "prijs" + #~ msgid "Present" #~ msgstr "Aanwezig" diff --git a/website/events/models.py b/website/events/models.py index e28548ba..b5de8461 100644 --- a/website/events/models.py +++ b/website/events/models.py @@ -230,6 +230,9 @@ class Registration(models.Model): return [{'field': field, 'value': field.get_value_for(self)} for field in fields] + def is_external(self): + return bool(self.name) + def clean(self): if ((self.member is None and not self.name) or (self.member and self.name)): diff --git a/website/events/templates/events/event.html b/website/events/templates/events/event.html new file mode 100644 index 00000000..a4f424a5 --- /dev/null +++ b/website/events/templates/events/event.html @@ -0,0 +1,255 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block body %} +

{{ event.title }}

+

{{ event.description }}

+ +
+
+ + + + + + + + + + + + + + + {% if event.price > 0 %} + + + + + {% endif %} + {% if event.registration_required %} + + + + + + + + + + + + + + {% if registration %} + + + + + {% endif %} + {% endif %} + + {# Todo implement aanmeldmechanisme #} +{# getRegistrationStatus();#} +{# $afterDeadline = false;#} +{##} +{# // Determine link and text for button#} +{# if(!$user->isLoggedIn() || $registration_status === ActivityModel::REGISTRATION_NOT_NEEDED) {#} +{# $show_button = false;#} +{# } elseif(!($registered && $registration->isRegisteredExcludeCoolDown())) {#} +{# // Not yet registered#} +{# if ($registration_count >= $activity->registration_limit) {#} +{# $button_text = "Op reservelijst";#} +{# } else {#} +{# $button_text = "Aanmelden";#} +{# }#} +{# if($registration !== null && $registration->isInCoolDown()) {#} +{# $button_text = "Afmelding annuleren";#} +{# }#} +{# $show_button = $registration_status === ActivityModel::REGISTRATION_OPEN || $registration_status === ActivityModel::REGISTRATION_CLOSED_REGISTER_ONLY;#} +{# $intent = "register";#} +{# } else {#} +{# // Already registered#} +{# $button_text = "Afmelden";#} +{# $show_button = true;#} +{# if(!$activity->canCancel())#} +{# {#} +{# $afterDeadline = true;#} +{# $costText = empty($activity->thalia_costs) ? '' : ' van ongeveer € ' . number_format($activity->thalia_costs, 2, ',', '.');#} +{# $confirmText = 'De deadline is verstreken, weet je zeker dat je je wilt afmelden en daarmee een boete krijgt' . $costText . '? Je kunt dit niet ongedaan maken.';#} +{##} +{# }#} +{# $intent = "cancel";#} +{# }#} +{# ?>#} + + + + + + + + + {# TODO IMPLEMENT PIZZA BUTTON #} +{# #} +{# #} +{# #} +{# #} +{# #} + +
{% trans "from"|capfirst %}{{ event.start }}
{% trans "until"|capfirst %}{{ event.end }}
{% trans "location"|capfirst %}{{ event.location }}
{% trans "price"|capfirst %} €{{ event.price }}
{% trans "registration deadline"|capfirst %}{{ event.registration_end }}
{% trans "cancellation deadline"|capfirst %}{{ event.cancel_deadline }}
{% trans "number of registrations"|capfirst %} + {% blocktrans count counter=registrations|length trimmed %} + {{ counter }} registration + {% plural %} + {{ counter }} registrations + {% endblocktrans %} + {% if event.max_participants > 0 %} + ({{ event.max_participants }} {% trans "max"|capfirst %}) + {% with prc=registration_percentage %} +
+
+
+ {% endwith %} + {% endif %} +
{% trans "registration status"|capfirst %} + TODO TODO TODO + {# TODO implement registration status #} + {# isRegisteredExcludeCoolDown()) {#} + {# $queuePos = $activity->getQueuePosition($registration);#} + {# if ($queuePos === 0) {#} + {# echo "Aangemeld";#} + {# } else {#} + {# echo "Op wachtlijst positie " . $queuePos;#} + {# }#} + {# } else if($registration->isLateCancellation()) {#} + {# echo "Te laat afgemeld";#} + {# } else {#} + {# echo "Afgemeld";#} + {# }#} + {# ?>#} +
+ +{# #} +{##} +{#
#} +{# hidden('activity_id', $activity->id); ?>#} +{# hidden('intent', $intent); ?>#} +{# #} +{# submit('submit', $button_text, ['class' => 'btn btn-style1', 'onclick' => 'return confirm(\'' . $confirmText . '\');']); ?>#} +{# #} +{# submit('submit', $button_text, ['class' => 'btn btn-style1']); ?>#} +{# #} +{# #} +{#
#} +{##} +{# #} +{# isRegisteredExcludeCoolDown() && count($activity->getExtraFields()) > 0):?>#} +{#
#} +{# hidden('activity_id', $activity->id);?>#} +{# hidden('intent', 'prompt_update_screen');?>#} +{# submit('submit', 'Bijwerken', ['class' => 'btn btn-style1']); ?>#} +{# #} +{#
#} +{# #} +
+ + + {% if not request.user.is_authenticated %} + {% trans "You have to log in before you can register for this event." %} + {% endif %} + + + {# Todo implement aanmeldtekst #} +{# isLoggedIn()):?>#} +{# isInCoolDown()): ?>#} +{# #} +{# Je hebt nog een onbekende hoeveelheid tijd om je afmelding ongedaan te maken.#} +{# #} +{# registration_not_needed_message !== null) : ?>#} +{# registration_not_needed_message; ?>#} +{# #} +{# Aanmelden hoeft niet#} +{# #} +{# #} +{# Aanmelden kan vanaf formatDate('begin_registration'); ?>#} +{# isRegisteredExcludeCoolDown() && ($registration_status === ActivityModel::REGISTRATION_CLOSED || $registration_status === ActivityModel::REGISTRATION_CLOSED_REGISTER_ONLY)): ?>#} +{# #} +{# Afmelden kan niet meer, tenzij je de boete betaalt .#} +{# isRegisteredExcludeCoolDown()) && ($registration_status === ActivityModel::REGISTRATION_CLOSED || $registration_status === ActivityModel::REGISTRATION_CLOSED_CANCEL_ONLY)): ?>#} +{# #} +{# Aanmelden kan niet meer#} +{# #} + +
#} +{# Pizza#} +{# #} +{# #} +{# Bestellen#} +{# #} +{#
+
+ +
+
+ +
+
+
+ + {% if user.is_authenticated and registrations|length > 0 %} + + {% endif %} +{% endblock body %} diff --git a/website/events/urls.py b/website/events/urls.py index 3a9f0663..3bf120fe 100644 --- a/website/events/urls.py +++ b/website/events/urls.py @@ -9,5 +9,6 @@ from . import views urlpatterns = [ url(r'admin/(?P\d+)/$', views.admin_details, name='admin-details'), url(r'admin/(?P\d+)/export/$', views.export, name='export'), + url(r'^(?P\d+)$', views.event, name='event'), url(r'^$', views.index, name='index'), ] diff --git a/website/events/views.py b/website/events/views.py index 2794a7ec..6f105e74 100644 --- a/website/events/views.py +++ b/website/events/views.py @@ -8,7 +8,7 @@ from django.contrib.auth.decorators import permission_required from django.utils import timezone from django.utils.text import slugify -from .models import Event +from .models import Event, Registration @staff_member_required @@ -96,3 +96,32 @@ def index(request): return render(request, 'events/index.html', { 'upcoming_activity': upcoming_activity }) + + +def event(request, event_id): + event = get_object_or_404( + Event.objects.filter(published=True), + pk=event_id + ) + registrations = event.registration_set.all() + + context = { + 'event': event, + 'registrations': registrations, + 'user': request.user, + } + + if event.max_participants: + perc = 100.0*len(registrations)/event.max_participants + context['registration_percentage'] = perc + + try: + registration = Registration.objects.get( + event=event, + member=request.user.member + ) + context['registration'] = registration + except: + pass + + return render(request, 'events/event.html', context) -- GitLab From 96e6d33fa4133bca8a87b1aaad7c2b5d5bd0ce7e Mon Sep 17 00:00:00 2001 From: Luuk Scholten Date: Tue, 20 Sep 2016 21:45:22 +0200 Subject: [PATCH 03/18] Implement duolingual iCal feed Todo: make old link redirect to the dutch ical feed --- requirements.txt | 1 + website/events/feeds.py | 42 ++++++++++++++++++ .../events/locale/nl/LC_MESSAGES/django.mo | Bin 4799 -> 4900 bytes .../events/locale/nl/LC_MESSAGES/django.po | 8 +++- website/events/templates/events/index.html | 7 ++- website/events/urls.py | 3 ++ 6 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 website/events/feeds.py diff --git a/requirements.txt b/requirements.txt index d5220635..3a1d6838 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ django-tinymce==2.3.0 pytz rcssmin djangorestframework==3.4.4 +django-ical==1.4 diff --git a/website/events/feeds.py b/website/events/feeds.py new file mode 100644 index 00000000..637b0eb1 --- /dev/null +++ b/website/events/feeds.py @@ -0,0 +1,42 @@ +from django.urls import reverse +from django.utils.translation import activate +from django_ical.views import ICalFeed +from events.models import Event +from django.utils.translation import ugettext as _ + + +class EventFeed(ICalFeed): + def __init__(self, lang='en'): + super().__init__() + self.lang = lang + + def product_id(self): + return '-//thalia.nu//EventCalendar//' + self.lang.upper() + + def file_name(self): + return "thalia_{}.ics".format(self.lang) + + def title(self): + activate(self.lang) + return _('Study Association Thalia event calendar') + + def items(self): + return Event.objects.filter(published=True).order_by('-start') + + def item_title(self, item): + return item.title + + def item_description(self, item): + return item.description + + def item_start_datetime(self, item): + return item.start + + def item_end_datetime(self, item): + return item.end + + def item_link(self, item): + return reverse('events:event', kwargs={'event_id': item.id}) + + def item_location(self, item): + return "{} - {}".format(item.location, item.map_location) diff --git a/website/events/locale/nl/LC_MESSAGES/django.mo b/website/events/locale/nl/LC_MESSAGES/django.mo index 318eb812cbd8397e56268bf20fbf53b55081e41a..047ec84ad029ca7cf158fb18a012cb8c170e2ee8 100644 GIT binary patch delta 1733 zcmYk+X>5!^9LMpoTkY0jsj_;pj<$|oyLwQHDyj+MYD6Pp*$o>kYMn2-64x6NQba;u zxPmx>B|;)3UPwyfjlOck8xh`A$_pgK_xE&~bh7{b%sl(dJTw29XP*^bDN26KN;_jH zO+-EsaTycFTWNezD$hp4VUa2)=^9L&uyW*AmtHZDN! zPns4gv$${$7h)HV#(bVK0;_NoHlZKaVbBa-0)QCC*Yf<-yk+ID-)WB`1lpjXj ze;n2SBI+%Q3D511C`k8m8b{Qp=REIdhjwNX(o&eWp*MT zbI5uYHU16M?@y84GjEV_lI8;yKIR)=O7UN72EFva;iwh(Q4`EV&XSo*%q9AYwr?6y zMCd*ht$3nS>pcgmI{zhD?p$a8$5NT@G&t#4L1^V#Wsp$OjAuBt-mehk5{*PP!JM2j zpKqnNiqK*v5jsZ=M1bgD^rz?L`A!-q-~wU_p})=kMgL}%g!W5^sGd+pRCKD#hypbz zbBLLQwnVRRF)^7?S=5V@ZN0WS_|BrnQ_*WxO1OxHgbt5RdSB7r>CMoV=+CDxRgiwr zHFafTcVvH{e%G$;8)M-_YS54itOm|aLvotf6Nb8VbDTI_17GXpp5o`#7;7tpP zv_n`#)FFig3wnX-U|^S^LrB&o>JUWK#jx*BCvezjKQsTAd3k1b$MT=$MP`$uuNkF@ z*hq9lnRVhwG+&ggShFx@<61n7UhKvop2t;q6L;Yq)T>`G8^2;2#;r6<#9U0lGUWM) z?Wa=Bg#m29SGXR1jIsvva2-}+Djvd8Jca6b6!rWhR^dDJU_7((qaTwnfLcHZ6R;6u z8Q)r|@Q!sXUNGxIt>BFF9LCe`MLpM#EXoEk4u_nhsQ2#SYJBM0Pf-KDKp)Py>mSj} z_%=sHDfxz)`2uF5hu-rr2i0*CDg(_JgI&m@cG}&)fF#c@y8Hd^{s1ZiL#Palp|)ZQ zBN}*`icz)t@(`F`#-1`=8=ziS&lkRL!~-^dOnDHzZ&)HsKwoQ z6xGiS)Rx{tO*}GAMIApub@ayFm_ZHj0X6ek)WF}7B-uRjF&_s-XC>1aLJd%ly5ENE zl69l{=|ettg)cH3vFl3}yNw#~0cr)~s2M&+&X5%nHN^k2g-V#nBlMh#_FVg`qF;)N zPJ9!wO%009k+xiAkADBfRJ3xy6I;<+%Tm9>4OUj9SVl$!A zxWv*=nY#XERrI%W)rYPl0dv6rJft!I0NJH4qBKfGR# gH&mWgQc+b}IlK^`rA8=RRTiEI`ldY-fy5ZkKT$DzM*si- diff --git a/website/events/locale/nl/LC_MESSAGES/django.po b/website/events/locale/nl/LC_MESSAGES/django.po index adb600fd..7ca13cb5 100644 --- a/website/events/locale/nl/LC_MESSAGES/django.po +++ b/website/events/locale/nl/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-16 08:49+0200\n" -"PO-Revision-Date: 2016-09-16 12:52+0200\n" +"POT-Creation-Date: 2016-09-20 21:26+0200\n" +"PO-Revision-Date: 2016-09-20 21:27+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: nl\n" @@ -38,6 +38,10 @@ msgstr "Publiceer geselecteerde evenementen" msgid "Unpublish selected events" msgstr "Publicatie van geselecteerde evenementen ongedaan maken" +#: feeds.py:23 +msgid "Study Association Thalia event calendar" +msgstr "Studievereniginig Thalia evenementenkalender" + #: models.py:13 msgid "No registration required" msgstr "Geen registratie vereist" diff --git a/website/events/templates/events/index.html b/website/events/templates/events/index.html index 023b5508..b720d19b 100644 --- a/website/events/templates/events/index.html +++ b/website/events/templates/events/index.html @@ -13,7 +13,12 @@ {% trans "Calendar" %} - + diff --git a/website/events/urls.py b/website/events/urls.py index 3bf120fe..ca884287 100644 --- a/website/events/urls.py +++ b/website/events/urls.py @@ -4,6 +4,7 @@ Events URL Configuration from django.conf.urls import url +from events.feeds import EventFeed from . import views urlpatterns = [ @@ -11,4 +12,6 @@ urlpatterns = [ url(r'admin/(?P\d+)/export/$', views.export, name='export'), url(r'^(?P\d+)$', views.event, name='event'), url(r'^$', views.index, name='index'), + url(r'^ical/nl.ics', EventFeed(lang='nl'), name='ical-nl'), + url(r'^ical/en.ics', EventFeed(), name='ical-en'), ] -- GitLab From 0ebeb9035b90a7cbb29a5bb58c19e8760bc2821a Mon Sep 17 00:00:00 2001 From: Luuk Scholten Date: Wed, 21 Sep 2016 19:38:53 +0200 Subject: [PATCH 04/18] Implement simple deprecated ical feed Always has events for the next week, starting at 13:37 --- website/events/feeds.py | 29 +++++++++++++++++++++++++++++ website/thaliawebsite/urls.py | 2 ++ 2 files changed, 31 insertions(+) diff --git a/website/events/feeds.py b/website/events/feeds.py index 637b0eb1..5619b173 100644 --- a/website/events/feeds.py +++ b/website/events/feeds.py @@ -1,3 +1,5 @@ +from datetime import datetime, timedelta + from django.urls import reverse from django.utils.translation import activate from django_ical.views import ICalFeed @@ -40,3 +42,30 @@ class EventFeed(ICalFeed): def item_location(self, item): return "{} - {}".format(item.location, item.map_location) + + +class DeprecationFeed(ICalFeed): + def product_id(self): + return '-//thalia.nu//DEPRECATED' + + def items(self): + return range(7) + + def item_start_datetime(self, item): + today = datetime.now().replace(hour=13, minute=37, second=0) + delta = timedelta(days=item) + return today + delta + + def item_end_datetime(self, item): + today = datetime.now().replace(hour=15, minute=37, second=0) + delta = timedelta(days=item) + return today + delta + + def item_title(self, item): + return ( + 'Oude Thalia Feed, gebruik https://thalia.nu/' + + reverse('events:ical-nl') + ) + + def item_link(self, item): + return reverse('events:ical-nl') + '?item={}'.format(item) diff --git a/website/thaliawebsite/urls.py b/website/thaliawebsite/urls.py index ff61fdf0..d156cd5f 100644 --- a/website/thaliawebsite/urls.py +++ b/website/thaliawebsite/urls.py @@ -23,6 +23,7 @@ from django.contrib.sitemaps.views import sitemap from django.views.generic import TemplateView from django.views.i18n import JavaScriptCatalog +from events.feeds import DeprecationFeed from utils.views import private_thumbnails import members @@ -49,6 +50,7 @@ urlpatterns = [ url(r'^members/', include('members.urls', namespace='members')), url(r'^nyi$', TemplateView.as_view(template_name='status/nyi.html'), name='#'), url(r'^events/', include('events.urls', namespace='events')), + url(r'^index\.php/events/ical/feed\.ics', DeprecationFeed()), url(r'^newsletters/', include('newsletters.urls', namespace='newsletters')), url(r'^association$', TemplateView.as_view( -- GitLab From 6e9625b6c6e8534874af1dcff319de91ce7ac1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Sun, 25 Sep 2016 21:10:34 +0200 Subject: [PATCH 05/18] Add API to partners app for events --- website/events/templates/events/index.html | 4 +- website/partners/admin.py | 10 +++++ website/partners/api/serializers.py | 23 +++++++++++ website/partners/api/urls.py | 7 ++++ website/partners/api/viewsets.py | 34 +++++++++++++++++ .../partners/migrations/0005_partnerevent.py | 33 ++++++++++++++++ website/partners/models.py | 38 +++++++++++++++++++ website/thaliawebsite/urls.py | 1 + 8 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 website/partners/api/serializers.py create mode 100644 website/partners/api/urls.py create mode 100644 website/partners/api/viewsets.py create mode 100644 website/partners/migrations/0005_partnerevent.py diff --git a/website/events/templates/events/index.html b/website/events/templates/events/index.html index b720d19b..0b634411 100644 --- a/website/events/templates/events/index.html +++ b/website/events/templates/events/index.html @@ -32,7 +32,7 @@ var sources = { events: "/api/events", birthdays: "/api/members/birthdays", -{# extern: ".nu/api/ext-events"#} {# TODO IMPLEMENT THIS #} + partners: "/api/partners/events" }; $('#calendar').fullCalendar({ @@ -77,7 +77,7 @@ titleRangeSeparator: ' - ', timeFormat: 'H:mm', eventLimit: true, // allow "more" link when too many events - eventSources: Cookies.get('showbirthdays') ? [sources.events, sources.birthdays, sources.extern] : [sources.events, sources.extern], + eventSources: Cookies.get('showbirthdays') ? [sources.events, sources.birthdays, sources.partners] : [sources.events, sources.partners], eventClick: function(event) { if(event.url && event.blank) { window.open(event.url, '_blank'); diff --git a/website/partners/admin.py b/website/partners/admin.py index ec76aae1..af83107b 100644 --- a/website/partners/admin.py +++ b/website/partners/admin.py @@ -7,6 +7,7 @@ from partners.models import ( PartnerImage, VacancyCategory, Vacancy, + PartnerEvent ) @@ -58,3 +59,12 @@ class VacancyAdmin(admin.ModelAdmin): 'fields': ('categories', ) }), ) + + +@admin.register(PartnerEvent) +class PartnerEventAdmin(TranslatedModelAdmin): + fields = ['partner', 'title', 'description', 'location', 'start', 'end', + 'url', 'published'] + list_display = ('title', 'start', 'end', + 'partner', 'published') + list_filter = ('start', 'published') diff --git a/website/partners/api/serializers.py b/website/partners/api/serializers.py new file mode 100644 index 00000000..afc37ed6 --- /dev/null +++ b/website/partners/api/serializers.py @@ -0,0 +1,23 @@ +from django.utils import timezone +from django.urls import reverse +from rest_framework import serializers + +from events.api.serializers import CalenderJSSerializer +from partners.models import PartnerEvent + + +class PartnerEventSerializer(CalenderJSSerializer): + class Meta(CalenderJSSerializer.Meta): + model = PartnerEvent + + def _background_color(self, instance): + return '#E62272' + + def _text_color(self, instance): + return '#FFFFF' + + def _url(self, instance): + return instance.url + + def _target_blank(self, instance): + return True diff --git a/website/partners/api/urls.py b/website/partners/api/urls.py new file mode 100644 index 00000000..b5108da5 --- /dev/null +++ b/website/partners/api/urls.py @@ -0,0 +1,7 @@ +from rest_framework import routers + +from partners.api import viewsets + +router = routers.SimpleRouter() +router.register(r'partners', viewsets.PartnerViewset) +urlpatterns = router.urls diff --git a/website/partners/api/viewsets.py b/website/partners/api/viewsets.py new file mode 100644 index 00000000..f28370a4 --- /dev/null +++ b/website/partners/api/viewsets.py @@ -0,0 +1,34 @@ +from rest_framework import viewsets +from rest_framework.exceptions import ParseError +from rest_framework.response import Response +from rest_framework.decorators import list_route +from django.utils import timezone +from datetime import datetime + +from partners.api.serializers import PartnerEventSerializer +from partners.models import Partner, PartnerEvent + + +class PartnerViewset(viewsets.ViewSet): + queryset = Partner.objects.all() + + @list_route() + def events(self, request): + try: + start = timezone.make_aware( + datetime.strptime(request.query_params['start'], '%Y-%m-%d') + ) + end = timezone.make_aware( + datetime.strptime(request.query_params['end'], '%Y-%m-%d') + ) + except: + raise ParseError(detail='start or end query parameters invalid') + + queryset = PartnerEvent.objects.filter( + end__gte=start, + start__lte=end, + published=True + ) + + serializer = PartnerEventSerializer(queryset, many=True) + return Response(serializer.data) diff --git a/website/partners/migrations/0005_partnerevent.py b/website/partners/migrations/0005_partnerevent.py new file mode 100644 index 00000000..4dcb32ba --- /dev/null +++ b/website/partners/migrations/0005_partnerevent.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-09-25 18:42 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('partners', '0004_add_tinymce_to_vacancy_and_partner'), + ] + + operations = [ + migrations.CreateModel( + name='PartnerEvent', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start', models.DateTimeField(verbose_name='start time')), + ('end', models.DateTimeField(verbose_name='end time')), + ('url', models.URLField(verbose_name='website')), + ('published', models.BooleanField(default=False, verbose_name='published')), + ('location_en', models.CharField(max_length=255, verbose_name='location (EN)')), + ('location_nl', models.CharField(max_length=255, verbose_name='location (NL)')), + ('title_en', models.CharField(max_length=100, verbose_name='title (EN)')), + ('title_nl', models.CharField(max_length=100, verbose_name='title (NL)')), + ('description_en', models.TextField(verbose_name='description (EN)')), + ('description_nl', models.TextField(verbose_name='description (NL)')), + ('partner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to='partners.Partner')), + ], + ), + ] diff --git a/website/partners/models.py b/website/partners/models.py index d209c1b9..f8fb3967 100644 --- a/website/partners/models.py +++ b/website/partners/models.py @@ -1,6 +1,8 @@ from django.db import models from django.core.validators import RegexValidator, URLValidator from django.urls import reverse +from django.utils.translation import ugettext, ugettext_lazy as _, \ + string_concat from utils.translation import MultilingualField, ModelTranslateMeta from tinymce.models import HTMLField @@ -126,3 +128,39 @@ class Vacancy(models.Model): class Meta: verbose_name_plural = 'Vacancies' + + +class PartnerEvent(models.Model, metaclass=ModelTranslateMeta): + partner = models.ForeignKey( + Partner, + on_delete=models.CASCADE, + related_name="events" + ) + + title = MultilingualField( + models.CharField, + _("title"), + max_length=100 + ) + + description = MultilingualField( + models.TextField, + _("description") + ) + + location = MultilingualField( + models.CharField, + _("location"), + max_length=255, + ) + + start = models.DateTimeField(_("start time")) + + end = models.DateTimeField(_("end time")) + + url = models.URLField(_("website")) + + published = models.BooleanField(_("published"), default=False) + + def __str__(self): + return self.title diff --git a/website/thaliawebsite/urls.py b/website/thaliawebsite/urls.py index d156cd5f..a5d75a3c 100644 --- a/website/thaliawebsite/urls.py +++ b/website/thaliawebsite/urls.py @@ -76,6 +76,7 @@ urlpatterns = [ url(r'^api/', include([ url(r'^', include('events.api.urls')), url(r'^', include('members.api.urls')), + url(r'^', include('partners.api.urls')), ])), url(r'^education/', include('education.urls', namespace='education')), # Default login helpers -- GitLab From c058990bf0ecd826b114fb94fa36c0ad89515320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Sun, 25 Sep 2016 22:25:11 +0200 Subject: [PATCH 06/18] Fix color and title of partner event --- website/partners/api/serializers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/website/partners/api/serializers.py b/website/partners/api/serializers.py index afc37ed6..ea8cc5e0 100644 --- a/website/partners/api/serializers.py +++ b/website/partners/api/serializers.py @@ -10,11 +10,14 @@ class PartnerEventSerializer(CalenderJSSerializer): class Meta(CalenderJSSerializer.Meta): model = PartnerEvent + def _title(self, instance): + return "{} ({})".format(instance.title, instance.partner.name) + def _background_color(self, instance): return '#E62272' def _text_color(self, instance): - return '#FFFFF' + return 'white' def _url(self, instance): return instance.url -- GitLab From b922e609a561d0c6143dc76dcafd5836509daf78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Sun, 25 Sep 2016 22:26:12 +0200 Subject: [PATCH 07/18] Fix fieldnames, fullcalendar expects camelCase --- website/events/api/serializers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/events/api/serializers.py b/website/events/api/serializers.py index de45c93b..492c6f38 100644 --- a/website/events/api/serializers.py +++ b/website/events/api/serializers.py @@ -10,7 +10,7 @@ class CalenderJSSerializer(serializers.ModelSerializer): fields = ( 'start', 'end', 'all_day', 'is_birthday', 'url', 'title', 'description', - 'background_color', 'text_color', 'target_blank' + 'backgroundColor', 'textColor', 'blank' ) start = serializers.SerializerMethodField('_start') @@ -20,9 +20,9 @@ class CalenderJSSerializer(serializers.ModelSerializer): url = serializers.SerializerMethodField('_url') title = serializers.SerializerMethodField('_title') description = serializers.SerializerMethodField('_description') - background_color = serializers.SerializerMethodField('_background_color') - text_color = serializers.SerializerMethodField('_text_color') - target_blank = serializers.SerializerMethodField('_target_blank') + backgroundColor = serializers.SerializerMethodField('_background_color') + textColor = serializers.SerializerMethodField('_text_color') + blank = serializers.SerializerMethodField('_target_blank') def _start(self, instance): return timezone.localtime(instance.start) -- GitLab From 7b1b9d52422e17460cd15ebd9f73ca7dca7d3aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Sun, 25 Sep 2016 22:38:28 +0200 Subject: [PATCH 08/18] Optimise imports --- website/partners/api/serializers.py | 4 ---- website/partners/models.py | 8 +++----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/website/partners/api/serializers.py b/website/partners/api/serializers.py index ea8cc5e0..6d3d1ff4 100644 --- a/website/partners/api/serializers.py +++ b/website/partners/api/serializers.py @@ -1,7 +1,3 @@ -from django.utils import timezone -from django.urls import reverse -from rest_framework import serializers - from events.api.serializers import CalenderJSSerializer from partners.models import PartnerEvent diff --git a/website/partners/models.py b/website/partners/models.py index f8fb3967..d0268ba0 100644 --- a/website/partners/models.py +++ b/website/partners/models.py @@ -1,11 +1,9 @@ -from django.db import models from django.core.validators import RegexValidator, URLValidator +from django.db import models from django.urls import reverse -from django.utils.translation import ugettext, ugettext_lazy as _, \ - string_concat - -from utils.translation import MultilingualField, ModelTranslateMeta +from django.utils.translation import ugettext_lazy as _ from tinymce.models import HTMLField +from utils.translation import MultilingualField, ModelTranslateMeta class Partner(models.Model): -- GitLab From 572122b607c414f9eba381ff5ca6d7e8dbd47f41 Mon Sep 17 00:00:00 2001 From: Luuk Scholten Date: Wed, 28 Sep 2016 20:53:18 +0200 Subject: [PATCH 09/18] Fix attendee view events - Correct image for non-members - Only show members which have not cancelled --- website/events/templates/events/event.html | 4 +++- website/events/views.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/website/events/templates/events/event.html b/website/events/templates/events/event.html index a4f424a5..bb4ed42a 100644 --- a/website/events/templates/events/event.html +++ b/website/events/templates/events/event.html @@ -217,7 +217,9 @@ {% if registration.is_external %}
-
{% static "members/images/default-avatar.jpg" %}
+
+ +

{{ registration.name }}

diff --git a/website/events/views.py b/website/events/views.py index 6f105e74..2441c10e 100644 --- a/website/events/views.py +++ b/website/events/views.py @@ -103,7 +103,7 @@ def event(request, event_id): Event.objects.filter(published=True), pk=event_id ) - registrations = event.registration_set.all() + registrations = event.registration_set.filter(date_cancelled=None) context = { 'event': event, -- GitLab From f2dd7e4e81db9fbdd8d2c8c12383576f79ee5999 Mon Sep 17 00:00:00 2001 From: Luuk Scholten Date: Wed, 28 Sep 2016 21:28:49 +0200 Subject: [PATCH 10/18] Add registration status --- .../events/locale/nl/LC_MESSAGES/django.mo | Bin 4900 -> 5231 bytes .../events/locale/nl/LC_MESSAGES/django.po | 37 +++++++++++++----- website/events/models.py | 16 ++++++++ website/events/templates/events/event.html | 30 ++++++-------- 4 files changed, 55 insertions(+), 28 deletions(-) diff --git a/website/events/locale/nl/LC_MESSAGES/django.mo b/website/events/locale/nl/LC_MESSAGES/django.mo index 047ec84ad029ca7cf158fb18a012cb8c170e2ee8..ea47ccbc558067be637d28363bb8bd547b1cca97 100644 GIT binary patch delta 1978 zcmaLXTWAzl9LMp0lb9G2HO8u~TAir5X;$MJHEJ58wQAIrAc9zLon$BLw(CZA*9%%) zXj6#yOOztwL!c;#FB%ZRyh$lSQ1L}1Ao}2o6_w(ff_{JT7>W-)?0-KqXJ*djf6mOG z6|btYk4y8;86`qYCOUJ>V)!DDKT21=Sr`xFIPAmW_y8+$01Gj}LD$55F{ z;4th&CTmCB^|MH_?L206Lyx=RGAaYtP#L(5TC+!}j-R8J-OI9gE(7hwpSkiIR6O?Ut`vs*X` z|3YQtsXN!YYVS_hG93s{4Xa3SV%lirp_ zoQPXc&+X+;Z%Y?)tKC8^O+RX&{~}|vcc_UJWyzl^CZH-RP-`7_E^)3vWg>z~;YK7` z_Jg|~Lp{ISo&Vze9ko|ZpqB6qYL8q--JiY6fkt{0)$kqElH5Z*@DSC&3)E}&8uj{& zV8PW;GivSE^QVEgq8k1g)y_UwpGCFbg&Oz?q~EMv;DF_@t4LO?59!11I-epxd&8fu z4`)=`WD`*hRiW;mk4o`s=LS@VTTv5eMGdeW*=mDjiQa#eCSs`$T+t?}C)A;e_K1oa zsw4PF+I&J~E}^}nqP?P`4~_<@iOnFY2#*L6+BegQ!Q%S26`U<2w7IGY%~)wxSxIPy zmBe?1c6bG$ujT^cQ_<|hgm%2jT4FY#-F)tBMXT04VY0f&5UU?OV)oI(@vB&dMftLzmTn|z~w7a0NVH?#cSC{e! zABd;DHb2=GBoaYXPyNqj&u^zCFS9fF^vK61qJbYx#FIhq^1>TA1@&Hd?&9#m?)ODy z?zq0UvN)1gv&nDUnMuTd8X9geG}<+R*BT@oKMW$Gm(|s$-1_VGuW9FUH|{EWrVc$EVnY6R57gFdzS50JD6?tiv#-V=HpM zW4fvA6w zOy>EGf_8`V;b(Y*N>o9(1%*tS){AEVjV)6v;MR$ zq894m>B>+%>I|ehRMa5{HDHl#2dxe0o?_G%wWH3!G1UD%$k^r-YT$lU$}gerzmDoZ zjCxBR;Vzs)jpHm)(VoY!(V9pGYT!K7K;`y&81;ZA)XZB@4?ct>&GaBcnbXL}3|Q}= z#veuf{u self.event.cancel_deadline) + + def is_registered(self): + return self.date_cancelled is None + + def queue_position(self): + if self.event.max_participants is None: + return 0 + + return max(self.event.registration_set.filter( + date_cancelled=None, + id__lte=self.id + ).count() - self.event.max_participants, 0) + def clean(self): if ((self.member is None and not self.name) or (self.member and self.name)): diff --git a/website/events/templates/events/event.html b/website/events/templates/events/event.html index bb4ed42a..9dd3ae0c 100644 --- a/website/events/templates/events/event.html +++ b/website/events/templates/events/event.html @@ -54,28 +54,21 @@ {% endif %} - {% if registration %} {% trans "registration status"|capfirst %} - TODO TODO TODO - {# TODO implement registration status #} - {# isRegisteredExcludeCoolDown()) {#} - {# $queuePos = $activity->getQueuePosition($registration);#} - {# if ($queuePos === 0) {#} - {# echo "Aangemeld";#} - {# } else {#} - {# echo "Op wachtlijst positie " . $queuePos;#} - {# }#} - {# } else if($registration->isLateCancellation()) {#} - {# echo "Te laat afgemeld";#} - {# } else {#} - {# echo "Afgemeld";#} - {# }#} - {# ?>#} + {% if registration.is_registered and registration.queue_position == 0 %} + {% trans "You are registered" %} + {% elif registration.is_registered and registration.queue_position > 0 %} + {% blocktrans with pos=registration.queue_position trimmed %} + Waiting list position {{ pos }} + {% endblocktrans %} + {% elif not registration.is_registered and registration.is_late_cancellation %} + {% trans "Your registration is cancelled after the cancellation deadline" %} + {% else %} + {% trans "Your registration is cancelled" %} + {% endif %} {% endif %} @@ -121,6 +114,7 @@ {# }#} {# ?>#} + -- GitLab From 96d12d8c542debcdd9bea7918257536057b61bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Fri, 30 Sep 2016 22:54:01 +0200 Subject: [PATCH 11/18] Add code for the homepage --- website/events/models.py | 3 ++- website/events/templates/events/cards.html | 21 +++++++++++++++++++++ website/events/templatetags/event_cards.py | 15 +++++++++++++++ website/thaliawebsite/templates/index.html | 17 ++++++++++------- 4 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 website/events/templates/events/cards.html create mode 100644 website/events/templatetags/event_cards.py diff --git a/website/events/models.py b/website/events/models.py index b790a42a..26fb8075 100644 --- a/website/events/models.py +++ b/website/events/models.py @@ -4,6 +4,7 @@ from django.db import models from django.db.models import Q from django.utils import timezone from django.utils.translation import ugettext_lazy as _, string_concat +from django.urls import reverse from utils.translation import MultilingualField, ModelTranslateMeta @@ -136,7 +137,7 @@ class Event(models.Model, metaclass=ModelTranslateMeta): raise ValidationError(errors) def get_absolute_url(self): - return '' + return reverse('events:event', args=[str(self.pk)]) def __str__(self): return '{}: {}'.format( diff --git a/website/events/templates/events/cards.html b/website/events/templates/events/cards.html new file mode 100644 index 00000000..7dfb8fec --- /dev/null +++ b/website/events/templates/events/cards.html @@ -0,0 +1,21 @@ +
\ No newline at end of file diff --git a/website/events/templatetags/event_cards.py b/website/events/templatetags/event_cards.py new file mode 100644 index 00000000..d58917a0 --- /dev/null +++ b/website/events/templatetags/event_cards.py @@ -0,0 +1,15 @@ +from django import template +from django.utils import timezone + +from events.models import Event + +register = template.Library() + + +@register.inclusion_tag('events/cards.html') +def show_cards(): + upcoming_events = Event.objects.filter( + published=True, + end__gte=timezone.now() + ).order_by('end') + return {'events': upcoming_events[:4]} diff --git a/website/thaliawebsite/templates/index.html b/website/thaliawebsite/templates/index.html index d01a26fc..2255faff 100644 --- a/website/thaliawebsite/templates/index.html +++ b/website/thaliawebsite/templates/index.html @@ -1,13 +1,16 @@ {% extends 'base.html' %} -{% block title %}Thaliawebsite{% endblock %} +{% load i18n event_cards %} {% block body %} -

Hello world

+

{% trans "Thalia, the study association of Computer Science and Information Science" %}

-

Dit is een statische placeholder. Dit werkt op dit moment: -

+

+{% blocktrans %}Thalia is the study association for the Computer Science and Information Science students at Radboud University in Nijmegen. Thalia provides you the necessary distractions during your studies, as we've been doing for the past 25 years. And via Thalia students can easily connect with teachers and students from other years.{% endblocktrans %}

+ +

{% trans "Upcoming events" %}

+ +{% show_cards %} + +

{% trans "Searching for another event?" %} {% trans "Take a look at the entire agenda" %}.

{% endblock %} -- GitLab From 99f51b1c8521862fb5d11e39cd5b878070f01af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Wed, 5 Oct 2016 21:42:33 +0200 Subject: [PATCH 12/18] Use CSS compress --- website/events/templates/events/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/events/templates/events/index.html b/website/events/templates/events/index.html index 0b634411..41c5b61e 100644 --- a/website/events/templates/events/index.html +++ b/website/events/templates/events/index.html @@ -1,11 +1,13 @@ {% extends "base.html" %} -{% load i18n static %}} +{% load i18n static compress %}} {% get_current_language as LANGUAGE_CODE %} {% block css_head %} {{ block.super }} + {% compress css %} + {% endcompress %} {% endblock %} {% block body %} -- GitLab From abe12e7727dc56953993c3a0915b4ae0d782baf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Wed, 5 Oct 2016 21:46:58 +0200 Subject: [PATCH 13/18] Fix // --- website/events/feeds.py | 2 +- website/events/templates/events/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/events/feeds.py b/website/events/feeds.py index 5619b173..7d66107f 100644 --- a/website/events/feeds.py +++ b/website/events/feeds.py @@ -63,7 +63,7 @@ class DeprecationFeed(ICalFeed): def item_title(self, item): return ( - 'Oude Thalia Feed, gebruik https://thalia.nu/' + + 'Oude Thalia Feed, gebruik https://thalia.nu' + reverse('events:ical-nl') ) diff --git a/website/events/templates/events/index.html b/website/events/templates/events/index.html index 41c5b61e..961c265f 100644 --- a/website/events/templates/events/index.html +++ b/website/events/templates/events/index.html @@ -34,7 +34,7 @@ var sources = { events: "/api/events", birthdays: "/api/members/birthdays", - partners: "/api/partners/events" + partners: "/api/partners/events" }; $('#calendar').fullCalendar({ -- GitLab From 302f49466886859d88ea6a3f0474a2af033bcf52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Wed, 5 Oct 2016 22:04:12 +0200 Subject: [PATCH 14/18] Fix error when entered dates are invalid --- website/events/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/website/events/models.py b/website/events/models.py index 26fb8075..63a5c097 100644 --- a/website/events/models.py +++ b/website/events/models.py @@ -107,9 +107,10 @@ class Event(models.Model, metaclass=ModelTranslateMeta): def clean(self): super().clean() errors = {} - if self.end < self.start: + if self.end is not None and self.start is not None and ( + self.end < self.start): errors.update({ - 'end': _("Can't have an event travel back in time")}) + 'end': _("Can't have an event travel back in time")}) if self.registration_required(): if self.no_registration_message: errors.update( @@ -127,7 +128,7 @@ class Event(models.Model, metaclass=ModelTranslateMeta): "If registration is required, you need an end of " "registration")}) if self.registration_start and self.registration_end and ( - self.registration_start >= self.registration_end): + self.registration_start >= self.registration_end): message = _('Registration start should be before ' 'registration end') errors.update({ -- GitLab From 2aec63a9249d74eab8d245f72a82d34b894e4201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Sun, 9 Oct 2016 15:12:20 +0200 Subject: [PATCH 15/18] Working basic registration --- website/events/models.py | 46 +++++++- website/events/templates/events/event.html | 104 +++++++++--------- .../events/templates/events/event_fields.html | 9 ++ website/events/templates/events/index.html | 2 + website/events/urls.py | 4 +- website/events/views.py | 90 ++++++++++++++- website/thaliawebsite/templates/base.html | 2 +- 7 files changed, 196 insertions(+), 61 deletions(-) create mode 100644 website/events/templates/events/event_fields.html diff --git a/website/events/models.py b/website/events/models.py index 63a5c097..6408d533 100644 --- a/website/events/models.py +++ b/website/events/models.py @@ -11,6 +11,13 @@ from utils.translation import MultilingualField, ModelTranslateMeta class Event(models.Model, metaclass=ModelTranslateMeta): """Represents events""" + REGISTRATION_NOT_NEEDED = -1 + REGISTRATION_NOT_YET_OPEN = 0 + REGISTRATION_OPEN = 1 + REGISTRATION_OPEN_NO_CANCEL = 2 + REGISTRATION_CLOSED = 3 + REGISTRATION_CLOSED_CANCEL_ONLY = 4 + DEFAULT_NO_REGISTRATION_MESSAGE = _('No registration required') title = MultilingualField( @@ -101,9 +108,34 @@ class Event(models.Model, metaclass=ModelTranslateMeta): published = models.BooleanField(_("published"), default=False) + def after_deadline(self): + return self.registration_end < timezone.now() + def registration_required(self): return bool(self.registration_start) or bool(self.registration_end) + def has_fields(self): + return self.registrationinformationfield_set.count() > 0 + + def reached_participants_limit(self): + return self.max_participants <= self.registration_set.count() + + def status(self): + now = timezone.now() + if bool(self.registration_start) or bool(self.registration_end): + if now <= self.registration_start: + return Event.REGISTRATION_NOT_YET_OPEN + elif self.registration_end <= now < self.cancel_deadline: + return Event.REGISTRATION_CLOSED_CANCEL_ONLY + elif self.cancel_deadline <= now < self.registration_end: + return Event.REGISTRATION_OPEN_NO_CANCEL + elif now >= self.registration_end and now >= self.cancel_deadline: + return Event.REGISTRATION_CLOSED + else: + return Event.REGISTRATION_OPEN + else: + return Event.REGISTRATION_NOT_NEEDED + def clean(self): super().clean() errors = {} @@ -127,6 +159,11 @@ class Event(models.Model, metaclass=ModelTranslateMeta): {'registration_end': _( "If registration is required, you need an end of " "registration")}) + if not self.cancel_deadline: + errors.update( + {'cancel_deadline': _( + "If registration is required, you need a deadline for " + "the cancellation")}) if self.registration_start and self.registration_end and ( self.registration_start >= self.registration_end): message = _('Registration start should be before ' @@ -237,7 +274,12 @@ class Registration(models.Model): def is_late_cancellation(self): return (self.date_cancelled and - self.date_cancelled > self.event.cancel_deadline) + self.date_cancelled > self.event.cancel_deadline and + self.event.registration_set.filter( + (Q(date_cancelled__gte=self.date_cancelled) | + Q(date_cancelled=None)) & + Q(date_lte=self.date) + ) < self.event.max_participants) def is_registered(self): return self.date_cancelled is None @@ -248,7 +290,7 @@ class Registration(models.Model): return max(self.event.registration_set.filter( date_cancelled=None, - id__lte=self.id + date__lte=self.date ).count() - self.event.max_participants, 0) def clean(self): diff --git a/website/events/templates/events/event.html b/website/events/templates/events/event.html index 9dd3ae0c..bdea9a8f 100644 --- a/website/events/templates/events/event.html +++ b/website/events/templates/events/event.html @@ -1,10 +1,21 @@ {% extends "base.html" %} {% load i18n static %} +{% block title %}{{ event.title }} — {% trans "Calendar"|capfirst %} — {{ block.super }}{% endblock %} + {% block body %}

{{ event.title }}

{{ event.description }}

+ {% if messages %} + {% for message in messages %} +
+ {{ message }} + +
+ {% endfor %} + {% endif %} +
@@ -45,7 +56,7 @@ {{ counter }} registrations {% endblocktrans %} {% if event.max_participants > 0 %} - ({{ event.max_participants }} {% trans "max"|capfirst %}) + ({{ event.max_participants }} {% trans "max" %}) {% with prc=registration_percentage %}
@@ -116,30 +127,31 @@
- @@ -147,35 +159,21 @@ @@ -204,7 +202,7 @@ {% if user.is_authenticated and registrations|length > 0 %}
-

Aanmeldingen

+

{% trans "Registrations" %}

    {% for registration in registrations %}
  • diff --git a/website/events/templates/events/event_fields.html b/website/events/templates/events/event_fields.html new file mode 100644 index 00000000..6144e375 --- /dev/null +++ b/website/events/templates/events/event_fields.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{{ event.title }} — {% trans "Calendar"|capfirst %} — {{ block.super }}{% endblock %} + +{% block body %} +

    {{ event.title }}

    + +{% endblock %} \ No newline at end of file diff --git a/website/events/templates/events/index.html b/website/events/templates/events/index.html index 961c265f..9dbce6bd 100644 --- a/website/events/templates/events/index.html +++ b/website/events/templates/events/index.html @@ -2,6 +2,8 @@ {% load i18n static compress %}} {% get_current_language as LANGUAGE_CODE %} +{% block title %}{% trans "Calendar"|capfirst %} — {{ block.super }}{% endblock %} + {% block css_head %} {{ block.super }} {% compress css %} diff --git a/website/events/urls.py b/website/events/urls.py index ca884287..0fb667d5 100644 --- a/website/events/urls.py +++ b/website/events/urls.py @@ -10,7 +10,9 @@ from . import views urlpatterns = [ url(r'admin/(?P\d+)/$', views.admin_details, name='admin-details'), url(r'admin/(?P\d+)/export/$', views.export, name='export'), - url(r'^(?P\d+)$', views.event, name='event'), + url(r'^(?P\d+)/$', views.event, name='event'), + url(r'^(?P\d+)/registration/(?P[-\w]+)/$', + views.registration, name='registration'), url(r'^$', views.index, name='index'), url(r'^ical/nl.ics', EventFeed(lang='nl'), name='ical-nl'), url(r'^ical/en.ics', EventFeed(), name='ical-en'), diff --git a/website/events/views.py b/website/events/views.py index 2441c10e..73665975 100644 --- a/website/events/views.py +++ b/website/events/views.py @@ -2,11 +2,13 @@ import csv from datetime import timedelta from django.http import HttpResponse -from django.shortcuts import render, get_object_or_404 +from django.shortcuts import render, get_object_or_404, redirect +from django.contrib import messages from django.contrib.admin.views.decorators import staff_member_required -from django.contrib.auth.decorators import permission_required +from django.contrib.auth.decorators import permission_required, login_required from django.utils import timezone from django.utils.text import slugify +from django.utils.translation import ugettext_lazy as _ from .models import Event, Registration @@ -79,6 +81,7 @@ def export(request, event_id): return item['date'] - timedelta(days=10000) else: return item['date'] + for row in sorted(rows, key=order): writer.writerow(row) @@ -112,7 +115,7 @@ def event(request, event_id): } if event.max_participants: - perc = 100.0*len(registrations)/event.max_participants + perc = 100.0 * len(registrations) / event.max_participants context['registration_percentage'] = perc try: @@ -121,7 +124,86 @@ def event(request, event_id): member=request.user.member ) context['registration'] = registration - except: + except Registration.DoesNotExist: pass return render(request, 'events/event.html', context) + + +@login_required +def registration(request, event_id, action=None): + event = get_object_or_404( + Event.objects.filter(published=True), + pk=event_id + ) + + if event.registration_required(): + try: + registration = Registration.objects.get( + event=event, + member=request.user.member + ) + except Registration.DoesNotExist: + registration = None + + success_message = None + error_message = None + show_fields = False + if action == 'register': + if event.has_fields(): + show_fields = True + + if registration is None: + registration = Registration() + registration.event = event + registration.member = request.user.member + elif registration.date_cancelled is not None: + registration.date = timezone.now() + registration.date_cancelled = None + else: + error_message = _("You were already registered.") + + if error_message is None: + success_message = _("Registration successful") + elif (action == 'update' and event.has_fields() and + registration is not None): + show_fields = True + elif action == 'cancel': + if (registration is not None and + registration.date_cancelled is None): + registration.date_cancelled = timezone.now() + success_message = _("Registration successfully cancelled") + else: + error_message = _("You were not registered for this event.") + + if show_fields: + # saved = False + # + # if request.POST: + # form = AddExamForm(request.POST, request.FILES) + # if form.is_valid(): + # saved = True + # obj = form.save(commit=False) + # obj.uploader = request.user + # obj.uploader_date = datetime.now() + # obj.save() + # + # form = AddExamForm() + # form.exam_date = datetime.now() + # else: + # obj = Exam() + # if id is not None: + # obj.course = Course.objects.get(id=id) + # form = AddExamForm(instance=obj) + # form.exam_date = datetime.now() + + context = {'event': event} + return render(request, 'events/event_fields.html', context) + else: + if success_message is not None: + messages.success(request, success_message) + elif error_message is not None: + messages.error(request, error_message) + registration.save() + + return redirect(event) diff --git a/website/thaliawebsite/templates/base.html b/website/thaliawebsite/templates/base.html index 1ed2fcf4..19d4279b 100644 --- a/website/thaliawebsite/templates/base.html +++ b/website/thaliawebsite/templates/base.html @@ -67,7 +67,7 @@ {% if not user.is_authenticated %} {% else %}
    -- GitLab From f71ea9c095d151b100a1bc678af88bd8134c573e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Sun, 9 Oct 2016 23:48:46 +0200 Subject: [PATCH 16/18] Extended the events registration to support fields --- website/events/admin.py | 23 ++-- website/events/forms.py | 52 +++++++++ .../migrations/0007_auto_20161009_2352.py | 26 +++++ website/events/models.py | 53 +++++++-- .../events/admin/registrations_table.html | 9 +- website/events/templates/events/event.html | 21 ++-- .../events/templates/events/event_fields.html | 36 +++++- website/events/views.py | 103 ++++++++++-------- 8 files changed, 239 insertions(+), 84 deletions(-) create mode 100644 website/events/forms.py create mode 100644 website/events/migrations/0007_auto_20161009_2352.py diff --git a/website/events/admin.py b/website/events/admin.py index b1f9ffa1..a96c4244 100644 --- a/website/events/admin.py +++ b/website/events/admin.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- -from django import forms -from django.urls import reverse +from activemembers.models import Committee from django.contrib import admin from django.http import HttpResponseRedirect +from django.urls import reverse from django.utils import timezone -from django.utils.http import is_safe_url from django.utils.html import format_html +from django.utils.http import is_safe_url from django.utils.translation import ugettext_lazy as _ - -from utils.translation import TranslatedModelAdmin -from activemembers.models import Committee from members.models import Member +from utils.translation import TranslatedModelAdmin + +from . import forms from . import models @@ -32,17 +32,8 @@ class DoNextModelAdmin(TranslatedModelAdmin): return _do_next(request, res) -class RegistrationInformationFieldForm(forms.ModelForm): - - order = forms.IntegerField(label=_('order'), initial=0) - - class Meta: - fields = '__all__' - model = models.RegistrationInformationField - - class RegistrationInformationFieldInline(admin.StackedInline): - form = RegistrationInformationFieldForm + form = forms.RegistrationInformationFieldForm extra = 0 model = models.RegistrationInformationField ordering = ('_order',) diff --git a/website/events/forms.py b/website/events/forms.py new file mode 100644 index 00000000..fb929273 --- /dev/null +++ b/website/events/forms.py @@ -0,0 +1,52 @@ +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from .models import RegistrationInformationField + + +class RegistrationInformationFieldForm(forms.ModelForm): + + order = forms.IntegerField(label=_('order'), initial=0) + + class Meta: + fields = '__all__' + model = RegistrationInformationField + widgets = { + 'type': forms.Select, + } + + +class FieldsForm(forms.Form): + def __init__(self, *args, **kwargs): + registration = kwargs.pop('registration') + super(FieldsForm, self).__init__(*args, **kwargs) + + self.information_fields = registration.registration_information() + + for information_field in self.information_fields: + field = information_field['field'] + key = "info_field_{}".format(field.id) + + if field.type == RegistrationInformationField.BOOLEAN_FIELD: + self.fields[key] = forms.BooleanField( + required=False + ) + elif field.type == RegistrationInformationField.INTEGER_FIELD: + self.fields[key] = forms.IntegerField() + elif field.type == RegistrationInformationField.TEXT_FIELD: + self.fields[key] = forms.CharField() + + self.fields[key].label = field.name + self.fields[key].help_text = field.description + self.fields[key].initial = information_field['value'] + if not field.type == RegistrationInformationField.BOOLEAN_FIELD: + self.fields[key].required = field.required + + def field_values(self): + for information_field in self.information_fields: + key = "info_field_{}".format(information_field['field'].id) + field = { + "field": information_field["field"], + "value": self.cleaned_data[key] + } + yield field diff --git a/website/events/migrations/0007_auto_20161009_2352.py b/website/events/migrations/0007_auto_20161009_2352.py new file mode 100644 index 00000000..16abf9d3 --- /dev/null +++ b/website/events/migrations/0007_auto_20161009_2352.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-10-09 21:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0006_auto_20160921_2201'), + ] + + operations = [ + migrations.AddField( + model_name='registrationinformationfield', + name='required', + field=models.BooleanField(default=True, verbose_name='required'), + preserve_default=False, + ), + migrations.AlterField( + model_name='registrationinformationfield', + name='type', + field=models.CharField(choices=[('boolean', 'Checkbox'), ('text', 'Text'), ('integer', 'Integer')], max_length=10, verbose_name='field type'), + ), + ] diff --git a/website/events/models.py b/website/events/models.py index 6408d533..c9417acd 100644 --- a/website/events/models.py +++ b/website/events/models.py @@ -2,9 +2,9 @@ from django.core import validators from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q +from django.urls import reverse from django.utils import timezone from django.utils.translation import ugettext_lazy as _, string_concat -from django.urls import reverse from utils.translation import MultilingualField, ModelTranslateMeta @@ -120,6 +120,7 @@ class Event(models.Model, metaclass=ModelTranslateMeta): def reached_participants_limit(self): return self.max_participants <= self.registration_set.count() + @property def status(self): now = timezone.now() if bool(self.registration_start) or bool(self.registration_end): @@ -188,9 +189,13 @@ class Event(models.Model, metaclass=ModelTranslateMeta): class RegistrationInformationField(models.Model): """Field description to ask for when registering""" - FIELD_TYPES = (('checkbox', _('checkbox')), - ('charfield', _('text field')), - ('intfield', _('integer field'))) + BOOLEAN_FIELD = 'boolean' + INTEGER_FIELD = 'integer' + TEXT_FIELD = 'text' + + FIELD_TYPES = ((BOOLEAN_FIELD, _('Checkbox')), + (TEXT_FIELD, _('Text')), + (INTEGER_FIELD, _('Integer')),) event = models.ForeignKey(Event, models.CASCADE) @@ -211,13 +216,18 @@ class RegistrationInformationField(models.Model): blank=True, ) + required = models.BooleanField( + _('required'), + ) + def get_value_for(self, registration): - if self.type == 'charfield': + if self.type == self.TEXT_FIELD: value_set = self.textregistrationinformation_set - elif self.type == 'checkbox': + elif self.type == self.BOOLEAN_FIELD: value_set = self.booleanregistrationinformation_set - elif self.type == 'intfield': + elif self.type == self.INTEGER_FIELD: value_set = self.integerregistrationinformation_set + try: return value_set.get(registration=registration).value except (TextRegistrationInformation.DoesNotExist, @@ -225,6 +235,31 @@ class RegistrationInformationField(models.Model): IntegerRegistrationInformation.DoesNotExist): return None + def set_value_for(self, registration, value): + if self.type == self.TEXT_FIELD: + value_set = self.textregistrationinformation_set + elif self.type == self.BOOLEAN_FIELD: + value_set = self.booleanregistrationinformation_set + elif self.type == self.INTEGER_FIELD: + value_set = self.integerregistrationinformation_set + + try: + field_value = value_set.get(registration=registration) + except BooleanRegistrationInformation.DoesNotExist: + field_value = BooleanRegistrationInformation() + except TextRegistrationInformation.DoesNotExist: + field_value = TextRegistrationInformation() + except IntegerRegistrationInformation.DoesNotExist: + field_value = IntegerRegistrationInformation() + + field_value.registration = registration + field_value.field = self + field_value.value = value + field_value.save() + + def __str__(self): + return "{} ({})".format(self.name, dict(self.FIELD_TYPES)[self.type]) + class Meta: order_with_respect_to = 'event' @@ -278,8 +313,8 @@ class Registration(models.Model): self.event.registration_set.filter( (Q(date_cancelled__gte=self.date_cancelled) | Q(date_cancelled=None)) & - Q(date_lte=self.date) - ) < self.event.max_participants) + Q(date__lte=self.date) + ).count() < self.event.max_participants) def is_registered(self): return self.date_cancelled is None diff --git a/website/events/templates/events/admin/registrations_table.html b/website/events/templates/events/admin/registrations_table.html index 7dced333..feb1dcdb 100644 --- a/website/events/templates/events/admin/registrations_table.html +++ b/website/events/templates/events/admin/registrations_table.html @@ -10,10 +10,14 @@ {% endfor %}
+ {% if registrations|length > 0 and registrations.0.date_cancelled is not None %} + + {% endif %} + {% trans "add"|capfirst %} {% endif %} + @@ -36,6 +40,9 @@ {% endfor %} + {% if registration.date_cancelled is not None %} + + {% endif %} {% empty %} diff --git a/website/events/templates/events/event.html b/website/events/templates/events/event.html index bdea9a8f..51034e76 100644 --- a/website/events/templates/events/event.html +++ b/website/events/templates/events/event.html @@ -128,9 +128,15 @@ {% endif %} {% endif %} - - {# Todo implement aanmeldmechanisme #} -{# getRegistrationStatus();#} -{# $afterDeadline = false;#} -{##} -{# // Determine link and text for button#} -{# if(!$user->isLoggedIn() || $registration_status === ActivityModel::REGISTRATION_NOT_NEEDED) {#} -{# $show_button = false;#} -{# } elseif(!($registered && $registration->isRegisteredExcludeCoolDown())) {#} -{# // Not yet registered#} -{# if ($registration_count >= $activity->registration_limit) {#} -{# $button_text = "Op reservelijst";#} -{# } else {#} -{# $button_text = "Aanmelden";#} -{# }#} -{# if($registration !== null && $registration->isInCoolDown()) {#} -{# $button_text = "Afmelding annuleren";#} -{# }#} -{# $show_button = $registration_status === ActivityModel::REGISTRATION_OPEN || $registration_status === ActivityModel::REGISTRATION_CLOSED_REGISTER_ONLY;#} -{# $intent = "register";#} -{# } else {#} -{# // Already registered#} -{# $button_text = "Afmelden";#} -{# $show_button = true;#} -{# if(!$activity->canCancel())#} -{# {#} -{# $afterDeadline = true;#} -{# $costText = empty($activity->thalia_costs) ? '' : ' van ongeveer € ' . number_format($activity->thalia_costs, 2, ',', '.');#} -{# $confirmText = 'De deadline is verstreken, weet je zeker dat je je wilt afmelden en daarmee een boete krijgt' . $costText . '? Je kunt dit niet ongedaan maken.';#} -{##} -{# }#} -{# $intent = "cancel";#} -{# }#} -{# ?>#} @@ -164,10 +128,6 @@ {% trans "You have to log in before you can register for this event." %} {% elif event.status == event.REGISTRATION_NOT_YET_OPEN %} {% blocktrans with datetime=event.registration_start %}Registration will open {{ datetime }}{% endblocktrans %} - {% elif event.status == event.REGISTRATION_OPEN_NO_CANCEL and registration.date_cancelled is None %} - {% if event.costs > 0 %} - {% blocktrans with costs=event.costs %}Cancellation isn't possible anymore without having to pay the full costs of {{ costs }}{% endblocktrans %} - {% endif %} {% elif event.status == event.REGISTRATION_CLOSED or event.status == event.REGISTRATION_CLOSED_CANCEL_ONLY %} {% blocktrans %}Registration is not possible anymore.{% endblocktrans %} {% elif status == event.REGISTRATION_NOT_NEEDED %} @@ -177,21 +137,27 @@ {% trans "No registration required" %} {% endif %} {% endif %} + {% if event.status == event.REGISTRATION_OPEN_NO_CANCEL or event.status == event.REGISTRATION_CLOSED %} + {% if registration.date_cancelled is None and event.cost > 0 %} + {% blocktrans with costs=event.cost %}Cancellation isn't possible anymore without having to pay the full costs of €{{ costs }}. Also note that you will be unable to re-register.{% endblocktrans %} + {% endif %} + {% endif %} - {# TODO IMPLEMENT PIZZA BUTTON #} -{# #} -{# #} -{# #} -{# #} -{# #} + + {#{% if event.pizzaevent %}#} + {##} + {##} + {##} + {##} + {#{% endif %}#}
- -{# #} -{##} -{#
#} -{# hidden('activity_id', $activity->id); ?>#} -{# hidden('intent', $intent); ?>#} -{# #} -{# submit('submit', $button_text, ['class' => 'btn btn-style1', 'onclick' => 'return confirm(\'' . $confirmText . '\');']); ?>#} -{# #} -{# submit('submit', $button_text, ['class' => 'btn btn-style1']); ?>#} -{# #} -{# #} -{#
#} -{##} -{# #} -{# isRegisteredExcludeCoolDown() && count($activity->getExtraFields()) > 0):?>#} -{#
#} -{# hidden('activity_id', $activity->id);?>#} -{# hidden('intent', 'prompt_update_screen');?>#} -{# submit('submit', 'Bijwerken', ['class' => 'btn btn-style1']); ?>#} -{# #} -{#
#} -{# #} + {% if request.user.is_authenticated %} + {% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_CLOSED_CANCEL_ONLY %} + {% if registration is None or registration.date_cancelled is not None %} + {% if event.status == event.REGISTRATION_OPEN %} + + {% if event.reached_participants_limit %} + {% trans "Put me on the waiting list" %} + {% else %} + {% trans "Register" %} + {% endif %} + + {% endif %} + {% elif registration is not None and registration.date_cancelled is None %} + {# TODO: Specific message to accept costs when cancelling after the deadline, unless member is on the waiting list #} + {% trans "Cancel registration" %} + {% endif %} + {% endif %} + {% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_OPEN_NO_CANCEL %} + {% if registration is not None and registration.date_cancelled is None and event.has_fields %} + {% trans "Update information" %} + {% endif %} + {% endif %} + {% else %} + {% trans "Login" %} + {% endif %}
- {% if not request.user.is_authenticated %} + {% if not request.user.is_authenticated %} {% trans "You have to log in before you can register for this event." %} + {% elif event.status == event.REGISTRATION_NOT_YET_OPEN %} + {% blocktrans with datetime=event.registration_start %}Registration will open {{ datetime }}{% endblocktrans %} + {% elif event.status == event.REGISTRATION_OPEN_NO_CANCEL %} + {% blocktrans with costs=event.costs %}Cancellation isn't possible anymore without having to pay the full costs of {{ costs }}{% endblocktrans %} + {% elif event.status == event.REGISTRATION_CLOSED or event.status == event.REGISTRATION_CLOSED_CANCEL_ONLY %} + {% blocktrans %}Registration is not possible anymore.{% endblocktrans %} + {% elif status == event.REGISTRATION_NOT_NEEDED %} + {% if event.no_registration_message %} + {{ event.no_registration_message }} + {% else %} + {% trans "No registration required" %} {% endif %} - - - {# Todo implement aanmeldtekst #} -{# isLoggedIn()):?>#} -{# isInCoolDown()): ?>#} -{# #} -{# Je hebt nog een onbekende hoeveelheid tijd om je afmelding ongedaan te maken.#} -{# #} -{# registration_not_needed_message !== null) : ?>#} -{# registration_not_needed_message; ?>#} -{# #} -{# Aanmelden hoeft niet#} -{# #} -{# #} -{# Aanmelden kan vanaf formatDate('begin_registration'); ?>#} -{# isRegisteredExcludeCoolDown() && ($registration_status === ActivityModel::REGISTRATION_CLOSED || $registration_status === ActivityModel::REGISTRATION_CLOSED_REGISTER_ONLY)): ?>#} -{# #} -{# Afmelden kan niet meer, tenzij je de boete betaalt .#} -{# isRegisteredExcludeCoolDown()) && ($registration_status === ActivityModel::REGISTRATION_CLOSED || $registration_status === ActivityModel::REGISTRATION_CLOSED_CANCEL_ONLY)): ?>#} -{# #} -{# Aanmelden kan niet meer#} -{# #} + {% endif %}
{% trans "present"|capfirst %} {% trans "paid"|capfirst %}{% trans "late"|capfirst %} {% if addlink != 0 %} - {% trans "add"|capfirst %}
{{ registration.present|yesno }} {{ registration.paid|yesno }}{{ registration.is_late_cancellation|yesno }}{% trans "change" %}
{% if request.user.is_authenticated %} - {% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_CLOSED_CANCEL_ONLY %} + {% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_OPEN_NO_CANCEL %} + {% if registration is not None and registration.date_cancelled is None and event.has_fields %} + {% trans "Update registration" %} +

+ {% endif %} + {% endif %} + {% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_CLOSED_CANCEL_ONLY or event.status == event.REGISTRATION_OPEN_NO_CANCEL %} {% if registration is None or registration.date_cancelled is not None %} - {% if event.status == event.REGISTRATION_OPEN %} + {% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_OPEN_NO_CANCEL %} {% if event.reached_participants_limit %} {% trans "Put me on the waiting list" %} @@ -144,11 +150,6 @@ {% trans "Cancel registration" %} {% endif %} {% endif %} - {% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_OPEN_NO_CANCEL %} - {% if registration is not None and registration.date_cancelled is None and event.has_fields %} - {% trans "Update information" %} - {% endif %} - {% endif %} {% else %} {% trans "Login" %} {% endif %} @@ -163,8 +164,10 @@ {% trans "You have to log in before you can register for this event." %} {% elif event.status == event.REGISTRATION_NOT_YET_OPEN %} {% blocktrans with datetime=event.registration_start %}Registration will open {{ datetime }}{% endblocktrans %} - {% elif event.status == event.REGISTRATION_OPEN_NO_CANCEL %} - {% blocktrans with costs=event.costs %}Cancellation isn't possible anymore without having to pay the full costs of {{ costs }}{% endblocktrans %} + {% elif event.status == event.REGISTRATION_OPEN_NO_CANCEL and registration.date_cancelled is None %} + {% if event.costs > 0 %} + {% blocktrans with costs=event.costs %}Cancellation isn't possible anymore without having to pay the full costs of {{ costs }}{% endblocktrans %} + {% endif %} {% elif event.status == event.REGISTRATION_CLOSED or event.status == event.REGISTRATION_CLOSED_CANCEL_ONLY %} {% blocktrans %}Registration is not possible anymore.{% endblocktrans %} {% elif status == event.REGISTRATION_NOT_NEEDED %} diff --git a/website/events/templates/events/event_fields.html b/website/events/templates/events/event_fields.html index 6144e375..8e2ccc00 100644 --- a/website/events/templates/events/event_fields.html +++ b/website/events/templates/events/event_fields.html @@ -1,9 +1,39 @@ {% extends "base.html" %} -{% load i18n static %} +{% load i18n static fieldtype %} -{% block title %}{{ event.title }} — {% trans "Calendar"|capfirst %} — {{ block.super }}{% endblock %} +{% block title %}{% if action == 'update' %}{% trans "Update registration" %}{% else %}{% trans "Complete registration" %}{% endif %} — {{ event.title }} — {% trans "Calendar" %} — {{ block.super }}{% endblock %} {% block body %} -

{{ event.title }}

+ {% if action == 'update' %} +

{% blocktrans with title=event.title %}Update registration for {{ title }}{% endblocktrans %}

+ {% else %} +

{% blocktrans with title=event.title %}Completing registration for {{ title }}{% endblocktrans %}

+ {% endif %} + +
+ {% csrf_token %} + + {% for field in form %} +
+ +
+ {{ field }} + + {% for error in field.errors %} + {{ error|escape }} + {% endfor %} + + {{ field.help_text|safe }} +
+
+ {% endfor %} + + {% if action == 'update' %} + + {% else %} + + {% endif %} + {% trans 'Cancel' %} +
{% endblock %} \ No newline at end of file diff --git a/website/events/views.py b/website/events/views.py index 73665975..2bc0bfd6 100644 --- a/website/events/views.py +++ b/website/events/views.py @@ -11,6 +11,7 @@ from django.utils.text import slugify from django.utils.translation import ugettext_lazy as _ from .models import Event, Registration +from .forms import FieldsForm @staff_member_required @@ -106,7 +107,8 @@ def event(request, event_id): Event.objects.filter(published=True), pk=event_id ) - registrations = event.registration_set.filter(date_cancelled=None) + registrations = event.registration_set.filter( + date_cancelled=None)[:event.max_participants] context = { 'event': event, @@ -137,73 +139,82 @@ def registration(request, event_id, action=None): pk=event_id ) - if event.registration_required(): + if (event.registration_required() and + event.status != Event.REGISTRATION_NOT_NEEDED): try: - registration = Registration.objects.get( + obj = Registration.objects.get( event=event, member=request.user.member ) except Registration.DoesNotExist: - registration = None + obj = None success_message = None error_message = None show_fields = False - if action == 'register': + if action == 'register' and ( + event.status == Event.REGISTRATION_OPEN or + event.status == Event.REGISTRATION_OPEN_NO_CANCEL + ): if event.has_fields(): show_fields = True - if registration is None: - registration = Registration() - registration.event = event - registration.member = request.user.member - elif registration.date_cancelled is not None: - registration.date = timezone.now() - registration.date_cancelled = None + if obj is None: + obj = Registration() + obj.event = event + obj.member = request.user.member + elif obj.date_cancelled is not None: + if obj.is_late_cancellation(): + error_message = _("You cannot re-register anymore since " + "you've cancelled after the deadline.") + else: + obj.date = timezone.now() + obj.date_cancelled = None else: error_message = _("You were already registered.") if error_message is None: success_message = _("Registration successful") - elif (action == 'update' and event.has_fields() and - registration is not None): + elif (action == 'update' + and event.has_fields() + and obj is not None and + event.status == Event.REGISTRATION_OPEN or + event.status == Event.REGISTRATION_OPEN_NO_CANCEL): show_fields = True - elif action == 'cancel': - if (registration is not None and - registration.date_cancelled is None): - registration.date_cancelled = timezone.now() + success_message = _("Registration successfully updated") + elif action == 'cancel' and ( + event.status == Event.REGISTRATION_OPEN or + event.status == Event.REGISTRATION_CLOSED_CANCEL_ONLY + ): + if (obj is not None and + obj.date_cancelled is None): + # Note that this doesn't remove the values for the + # information fields that the user entered upon registering. + # But this is regarded as a feature, not a bug. Especially + # since the values will still appear in the backend. + obj.date_cancelled = timezone.now() success_message = _("Registration successfully cancelled") else: error_message = _("You were not registered for this event.") if show_fields: - # saved = False - # - # if request.POST: - # form = AddExamForm(request.POST, request.FILES) - # if form.is_valid(): - # saved = True - # obj = form.save(commit=False) - # obj.uploader = request.user - # obj.uploader_date = datetime.now() - # obj.save() - # - # form = AddExamForm() - # form.exam_date = datetime.now() - # else: - # obj = Exam() - # if id is not None: - # obj.course = Course.objects.get(id=id) - # form = AddExamForm(instance=obj) - # form.exam_date = datetime.now() - - context = {'event': event} - return render(request, 'events/event_fields.html', context) - else: - if success_message is not None: - messages.success(request, success_message) - elif error_message is not None: - messages.error(request, error_message) - registration.save() + if request.POST: + form = FieldsForm(request.POST, registration=obj) + if form.is_valid(): + form_field_values = form.field_values() + for field in form_field_values: + field['field'].set_value_for(obj, + field['value']) + obj.save() + else: + form = FieldsForm(registration=obj) + context = {'event': event, 'form': form, 'action': action} + return render(request, 'events/event_fields.html', context) + + if success_message is not None: + messages.success(request, success_message) + elif error_message is not None: + messages.error(request, error_message) + obj.save() return redirect(event) -- GitLab From 324e289fd6c4d50e7d9a162a564d18ca2b875219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Mon, 10 Oct 2016 01:05:00 +0200 Subject: [PATCH 17/18] Clean HTML, fix cancellation and enable cancelling after deadline with special confirmation. --- website/events/models.py | 4 +- website/events/templates/events/event.html | 86 +++++++--------------- website/events/views.py | 23 +++--- 3 files changed, 38 insertions(+), 75 deletions(-) diff --git a/website/events/models.py b/website/events/models.py index c9417acd..e2f64ea3 100644 --- a/website/events/models.py +++ b/website/events/models.py @@ -108,8 +108,8 @@ class Event(models.Model, metaclass=ModelTranslateMeta): published = models.BooleanField(_("published"), default=False) - def after_deadline(self): - return self.registration_end < timezone.now() + def after_cancel_deadline(self): + return self.cancel_deadline <= timezone.now() def registration_required(self): return bool(self.registration_start) or bool(self.registration_end) diff --git a/website/events/templates/events/event.html b/website/events/templates/events/event.html index 51034e76..01fd0f0c 100644 --- a/website/events/templates/events/event.html +++ b/website/events/templates/events/event.html @@ -84,50 +84,10 @@
- {% if request.user.is_authenticated %} + {% if request.user.is_authenticated and request.user.member is not None and request.user.member.current_membership is not None %} {% if event.status == event.REGISTRATION_OPEN or event.status == event.REGISTRATION_OPEN_NO_CANCEL %} {% if registration is not None and registration.date_cancelled is None and event.has_fields %} {% trans "Update registration" %} @@ -146,11 +106,15 @@ {% endif %} {% elif registration is not None and registration.date_cancelled is None %} - {# TODO: Specific message to accept costs when cancelling after the deadline, unless member is on the waiting list #} - {% trans "Cancel registration" %} + {# Special message to accept costs when cancelling after the deadline, unless member is on the waiting list #} + {% if registration.queue_position == 0 and event.after_cancel_deadline %} + {% trans "Cancel registration" %} + {% else %} + {% trans "Cancel registration" %} + {% endif %} {% endif %} {% endif %} - {% else %} + {% elif request.user.is_authenticated is False %} {% trans "Login" %} {% endif %}
#} -{# Pizza#} -{# #} -{# #} -{# Bestellen#} -{# #} -{#
#} + {#Pizza#} + {##} + {##} + {#{% trans "Order" %}#} + {##} + {#
diff --git a/website/events/views.py b/website/events/views.py index 2bc0bfd6..4aa374e2 100644 --- a/website/events/views.py +++ b/website/events/views.py @@ -139,8 +139,8 @@ def registration(request, event_id, action=None): pk=event_id ) - if (event.registration_required() and - event.status != Event.REGISTRATION_NOT_NEEDED): + if (event.status != Event.REGISTRATION_NOT_NEEDED and + request.user.member.current_membership is not None): try: obj = Registration.objects.get( event=event, @@ -174,18 +174,15 @@ def registration(request, event_id, action=None): error_message = _("You were already registered.") if error_message is None: - success_message = _("Registration successful") + success_message = _("Registration successful.") elif (action == 'update' and event.has_fields() - and obj is not None and - event.status == Event.REGISTRATION_OPEN or - event.status == Event.REGISTRATION_OPEN_NO_CANCEL): + and obj is not None + and (event.status == Event.REGISTRATION_OPEN or + event.status == Event.REGISTRATION_OPEN_NO_CANCEL)): show_fields = True - success_message = _("Registration successfully updated") - elif action == 'cancel' and ( - event.status == Event.REGISTRATION_OPEN or - event.status == Event.REGISTRATION_CLOSED_CANCEL_ONLY - ): + success_message = _("Registration successfully updated.") + elif action == 'cancel': if (obj is not None and obj.date_cancelled is None): # Note that this doesn't remove the values for the @@ -193,7 +190,7 @@ def registration(request, event_id, action=None): # But this is regarded as a feature, not a bug. Especially # since the values will still appear in the backend. obj.date_cancelled = timezone.now() - success_message = _("Registration successfully cancelled") + success_message = _("Registration successfully cancelled.") else: error_message = _("You were not registered for this event.") @@ -201,11 +198,11 @@ def registration(request, event_id, action=None): if request.POST: form = FieldsForm(request.POST, registration=obj) if form.is_valid(): + obj.save() form_field_values = form.field_values() for field in form_field_values: field['field'].set_value_for(obj, field['value']) - obj.save() else: form = FieldsForm(registration=obj) context = {'event': event, 'form': form, 'action': action} -- GitLab From d0d9e74bb329845b4d7d479017d1fdf868f99553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastiaan=20Versteeg?= Date: Wed, 12 Oct 2016 21:29:44 +0200 Subject: [PATCH 18/18] Add events sitemap --- website/events/sitemaps.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 website/events/sitemaps.py diff --git a/website/events/sitemaps.py b/website/events/sitemaps.py new file mode 100644 index 00000000..4db5c267 --- /dev/null +++ b/website/events/sitemaps.py @@ -0,0 +1,29 @@ +from django.contrib import sitemaps +from django.urls import reverse + +from . import models + + +class StaticViewSitemap(sitemaps.Sitemap): + changefreq = 'daily' + + def items(self): + return ['events:index'] + + def location(self, item): + return reverse(item) + + +class EventSitemap(sitemaps.Sitemap): + + def items(self): + return models.Event.objects.filter(published=True) + + def location(self, item): + return item.get_absolute_url() + + +sitemap = { + 'events-static': StaticViewSitemap, + 'events-events': EventSitemap, +} -- GitLab