Merge lp:~trimardio/widelands-website/module_scheduling into lp:widelands-website

Proposed by Trimardio
Status: Merged
Merged at revision: 480
Proposed branch: lp:~trimardio/widelands-website/module_scheduling
Merge into: lp:widelands-website
Diff against target: 1596 lines (+1455/-5)
19 files modified
media/css/jquery-ui.multidatespicker.css (+18/-0)
media/css/scheduling.css (+273/-0)
media/js/jquery-ui.multidatespicker.js (+498/-0)
media/js/scheduling.js (+305/-0)
settings.py (+1/-0)
templates/login_box.html (+8/-5)
templates/wlscheduling/base.html (+12/-0)
templates/wlscheduling/clock-svg.html (+26/-0)
templates/wlscheduling/find.html (+27/-0)
templates/wlscheduling/main.html (+22/-0)
templates/wlscheduling/other-users.html (+24/-0)
templates/wlscheduling/scheduling.html (+66/-0)
templates/wlscheduling/timezone-msg.html (+4/-0)
urls.py (+1/-0)
wlscheduling/management/commands/remove_old_dates.py (+11/-0)
wlscheduling/migrations/0001_initial.py (+23/-0)
wlscheduling/models.py (+11/-0)
wlscheduling/urls.py (+10/-0)
wlscheduling/views.py (+115/-0)
To merge this branch: bzr merge lp:~trimardio/widelands-website/module_scheduling
Reviewer Review Type Date Requested Status
kaputtnik (community) Needs Fixing
Review via email: mp+335570@code.launchpad.net

Description of the change

Added the scheduling module.

Widelands games are long. And the player base is relatively small. Thus on IRC it is hard to find someone who has the time to play.

To solve that problem the goal of the scheduling module is to allow players to set the times at which they are available for a game and display it for other.

There are two pages for this module:
- the "find" page allows to see the availabilities of all the players.
- the "scheduling" page allow to set the times when the player will have time to play. It then allow him to see who will be available at the same times.

This is the third attempt I make at merging this cleanly. I'm not a bzr person.

To post a comment you must log in.
Revision history for this message
Trimardio (trimardio) wrote :

As Sirver said about my database cleanup:

"deletion can make this execute arbitrarily slow. Deletion of old objects should be done in ./manage.py cleanup. Add a TODO for this?"

Any idea where I should add my code for it to execute on cleanup?

Revision history for this message
kaputtnik (franku) wrote :

You have to write a management command for this. See:

https://docs.djangoproject.com/en/1.8/howto/custom-management-commands/

We use such of those commands in a cron job on the server which automatically run every day. Eg. for updating the search index or the encyclopedia.

Revision history for this message
kaputtnik (franku) wrote :

I did a small test without a deeper look into the code and found some things which needs fixing:

On http://localhost:8000/scheduling/scheduling/ :

- There is a small area showing the wooden background below the footer. I remember having the same when implementing the datepicker for the search, but i didn't remember how i solved it :-D
- Selecting multiple dates do not work, e.g. Click on 8. January hold mouse button down and move the mouse to 10. January -> no date blocks are shown. Do i misunderstand the multidatepicker?
- If one chooses a date the form where one should choose the available time has more width than the div "blogEntry". Resolution here 1366x768, Browser is Opera

I will be rarely online the next few days.

480. By Trimard \<email address hidden>

added django command to cleanup database of old dates, no more cleanup in view.py

481. By Trimard \<email address hidden>

fixed div showing at the wrong place, date form is now responsive again

Revision history for this message
Trimardio (trimardio) wrote :

Ok, fixed the bugs you were talking about except this one:

"- Selecting multiple dates do not work, e.g. Click on 8. January hold mouse button down and move the mouse to 10. January -> no date blocks are shown. Do i misunderstand the multidatepicker?"

That's normal actually. You should select each dates one by one. You think it's too confusing? I'm not sure many people will be available on that many days that they can't click the dates one by one. I might be wrong though.

Revision history for this message
Trimardio (trimardio) :
Revision history for this message
kaputtnik (franku) wrote :

I just do not know why the additional multidatespicker library has to be used.

An idea for the page where other users available times are displayed: Currently there is a bar shown which is not very intuitive to understand, although the tooltips say the time. Maybe we can exchange the bar with some sort of an analog clock, showing the hours as colored circular sector (cake slice). What do you think?

Revision history for this message
Trimardio (trimardio) wrote :

> I just do not know why the additional multidatespicker library has to be used.

For selecting multiple dates at the same time. You cannot do it directly with jquery ui. If you have an idea on how to do without im all ears :)

Very giod idea for the timer!! Though a 24hour clock might be weird? I need to test that!

Revision history for this message
kaputtnik (franku) wrote :

> For selecting multiple dates at the same time. You cannot do it directly with jquery ui. If you have an idea on how to do without im all ears :)

This looks very simple to me: http://jsfiddle.net/gydL0epa/
Derived from: https://stackoverflow.com/questions/1452066/jquery-ui-datepicker-multiple-date-selections

But if you think you need it, it's ok :-)

We could have two clocks: AM and PM. The idea needs some images though. A good playground for using image sprites.

Revision history for this message
Trimardio (trimardio) wrote :

>We could have two clocks: AM and PM. The idea needs some images though. A good playground for using image sprites.

I prefered to go directly and implement it. Wasn't sure svg would work fine.

[Here is the link](https://i.imgur.com/Zt3fnQn.png)

Only problem I have with this is that the clock will have to stay big because otherwise it will be hard to select each day.

I can try and make them smaller a bit though. Or maybe a button to make them smaller/bigger?

>But if you think you need it, it's ok :-)

I'll keep it for now then :P. Simply to avoid extra work at anyway^^

Revision history for this message
kaputtnik (franku) wrote :

Oh a misunderstanding. I meant the clock not for setting the availabilities, but for showing other users availabilities set. Over there it could be much smaller, imho. Some numbers could be omitted also.

The clock looks good otherwise :-)

Revision history for this message
Trimardio (trimardio) wrote :

Like that then? :)

https://imgur.com/Jh8tPqE

It looks a bit confusing for me, but maybe people will have less chaotic availabilities than my examples?

Revision history for this message
kaputtnik (franku) wrote :

I agree, your examples looks a bit chaotic :-D

I believe many people do not have that many times available. Maybe give the cake slices a border? Or/and using another background for not available hours?

Let's wait for GunChleoc, she has good ideas for such things :-)

Can you update your branch so i can download the latest changes?

Revision history for this message
Trimardio (trimardio) wrote :

Yeah border was a good idea, it's clearer now. Clear enough though? https://imgur.com/PeIVvyH

There is some finition to the design to do (bad alignments, some pixel are black when they shouldn't etc). But I'll do that when we agree at least on the basis :P

482. By Trimard \<email address hidden>

added clock representation of other users avaibilities

Revision history for this message
GunChleoc (gunchleoc) wrote :

I think the numbers on the clock are a bit hard to read now - might just be the quality on Imgur though. How about giving the numbers a border too, or only showing the 4 cardinal numbers?

Revision history for this message
Trimardio (trimardio) wrote :

He're the numbers:
https://imgur.com/qldkcnz

Ok and that one was fun to do:
https://imgur.com/RgJly1q

Revision history for this message
Trimardio (trimardio) wrote :

Ok I definitely should add latin numbers to set the hours too. That's far more easier to read. And more widelandish I would say?

Revision history for this message
Trimardio (trimardio) wrote :

Actual test with number border (sry wrong upload):

https://imgur.com/CTQ6AwE

Oh and the cardinal test too, forgot it:

https://imgur.com/XOUMyBY

Revision history for this message
kaputtnik (franku) wrote :

I like the one with less numbers better. Just switch the AM and PM to the top of the clocks?

I'll try to review the code this week. Please merge trunk and push again, to get the latest change in.

Sorry for replying so late...

Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for the screenshots!

Can you use a sans serif font (e.g. Arial) for the Latin numbers rather than the serif font that you have like now? his will greatly increase legibility.

As a rule of thumb, use serif for printed publications and sans serif for computer screens.

Revision history for this message
Trimardio (trimardio) wrote :

Always so cool working with you guys! All your suggestions were indeed on point:

https://imgur.com/4Oaw59y

483. By Trimard \<email address hidden>

changed clock display. Now only cardinals numbers are showed

484. By Trimard \<email address hidden>

merged with trunk

485. By Trimard \<email address hidden>

fixed back hour display in current user avaibilities

486. By Trimard \<email address hidden>

fixed back hour display in other user avaibilities

Revision history for this message
kaputtnik (franku) wrote :

Thanks :-) Looks better now.

I did a first round of proofreading and added some comments, mainly nits.

I am not sure if we should show the 'Scheduling' in the main menu, which has already many entrys. Since both links in that view needs logging in, i think showing 'Scheduling' should also be only shown if a user has logged in? Not sure though.

I have no time left for now...

Revision history for this message
kaputtnik (franku) wrote :

Another round of proofreading. Sorry for being so nitpicking...

Comments in the code.

Revision history for this message
Trimardio (trimardio) wrote :

Thanks of the inputs! Changes were quick :)

487. By Trimard \<email address hidden>

some fixing, typos and refacto thanks to kaputnik

Revision history for this message
kaputtnik (franku) wrote :

Thanks for keeping on it :-)

How about moving the link to schedule into the loginbox? Replacing "You have x new messages" with the link to scheduling_main and add the numbers of new messages (if there are any) in parenthesis behind the String "Messages".

http://image.ibb.co/jONJGH/shedule_in_loginbox.png

