Browse code

"Initial commit"

Doug Le Tough authored on 26/02/2018 09:16:29
Showing 39 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+*un~
1
+*.swp
2
+*.pyc
3
+*.wsgi
0 4
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+SQLALCHEMY_TRACK_MODIFICATIONS = True
1
+SQLALCHEMY_DATABASE_URI = "postgresql://tetawebapp:tetawebapp@localhost/tetawebapp"
2
+UPLOADED_FILES_DEST = "./upload"
0 3
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+config.local.py
0 1
\ No newline at end of file
1 2
new file mode 100644
2 3
Binary files /dev/null and b/participer.thsf.net/static/fonts/RobotoCondensed-Bold.ttf differ
3 4
new file mode 100644
4 5
Binary files /dev/null and b/participer.thsf.net/static/fonts/RobotoCondensed-Regular.ttf differ
5 6
new file mode 100644
6 7
Binary files /dev/null and b/participer.thsf.net/static/images/404.png differ
7 8
new file mode 100644
8 9
Binary files /dev/null and b/participer.thsf.net/static/images/add.png differ
9 10
new file mode 100644
10 11
Binary files /dev/null and b/participer.thsf.net/static/images/dummy_pic.png differ
11 12
new file mode 100644
12 13
Binary files /dev/null and b/participer.thsf.net/static/images/edit.png differ
13 14
new file mode 100644
14 15
Binary files /dev/null and b/participer.thsf.net/static/images/favicon.png differ
15 16
new file mode 100644
16 17
Binary files /dev/null and b/participer.thsf.net/static/images/login.png differ
17 18
new file mode 100644
18 19
Binary files /dev/null and b/participer.thsf.net/static/images/logo.png differ
19 20
new file mode 100644
20 21
Binary files /dev/null and b/participer.thsf.net/static/images/logout.png differ
21 22
new file mode 100644
22 23
Binary files /dev/null and b/participer.thsf.net/static/images/refresh.png differ
23 24
new file mode 100644
24 25
Binary files /dev/null and b/participer.thsf.net/static/images/save.png differ
25 26
new file mode 100644
26 27
Binary files /dev/null and b/participer.thsf.net/static/images/search.png differ
27 28
new file mode 100644
28 29
Binary files /dev/null and b/participer.thsf.net/static/images/trash.png differ
29 30
new file mode 100644
30 31
Binary files /dev/null and b/participer.thsf.net/static/images/upload.png differ
31 32
new file mode 100644
... ...
@@ -0,0 +1,249 @@
0
+var red = "#FF0000";
1
+var green = "#00FF00";
2
+var light_red = "#FCD5DC";
3
+var light_green = "#D5FCD8";
4
+var base_bg = "#FFFFFF";
5
+var base_border = "#888888";
6
+var coloured_bg = "#FF5D00";
7
+var clear_bg = "#E5E5E5";
8
+var text_color = "#555555";
9
+
10
+/* **************************************************************************************
11
+ * GLOBAL
12
+ * **************************************************************************************/
13
+
14
+// Cookies
15
+function setcookie(cname, cvalue, exdays) {
16
+    // Set cookie
17
+    var d = new Date();
18
+    d.setTime(d.getTime() + (exdays*24*60*60*1000));
19
+    var expires = "expires="+ d.toUTCString();
20
+    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
21
+}
22
+
23
+function getcookie(cname) {
24
+  // Get cookie by name
25
+  var value = "; " + document.cookie;
26
+  var parts = value.split("; " + cname + "=");
27
+  if (parts.length == 2) return parts.pop().split(";").shift();
28
+}
29
+
30
+// Eye candies
31
+function valid_input(obj) {
32
+  // Valid input makes obj background to glow green for 2 seconds
33
+  // If obj borders were red, they get they normal color back
34
+  obj.style.backgroundColor = light_green;
35
+  obj.style.borderColor = base_border;
36
+  setTimeout( function() {
37
+    obj.style.backgroundColor = base_bg;
38
+    }
39
+  , 2000);
40
+}
41
+
42
+function invalid_input(obj) {
43
+  // Invalid input makes obj borders and background to glow red for 2 seconds
44
+  // Border color will stay red until a valid input is sent
45
+  obj.style.backgroundColor = light_red;
46
+  obj.style.borderColor = red;
47
+  setTimeout( function() {
48
+    obj.style.backgroundColor = base_bg;
49
+    }
50
+  , 2000);
51
+}
52
+
53
+function valid_upload(obj) {
54
+  // Valid input makes obj background to glow green for 2 seconds
55
+  // If obj borders were red, they get they normal color back
56
+  obj.style.backgroundColor = green;
57
+  obj.style.borderColor = text_color;
58
+  obj.style.borderStyle = 'solid';
59
+  setTimeout( function() {
60
+    obj.style.backgroundColor = clear_bg;
61
+    obj.style.borderStyle = 'none';
62
+    }
63
+  , 2000);
64
+}
65
+
66
+function invalid_upload(obj) {
67
+  // Invalid input makes obj borders and background to glow red for 2 seconds
68
+  // Border color will stay red until a valid input is sent
69
+  obj.style.backgroundColor = red;
70
+  obj.style.borderColor = text_color;
71
+  obj.style.borderStyle = 'solid';
72
+  setTimeout( function() {
73
+    obj.style.borderStyle = 'solid';
74
+    obj.style.backgroundColor = clear_bg;
75
+    obj.style.borderColor = red;
76
+    }
77
+  , 2000);
78
+}
79
+
80
+function lit(obj) {
81
+  // Lit bacground and border on obj (use by input type=file)
82
+  obj.style.backgroundColor = coloured_bg;
83
+  obj.style.borderColor = text_color;
84
+  obj.style.borderStyle = 'solid';
85
+}
86
+
87
+function unlit(obj) {
88
+  // Unlit bacground and border on obj (use by input type=file)
89
+  obj.style.backgroundColor = clear_bg;
90
+  obj.style.borderColor = clear_bg;
91
+  obj.style.borderStyle = 'none';
92
+}
93
+
94
+
95
+function verify_login() {
96
+  // Verify login inputs
97
+  login = document.getElementById('login');
98
+  password = document.getElementById('password');
99
+  if (login.value.length > 0) {
100
+    valid_input(login);
101
+    if (password.value.length > 0) {
102
+      valid_input(password);
103
+      return true;
104
+    }
105
+    invalid_input(password);
106
+    return false;
107
+  }
108
+  invalid_input(login);
109
+  return false;
110
+}
111
+
112
+function logout() {
113
+  // Logout user
114
+  setcookie('token', '', 30);
115
+  document.location = '/';
116
+}
117
+
118
+/* **************************************************************************************
119
+ * AJAX
120
+ * **************************************************************************************/
121
+
122
+function get_html_from_ajax(obj, url) {
123
+  // Get HTML content from AJAX request from url argument
124
+  // HTML content is then put as innerHTML to obj
125
+  var xhttp = new XMLHttpRequest();
126
+  xhttp.onerror = function(){
127
+    obj.innerHTML = "Error while getting content (1)";
128
+  };
129
+
130
+  xhttp.onload = function(){
131
+    if (xhttp.status != 200) {
132
+      obj.innerHTML = "Error while getting content (2)";
133
+      } 
134
+  };
135
+
136
+  xhttp.onreadystatechange = function() {
137
+    if (xhttp.readyState == 4 && xhttp.status == 200) {
138
+      var response = xhttp.responseText;
139
+      obj.innerHTML = response;
140
+    }
141
+  };
142
+  xhttp.open('POST', url, true);
143
+  xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
144
+  xhttp.send();
145
+}
146
+
147
+function set_value_from_ajax(obj, url, err_code) {
148
+  // Send value from obj.value via AJAX request to url argument
149
+  // obj.value is passed to URL in a REST sheme like <URL>/<VALUE>
150
+  // If err_code response is received, then a server side
151
+  // error has occured and input is invalidated.
152
+  url = url + '/' + obj.value;
153
+  var xhttp = new XMLHttpRequest();
154
+  xhttp.onerror = function(){
155
+    invalid_input(obj);
156
+  };
157
+
158
+  xhttp.onload = function(){
159
+    if (xhttp.status != 200) {
160
+      invalid_input(obj);
161
+      } 
162
+  };
163
+
164
+  xhttp.onreadystatechange = function() {
165
+    if (xhttp.readyState == 4 && xhttp.status == 200) {
166
+      var response = xhttp.responseText;
167
+      if (response == err_code) {
168
+        invalid_input(obj);
169
+        return;
170
+      }
171
+      valid_input(obj);
172
+      return;
173
+    }
174
+  };
175
+  xhttp.open('POST', url, true);
176
+  xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
177
+  xhttp.send();
178
+}
179
+
180
+function get_value_from_ajax(obj, url, err_code) {
181
+  // Get value from AJAX request
182
+  // The returned value is then set to obj.value.
183
+  // If err_code response is received, then a server side
184
+  // error has occured and input is invalidated
185
+  var xhttp = new XMLHttpRequest();
186
+  xhttp.onerror = function(){
187
+    invalid_input(obj);
188
+  };
189
+
190
+  xhttp.onload = function(){
191
+    if (xhttp.status != 200) {
192
+      invalid_input(obj);
193
+      }
194
+  };
195
+
196
+  xhttp.onreadystatechange = function() {
197
+    if (xhttp.readyState == 4 && xhttp.status == 200) {
198
+      var response = xhttp.responseText;
199
+      if (response == err_code) {
200
+        invalid_input(obj);
201
+        return;
202
+      }
203
+      obj.value = response;
204
+      valid_input(obj);
205
+      return;
206
+    }
207
+  };
208
+  xhttp.open('POST', url, true);
209
+  xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
210
+  xhttp.send();
211
+}
212
+
213
+function upload_file_from_ajax(obj, url, err_code) {
214
+  // Upload files get from <obj> to the specified <url>
215
+  // if <errcode> is returned input is invalidated
216
+  var files = obj.files;
217
+  var icon_id = obj.id.substring(obj.id.lastIndexOf("_") + 1);
218
+  var icon_obj = document.getElementById("upload_icon_" + icon_id)
219
+  var xhttp = new XMLHttpRequest();
220
+  xhttp.onerror = function(){
221
+    invalid_upload(icon_obj);
222
+  };
223
+
224
+  xhttp.onload = function(){
225
+    if (xhttp.status != 200) {
226
+      invalid_upload(icon_obj);
227
+      } 
228
+  };
229
+
230
+  xhttp.onreadystatechange = function() {
231
+    if (xhttp.readyState == 4 && xhttp.status == 200) {
232
+      var response = xhttp.responseText;
233
+      if (response == err_code) {
234
+        invalid_upload(icon_obj);
235
+        return;
236
+      }
237
+      valid_upload(icon_obj);
238
+      return;
239
+    }
240
+  };
241
+  
242
+  xhttp.open('POST', url, true);
243
+  var formData = new FormData();
244
+  for (var i=0; i < files.length; i++){
245
+    formData.append("files", files[i], files[i].name);
246
+  }
247
+  xhttp.send(formData);
248
+}
0 249
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+/*
1
+* Here are the base color scheme and icon set.
2
+* You can modify it or create your own using the same variables
3
+* and make it loaded after this one but before the fonts.css in
4
+* the HTML header section of the index.html template file.
5
+*/
6
+:root {
7
+    --coloured-bg: #FF5D00;
8
+    --light-coloured-bg: #FFB387;
9
+    --clear-bg: #E5E5E5;
10
+    --mid-bg: #BBBBBB;
11
+    --dark-bg: #2B2B2B;
12
+    --dark-border: #888888;
13
+    --text-color: #555555;
14
+    --white: #FFFFFF;
15
+    --black: #000000;
16
+    --font-normal: url("/static/fonts/RobotoCondensed-Regular.ttf") format("truetype");
17
+    --font-bold: url("/static/fonts/RobotoCondensed-Bold.ttf") format("truetype");
18
+    --banner-logo: url(/static/images/logo.png);
19
+    --add_icon: url(/static/images/add.png);
20
+    --edit_icon: url(/static/images/edit.png);
21
+    --login_icon: url(/static/images/login.png);
22
+    --logout_icon: url(/static/images/logout.png);
23
+    --refresh_icon: url(/static/images/refresh.png);
24
+    --save_icon: url(/static/images/save.png);
25
+    --search_icon: url(/static/images/search.png);
26
+    --trash_icon: url(/static/images/trash.png);
27
+    --upload_icon: url(/static/images/upload.png);
28
+}
0 29
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+/*
1
+* Here are the font definitions.
2
+* You can modify it or create your own and make it loaded
3
+* after this one in the HTML header section of the index.html
4
+* template file.
5
+*/
6
+
7
+@font-face {
8
+	font-family: "Roboto Condensed";
9
+	font-style: normal;
10
+	font-weight: 400;
11
+	src: var(--font-normal);
12
+}
13
+
14
+@font-face {
15
+	font-family: "Roboto Condensed";
16
+	font-style: normal;
17
+	font-weight: 700;
18
+	src: var(--font-bold);
19
+}
0 20
new file mode 100644
... ...
@@ -0,0 +1,370 @@
0
+/*
1
+* Do NOT modify this file:
2
+* ------------------------
3
+* If you want to add or modify classes, create a new
4
+* CSS files and make it loaded after this one in the
5
+* HTML header section of the index.html template file.
6
+*/
7
+
8
+* {
9
+ box-sizing: border-box; 
10
+}
11
+
12
+body {
13
+  margin: 10px;
14
+  font-family: "Roboto Condensed";
15
+  background-color:  var(--dark-bg);
16
+}
17
+
18
+div.content {
19
+  display: flex;
20
+  min-height: calc(100vh - 110px);
21
+}
22
+
23
+main > article {
24
+  flex: 1;
25
+  background-color:  var(--clear-bg);
26
+}
27
+
28
+main > section.inline {
29
+  display: flex;
30
+  background-color:  var(--clear-bg);
31
+}
32
+
33
+main > section.inline > article.left {
34
+  flex: 0 0 50%;
35
+  margin-left: 10px;
36
+}
37
+
38
+main > section.inline > article.right {
39
+  flex: 1;
40
+  margin-left: 10px;
41
+}
42
+
43
+div.content > nav.vertical {
44
+  flex: 0 0 200px;
45
+  background-color:  var(--clear-bg);
46
+  border-right-color: var(--mid-bg);
47
+  border-right-style: solid;
48
+  border-right-width: 1px;
49
+}
50
+
51
+div.content > nav.vertical {
52
+  order: -1;
53
+  display: block;
54
+}
55
+
56
+div.content > nav.vertical > a {
57
+  display: block;
58
+  background-color: var(--clear-bg);
59
+  font-size: 20px;
60
+  color: var(--text-color);
61
+  padding: 5px;
62
+  text-decoration: none;
63
+}
64
+
65
+div.content > nav.vertical > a:hover {
66
+  background-color:  var(--coloured-bg);
67
+  color: var(--white);
68
+  cursor: pointer;
69
+}
70
+
71
+div.content > nav.vertical > a.selected {
72
+  background-color:  var(--light-coloured-bg);
73
+}
74
+
75
+main {
76
+  color: var(--text-color);
77
+  background-color: var(--clear-bg);
78
+  width: 100%;
79
+}
80
+
81
+main > div.navbar_container {
82
+  text-align: center;
83
+  padding: 0;
84
+  margin: 0;
85
+}
86
+
87
+main > div.navbar_container > ul.horizontal {
88
+  display: inline-block;
89
+  list-style-type: none;
90
+  margin: 10px;
91
+  padding: 0;
92
+  overflow: hidden;
93
+  background-color: var(--white);
94
+  border-color: var(--coloured-bg);
95
+  border-style: solid;
96
+  border-width: 1px;
97
+  color: var(--text-color);
98
+  border-radius: 2px;
99
+}
100
+
101
+main > div.navbar_container > ul.horizontal > li {
102
+  float: left;
103
+}
104
+
105
+main > div.navbar_container > ul.horizontal > li > a {
106
+  display: block;
107
+  color: var(--text-color);
108
+  text-align: center;
109
+  padding: 5px;
110
+  text-decoration: none;
111
+}
112
+
113
+main > div.navbar_container > ul.horizontal > li > a:hover {
114
+  background-color: var(--coloured-bg);
115
+  color: var(--white);
116
+}
117
+
118
+main > div.navbar_container > ul.horizontal > li > a.right_border {
119
+  border-right-color: var(--coloured-bg);
120
+  border-right-style: solid;
121
+  border-right-width: 1px;
122
+}
123
+
124
+main > div.navbar_container > ul.horizontal > li > a.selected {
125
+  background-color: var(--light-coloured-bg);
126
+}
127
+
128
+main > article {
129
+  padding: 10px;
130
+  color: var(--text-color);
131
+  display: block;
132
+}
133
+
134
+main > article.error,
135
+main > article.error > p {
136
+  padding: 10px;
137
+  color: var(--text-color);
138
+  display: block;
139
+  text-align: center;
140
+}
141
+
142
+main > article > h3,
143
+main > section.inline > article > h3 {
144
+  font-size: 30px;
145
+  color: var(--text-color);
146
+  margin-bottom: 10px;
147
+}
148
+
149
+main > article > p,
150
+main > article > ul,
151
+main > article > ol {
152
+  color: var(--text-color);
153
+  text-align: justify;
154
+  text-justify: distribute;
155
+}
156
+
157
+main > hr {
158
+  border-color: var(--mid-bg);
159
+  border-style: solid;
160
+  border-width: 1px;
161
+}
162
+
163
+main > article > img {
164
+  display:inline-block;
165
+  border-color: var(--mid-bg);
166
+  border-style: solid;
167
+  border-width: 1px;
168
+  border-radius: 4px;
169
+}
170
+
171
+main > article > p > a {
172
+  color: var(--coloured-bg);
173
+}
174
+
175
+main > article > p > a:hover {
176
+  text-decoration: none;
177
+}
178
+
179
+main > article.right > img {
180
+  float: right;
181
+  margin: 0 0 0px 10px;
182
+}
183
+
184
+main > article.left > img {
185
+  float: left;
186
+  margin: 0 10px 0px 0;
187
+}
188
+
189
+header {
190
+  height: 65px;
191
+  font-size: 34px;
192
+  padding: 10px;
193
+  text-align: right;
194
+  color: var(--white);
195
+  background: var(--banner-logo);
196
+  background-repeat: no-repeat;
197
+  background-position: 10px;
198
+  text-shadow: 0 0 1px var(--black);
199
+  border-bottom-color: var(--dark-border);
200
+  border-bottom-style: solid;
201
+  border-bottom-width: 1px;
202
+  border-top-color: var(--white);
203
+  border-top-style: solid;
204
+  border-top-width: 1px;
205
+}
206
+
207
+footer {
208
+  height: 35px;
209
+  font-size: 12px;
210
+  text-align: center;
211
+  padding: 1em;
212
+  border-bottom-color: var(--white);
213
+  border-bottom-style: solid;
214
+  border-bottom-width: 1px;
215
+  border-top-color: var(--dark-border);
216
+  border-top-style: solid;
217
+  border-top-width: 1px;
218
+}
219
+
220
+header,
221
+footer {
222
+  background-color: var(--coloured-bg);
223
+  color: var(--white);
224
+}
225
+
226
+input[type="text"],
227
+input[type="password"],
228
+textarea,
229
+select,
230
+pre {
231
+  border-color: var(--dark-border);
232
+  border-style: solid;
233
+  border-width: 1px;
234
+  background-color: var(--white);
235
+  color: var(--text-color);
236
+  padding: 5px;
237
+  font-family: "Roboto Condensed";
238
+  margin: 5px;
239
+}
240
+
241
+pre {
242
+  border-color: var(--coloured-bg);
243
+}
244
+
245
+button,
246
+input[type="button"],
247
+input[type="submit"] {
248
+  border-color: var(--dark-border);
249
+  border-style: solid;
250
+  border-width: 1px;
251
+  background-color: var(--coloured-bg);
252
+  color: var(--white);
253
+  font-weight: bold;
254
+  padding: 5px;
255
+  font-family: "Roboto Condensed";
256
+  margin: 5px;
257
+  border-radius: 4px;
258
+}
259
+
260
+button:hover,
261
+input[type="button"]:hover,
262
+input[type="submit"]:hover,
263
+input[type="file"]:hover {
264
+  background-color: var(--light-coloured-bg);
265
+  color: var(--text-color);
266
+  cursor: pointer;
267
+}
268
+
269
+div.file_upload {
270
+  display: inline-block;
271
+  position: relative;
272
+  width: 20px;
273
+  height: 20px;
274
+  margin: 0;
275
+  padding: 0;
276
+  border-radius: 2px;
277
+  border-style: solid;
278
+  border-width: 1px;
279
+  border-color: var(--clear-bg);
280
+}
281
+
282
+input[type="file"] {
283
+  position: absolute;
284
+  width: 18px;
285
+  height: 18px;
286
+  left: 0;
287
+  top: 1px;
288
+  opacity: 0;
289
+}
290
+
291
+
292
+input.add,
293
+input.edit,
294
+input.login,
295
+input.logout,
296
+input.refresh,
297
+input.save,
298
+input.search,
299
+input.trash,
300
+input.upload {
301
+  width: 20px;
302
+  height: 20px;
303
+  margin: 0;
304
+  padding: 0;
305
+  border-radius: 2px;
306
+  border-style: none;
307
+}
308
+
309
+input.add:hover,
310
+input.edit:hover,
311
+input.login:hover,
312
+input.logout:hover,
313
+input.refresh:hover,
314
+input.save:hover,
315
+input.search:hover,
316
+input.trash:hover,
317
+input.upload:hover {
318
+  border-color: var(--text-color);
319
+  border-style: solid;
320
+  border-width: 1px;
321
+  background-color: var(--coloured-bg);
322
+  cursor: pointer;
323
+}
324
+
325
+input.add {
326
+  background: var(--add_icon);
327
+  background-repeat: no-repeat;
328
+  background-position: center center;
329
+}
330
+input.edit {
331
+  background: var(--edit_icon);
332
+  background-repeat: no-repeat;
333
+  background-position: center center;
334
+}
335
+input.login {
336
+  background: var(--login_icon);
337
+  background-repeat: no-repeat;
338
+  background-position: center center;
339
+}
340
+input.logout {
341
+  background: var(--logout_icon);
342
+  background-repeat: no-repeat;
343
+  background-position: center center;
344
+}
345
+input.refresh {
346
+  background: var(--refresh_icon);
347
+  background-repeat: no-repeat;
348
+  background-position: center center;
349
+}
350
+input.save {
351
+  background: var(--save_icon);
352
+  background-repeat: no-repeat;
353
+  background-position: center center;
354
+}
355
+input.search {
356
+  background: var(--search_icon);
357
+  background-repeat: no-repeat;
358
+  background-position: center center;
359
+}
360
+input.trash {
361
+  background: var(--trash_icon);
362
+  background-repeat: no-repeat;
363
+  background-position: center center;
364
+}
365
+input.upload {
366
+  background: var(--upload_icon);
367
+  background-repeat: no-repeat;
368
+  background-position: center center;
369
+}
0 370
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+{% extends "index.html" %}
1
+{% block title %}Ajax{% endblock %}
2
+      {% block main %}
3
+      <section class='inline'>
4
+        <article class='left'>
5
+          <h3>Get HTML response from AJAX</h3>
6
+          <p>Click the refresh button to get the HTML response.</p>
7
+          <p>The response may randomly be a voluntary error so you should try it more than once.</p>
8
+          Refresh: <input type='button' class='refresh' value=' '
9
+                          onclick='javascript:get_html_from_ajax(document.getElementById("html_container"), "/get_html_from_ajax");'>
10
+        </article>
11
+        <article class='right'>
12
+          <h3>Upload files with AJAX</h3>
13
+          <p>Select files to upload</p>
14
+          <p>The response may randomly be a voluntary error so you should try it more than once.</p>
15
+          Upload files: 
16
+          <div class='file_upload'>
17
+            <!--
18
+              Input file is a tricky hack (see tetawebapp.css and tetawebapp.js)
19
+            -->
20
+            <input type='button' id='upload_icon_1' class='upload' title='Upload' value=' '/>
21
+            <input type='file' id='upload_input_1' multiple
22
+                   title='Upload'
23
+                   onchange='javascript:upload_file_from_ajax(this, "/upload", "TETA_ERR");'
24
+                   onmouseover='javascript:lit(document.getElementById("upload_icon_1"));'
25
+                   onmouseout='javascript:unlit(document.getElementById("upload_icon_1"));'/>
26
+          </div>
27
+        </article>
28
+      </section>
29
+      <hr/>
30
+      <article class='right' id='html_container'></article>
31
+      <hr/>
32
+      <section class='inline'>
33
+        <article class='left'>
34
+          <h3>Set value via AJAX</h3>
35
+          <p>Send value to the application.</p>
36
+          <p>If value is empty or is "We Make Porn" (case sensitive), an error is raised.</p>
37
+          <input type='text' id='value_sender'>
38
+          <input type='button' value="Try me" onclick='javascript:set_value_from_ajax(document.getElementById("value_sender"), "/set_value_from_ajax", "TETA_ERR");'>
39
+        </article>
40
+        <article class='right'>
41
+          <h3>Get value from AJAX</h3>
42
+          <p>Get a random value from the application.</p>
43
+          <p>Randomly raises a voluntary error so you should try it more than once.</p>
44
+          <input type='text' id='value_receiver'>
45
+          <input type='button' value="Try me" onclick='javascript:get_value_from_ajax(document.getElementById("value_receiver"), "/get_value_from_ajax", "TETA_ERR");'>
46
+        </article>
47
+      </section>
48
+      {% endblock %}
0 49
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+<h3>This is the title</h3>
1
+<img src='/static/images/dummy_pic.png' alt='dummy pic' title='dummy pic'/>
2
+<p>
3
+  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
4
+  et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
5
+  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
6
+  dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
7
+  officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit,
8
+  sed do eiusmod tempor incididunt ut labore
9
+</p>
10
+<p>This <a href='/plop.html'>link</a> will lead to an error page</p>
11
+<p>
12
+  et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
13
+  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
14
+  dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
15
+  officia desers unt mollit anim id est laborum.
16
+</p>
17
+<p>
18
+  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
19
+  et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
20
+  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
21
+  dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
22
+  officia deserunt mollit anim id est laborum.
23
+</p>
0 24
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+{% extends "index.html" %}
1
+{% block title %}Articles{% endblock %}
2
+      {% block main %}
3
+      <article>
4
+        <h3>Choose your article</h3>
5
+        <p>
6
+          Please select your article
7
+        </p>
8
+      </article>
9
+      <article id='article_receiver'>
10
+      </article>
11
+      {% endblock %}
0 12
new file mode 100644
... ...
@@ -0,0 +1,65 @@
0
+{% extends "index.html" %}
1
+{% block title %}Articles{% endblock %}
2
+      {% block main %}
3
+      <article class='right'>
4
+        <h3>Article #{{ ID }}</h3>
5
+        <img src='/static/images/dummy_pic.png' alt='dummy pic' title='dummy pic'/>
6
+        <p>
7
+          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
8
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
9
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
10
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
11
+          officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit,
12
+          sed do eiusmod tempor incididunt ut labore
13
+        </p>
14
+        <p>This <a href='/plop.html'>link</a> will lead to an error page</p>
15
+        <p>
16
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
17
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
18
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
19
+          officia desers unt mollit anim id est laborum.
20
+        </p>
21
+        <ul>
22
+          <li>plop</li>
23
+          <li>plap</li>
24
+          <li>plip</li>
25
+        </ul>
26
+        <ol>
27
+          <li>plop</li>
28
+          <li>plap</li>
29
+          <li>plip</li>
30
+        </ol>
31
+        <p>
32
+          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
33
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
34
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
35
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
36
+          officia deserunt mollit anim id est laborum.
37
+        </p>
38
+      </article>
39
+      <article class='left'>
40
+        <h3>Another disposition</h3>
41
+        <img src='/static/images/dummy_pic.png' alt='dummy pic' title='dummy pic'/>
42
+        <p>
43
+          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
44
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
45
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
46
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
47
+          officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit,
48
+          sed do eiusmod tempor incididunt ut labore
49
+        </p>
50
+        <p>
51
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
52
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
53
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
54
+          officia deserunt mollit anim id est laborum.
55
+        </p>
56
+        <p>
57
+          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
58
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
59
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
60
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
61
+          officia deserunt mollit anim id est laborum.
62
+        </p>
63
+      </article>
64
+      {% endblock %}
0 65
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+{% extends "index.html" %}
1
+{% block title %}Basics{% endblock %}
2
+      {% block main %}
3
+      <article class='right'>
4
+        <h3>Basics</h3>
5
+        <p>
6
+          Thanks to <a href='http://flask.pocoo.org/'>Python/Flask</a> with <strong>TetaWebApp</strong> most of the output things come to life via
7
+          <a href='http://jinja.pocoo.org/docs/2.10/'>Jinja2 HTML templates</a>
8
+          and is 100% <strong title='Bulshit inside'>HTML5 ready©</strong>.
9
+        </p>
10
+        <p>
11
+          Colors and fonts are managed from separated CSS files letting you easily
12
+          change the default theme to your favorite colors and icon set.
13
+        </p>
14
+        <pre>
15
+/*
16
+* Here are the font definitions.
17
+* You can modify it or create your own and make it loaded
18
+* after this one in the HTML header section of the index.html
19
+* template file.
20
+*/
21
+
22
+@font-face {
23
+	font-family: "Roboto Condensed";
24
+	font-style: normal;
25
+	font-weight: 400;
26
+	src: var(--font-normal);
27
+}
28
+
29
+@font-face {
30
+	font-family: "Roboto Condensed";
31
+	font-style: normal;
32
+	font-weight: 700;
33
+	src: var(--font-bold);
34
+}
35
+        </pre>
36
+        <pre>
37
+/*
38
+* Here are the base color scheme and icon set.
39
+* You can modify it or create your own using the same variables
40
+* and make it loaded after this one but before the fonts.css in
41
+* the HTML header section of the index.html template file.
42
+*/
43
+:root {
44
+    --coloured-bg: #FF5D00;
45
+    --light-coloured-bg: #FFB387;
46
+    --clear-bg: #E5E5E5;
47
+    --mid-bg: #BBBBBB;
48
+    --dark-bg: #2B2B2B;
49
+    --dark-border: #888888;
50
+    --text-color: #555555;
51
+    --white: #FFFFFF;
52
+    --black: #000000;
53
+    --font-normal: url("/static/fonts/RobotoCondensed-Regular.ttf") format("truetype");
54
+    --font-bold: url("/static/fonts/RobotoCondensed-Bold.ttf") format("truetype");
55
+    --banner-logo: url(/static/images/logo.png);
56
+    --add_icon: url(/static/images/add.png);
57
+    --edit_icon: url(/static/images/edit.png);
58
+    --login_icon: url(/static/images/login.png);
59
+    --logout_icon: url(/static/images/logout.png);
60
+    --refresh_icon: url(/static/images/refresh.png);
61
+    --save_icon: url(/static/images/save.png);
62
+    --search_icon: url(/static/images/search.png);
63
+    --trash_icon: url(/static/images/trash.png);
64
+}
65
+        </pre>
66
+      </article>
67
+      {% endblock %}
0 68
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+{% extends "index.html" %}
1
+{% block title %}Database{% endblock %}
2
+      {% block main %}
3
+      <article class='right'>
4
+        <h3>Accessing database</h3>
5
+        <p>
6
+          Even if using <a href='http://flask-sqlalchemy.pocoo.org/2.3/'>Flask-SQLAlchemy</a> to retrieve data
7
+          stored in <strong>Postgres</strong> databases is the recommended way to use <strong>TetaWebApp</strong>, 
8
+          you're free to use the database connector that suits your need.
9
+        </p>
10
+      </article>
11
+      {% endblock %}
0 12
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+{% extends "index.html" %}
1
+{% block title %}Erreur{% endblock %}
2
+{% block nav %}{% endblock %}
3
+{% block main %}
4
+    <article class='error'>
5
+      <h3>404 - Not found</h3>
6
+      <p>The page you asked for was not found on this server.<br/>
7
+      It may has been lost, it may has never existed.<br/>
8
+      Maybe we don't care at all...</p>
9
+      <p>
10
+        <input type='button' value='Get me back to business' onclick='javascript:document.location="/";'/>
11
+      </p>
12
+      <img src='/static/images/404.png' alt='404 - Not found' title='404 - Not found'/>
13
+    </article>
14
+{% endblock %}
0 15
new file mode 100644
... ...
@@ -0,0 +1,106 @@
0
+<!DOCTYPE html>
1
+<html lang='zxx'>
2
+<head>
3
+  <title>TetaWebApp - {% block title %}Accueil{% endblock %}</title>
4
+  <meta name="viewport" content="initial-scale=1.0" />
5
+  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
6
+  <link rel="stylesheet" type="text/css" href="/static/styles/colors.css" />
7
+  <link rel="stylesheet" type="text/css" href="/static/styles/fonts.css" />
8
+  <link rel="stylesheet" type="text/css" href="/static/styles/tetawebapp.css" />
9
+  <link rel="icon" type="image/png" href="/static/images/favicon.png" />
10
+  <script src="/static/scripts/tetawebapp.js"></script>
11
+</head>
12
+{% block bodyheader %}
13
+<body>
14
+{% endblock %}
15
+  <header>{% block banner %}TetaWebApp{% endblock %}</header>
16
+  <div class='content'>
17
+    {% block nav %}
18
+    <nav class='vertical'>
19
+      {% block menu %}
20
+        {% for item in menu %}
21
+          {% for key in item[1] %}
22
+            {% if item[2] == 1 %}
23
+              <a class='selected' href='{{ key }}'>{{ item[0] }}</a>
24
+            {% else %}
25
+              <a href='{{ key }}'>{{ item[0] }}</a>
26
+            {% endif %}
27
+          {% endfor %}
28
+        {% endfor %}
29
+      {% endblock %}
30
+    </nav>
31
+    {% endblock%}
32
+    <main>
33
+        {% if navbar %}
34
+          <div class='navbar_container'>
35
+            <ul class='horizontal'>
36
+            {% for item in navbar %}
37
+              {% set selected = ['', 'selected'] %}
38
+              {% set last = ['right_border', 'last'] %}
39
+              {% for url in item[1] %}
40
+                <li><a class='{{ last[item[3]] }} {{ selected[item[2]] }}' href='{{ url }}'>{{ item[0] }}</a></li>
41
+              {% endfor %}
42
+            {% endfor %}
43
+            </ul>
44
+          </div>
45
+        {% endif %}
46
+      {% block main %}
47
+      <article class='right'>
48
+        <h3>TetaWebApp demo</h3>
49
+        <p>
50
+          Welcome to the <strong>TetaWebApp</strong> demo
51
+        </p>
52
+        <p>
53
+          TetaWebApp is a basic web application template based on <a href='http://flask.pocoo.org/'>Python/Flask</a>
54
+          and <a href='https://www.w3schools.com/js/js_ajax_intro.asp'>AJAX</a> made by
55
+          <a href='mailto:doug.letough@free.fr'>Doug Le Tough</a> from <a href='https://www.tetalab.org'>Tetalab</a>.
56
+        </p>
57
+        <p>
58
+          The goal of this project is to provide a basic framework to make any web application you need while
59
+          letting you complete freedom on how to use or extend it <strong>without</strong> using any Google,
60
+          Bootstrap or any other piece of <strong>shitty free spyware</strong>.
61
+        </p>
62
+          TetaWebApp will <strong>never</strong> download or upload anything in any way.
63
+        <p>
64
+        </p>
65
+        <p>There is <strong>no</strong> limitation, you can use all or only parts of <strong>TetaWebApp</strong>
66
+        and you can <strong title='bullshit inside'>virtually</strong> do any app you want with TetaWebApp.
67
+        </p>
68
+        <p>
69
+          But be sure that freedom has a cost: You <strong>will</strong> need work to make it work ;-)
70
+        </p>
71
+        <p>
72
+          <strong>TetaWebApp</strong> is released under the only real <strong>free</strong> license: The
73
+          <a href='http://www.wtfpl.net/'><img src='http://www.wtfpl.net/wp-content/uploads/2012/12/wtfpl-badge-2.png'
74
+          title='WTFPL' alt='WTFPL' /></a>.
75
+        </p>
76
+        <pre>
77
+        DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 
78
+                    Version 2, December 2004 
79
+
80
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> 
81
+
82
+ Everyone is permitted to copy and distribute verbatim or modified 
83
+ copies of this license document, and changing it is allowed as long 
84
+ as the name is changed. 
85
+
86
+            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 
87
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 
88
+
89
+  0. You just DO WHAT THE FUCK YOU WANT TO.
90
+        </pre>
91
+        <p>
92
+          Get a copy of <strong>TetaWebApp</strong>:<br/>
93
+          <pre>
94
+git clone git://git.tetalab.org/tetalab/tetawebapp
95
+          </pre>
96
+        </p>
97
+      </article>
98
+      {% endblock %}
99
+    </main>
100
+  </div>
101
+  {% block footer %}
102
+  <footer>© - Tetalab - Le hacker space Toulousaing' putaing' cong' -</footer>
103
+  {% endblock%}
104
+</body>
105
+</html>
0 106
new file mode 100644
... ...
@@ -0,0 +1,53 @@
0
+{% extends "index.html" %}
1
+{% block title %}Inputs{% endblock %}
2
+      {% block main %}
3
+      <article class='right'>
4
+        <h3>The input collection</h3>
5
+        <p>
6
+          Have a look to the input collection:
7
+        </p>
8
+        <input type='text'/>
9
+        <button>Click me</button>
10
+        <br/>
11
+        <textarea cols='25'></textarea>
12
+        <br/>
13
+        <select>
14
+          {% for item in menu %}
15
+          <option>{{ item[0] }}</option>
16
+          {% endfor %}
17
+        </select>
18
+        <input type='submit' value='Click me too'/>
19
+        <br/>
20
+        <input type='button' value='And me !' />
21
+        <br/>
22
+        <input type='button' class='add' title='Add' value=' '/>
23
+        <input type='button' class='edit' title='Edit' value=' '/>
24
+        <input type='button' class='login' title='Login' value=' '/>
25
+        <input type='button' class='logout' title='Logout' value=' ' onclick='javascript:logout();'/>
26
+        <input type='button' class='refresh' title='Refresh' value=' '/>
27
+        <input type='button' class='save' title='Save' value=' '/>
28
+        <input type='button' class='search' title='Search' value=' '/>
29
+        <input type='button' class='trash' title='Trash' value=' '/>
30
+        <!--
31
+          Input file is a tricky hack (see tetawebapp.css and tetawebapp.js)
32
+        -->
33
+        <div class='file_upload'>
34
+          <input type='button' id='upload_icon_1' class='upload' title='Upload' value=' '/>
35
+          <input type='file' id='upload_input_1' name='files' multiple
36
+                 title='Upload'
37
+                 onchange='javascript:upload_file_from_ajax(this, "/upload", "TETA_ERR");'
38
+                 onmouseover='javascript:lit(document.getElementById("upload_icon_1"));'
39
+                 onmouseout='javascript:unlit(document.getElementById("upload_icon_1"));'/>
40
+        </div>
41
+        <br/>
42
+        <pre>
43
+#!/bin/sh
44
+# This is code sample
45
+while [ 1 ]
46
+do
47
+  echo "Tits or GTFO !"
48
+  sleep .1
49
+done
50
+        </pre>
51
+      </article>
52
+      {% endblock %}
0 53
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+{% extends "index.html" %}
1
+{% block title %}Login{% endblock %}
2
+{% block nav %}{% endblock %}
3
+{% block main %}
4
+    <article class='login'>
5
+      <h3>Login</h3>
6
+      <p>The demo login is:</p>
7
+      <ul>
8
+        <li>Login: demo</li>
9
+        <li>Password: demo</li>
10
+      </ul>
11
+    </article>
12
+    {% if message != '' %}
13
+      <pre>{{ message }}</pre>
14
+    {% endif %}
15
+    <article class='left'>
16
+        <form method='POST' action='/login'>
17
+        Login: <input id='login' name='login' type='text' />
18
+        Password: <input id='password' name='password' type='password' />
19
+        <input type='submit' value='Log me in' onclick='javascript:return verify_login();'>
20
+      </form>
21
+    </article>
22
+{% endblock %}
0 23
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+{% extends "index.html" %}
1
+{% block title %}TODO{% endblock %}
2
+      {% block main %}
3
+      <article class='right'>
4
+        <h3>TODO list</h3>
5
+        <ul>
6
+          <li><strike>Basic menu management</strike></li>
7
+          <li>Installation wizard</li>
8
+          <li>Back office for basic content management</li>
9
+          <li><strike>Basic Ajax support</strike></li>
10
+          <li><strike>Session management</strike></li>
11
+          <li>File upload</li>
12
+          <li>Basic documentation</li>
13
+          <li><strike>Horizontal navbar</strike></li>
14
+          <li><strike>License</strike></li>
15
+        </ul>
16
+      </article>
17
+      {% endblock %}
0 18
new file mode 100755
... ...
@@ -0,0 +1,304 @@
0
+#!/usr/bin/env python
1
+# -*- coding: utf-8
2
+
3
+# Required modules
4
+import os
5
+import inspect
6
+import random
7
+import binascii
8
+import bcrypt
9
+from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
10
+from functools import wraps
11
+
12
+# Optionnal modules
13
+import psycopg2
14
+from flask_sqlalchemy import SQLAlchemy
15
+
16
+########################################################################
17
+# App settings
18
+########################################################################
19
+app = Flask(__name__)
20
+# Path to static files
21
+app.static_url_path='/static'
22
+# Set debug mode to False for production
23
+app.debug = True
24
+# Various configuration settings belong here (optionnal)
25
+app.config.from_pyfile('config.local.py')
26
+# Generate a new key: head -n 40 /dev/urandom | md5sum | cut -d' ' -f1
27
+app.secret_key = 'ce1d1c9ff0ff388a838b3a1e3207dd27'
28
+# Feel free to use SQLAlchemy (or not)
29
+db = SQLAlchemy(app)
30
+
31
+
32
+########################################################################
33
+# Sample user database
34
+########################################################################
35
+class Tetawebapp_users(db.Model):
36
+  __tablename__ = 'tetawebapp_users'
37
+  id = db.Column(db.Integer, primary_key=True)
38
+  mail = db.Column(db.Text, nullable=False)
39
+  password = db.Column(db.Text, nullable=False)
40
+  name = db.Column(db.Text, nullable=False)
41
+
42
+
43
+########################################################################
44
+# Menu and navigation management
45
+########################################################################
46
+
47
+def get_menu(page):
48
+  """  The main menu is a list of lists in the followin format:
49
+          [unicode caption,
50
+            {unicode URL endpoint: [unicode route, ...]},
51
+            int 0]
52
+        - The URL end point is the URL where to point to (href)
53
+        - One of the routes MUST match the route called by request
54
+        - The int 0 is used to determine which menu entry is actally called.
55
+          The value MUST be 0."""
56
+  menu = [[u'Home', {u'/': [u'/']}, 0],
57
+          [u'Articles', {u'/articles': [u'/articles', u'/articles/<ID>']}, 0],
58
+          [u'Basics', {u'/basics': [u'/basics']}, 0],
59
+          [u'Inputs', {u'/inputs': [u'/inputs']}, 0],
60
+          [u'Ajax', {u'/ajax': [u'/ajax']}, 0],
61
+          [u'Database', {u'/database': [u'/database']}, 0],
62
+          [u'Todo', {u'/todo': [u'/todo']}, 0],
63
+          ]
64
+  for item in menu:
65
+    for url in item[1]:
66
+      for route in item[1][url]:
67
+        if route == page:
68
+          item[2] = 1
69
+          return menu
70
+  # This should never happen
71
+  return menu
72
+
73
+def get_navbar(page, selected):
74
+  """  The horizontal navbar is a list of lists in the followin format:
75
+          [unicode caption, {unicode URL endpoint: [unicode route, ...]}, int 0]
76
+        - The URL end point is the URL where to point to (href)
77
+        - One of the routes MUST match the route called by request
78
+        - The int 0 is used to de """
79
+  navbars = [[u'First article', {u'/articles/1': [u'/articles', u'/articles/<ID>']}, 0, 0],
80
+            [u'Second article', {u'/articles/2': [u'/articles', u'/articles/<ID>']}, 0, 0],
81
+            [u'Third article', {u'/articles/3': [u'/articles', u'/articles/<ID>']}, 0, 0]
82
+            ]
83
+  navbar = []
84
+  for item in navbars:
85
+    for url in item[1]:
86
+      if url == selected:
87
+        item[2] = 1
88
+      for route in item[1][url]:
89
+        if route == page:
90
+          navbar.append(item)
91
+  navbar[len(navbar) - 1][3] = 1
92
+  return navbar
93
+
94
+########################################################################
95
+# Session management
96
+########################################################################
97
+
98
+def sync_session(request, session):
99
+  """ Synchronize cookies with session """
100
+  for key in request.cookies:
101
+    session[key] = request.cookies[key].encode('utf8')
102
+
103
+def sync_cookies(response, session):
104
+  """ Synchronize session with cookies """
105
+  for key in session:
106
+    response.set_cookie(key, value=str(session[key]))
107
+
108
+def check_session(func):
109
+  """ Check if the session has required token cookie set.
110
+      If not, redirects to the login page. """
111
+  @wraps(func)
112
+  def check(*args, **kwargs):
113
+    try:
114
+      if session['token'] == request.cookies['token'] and len(session['token']) > 0:
115
+        return func(*args, **kwargs)
116
+      else:
117
+        session['token'] = ''
118
+        response = app.make_response(render_template('login.html', message=''))
119
+        sync_cookies(response, session)
120
+        return response
121
+    except KeyError:
122
+      return render_template('login.html', message='')
123
+  return check
124
+
125
+def check_login(login, password):
126
+  """ Puts the login verification code here """
127
+  password = password.encode('utf-8')
128
+  hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
129
+  stored_hash = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.password).first()
130
+  if stored_hash:
131
+    if bcrypt.checkpw(password, stored_hash[0].encode('utf-8')):
132
+      return True
133
+  return False
134
+
135
+def gen_token():
136
+  """ Generate a random token to be stored in session and cookie """
137
+  token = binascii.hexlify(os.urandom(42))
138
+  return token
139
+
140
+########################################################################
141
+# Routes:
142
+# -------
143
+# Except for the index function, the function name MUST have the same
144
+# name than the URL endpoint to make the menu work properly
145
+########################################################################
146
+@app.errorhandler(404)
147
+def page_not_found(e):
148
+  """ 404 not found """
149
+  return render_template('error.html'), 404
150
+
151
+@app.route("/login", methods=['GET', 'POST'])
152
+def login():
153
+  login = request.form.get('login')
154
+  password = request.form.get('password')
155
+  if check_login(login, password):
156
+    # Generate and store a token in session
157
+    session['token'] = gen_token()
158
+    # Return user to index page
159
+    page = '/'
160
+    menu = get_menu(page)
161
+    response = app.make_response(render_template('index.html', menu=menu))
162
+    # Push token to cookie
163
+    sync_cookies(response, session)
164
+    return response
165
+  # Credentials are not valid
166
+  response = app.make_response(render_template('login.html', message='Invalid user or password'))
167
+  session['token'] = ''
168
+  sync_cookies(response, session)
169
+  return response
170
+
171
+@app.route("/", methods=['GET', 'POST'])
172
+@check_session
173
+def index():
174
+  """ Index page """
175
+  page = str(request.url_rule)
176
+  menu = get_menu(page)
177
+  return render_template('index.html', menu=menu)
178
+
179
+@app.route("/articles", methods=['GET', 'POST'])
180
+@check_session
181
+def articles():
182
+  """ Arcticles page """
183
+  page = str(request.url_rule)
184
+  menu = get_menu(page)