Merge lp:~trimardio/widelands-website/module_scheduling into lp:widelands-website
- module_scheduling
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
kaputtnik (community) | Needs Fixing | ||
Review via email: mp+335570@code.launchpad.net |
Commit message
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.
Trimardio (trimardio) wrote : | # |
kaputtnik (franku) wrote : | # |
You have to write a management command for this. See:
https:/
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.
kaputtnik (franku) wrote : | # |
I did a small test without a deeper look into the code and found some things which needs fixing:
On http://
- 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.
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.
Trimardio (trimardio) : | # |
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?
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!
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://
Derived from: https:/
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.
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:/
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^^
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 :-)
Trimardio (trimardio) wrote : | # |
Like that then? :)
It looks a bit confusing for me, but maybe people will have less chaotic availabilities than my examples?
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?
Trimardio (trimardio) wrote : | # |
Yeah border was a good idea, it's clearer now. Clear enough though? https:/
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
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?
Trimardio (trimardio) wrote : | # |
He're the numbers:
https:/
Ok and that one was fun to do:
https:/
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?
Trimardio (trimardio) wrote : | # |
Actual test with number border (sry wrong upload):
Oh and the cardinal test too, forgot it:
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...
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.
Trimardio (trimardio) wrote : | # |
Always so cool working with you guys! All your suggestions were indeed on point:
- 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
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...
kaputtnik (franku) wrote : | # |
Another round of proofreading. Sorry for being so nitpicking...
Comments in the code.
Trimardio (trimardio) wrote : | # |
Thanks of the inputs! Changes were quick :)
- 487. By Trimard \<email address hidden>
-
some fixing, typos and refacto thanks to kaputnik
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://
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_
kaputtnik (franku) wrote : | # |
The view of Players available needs fixing:
http://
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.
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
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_
{% if messages_
<a href="{% url 'messages_inbox' %}" title="You have {{ messages_
{% 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.
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!
kaputtnik (franku) wrote : | # |
This merged and deployed now.
Thanks Trimard for this feature and being that patient with my slow answers :-)
Preview Diff
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' |
304 | Binary 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 |
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?