If there are any new messages, this could be also displayed in a tooltip.

One thing in base.html: The block extra_head has to be closed before block title begins. Otherwise the blocks aren't parsed correctly and the string "Scheduling - widelands.org" appears as a white string above the logo.

Can you please remove the proposal to merge from the other branch 'scheduling_module'? Thanks :-)

488. By Trimard \<email address hidden>

added link for navigation to scheduling in login box

489. By Trimard \<email address hidden>

changed display of scheduling link, fixed title bock

Revision history for this message
kaputtnik (franku) wrote :

The view of Players available needs fixing:

http://preview.ibb.co/iewcqn/scheduling_show_dates.png

The position of the PM clock is messed up. Currently the available dates/hours for one user is shown in a column and the next column shows the available times for the next user. I think it would be better to show all available times for one user in the first row(s). If another user has available times, it should be on its own row(s) then.

For the users name it should be shown as 'is', not with uppercase first letter, imho.

review: Needs Fixing
490. By Trimard \<email address hidden>

fixed clock display on small screen, added hide/show btn for clocks, some refacto

491. By Trimard \<email address hidden>

now display all the other users playtime in a row

Revision history for this message
Trimardio (trimardio) wrote :

Ok bug fixed, and indeed a row organization is easier to understand!

I added also a button to hide/show the clocks, because in my tests I found that it becomes quite cumbersum to scroll when some user have a lot of available hours.

492. By Trimard \<email address hidden>

added needed files for 2 previous commit

Revision history for this message
kaputtnik (franku) wrote :

> I added also a button to hide/show the clocks, because in my tests I found that it becomes quite cumbersum to scroll when some user have a lot of available hours.

Good idea :-) Currently the clocks are hidden by default, but i think the other way around would be better. So: Show the clocks by default and make hiding optional.

I tried that my self but got lost in the js file :-D

The rest i could do by myself, so feel free to change that or not.

I think the clocks are also a bit big. Reducing to 120px or 100 is big enough, imho.

Please make the parenthesis for messages_inbox_count optional and add a title. Something like:

{% if messages_inbox_count %}
 <a href="{% url 'messages_inbox' %}" title="You have {{ messages_inbox_count }} new messages">Messages</a>
{% else %}
 <a href="{% url 'messages_inbox' %}">Messages</a>
{% endif %}

And the last thing ( really ;) ): Replace 'Schedule your playtimes' with 'Playtime Scheduler'. I think that fits better.

Revision history for this message
Trimardio (trimardio) wrote :

> Reducing to 120px or 100 is big enough, imho.

100px is too small to be readable IMO so I didn't reduce it further than 120px but if we lack space we could also find some other rearrangement possible. I also decrease the average width of their container, that simplify already :)

I applied approximately all your other suggestions. Indeed good points!

493. By Trimard \<email address hidden>

clock size reduction, clocks now showed by default, login box text fixes

494. By Trimard \<email address hidden>

minor display fix

Revision history for this message
kaputtnik (franku) wrote :

This merged and deployed now.

Thanks Trimard for this feature and being that patient with my slow answers :-)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'media/css/jquery-ui.multidatespicker.css'
2--- media/css/jquery-ui.multidatespicker.css 1970-01-01 00:00:00 +0000
3+++ media/css/jquery-ui.multidatespicker.css 2018-02-16 15:49:47 +0000
4@@ -0,0 +1,18 @@
5+/* jQuery UI Datepicker moving pixels fix */
6+table.ui-datepicker-calendar {
7+ border-collapse: separate;
8+}
9+.ui-datepicker-calendar td {
10+ border: 1px solid transparent;
11+}
12+
13+/* jQuery UI Datepicker hide datepicker helper */
14+#ui-datepicker-div {
15+ display:none !important;
16+}
17+
18+/* jQuery UI Datepicker emphasis on selected dates */
19+.ui-datepicker .ui-datepicker-calendar .ui-state-highlight a {
20+ background: #743620 none;
21+ color: white;
22+}
23\ No newline at end of file
24
25=== added file 'media/css/scheduling.css'
26--- media/css/scheduling.css 1970-01-01 00:00:00 +0000
27+++ media/css/scheduling.css 2018-02-16 15:49:47 +0000
28@@ -0,0 +1,273 @@
29+/* Main */
30+.main-choices {
31+ display: flex;
32+ justify-content: space-around;
33+ margin-top: 40px;
34+}
35+
36+.main-choices a {
37+ width: calc(50% - 30px);
38+}
39+
40+.main-choices button {
41+ width: 100%;
42+ height: 50px;
43+ font-size: 1em;
44+}
45+
46+/* actual scheduling module */
47+#calender {
48+ display: flex;
49+ justify-content: center;
50+}
51+
52+/* Hours of each day display for the user */
53+.day {
54+ margin-bottom: 15px;
55+ background-image: url("../img/black50.png");
56+ padding: 10px;
57+}
58+
59+.day-title h3{
60+ color:white;
61+ font-weight: bold;
62+ margin-top: 0;
63+}
64+
65+.hours-wrapper {
66+ display: flex;
67+}
68+
69+.hours-title-wrapper {
70+ display: flex;
71+ width: 100%;
72+}
73+
74+.hours-title-wrapper p {
75+ width: 42px;
76+ user-select: none;
77+}
78+
79+.hours {
80+ border: 1px solid #909090;
81+ height:40px;
82+ width: 40px;
83+ cursor: pointer;
84+}
85+
86+.hours.selected {
87+ background-color: #118811;
88+}
89+
90+.hours:hover {
91+ background-color: lightgreen;
92+}
93+
94+.hidden-hour {
95+ height: 40px;
96+ width: 40px;
97+}
98+
99+/*********************************/
100+/* Display for the other users */
101+/*********************************/
102+#other-users-wrapper {
103+ display: flex;
104+ flex-wrap: wrap;
105+}
106+
107+.other-user-div{
108+ width: 100%;
109+ border: 1px solid black;
110+ background-image: url(../img/black50.png);
111+ padding: 11px;
112+ margin-top: 5px;
113+ margin-right: 5px;
114+}
115+
116+
117+.other-days-container {
118+ display: flex;
119+ flex-wrap: wrap;
120+}
121+
122+.other-days-container > div {
123+ margin-top: 14px;
124+ margin-right: 7px;
125+ width: 290px;
126+}
127+
128+.other-user-div .title {
129+ height: 36px;
130+ display: flex;
131+ justify-content: space-between;
132+ margin-top: 0;
133+}
134+
135+.other-user-div .title p {
136+ font-size: 20px;
137+ margin-top: 5px;
138+ white-space: nowrap;
139+ overflow: hidden !important;
140+ text-overflow: ellipsis;
141+ color: #feea72;
142+ font-weight: bold;
143+}
144+
145+.other-user-div .title button {
146+ min-width: 114px;
147+}
148+
149+.other-user-div .title button:hover {
150+ background-color:#118811;
151+}
152+
153+.other-user-div .title img {
154+ margin-right: 4px;
155+}
156+
157+
158+.show-clock-btn {
159+ height: 35px;
160+ width: 100%;
161+ text-align: center;
162+ margin-top: 8px;
163+ padding-top: 5px;
164+}
165+
166+.show-clock-btn:hover {
167+ background-color: #118811;
168+}
169+
170+.show-clock-btn .arrow {
171+ width: 18px;
172+ height: 18px;
173+ font-size: inherit;
174+ border: none;
175+ color: rgba(254, 234, 138, 1);
176+ background-image: url(../img/arrow_up_short.png);
177+ background-repeat: no-repeat;
178+ padding: 0px 20px 3px 2px;
179+ background-position: center;
180+}
181+
182+.hide .show-clock-btn .arrow {
183+ background-image: url(../img/arrow_down_short.png);
184+}
185+
186+
187+/********** clock **********/
188+.other-user-div.hide .clocks-wrapper {
189+ display: none;
190+}
191+
192+.clocks-wrapper {
193+ display:flex;
194+ flex-flow: row wrap;
195+ justify-content: space-around;
196+ margin-top: 20px;
197+ padding: 0px 22px;
198+ background-image: url("../img/black50.png");
199+}
200+
201+.clocks-wrapper h2 {
202+ text-align: center;
203+}
204+
205+svg.clock-svg {
206+ width: 120px;
207+ height: 120px;
208+}
209+
210+.hour-area {
211+ stroke-linejoin:round;
212+ stroke-width:8;
213+ fill: #312925;
214+ stroke: black;
215+}
216+
217+.hour-area.active {
218+ fill: #118811;
219+}
220+
221+
222+.number-wrapper {
223+ line-height:100%;
224+ stroke-width:3.7;
225+}
226+
227+.number {
228+ -inkscape-font-specification: "sans-serif";
229+ font-family:"sans-serif";
230+ fill: white;
231+ stroke:black;
232+}
233+
234+.circle {
235+ fill: #312925;
236+}
237+
238+/* btn */
239+#validate-btn {
240+ margin-top: 10px;
241+}
242+
243+
244+/**************************/
245+/* CSS for the datepicker */
246+/**************************/
247+
248+.ui-datepicker {
249+ background-image: url("../img/wood.png");
250+ border: 1px solid black;
251+ border-radius: 4px;
252+ box-shadow: 4px 4px 4px 0px rgba(0, 0, 0, 0.7);
253+ padding: 0.8em;
254+ display: none;
255+}
256+
257+.ui-datepicker-header {
258+ text-align: center;
259+ border: 1px inset darkgray;
260+ background-image: url("../img/black50.png");
261+ margin-bottom: 5px;
262+}
263+
264+.ui-datepicker-calendar {
265+ border: 1px inset darkgray;
266+ background-image: url("../img/black20.png");
267+}
268+
269+.ui-datepicker-prev {
270+ padding: 0 3em 0 0;
271+ cursor: pointer;
272+}
273+
274+.ui-datepicker-next {
275+ padding: 0 0 0 3em;
276+ cursor: pointer;
277+}
278+
279+.ui-widget-content {
280+ display: flex !important;
281+ justify-content: space-around;
282+ width: 540px !important;
283+}
284+
285+.ui-datepicker-header {
286+ height: 42px;
287+ position: relative;
288+}
289+
290+.ui-datepicker-title {
291+ position: absolute;
292+ transform: translateX(-50%);
293+ left: 50%;
294+ bottom: 0;
295+}
296+
297+.ui-datepicker .ui-datepicker-calendar .ui-state-highlight a {
298+ background: #118811 none;
299+}
300+
301+
302
303=== added file 'media/img/arrow_up_short.png'
304Binary files media/img/arrow_up_short.png 1970-01-01 00:00:00 +0000 and media/img/arrow_up_short.png 2018-02-16 15:49:47 +0000 differ
305=== added file 'media/js/jquery-ui.multidatespicker.js'
306--- media/js/jquery-ui.multidatespicker.js 1970-01-01 00:00:00 +0000
307+++ media/js/jquery-ui.multidatespicker.js 2018-02-16 15:49:47 +0000
308@@ -0,0 +1,498 @@
309+/*
310+ * MultiDatesPicker v1.6.4
311+ * http://multidatespickr.sourceforge.net/
312+ *
313+ * Copyright 2014, Luca Lauretta
314+ * Dual licensed under the MIT or GPL version 2 licenses.
315+ */
316+(function( $ ){
317+ $.extend($.ui, { multiDatesPicker: { version: "1.6.4" } });
318+
319+ $.fn.multiDatesPicker = function(method) {
320+ var mdp_arguments = arguments;
321+ var ret = this;
322+ var today_date = new Date();
323+ var day_zero = new Date(0);
324+ var mdp_events = {};
325+
326+ function removeDate(date, type) {
327+ if(!type) type = 'picked';
328+ date = dateConvert.call(this, date);
329+ for(var i = 0; i < this.multiDatesPicker.dates[type].length; i++)
330+ if(!methods.compareDates(this.multiDatesPicker.dates[type][i], date))
331+ return this.multiDatesPicker.dates[type].splice(i, 1).pop();
332+ }
333+ function removeIndex(index, type) {
334+ if(!type) type = 'picked';
335+ return this.multiDatesPicker.dates[type].splice(index, 1).pop();
336+ }
337+ function addDate(date, type, no_sort) {
338+ if(!type) type = 'picked';
339+ date = dateConvert.call(this, date);
340+
341+ // @todo: use jQuery UI datepicker method instead
342+ date.setHours(0);
343+ date.setMinutes(0);
344+ date.setSeconds(0);
345+ date.setMilliseconds(0);
346+
347+ if (methods.gotDate.call(this, date, type) === false) {
348+ this.multiDatesPicker.dates[type].push(date);
349+ if(!no_sort) this.multiDatesPicker.dates[type].sort(methods.compareDates);
350+ }
351+ }
352+ function sortDates(type) {
353+ if(!type) type = 'picked';
354+ this.multiDatesPicker.dates[type].sort(methods.compareDates);
355+ }
356+ function dateConvert(date, desired_type, date_format) {
357+ if(!desired_type) desired_type = 'object';/*
358+ if(!date_format && (typeof date == 'string')) {
359+ date_format = $(this).datepicker('option', 'dateFormat');
360+ if(!date_format) date_format = $.datepicker._defaults.dateFormat;
361+ }
362+ */
363+ return methods.dateConvert.call(this, date, desired_type, date_format);
364+ }
365+
366+ var methods = {
367+ init : function( options ) {
368+ var $this = $(this);
369+ this.multiDatesPicker.changed = false;
370+
371+ var mdp_events = {
372+ beforeShow: function(input, inst) {
373+ this.multiDatesPicker.changed = false;
374+ if(this.multiDatesPicker.originalBeforeShow)
375+ this.multiDatesPicker.originalBeforeShow.call(this, input, inst);
376+ },
377+ onSelect : function(dateText, inst) {
378+ var $this = $(this);
379+ this.multiDatesPicker.changed = true;
380+
381+ if (dateText) {
382+ $this.multiDatesPicker('toggleDate', dateText);
383+ this.multiDatesPicker.changed = true;
384+ // @todo: this will be optimized when I'll move methods to the singleton.
385+ }
386+
387+ if (this.multiDatesPicker.mode == 'normal' && this.multiDatesPicker.pickableRange) {
388+ if(this.multiDatesPicker.dates.picked.length > 0) {
389+ var min_date = this.multiDatesPicker.dates.picked[0],
390+ max_date = new Date(min_date.getTime());
391+
392+ methods.sumDays(max_date, this.multiDatesPicker.pickableRange-1);
393+
394+ // counts the number of disabled dates in the range
395+ if(this.multiDatesPicker.adjustRangeToDisabled) {
396+ var c_disabled,
397+ disabled = this.multiDatesPicker.dates.disabled.slice(0);
398+ do {
399+ c_disabled = 0;
400+ for(var i = 0; i < disabled.length; i++) {
401+ if(disabled[i].getTime() <= max_date.getTime()) {
402+ if((min_date.getTime() <= disabled[i].getTime()) && (disabled[i].getTime() <= max_date.getTime()) ) {
403+ c_disabled++;
404+ }
405+ disabled.splice(i, 1);
406+ i--;
407+ }
408+ }
409+ max_date.setDate(max_date.getDate() + c_disabled);
410+ } while(c_disabled != 0);
411+ }
412+
413+ if(this.multiDatesPicker.maxDate && (max_date > this.multiDatesPicker.maxDate))
414+ max_date = this.multiDatesPicker.maxDate;
415+
416+ $this
417+ .datepicker("option", "minDate", min_date)
418+ .datepicker("option", "maxDate", max_date);
419+ } else {
420+ $this
421+ .datepicker("option", "minDate", this.multiDatesPicker.minDate)
422+ .datepicker("option", "maxDate", this.multiDatesPicker.maxDate);
423+ }
424+ }
425+
426+ if(this.multiDatesPicker.originalOnSelect && dateText)
427+ this.multiDatesPicker.originalOnSelect.call(this, dateText, inst);
428+
429+ },
430+ beforeShowDay : function(date) {
431+ var $this = $(this),
432+ gotThisDate = $this.multiDatesPicker('gotDate', date) !== false,
433+ isDisabledCalendar = $this.datepicker('option', 'disabled'),
434+ isDisabledDate = $this.multiDatesPicker('gotDate', date, 'disabled') !== false,
435+ areAllSelected = this.multiDatesPicker.maxPicks <= this.multiDatesPicker.dates.picked.length;
436+
437+ var bsdReturn = [true, '', null];
438+ if(this.multiDatesPicker.originalBeforeShowDay)
439+ bsdReturn = this.multiDatesPicker.originalBeforeShowDay.call(this, date);
440+
441+ bsdReturn[1] = gotThisDate ? 'ui-state-highlight '+bsdReturn[1] : bsdReturn[1];
442+ bsdReturn[0] = bsdReturn[0] && !(isDisabledCalendar || isDisabledDate || (areAllSelected && !bsdReturn[1]));
443+ return bsdReturn;
444+ }
445+ };
446+
447+ // value have to be extracted before datepicker is initiated
448+ if($this.val()) var inputDates = $this.val()
449+
450+ if(options) {
451+ // value have to be extracted before datepicker is initiated
452+ //if(options.altField) var inputDates = $(options.altField).val();
453+ if(options.separator) this.multiDatesPicker.separator = options.separator;
454+ if(!this.multiDatesPicker.separator) this.multiDatesPicker.separator = ', ';
455+
456+ this.multiDatesPicker.originalBeforeShow = options.beforeShow;
457+ this.multiDatesPicker.originalOnSelect = options.onSelect;
458+ this.multiDatesPicker.originalBeforeShowDay = options.beforeShowDay;
459+ this.multiDatesPicker.originalOnClose = options.onClose;
460+
461+ // datepicker init
462+ $this.datepicker(options);
463+
464+ this.multiDatesPicker.minDate = $.datepicker._determineDate(this, options.minDate, null);
465+ this.multiDatesPicker.maxDate = $.datepicker._determineDate(this, options.maxDate, null);
466+ if(options.addDates) methods.addDates.call(this, options.addDates);
467+
468+ if(options.addDisabledDates)
469+ methods.addDates.call(this, options.addDisabledDates, 'disabled');
470+
471+ methods.setMode.call(this, options);
472+ } else {
473+ $this.datepicker();
474+ }
475+ $this.datepicker('option', mdp_events);
476+
477+ // adds any dates found in the input or alt field
478+ if(inputDates) $this.multiDatesPicker('value', inputDates);
479+
480+ // generates the new string of added dates
481+ var inputs_values = $this.multiDatesPicker('value');
482+
483+ // fills the input field back with all the dates in the calendar
484+ $this.val(inputs_values);
485+
486+ // Fixes the altField filled with defaultDate by default
487+ var altFieldOption = $this.datepicker('option', 'altField');
488+ if (altFieldOption) $(altFieldOption).val(inputs_values);
489+
490+ // Updates the calendar view
491+ $this.datepicker('refresh');
492+ },
493+ compareDates : function(date1, date2) {
494+ date1 = dateConvert.call(this, date1);
495+ date2 = dateConvert.call(this, date2);
496+ // return > 0 means date1 is later than date2
497+ // return == 0 means date1 is the same day as date2
498+ // return < 0 means date1 is earlier than date2
499+ var diff = date1.getFullYear() - date2.getFullYear();
500+ if(!diff) {
501+ diff = date1.getMonth() - date2.getMonth();
502+ if(!diff)
503+ diff = date1.getDate() - date2.getDate();
504+ }
505+ return diff;
506+ },
507+ sumDays : function( date, n_days ) {
508+ var origDateType = typeof date;
509+ obj_date = dateConvert.call(this, date);
510+ obj_date.setDate(obj_date.getDate() + n_days);
511+ return dateConvert.call(this, obj_date, origDateType);
512+ },
513+ dateConvert : function( date, desired_format, dateFormat ) {
514+ var from_format = typeof date;
515+ var $this = $(this);
516+
517+ if(from_format == desired_format) {
518+ if(from_format == 'object') {
519+ try {
520+ date.getTime();
521+ } catch (e) {
522+ $.error('Received date is in a non supported format!');
523+ return false;
524+ }
525+ }
526+ return date;
527+ }
528+
529+ if(typeof date == 'undefined') date = new Date(0);
530+
531+ if(desired_format != 'string' && desired_format != 'object' && desired_format != 'number')
532+ $.error('Date format "'+ desired_format +'" not supported!');
533+
534+ if(!dateFormat) {
535+ // thanks to bibendus83 -> http://sourceforge.net/tracker/index.php?func=detail&aid=3213174&group_id=358205&atid=1495382
536+ var dp_dateFormat = $this.datepicker('option', 'dateFormat');
537+ if (dp_dateFormat) {
538+ dateFormat = dp_dateFormat;
539+ } else {
540+ dateFormat = $.datepicker._defaults.dateFormat;
541+ }
542+ }
543+
544+ // converts to object as a neutral format
545+ switch(from_format) {
546+ case 'object': break;
547+ case 'string': date = $.datepicker.parseDate(dateFormat, date); break;
548+ case 'number': date = new Date(date); break;
549+ default: $.error('Conversion from "'+ desired_format +'" format not allowed on jQuery.multiDatesPicker');
550+ }
551+ // then converts to the desired format
552+ switch(desired_format) {
553+ case 'object': return date;
554+ case 'string': return $.datepicker.formatDate(dateFormat, date);
555+ case 'number': return date.getTime();
556+ default: $.error('Conversion to "'+ desired_format +'" format not allowed on jQuery.multiDatesPicker');
557+ }
558+ return false;
559+ },
560+ gotDate : function( date, type ) {
561+ if(!type) type = 'picked';
562+ for(var i = 0; i < this.multiDatesPicker.dates[type].length; i++) {
563+ if(methods.compareDates.call(this, this.multiDatesPicker.dates[type][i], date) === 0) {
564+ return i;
565+ }
566+ }
567+ return false;
568+ },
569+ value : function( value ) {
570+ if(value && typeof value == 'string') {
571+ methods.addDates.call(this, value.split(this.multiDatesPicker.separator));
572+ } else {
573+ var dates = methods.getDates.call(this, 'string');
574+ return dates.length
575+ ? dates.join(this.multiDatesPicker.separator)
576+ : "";
577+ }
578+ },
579+ getDates : function( format, type ) {
580+ if(!format) format = 'string';
581+ if(!type) type = 'picked';
582+ switch (format) {
583+ case 'object':
584+ return this.multiDatesPicker.dates[type];
585+ case 'string':
586+ case 'number':
587+ var o_dates = new Array();
588+ for(var i in this.multiDatesPicker.dates[type])
589+ o_dates.push(
590+ dateConvert.call(
591+ this,
592+ this.multiDatesPicker.dates[type][i],
593+ format
594+ )
595+ );
596+ return o_dates;
597+
598+ default: $.error('Format "'+format+'" not supported!');
599+ }
600+ },
601+ addDates : function( dates, type ) {
602+ if(dates.length > 0) {
603+ if(!type) type = 'picked';
604+ switch(typeof dates) {
605+ case 'object':
606+ case 'array':
607+ if(dates.length) {
608+ for(var i = 0; i < dates.length; i++)
609+ addDate.call(this, dates[i], type, true);
610+ sortDates.call(this, type);
611+ break;
612+ } // else does the same as 'string'
613+ case 'string':
614+ case 'number':
615+ addDate.call(this, dates, type);
616+ break;
617+ default:
618+ $.error('Date format "'+ typeof dates +'" not allowed on jQuery.multiDatesPicker');
619+ }
620+ //$(this).datepicker('refresh');
621+ } else {
622+ $.error('Empty array of dates received.');
623+ }
624+ },
625+ removeDates : function( dates, type ) {
626+ if(!type) type = 'picked';
627+ var removed = [];
628+ if (Object.prototype.toString.call(dates) === '[object Array]') {
629+ for(var i in dates.sort(function(a,b){return b-a})) {
630+ removed.push(removeDate.call(this, dates[i], type));
631+ }
632+ } else {
633+ removed.push(removeDate.call(this, dates, type));
634+ }
635+ return removed;
636+ },
637+ removeIndexes : function( indexes, type ) {
638+ if(!type) type = 'picked';
639+ var removed = [];
640+ if (Object.prototype.toString.call(indexes) === '[object Array]') {
641+ for(var i in indexes.sort(function(a,b){return b-a})) {
642+ removed.push(removeIndex.call(this, indexes[i], type));
643+ }
644+ } else {
645+ removed.push(removeIndex.call(this, indexes, type));
646+ }
647+ return removed;
648+ },
649+ resetDates : function ( type ) {
650+ if(!type) type = 'picked';
651+ this.multiDatesPicker.dates[type] = [];
652+ },
653+ toggleDate : function( date, type ) {
654+ if(!type) type = 'picked';
655+
656+ switch(this.multiDatesPicker.mode) {
657+ case 'daysRange':
658+ this.multiDatesPicker.dates[type] = []; // deletes all picked/disabled dates
659+ var end = this.multiDatesPicker.autoselectRange[1];
660+ var begin = this.multiDatesPicker.autoselectRange[0];
661+ if(end < begin) { // switch
662+ end = this.multiDatesPicker.autoselectRange[0];
663+ begin = this.multiDatesPicker.autoselectRange[1];
664+ }
665+ for(var i = begin; i < end; i++)
666+ methods.addDates.call(this, methods.sumDays.call(this,date, i), type);
667+ break;
668+ default:
669+ if(methods.gotDate.call(this, date) === false) // adds dates
670+ methods.addDates.call(this, date, type);
671+ else // removes dates
672+ methods.removeDates.call(this, date, type);
673+ break;
674+ }
675+ },
676+ setMode : function( options ) {
677+ var $this = $(this);
678+ if(options.mode) this.multiDatesPicker.mode = options.mode;
679+
680+ switch(this.multiDatesPicker.mode) {
681+ case 'normal':
682+ for(option in options)
683+ switch(option) {
684+ case 'maxPicks':
685+ case 'minPicks':
686+ case 'pickableRange':
687+ case 'adjustRangeToDisabled':
688+ this.multiDatesPicker[option] = options[option];
689+ break;
690+ //default: $.error('Option ' + option + ' ignored for mode "'.options.mode.'".');
691+ }
692+ break;
693+ case 'daysRange':
694+ case 'weeksRange':
695+ var mandatory = 1;
696+ for(option in options)
697+ switch(option) {
698+ case 'autoselectRange':
699+ mandatory--;
700+ case 'pickableRange':
701+ case 'adjustRangeToDisabled':
702+ this.multiDatesPicker[option] = options[option];
703+ break;
704+ //default: $.error('Option ' + option + ' does not exist for setMode on jQuery.multiDatesPicker');
705+ }
706+ if(mandatory > 0) $.error('Some mandatory options not specified!');
707+ break;
708+ }
709+
710+ /*
711+ if(options.pickableRange) {
712+ $this.datepicker("option", "maxDate", options.pickableRange);
713+ $this.datepicker("option", "minDate", this.multiDatesPicker.minDate);
714+ }
715+ */
716+
717+ if(mdp_events.onSelect)
718+ mdp_events.onSelect();
719+ },
720+ destroy: function(){
721+ this.multiDatesPicker = null;
722+ $(this).datepicker('destroy');
723+ }
724+ };
725+
726+ this.each(function() {
727+ var $this = $(this);
728+ if (!this.multiDatesPicker) {
729+ this.multiDatesPicker = {
730+ dates: {
731+ picked: [],
732+ disabled: []
733+ },
734+ mode: 'normal',
735+ adjustRangeToDisabled: true
736+ };
737+ }
738+
739+ if(methods[method]) {
740+ var exec_result = methods[method].apply(this, Array.prototype.slice.call(mdp_arguments, 1));
741+ switch(method) {
742+ case 'removeDates':
743+ case 'removeIndexes':
744+ case 'resetDates':
745+ case 'toggleDate':
746+ case 'addDates':
747+ var altField = $this.datepicker('option', 'altField');
748+ // @todo: should use altFormat for altField
749+ var dates_string = methods.value.call(this);
750+ if (altField !== undefined && altField != "") {
751+ $(altField).val(dates_string);
752+ }
753+ $this.val(dates_string);
754+
755+ $.datepicker._refreshDatepicker(this);
756+ }
757+ switch(method) {
758+ case 'removeDates':
759+ case 'getDates':
760+ case 'gotDate':
761+ case 'sumDays':
762+ case 'compareDates':
763+ case 'dateConvert':
764+ case 'value':
765+ ret = exec_result;
766+ }
767+ return exec_result;
768+ } else if( typeof method === 'object' || ! method ) {
769+ return methods.init.apply(this, mdp_arguments);
770+ } else {
771+ $.error('Method ' + method + ' does not exist on jQuery.multiDatesPicker');
772+ }
773+ return false;
774+ });
775+
776+ return ret;
777+ };
778+
779+ var PROP_NAME = 'multiDatesPicker';
780+ var dpuuid = new Date().getTime();
781+ var instActive;
782+
783+ $.multiDatesPicker = {version: false};
784+ //$.multiDatesPicker = new MultiDatesPicker(); // singleton instance
785+ $.multiDatesPicker.initialized = false;
786+ $.multiDatesPicker.uuid = new Date().getTime();
787+ $.multiDatesPicker.version = $.ui.multiDatesPicker.version;
788+
789+ // allows MDP not to hide everytime a date is picked
790+ $.multiDatesPicker._hideDatepicker = $.datepicker._hideDatepicker;
791+ $.datepicker._hideDatepicker = function(){
792+ var target = this._curInst.input[0];
793+ var mdp = target.multiDatesPicker;
794+ if(!mdp || (this._curInst.inline === false && !mdp.changed)) {
795+ return $.multiDatesPicker._hideDatepicker.apply(this, arguments);
796+ } else {
797+ mdp.changed = false;
798+ $.datepicker._refreshDatepicker(target);
799+ return;
800+ }
801+ };
802+
803+ // Workaround for #4055
804+ // Add another global to avoid noConflict issues with inline event handlers
805+ window['DP_jQuery_' + dpuuid] = $;
806+})( jQuery );
807\ No newline at end of file
808
809=== added file 'media/js/scheduling.js'
810--- media/js/scheduling.js 1970-01-01 00:00:00 +0000
811+++ media/js/scheduling.js 2018-02-16 15:49:47 +0000
812@@ -0,0 +1,305 @@
813+// global variables
814+var lastSelectedDates = []
815+
816+// Create calender and return it as an object. Useful for callbacks.
817+function createCalendar() {
818+ $('#scheduling-datepicker').multiDatesPicker({
819+ dateFormat: "yy-mm-dd",
820+ showAnim: "slideDown",
821+ numberOfMonths: 3,
822+ minDate: 0,
823+ onSelect: function(date) {
824+ selectedDate = date;
825+ updateAvailableDate(date)
826+ },
827+ });
828+}
829+
830+
831+// Add warning in case of disparency between browser and profil timezone
832+function addTimeZoneWarningIfNeeded() {
833+ var userTimeZone = document.getElementById('django-data').getAttribute('user-time-zone')
834+ var browserTimeZone = - new Date().getTimezoneOffset()/60
835+ if ( browserTimeZone != userTimeZone) {
836+ document.getElementById('timezone-error').removeAttribute("hidden");
837+ profilTime = document.getElementsByClassName('profil-time');
838+ for (var element in profilTime) {
839+ profilTime[element].innerHTML = cleanAndAddSign(userTimeZone);
840+ }
841+ document.getElementById('browser-time').innerHTML = cleanAndAddSign(browserTimeZone);
842+ }
843+}
844+
845+function addPreviousDateFromUser(calendar) {
846+ // Populate the current date with already filled date by the user
847+ old_availabilities_string_JSON = document.getElementById('django-data').getAttribute('day-to-fill')
848+ old_availabilities_list = stringJSONtoJSList(old_availabilities_string_JSON)
849+ if (old_availabilities_list == "") {
850+ return;
851+ }
852+ dateToAdd = []
853+ for (var date in old_availabilities_list) {
854+ // Extract day of the date. See format in view.py.
855+ dateString = old_availabilities_list[date].substring(1,11);
856+ if (!existInList(dateToAdd, dateString)){
857+ dateToAdd.push(dateString)
858+ updateAvailableDate(dateString)
859+ }
860+ }
861+ $('#scheduling-datepicker').multiDatesPicker('addDates', dateToAdd);
862+ for (var date in old_availabilities_list) {
863+ // Extract hour of the date. See format in view.py.
864+ hourString = old_availabilities_list[date].substring(12,14);
865+ // Extract day of the date. See format in view.py.
866+ dateString = old_availabilities_list[date].substring(1,11);
867+ hourString = removeZeroIfUnderTen(hourString);
868+ displayHourForDate(dateString, hourString);
869+ }
870+}
871+
872+function addOtherUsersAvailabilities() {
873+ // Populate the result area showing other users and their disponibilities
874+ if (document.getElementById('django-data').getAttribute('users-to-fill')) {
875+ var otherPlayerAvailabilitiesJSON = document.getElementById('django-data').getAttribute('users-to-fill');
876+ otherPlayerAvailabilities = JSON.parse(otherPlayerAvailabilitiesJSON)
877+ }
878+ noOtherUser = true;
879+ for (var user in otherPlayerAvailabilities) {
880+ noOtherUser = false;
881+ dateList = otherPlayerAvailabilities[user]
882+ for (var date in dateList) {
883+ createUserDivOrUpdateIt(user, dateList[date])
884+ }
885+ }
886+ if (noOtherUser) {
887+ document.getElementById('no-user-to-display').removeAttribute("hidden");
888+ }
889+}
890+
891+function sendDataAsForm(calendar) {
892+ // Get informations from selected hours
893+ var selectedDates = $( "#scheduling-datepicker" ).multiDatesPicker( "getDates" );
894+ var selectedDatesList = [];
895+ for (var d in selectedDates) {
896+ // remove whitespace
897+ var dateID = 'day-' + selectedDates[d];
898+ dateObj = document.getElementById(dateID);
899+ if (dateObj) {
900+ hoursList = dateObj.getElementsByClassName('hours');
901+ selectedHours = dateObj.getElementsByClassName('selected');
902+ if (selectedHours[0]){
903+ for (var h in hoursList) {
904+ if (hasClass(hoursList[h] , 'selected')){
905+ var hourAsDate = selectedDates[d] + "T" + addZeroIfUnderTen(h);
906+ selectedDatesList.push(hourAsDate);
907+ }
908+ }
909+ }
910+ }
911+ }
912+ // Send informations to server
913+ console.log(selectedDatesList);
914+ post('.', selectedDatesList)
915+}
916+
917+// Add or remove available dates in the ui.
918+function updateAvailableDate(date) {
919+ newDateID = "day-" + date
920+ dateAlreadyExist = !!document.getElementById(newDateID);
921+ document.getElementById('second-step').removeAttribute('hidden');
922+ if (dateAlreadyExist) {
923+ document.getElementById(newDateID).remove();
924+ } else {
925+ var original = document.getElementById('day-template');
926+ // We clone the date and fix different attributes
927+ var newDate = original.cloneNode(true);
928+ newDate.id = "day-" + date;
929+ newDate.removeAttribute("hidden");
930+ var textDate = new Date(date);
931+ textDate = textDate.toDateString();
932+ newDate.getElementsByClassName('day-title')[0].innerHTML = '<h3>' + textDate + '</h3>';
933+ // We add the listeners to each hours One for the click event, the other for the hover when the mouse is pressed
934+ hoursObj = newDate.getElementsByClassName('hours');
935+ for (var i = 0; i < hoursObj.length; i++) {
936+ hoursObj[i].addEventListener('click', updateHour, false);
937+ hoursObj[i].addEventListener("mouseover", function(e){
938+ if(e.buttons == 1 || e.buttons == 3){
939+ updateHour(e);
940+ }
941+ })
942+ }
943+ // We look for the order the new date should be in
944+ daysList = document.getElementById('days-wrapper').getElementsByClassName('days');
945+ // We finally add the new date
946+ document.getElementById('days-wrapper').appendChild(newDate);
947+ }
948+
949+}
950+
951+function updateHour (event) {
952+ var div = (event.fromElement ? event.fromElement : event.currentTarget);
953+ isAlreadySelected = hasClass(div, 'selected');
954+ if (isAlreadySelected) {
955+ div.className = 'hours';
956+ } else {
957+ div.className += ' selected';
958+ }
959+}
960+
961+function displayHourForDate(date, hour, user) {
962+ var dateDivID = 'day-' + date
963+ if (user) {
964+ dateDivID = user + '-day-' + date
965+ }
966+ dateDiv = document.getElementById(dateDivID);
967+
968+ // For the current user hours display
969+ hourDiv = dateDiv.getElementsByClassName('hours');
970+ for (var hourInDiv in hourDiv) {
971+ if (hourInDiv == hour) {
972+ hourDiv[hourInDiv].className += ' selected';
973+ }
974+ }
975+
976+ // For the other users hours display (the svg clock)
977+ hoursArea = dateDiv.getElementsByClassName('hour-area');
978+ for (var hourArea in hoursArea) {
979+ if (hourArea == hour) {
980+ hoursArea[hourArea].className.baseVal += ' active';
981+ }
982+ }
983+}
984+
985+function createUserDivOrUpdateIt(user, availTime) {
986+ if (!document.getElementById("user-" + user)){
987+ var original = document.getElementById('other-user-template');
988+ // We clone the date and fix different attributes
989+ var otherUser = original.cloneNode(true);
990+ otherUser.id = "user-" + user;
991+ otherUser.removeAttribute("hidden");
992+
993+ var userTitle = otherUser.getElementsByClassName('title')[0];
994+ button = userTitle.querySelector('button');
995+ button.onclick = function(){
996+ window.location.href = '/messages/compose/' + user;
997+ }
998+
999+ var usernameP = userTitle.querySelector('p');
1000+ usernameP.innerHTML = user;
1001+
1002+ var showClockBtn = otherUser.getElementsByClassName('show-clock-btn')[0];
1003+ showClockBtn.onclick = function (){
1004+ otherUser.classList.toggle('hide');
1005+ var showClockText = '<i class="arrow"></i>Show clock';
1006+ var hideClockText = '<i class="arrow"></i>Hide clock';
1007+ if (showClockBtn.innerHTML == showClockText) {
1008+ showClockBtn.innerHTML = hideClockText;
1009+ } else {
1010+ showClockBtn.innerHTML = showClockText;
1011+ }
1012+ }
1013+ } else {
1014+ otherUser = document.getElementById("user-" + user);
1015+ }
1016+ var dtavailTime = new Date(availTime + ":00:00");
1017+ // Remove timezone offset which js automatically add...
1018+ js_offset = dtavailTime.getTimezoneOffset()/60
1019+ dtavailTime = dtavailTime.addHours(js_offset);
1020+ textDate = dtavailTime.toDateString();
1021+ var dateFormated = dtavailTime.getFullYear() + "-" + dtavailTime.getMonth() + '-' + dtavailTime.getDay()
1022+ var availTimeFormated = dtavailTime.getHours()
1023+ var originalDay = document.getElementById('other-day-template')
1024+ if (!document.getElementById(user + "-day-" + dateFormated)) {
1025+ var day = originalDay.cloneNode(true);
1026+ day.id = user + "-day-" + dateFormated;
1027+ day.removeAttribute("hidden");
1028+ day.getElementsByClassName('day-title')[0].innerHTML = '<h3>' + textDate + '</h3>';
1029+ otherUser.getElementsByClassName('other-days-container')[0].appendChild(day)
1030+ }
1031+ document.getElementById('other-users-wrapper').appendChild(otherUser);
1032+ displayHourForDate(dateFormated, availTimeFormated, user);
1033+}
1034+
1035+/*****************************/
1036+/********* utilities *********/
1037+/*****************************/
1038+// From stackoverflow: https://stackoverflow.com/questions/5898656/test-if-an-element-contains-a-class
1039+function hasClass(element, cls) {
1040+ return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
1041+}
1042+
1043+// We need a custom function to submit a form because our data isn't formated as a form.
1044+function post(path, params, method) {
1045+ method = method || "post"; // Set method to post by default if not specified.
1046+ // The rest of this code assumes you are not using a library.
1047+ // It can be made less wordy if you use one.
1048+ var form = document.createElement("form");
1049+ form.setAttribute("method", method);
1050+ form.setAttribute("action", path);
1051+ for(var key in params) {
1052+ var hiddenField = document.createElement("input");
1053+ hiddenField.setAttribute("type", "hidden");
1054+ hiddenField.setAttribute("name", key);
1055+ hiddenField.setAttribute("value", params[key]);
1056+
1057+ form.appendChild(hiddenField);
1058+ }
1059+ // CSRF token
1060+ var csrf_div = document.createElement("div");
1061+ csrf_div.innerHTML = document.getElementById('django-data').getAttribute('csrf-token')
1062+ form.appendChild(csrf_div);
1063+ document.body.appendChild(form);
1064+ form.submit();
1065+}
1066+
1067+function cleanJSONfromWhiteSpace(json) {
1068+ var name, newName;
1069+ for (var name in json) {
1070+ // Get the name without spaces
1071+ newName = name.replace(/ /g, "");
1072+ // If that's different...
1073+ if (newName != name) {
1074+ // Create the new property
1075+ json[newName] = json[name];
1076+ // Delete the old one
1077+ delete json[name];
1078+ }
1079+ }
1080+ return json;
1081+}
1082+
1083+function stringJSONtoJSList(json) {
1084+ // Removes brackets
1085+ json = json.substring(1, json.length-1);
1086+ var jsonList= json.split(",")
1087+ for (var i in jsonList){
1088+ jsonList[i] = jsonList[i].replace(/\s/g, '');
1089+ }
1090+ return jsonList
1091+
1092+}
1093+
1094+function addZeroIfUnderTen(number) {
1095+ return ('0' + number).slice(-2)
1096+}
1097+
1098+function removeZeroIfUnderTen(number) {
1099+ return parseInt(number, 10);
1100+}
1101+
1102+Date.prototype.addHours = function(h) {
1103+ this.setTime(this.getTime() + (h*60*60*1000));
1104+ return this;
1105+}
1106+
1107+function existInList(list, value) {
1108+ return list.indexOf(value) > -1
1109+}
1110+
1111+function cleanAndAddSign(number) {
1112+ if (number < 0) {
1113+ return ' ' + parseInt(number)
1114+ } else {
1115+ return '+ ' + parseInt(number)
1116+ }
1117+}
1118\ No newline at end of file
1119
1120=== modified file 'settings.py'
1121--- settings.py 2017-11-24 09:24:41 +0000
1122+++ settings.py 2018-02-16 15:49:47 +0000
1123@@ -94,6 +94,7 @@
1124 'wlmaps',
1125 'wlscreens',
1126 'wlggz',
1127+ 'wlscheduling',
1128 'check_input',
1129 'haystack', # search engine; see option HAYSTACK_CONNECTIONS
1130
1131
1132=== modified file 'templates/login_box.html'
1133--- templates/login_box.html 2016-05-17 19:28:38 +0000
1134+++ templates/login_box.html 2018-02-16 15:49:47 +0000
1135@@ -4,14 +4,17 @@
1136 {% if user.is_authenticated %}
1137 <div class="small posLeft">
1138 Welcome {{ user|user_link }},<br/>
1139- {% comment %}
1140- you have <a href="{% url 'messages_inbox' request.user %}">{{ messages_inbox_count}} new message{{ messages_inbox_count|pluralize }}</a>.
1141- {% endcomment %}
1142- you have <a href="{% url 'messages_inbox' %}">{{ messages_inbox_count}} new message{{ messages_inbox_count|pluralize }}</a>.
1143+ <a href="{% url 'scheduling_main' %}">Playtime Scheduler</a>
1144 </div>
1145 <div class="right small posRight">
1146 <ul>
1147- <li><a href="{% url 'messages_inbox' %}">Messages</a></li>
1148+ <li>
1149+ {% if messages_inbox_count %}
1150+ <a href="{% url 'messages_inbox' %}" title="You have {{ messages_inbox_count }} new messages">Messages ({{ messages_inbox_count }})</a>
1151+ {% else %}
1152+ <a href="{% url 'messages_inbox' %}" title="No new message">Messages</a>
1153+ {% endif %}
1154+ </li>
1155 <li><a href="{% url 'notification_notices' %}">Notifications</a></li>
1156 <li><a href="{% url 'profile_edit' %}">Edit Profile</a></li>
1157 <li><a href="{% url 'auth_logout' %}?next={{ request.path|iriencode }}">Logout</a></li>
1158
1159=== added directory 'templates/wlscheduling'
1160=== added file 'templates/wlscheduling/base.html'
1161--- templates/wlscheduling/base.html 1970-01-01 00:00:00 +0000
1162+++ templates/wlscheduling/base.html 2018-02-16 15:49:47 +0000
1163@@ -0,0 +1,12 @@
1164+{% extends "base.html" %}
1165+{% block extra_head %}
1166+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/jquery-ui.multidatespicker.css" >
1167+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/scheduling.css" >
1168+
1169+<script src="{{ MEDIA_URL }}js/jquery-ui.multidatespicker.js" type="text/javascript"></script>
1170+<script src="{{ MEDIA_URL }}js/scheduling.js" type="text/javascript"></script>
1171+{% endblock %}
1172+
1173+{% block title %}
1174+ Scheduling - {{block.super}}
1175+{% endblock %}
1176\ No newline at end of file
1177
1178=== added file 'templates/wlscheduling/clock-svg.html'
1179--- templates/wlscheduling/clock-svg.html 1970-01-01 00:00:00 +0000
1180+++ templates/wlscheduling/clock-svg.html 2018-02-16 15:49:47 +0000
1181@@ -0,0 +1,26 @@
1182+<svg class="clock-svg" viewBox="0 0 500 500">
1183+ <path d="M224.5.684c41.6 1.3 77.5 9.6 113.5 32.5l-113.1 195.1z" class="hour-area" id="path4140" />
1184+ <path d="M337.5 29.984c35.4 22 62.3 47.2 81.8 85.1l-195.8 111.9z" class="hour-area" id="path4142" />
1185+ <path d="M420.4 111.684c19.6 36.8 30.3 72 28.2 114.6l-225.6-1.1z" class="hour-area" id="path4144" />
1186+ <path d="M451.2 223.084c-1.3 41.6-9.6 77.5-32.5 113.5l-195.2-113.1z" class="hour-area" id="path4146" />
1187+ <path d="M421.9 336.084c-22 35.4-47.2 62.3-85 81.8l-112.1-195.7z" class="hour-area" id="path4148" />
1188+ <path d="M340.1 418.984c-36.8 19.6-72 30.3-114.6 28.2l1.1-225.6z" class="hour-area" id="path4150" />
1189+ <path d="M228.7 449.784c-41.6-1.3-77.5-9.6-113.5-32.5l113.1-195.1z" class="hour-area" id="path4152" />
1190+ <path d="M115.7 420.484c-35.4-22-62.3-47.2-81.8-85l195.8-112z" class="hour-area" id="path4154" />
1191+ <path d="M32.8 338.784c-19.5-36.8-30.2-72.1-28.2-114.6l225.6 1.1z" class="hour-area" id="path4156" />
1192+ <path d="M2 227.284c1.3-41.6 9.6-77.5 32.5-113.5l195.2 113.1z" class="hour-area" id="path4158" />
1193+ <path d="M31.3 114.384c22-35.4 47.2-62.3 85.1-81.8l112 195.7z" class="hour-area" id="path4160" />
1194+ <path d="M114.22 29.804c36.8-19.6 72-30.3 114.6-28.2l-1.1 225.6z" class="hour-area" id="path4162" />
1195+ <text font-size="90" letter-spacing="0" word-spacing="0" class="number-wrapper" style="line-height:100%" >
1196+ <tspan x="178.575" y="85" class="number" >12</tspan>
1197+ </text>
1198+ <text font-size="90" letter-spacing="0" word-spacing="0" class="number-wrapper" style="line-height:100%" >
1199+ <tspan x="383" y="250" class="number" >3</tspan>
1200+ </text>
1201+ <text font-size="90" letter-spacing="0" word-spacing="0" class="number-wrapper" style="line-height:100%" >
1202+ <tspan y="250" x="10" class="number">9</tspan>
1203+ </text>
1204+ <text font-size="90" letter-spacing="0" word-spacing="0" class="number-wrapper" style="line-height:100%" >
1205+ <tspan y="435" x="200" class="number" >6</tspan>
1206+ </text>
1207+</svg>
1208
1209=== added file 'templates/wlscheduling/find.html'
1210--- templates/wlscheduling/find.html 1970-01-01 00:00:00 +0000
1211+++ templates/wlscheduling/find.html 2018-02-16 15:49:47 +0000
1212@@ -0,0 +1,27 @@
1213+{% extends "wlscheduling/base.html" %}
1214+{% comment %}
1215+ vim:ft=htmldjango
1216+{% endcomment %}
1217+
1218+{% block content %}
1219+<script type="text/javascript">
1220+document.addEventListener('DOMContentLoaded', function(){
1221+ addTimeZoneWarningIfNeeded();
1222+ addOtherUsersAvailabilities();
1223+}, false);
1224+</script>
1225+
1226+<div class="blogEntry">
1227+ <h1>Times other players are available</h1>
1228+ {% include "wlscheduling/timezone-msg.html" %}
1229+ <div id="other-users-wrapper"></div>
1230+ <div id="no-user-to-display" hidden="hidden">Sorry currently there isn't anybody who has scheduled a gaming session. Don't hesitate to signal to other when you'll be available <a href="{% url 'scheduling_scheduling' %}">here</a></div>
1231+
1232+</div>
1233+
1234+<!-- Templates for the js script -->
1235+{% include "wlscheduling/other-users.html" %}
1236+
1237+<!-- Div to send django data to js file -->
1238+<div id="django-data" user-time-zone="{{user.wlprofile.time_zone}}" users-to-fill="{{other_users_availabilities}}"></div>
1239+{% endblock %}
1240\ No newline at end of file
1241
1242=== added file 'templates/wlscheduling/main.html'
1243--- templates/wlscheduling/main.html 1970-01-01 00:00:00 +0000
1244+++ templates/wlscheduling/main.html 2018-02-16 15:49:47 +0000
1245@@ -0,0 +1,22 @@
1246+
1247+{% extends "wlscheduling/base.html" %}
1248+{% comment %}
1249+ vim:ft=htmldjango
1250+{% endcomment %}
1251+
1252+{% block content %}
1253+
1254+
1255+<div class="blogEntry">
1256+ <h1>Welcome in the scheduling module!</h1>
1257+ <div id="main-choices" class="main-choices">
1258+ <a href="{% url 'scheduling_scheduling' %}" >
1259+ <button title="You know when you will be available to play and want to display when you're available and find other players that will be available at these hours">Define your playtime and find a game</button>
1260+ </a>
1261+ <a href="{% url 'scheduling_find' %}" >
1262+ <button title="You just want to know when everybody is going to be playing" >Show other users playtime</button>
1263+ </a>
1264+ </div>
1265+</div>
1266+
1267+{% endblock %}
1268\ No newline at end of file
1269
1270=== added file 'templates/wlscheduling/other-users.html'
1271--- templates/wlscheduling/other-users.html 1970-01-01 00:00:00 +0000
1272+++ templates/wlscheduling/other-users.html 2018-02-16 15:49:47 +0000
1273@@ -0,0 +1,24 @@
1274+<div id="other-user-template" class="other-user-div" hidden="hidden">
1275+ <div class="title">
1276+ <p></p>
1277+ <button>
1278+ <img src="/wlmedia/forum/img/send_pm.png" alt="" class="middle"><span class="middle">Send PM</span>
1279+ </button>
1280+ </div>
1281+ <div class="other-days-container"></div>
1282+ <button class="show-clock-btn"><i class="arrow"></i>Hide clock</button>
1283+</div>
1284+
1285+<div id="other-day-template" hidden="hidden">
1286+ <div class="day-title"></div>
1287+ <div class="clocks-wrapper">
1288+ <div>
1289+ <h2>AM </h2>
1290+ {% include "wlscheduling/clock-svg.html" %}
1291+ </div>
1292+ <div>
1293+ <h2>PM </h2>
1294+ {% include "wlscheduling/clock-svg.html" %}
1295+ </div>
1296+ </div>
1297+</div>
1298\ No newline at end of file
1299
1300=== added file 'templates/wlscheduling/scheduling.html'
1301--- templates/wlscheduling/scheduling.html 1970-01-01 00:00:00 +0000
1302+++ templates/wlscheduling/scheduling.html 2018-02-16 15:49:47 +0000
1303@@ -0,0 +1,66 @@
1304+
1305+{% extends "wlscheduling/base.html" %}
1306+{% comment %}
1307+ vim:ft=htmldjango
1308+{% endcomment %}
1309+
1310+
1311+
1312+{% block content %}
1313+<script type="text/javascript">
1314+document.addEventListener('DOMContentLoaded', function(){
1315+ var calendar = createCalendar();
1316+ addTimeZoneWarningIfNeeded();
1317+ addPreviousDateFromUser(calendar);
1318+ addOtherUsersAvailabilities();
1319+
1320+ //Validate btn
1321+ document.getElementById('validate-btn').onclick = function () {
1322+ sendDataAsForm(calendar);
1323+ }
1324+}, false);
1325+</script>
1326+
1327+<h1>Multiplayer</h1>
1328+
1329+<div class="blogEntry">
1330+ <h2>Select the dates you'll be available on</h2>
1331+ {% include "wlscheduling/timezone-msg.html" %}
1332+ <div id="scheduling-datepicker"></div>
1333+
1334+ <div id="second-step" hidden="hidden">
1335+ <h2>Select the hours you'll be available on these dates</h2>
1336+ <div id="days-wrapper"></div>
1337+ <h2>Players that are currently available at the same hours as you</h2>
1338+ <div id="other-users-wrapper"></div>
1339+ <div id="no-user-to-display" hidden="hidden">Sorry, there are currently no players available at these hours. But your availabilities are noted, and another user might want to play with you!</div>
1340+ <button id="validate-btn">Update</button>
1341+ </div>
1342+</div>
1343+
1344+<!-- Templates for the js script -->
1345+<!-- Template for the days of avaibility of the current user -->
1346+<div id="day-template" class="day" hidden="hidden">
1347+ <div class="day-title"></div>
1348+ <div class="hours-title-wrapper">
1349+ {% for i in "xxxxxxxxxxxxxxxxxxxxxxxxx" %}
1350+ <p>{{ forloop.counter0 }}</p>
1351+ {% endfor %}
1352+ </div>
1353+ <div class="hours-wrapper">
1354+ {% for i in "xxxxxxxxxxxxxxxxxxxxxxxx" %}
1355+ <div class="hours"></div>
1356+ {% endfor %}
1357+ <div class="hidden-hour"></div>
1358+ </div>
1359+</div>
1360+
1361+
1362+<!-- Template for the days of avaibility of the other users -->
1363+{% include "wlscheduling/other-users.html" %}
1364+
1365+<!-- Div to send django data to js file -->
1366+<div id="django-data" user-time-zone="{{user.wlprofile.time_zone}}" day-to-fill = "{{ current_user_availabilities }}" users-to-fill="{{other_users_availabilities}}" csrf-token = "{% csrf_token %}"></div>
1367+
1368+
1369+{% endblock %}
1370
1371=== added file 'templates/wlscheduling/timezone-msg.html'
1372--- templates/wlscheduling/timezone-msg.html 1970-01-01 00:00:00 +0000
1373+++ templates/wlscheduling/timezone-msg.html 2018-02-16 15:49:47 +0000
1374@@ -0,0 +1,4 @@
1375+<h3 id="timezone-msg">You are currently noted as UTC <span class="profil-time"></span></h3>
1376+<div id="timezone-error" class="errormessage" hidden="hidden">WARNING! The time indicated by your browser (UTC <span id="browser-time"></span>) is different from the one stored in your profile (UTC <span class="profil-time"></span>). By default we will use the one stored in your profile.<br />
1377+ <a href="{% url 'profile_edit' %}">Edit Profile</a>
1378+</div>
1379
1380=== modified file 'urls.py'
1381--- urls.py 2017-11-24 09:24:41 +0000
1382+++ urls.py 2018-02-16 15:49:47 +0000
1383@@ -66,6 +66,7 @@
1384 url(r'^screenshots/', include('wlscreens.urls')),
1385 url(r'^ggz/', include('wlggz.urls')),
1386 url(r'^moderated/', include('check_input.urls')),
1387+ url(r'^scheduling/', include('wlscheduling.urls')),
1388 ]
1389
1390 try:
1391
1392=== added directory 'wlscheduling'
1393=== added file 'wlscheduling/__init__.py'
1394=== added directory 'wlscheduling/management'
1395=== added file 'wlscheduling/management/__init__.py'
1396=== added directory 'wlscheduling/management/commands'
1397=== added file 'wlscheduling/management/commands/__init__.py'
1398=== added file 'wlscheduling/management/commands/remove_old_dates.py'
1399--- wlscheduling/management/commands/remove_old_dates.py 1970-01-01 00:00:00 +0000
1400+++ wlscheduling/management/commands/remove_old_dates.py 2018-02-16 15:49:47 +0000
1401@@ -0,0 +1,11 @@
1402+from django.core.management.base import BaseCommand, CommandError
1403+from wlscheduling.models import Availabilities
1404+from datetime import datetime
1405+
1406+class Command(BaseCommand):
1407+ help = 'Removes dates that are already passed'
1408+
1409+ def handle(self, *args, **options):
1410+ for date in Availabilities.objects.all():
1411+ if datetime.utcnow() > date.avail_time:
1412+ date.delete()
1413\ No newline at end of file
1414
1415=== added directory 'wlscheduling/migrations'
1416=== added file 'wlscheduling/migrations/0001_initial.py'
1417--- wlscheduling/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
1418+++ wlscheduling/migrations/0001_initial.py 2018-02-16 15:49:47 +0000
1419@@ -0,0 +1,23 @@
1420+# -*- coding: utf-8 -*-
1421+from __future__ import unicode_literals
1422+
1423+from django.db import models, migrations
1424+from django.conf import settings
1425+
1426+
1427+class Migration(migrations.Migration):
1428+
1429+ dependencies = [
1430+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
1431+ ]
1432+
1433+ operations = [
1434+ migrations.CreateModel(
1435+ name='Availabilities',
1436+ fields=[
1437+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
1438+ ('avail_time', models.DateTimeField(default=0, verbose_name=b'one hour of availability')),
1439+ ('user', models.ForeignKey(related_name='availabilities', to=settings.AUTH_USER_MODEL)),
1440+ ],
1441+ ),
1442+ ]
1443
1444=== added file 'wlscheduling/migrations/__init__.py'
1445=== added file 'wlscheduling/models.py'
1446--- wlscheduling/models.py 1970-01-01 00:00:00 +0000
1447+++ wlscheduling/models.py 2018-02-16 15:49:47 +0000
1448@@ -0,0 +1,11 @@
1449+#!/usr/bin/env python
1450+# encoding: utf-8
1451+
1452+from django.db import models
1453+from django.contrib.auth.models import User
1454+
1455+
1456+class Availabilities(models.Model):
1457+ user = models.ForeignKey(User, related_name='availabilities')
1458+ avail_time = models.DateTimeField(
1459+ help_text="this user is available for this whole hour", default=0)
1460\ No newline at end of file
1461
1462=== added file 'wlscheduling/urls.py'
1463--- wlscheduling/urls.py 1970-01-01 00:00:00 +0000
1464+++ wlscheduling/urls.py 2018-02-16 15:49:47 +0000
1465@@ -0,0 +1,10 @@
1466+#!/usr/bin/env python -tt
1467+# encoding: utf-8
1468+from django.conf.urls import *
1469+from views import scheduling, scheduling_main, scheduling_find
1470+
1471+urlpatterns = [
1472+ url(r'^scheduling/$', scheduling, name='scheduling_scheduling'),
1473+ url(r'^main/$', scheduling_main, name='scheduling_main'),
1474+ url(r'^find/$', scheduling_find, name='scheduling_find'),
1475+]
1476
1477=== added file 'wlscheduling/views.py'
1478--- wlscheduling/views.py 1970-01-01 00:00:00 +0000
1479+++ wlscheduling/views.py 2018-02-16 15:49:47 +0000
1480@@ -0,0 +1,115 @@
1481+#!/usr/bin/env python -tt
1482+# encoding: utf-8
1483+#
1484+
1485+from django.shortcuts import render
1486+from models import Availabilities
1487+from django.contrib.auth.decorators import login_required
1488+import json
1489+from datetime import datetime, timedelta
1490+
1491+###########
1492+# Options #
1493+###########
1494+TIME_FORMAT = '%Y-%m-%dT%H'
1495+
1496+#########
1497+# Views #
1498+#########
1499+def scheduling_main (request):
1500+ return render(request, 'wlscheduling/main.html')
1501+
1502+@login_required
1503+def scheduling_find (request):
1504+ current_user = request.user
1505+ other_users_availabilities = {}
1506+ for a in Availabilities.objects.exclude(user=current_user).order_by('avail_time'):
1507+ user_utc_dt_avail_time = a.avail_time
1508+ if datetime.now() < user_utc_dt_avail_time:
1509+ other_user = a.user
1510+ current_user_timezone = current_user.wlprofile.time_zone
1511+ user_dt_avail_time = user_utc_dt_avail_time + timedelta(hours= current_user_timezone)
1512+ user_string_avail_time = datetime.strftime(user_dt_avail_time, TIME_FORMAT)
1513+
1514+ if not other_user.username in other_users_availabilities:
1515+ other_users_availabilities[other_user.username] = []
1516+ other_users_availabilities[other_user.username].append(user_string_avail_time)
1517+ return render(request, 'wlscheduling/find.html', {'other_users_availabilities': json.dumps(other_users_availabilities)})
1518+
1519+@login_required
1520+def scheduling(request):
1521+ current_user = request.user
1522+ current_user_availabilities = []
1523+ user_timezone = current_user.wlprofile.time_zone
1524+
1525+ # Update of user's availabilities when post mode
1526+ if request.method == 'POST':
1527+ request_avail_times = []
1528+ for r in request.POST:
1529+ if r != "csrfmiddlewaretoken":
1530+ request_avail_times.append(request.POST[r])
1531+
1532+
1533+ current_user_availabilities = []
1534+ for avail_time in request_avail_times:
1535+ dt_avail_time = datetime.strptime(avail_time, TIME_FORMAT)
1536+ utc_dt_avail_time = dt_avail_time + timedelta(hours= - user_timezone)
1537+
1538+ # We append the string to the list because apparently datetime objects cannot be stored in a list?
1539+ utc_string_avail_time = datetime.strftime(utc_dt_avail_time, TIME_FORMAT)
1540+ current_user_availabilities.append(utc_string_avail_time)
1541+
1542+
1543+ for request_avail_time in request_avail_times:
1544+ dt_avail_time = datetime.strptime(request_avail_time, TIME_FORMAT)
1545+ # Actual change of timezone, we got back to UTC
1546+ utc_dt_avail_time = dt_avail_time + timedelta(hours= - user_timezone)
1547+ avail_time_already_exist = False
1548+ for a in Availabilities.objects.filter(user=current_user, avail_time=utc_dt_avail_time):
1549+ avail_time_already_exist = True
1550+
1551+ if not avail_time_already_exist:
1552+ a = Availabilities.objects.create(
1553+ user=current_user,
1554+ avail_time=utc_dt_avail_time
1555+ )
1556+ a.save()
1557+
1558+ # We remove any previously stored date that is not present in the request anymore
1559+ for a in Availabilities.objects.filter(user=current_user):
1560+ utc_dt_avail_time = a.avail_time
1561+ to_remove = True
1562+ for utc_string_avail_time in current_user_availabilities:
1563+ request_utc_dt_avail_time = datetime.strptime(utc_string_avail_time, TIME_FORMAT)
1564+ if utc_dt_avail_time == request_utc_dt_avail_time:
1565+ to_remove = False
1566+ if to_remove:
1567+ a.delete()
1568+
1569+
1570+
1571+ current_user_availabilities = []
1572+ for a in Availabilities.objects.filter(user=current_user).order_by('avail_time'):
1573+ utc_dt_avail_time = a.avail_time
1574+ # We display the time with current user timezone
1575+ dt_avail_time = utc_dt_avail_time + timedelta(hours=user_timezone)
1576+ string_avail_time = datetime.strftime(dt_avail_time, TIME_FORMAT)
1577+ current_user_availabilities.append(string_avail_time)
1578+
1579+ other_users_availabilities = {}
1580+ for current_user_a in Availabilities.objects.filter(user=current_user).order_by('avail_time'):
1581+ current_user_utc_dt_avail_time = current_user_a.avail_time
1582+ for a in Availabilities.objects.filter(avail_time=current_user_utc_dt_avail_time).exclude(user=current_user).order_by('avail_time'):
1583+ user_utc_dt_avail_time = a.avail_time
1584+ other_user = a.user
1585+ current_user_timezone = current_user.wlprofile.time_zone
1586+ user_dt_avail_time = user_utc_dt_avail_time + timedelta(hours= current_user_timezone)
1587+ user_string_avail_time = datetime.strftime(user_dt_avail_time, TIME_FORMAT)
1588+
1589+ if not other_user.username in other_users_availabilities:
1590+ other_users_availabilities[other_user.username] = []
1591+ other_users_availabilities[other_user.username].append(user_string_avail_time)
1592+
1593+
1594+ return render(request, 'wlscheduling/scheduling.html', {'current_user_availabilities': json.dumps(current_user_availabilities),
1595+'other_users_availabilities': json.dumps(other_users_availabilities)})
1596\ No newline at end of file

Subscribers

People subscribed via source and target branches