Browse Source

Initial commit

Doug Le Tough 2 years ago
commit
99181ffa08

+ 4
- 0
.gitignore View File

@@ -0,0 +1,4 @@
1
+*un~
2
+*.swp
3
+*.pyc
4
+*.wsgi

+ 3
- 0
config.local.py View File

@@ -0,0 +1,3 @@
1
+SQLALCHEMY_TRACK_MODIFICATIONS = True
2
+SQLALCHEMY_DATABASE_URI = "postgresql://tetawebapp:tetawebapp@localhost/tetawebapp"
3
+UPLOADED_FILES_DEST = "./upload"

+ 1
- 0
config.py View File

@@ -0,0 +1 @@
1
+config.local.py

BIN
static/fonts/RobotoCondensed-Bold.ttf View File


BIN
static/fonts/RobotoCondensed-Regular.ttf View File


BIN
static/images/404.png View File


BIN
static/images/add.png View File


BIN
static/images/dummy_pic.png View File


BIN
static/images/edit.png View File


BIN
static/images/favicon.png View File


BIN
static/images/login.png View File


BIN
static/images/logo.png View File


BIN
static/images/logout.png View File


BIN
static/images/refresh.png View File


BIN
static/images/save.png View File


BIN
static/images/search.png View File


BIN
static/images/trash.png View File


BIN
static/images/upload.png View File


+ 249
- 0
static/scripts/tetawebapp.js View File

@@ -0,0 +1,249 @@
1
+var red = "#FF0000";
2
+var green = "#00FF00";
3
+var light_red = "#FCD5DC";
4
+var light_green = "#D5FCD8";
5
+var base_bg = "#FFFFFF";
6
+var base_border = "#888888";
7
+var coloured_bg = "#FF5D00";
8
+var clear_bg = "#E5E5E5";
9
+var text_color = "#555555";
10
+
11
+/* **************************************************************************************
12
+ * GLOBAL
13
+ * **************************************************************************************/
14
+
15
+// Cookies
16
+function setcookie(cname, cvalue, exdays) {
17
+    // Set cookie
18
+    var d = new Date();
19
+    d.setTime(d.getTime() + (exdays*24*60*60*1000));
20
+    var expires = "expires="+ d.toUTCString();
21
+    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
22
+}
23
+
24
+function getcookie(cname) {
25
+  // Get cookie by name
26
+  var value = "; " + document.cookie;
27
+  var parts = value.split("; " + cname + "=");
28
+  if (parts.length == 2) return parts.pop().split(";").shift();
29
+}
30
+
31
+// Eye candies
32
+function valid_input(obj) {
33
+  // Valid input makes obj background to glow green for 2 seconds
34
+  // If obj borders were red, they get they normal color back
35
+  obj.style.backgroundColor = light_green;
36
+  obj.style.borderColor = base_border;
37
+  setTimeout( function() {
38
+    obj.style.backgroundColor = base_bg;
39
+    }
40
+  , 2000);
41
+}
42
+
43
+function invalid_input(obj) {
44
+  // Invalid input makes obj borders and background to glow red for 2 seconds
45
+  // Border color will stay red until a valid input is sent
46
+  obj.style.backgroundColor = light_red;
47
+  obj.style.borderColor = red;
48
+  setTimeout( function() {
49
+    obj.style.backgroundColor = base_bg;
50
+    }
51
+  , 2000);
52
+}
53
+
54
+function valid_upload(obj) {
55
+  // Valid input makes obj background to glow green for 2 seconds
56
+  // If obj borders were red, they get they normal color back
57
+  obj.style.backgroundColor = green;
58
+  obj.style.borderColor = text_color;
59
+  obj.style.borderStyle = 'solid';
60
+  setTimeout( function() {
61
+    obj.style.backgroundColor = clear_bg;
62
+    obj.style.borderStyle = 'none';
63
+    }
64
+  , 2000);
65
+}
66
+
67
+function invalid_upload(obj) {
68
+  // Invalid input makes obj borders and background to glow red for 2 seconds
69
+  // Border color will stay red until a valid input is sent
70
+  obj.style.backgroundColor = red;
71
+  obj.style.borderColor = text_color;
72
+  obj.style.borderStyle = 'solid';
73
+  setTimeout( function() {
74
+    obj.style.borderStyle = 'solid';
75
+    obj.style.backgroundColor = clear_bg;
76
+    obj.style.borderColor = red;
77
+    }
78
+  , 2000);
79
+}
80
+
81
+function lit(obj) {
82
+  // Lit bacground and border on obj (use by input type=file)
83
+  obj.style.backgroundColor = coloured_bg;
84
+  obj.style.borderColor = text_color;
85
+  obj.style.borderStyle = 'solid';
86
+}
87
+
88
+function unlit(obj) {
89
+  // Unlit bacground and border on obj (use by input type=file)
90
+  obj.style.backgroundColor = clear_bg;
91
+  obj.style.borderColor = clear_bg;
92
+  obj.style.borderStyle = 'none';
93
+}
94
+
95
+
96
+function verify_login() {
97
+  // Verify login inputs
98
+  login = document.getElementById('login');
99
+  password = document.getElementById('password');
100
+  if (login.value.length > 0) {
101
+    valid_input(login);
102
+    if (password.value.length > 0) {
103
+      valid_input(password);
104
+      return true;
105
+    }
106
+    invalid_input(password);
107
+    return false;
108
+  }
109
+  invalid_input(login);
110
+  return false;
111
+}
112
+
113
+function logout() {
114
+  // Logout user
115
+  setcookie('token', '', 30);
116
+  document.location = '/';
117
+}
118
+
119
+/* **************************************************************************************
120
+ * AJAX
121
+ * **************************************************************************************/
122
+
123
+function get_html_from_ajax(obj, url) {
124
+  // Get HTML content from AJAX request from url argument
125
+  // HTML content is then put as innerHTML to obj
126
+  var xhttp = new XMLHttpRequest();
127
+  xhttp.onerror = function(){
128
+    obj.innerHTML = "Error while getting content (1)";
129
+  };
130
+
131
+  xhttp.onload = function(){
132
+    if (xhttp.status != 200) {
133
+      obj.innerHTML = "Error while getting content (2)";
134
+      } 
135
+  };
136
+
137
+  xhttp.onreadystatechange = function() {
138
+    if (xhttp.readyState == 4 && xhttp.status == 200) {
139
+      var response = xhttp.responseText;
140
+      obj.innerHTML = response;
141
+    }
142
+  };
143
+  xhttp.open('POST', url, true);
144
+  xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
145
+  xhttp.send();
146
+}
147
+
148
+function set_value_from_ajax(obj, url, err_code) {
149
+  // Send value from obj.value via AJAX request to url argument
150
+  // obj.value is passed to URL in a REST sheme like <URL>/<VALUE>
151
+  // If err_code response is received, then a server side
152
+  // error has occured and input is invalidated.
153
+  url = url + '/' + obj.value;
154
+  var xhttp = new XMLHttpRequest();
155
+  xhttp.onerror = function(){
156
+    invalid_input(obj);
157
+  };
158
+
159
+  xhttp.onload = function(){
160
+    if (xhttp.status != 200) {
161
+      invalid_input(obj);
162
+      } 
163
+  };
164
+
165
+  xhttp.onreadystatechange = function() {
166
+    if (xhttp.readyState == 4 && xhttp.status == 200) {
167
+      var response = xhttp.responseText;
168
+      if (response == err_code) {
169
+        invalid_input(obj);
170
+        return;
171
+      }
172
+      valid_input(obj);
173
+      return;
174
+    }
175
+  };
176
+  xhttp.open('POST', url, true);
177
+  xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
178
+  xhttp.send();
179
+}
180
+
181
+function get_value_from_ajax(obj, url, err_code) {
182
+  // Get value from AJAX request
183
+  // The returned value is then set to obj.value.
184
+  // If err_code response is received, then a server side
185
+  // error has occured and input is invalidated
186
+  var xhttp = new XMLHttpRequest();
187
+  xhttp.onerror = function(){
188
+    invalid_input(obj);
189
+  };
190
+
191
+  xhttp.onload = function(){
192
+    if (xhttp.status != 200) {
193
+      invalid_input(obj);
194
+      }
195
+  };
196
+
197
+  xhttp.onreadystatechange = function() {
198
+    if (xhttp.readyState == 4 && xhttp.status == 200) {
199
+      var response = xhttp.responseText;
200
+      if (response == err_code) {
201
+        invalid_input(obj);
202
+        return;
203
+      }
204
+      obj.value = response;
205
+      valid_input(obj);
206
+      return;
207
+    }
208
+  };
209
+  xhttp.open('POST', url, true);
210
+  xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
211
+  xhttp.send();
212
+}
213
+
214
+function upload_file_from_ajax(obj, url, err_code) {
215
+  // Upload files get from <obj> to the specified <url>
216
+  // if <errcode> is returned input is invalidated
217
+  var files = obj.files;
218
+  var icon_id = obj.id.substring(obj.id.lastIndexOf("_") + 1);
219
+  var icon_obj = document.getElementById("upload_icon_" + icon_id)
220
+  var xhttp = new XMLHttpRequest();
221
+  xhttp.onerror = function(){
222
+    invalid_upload(icon_obj);
223
+  };
224
+
225
+  xhttp.onload = function(){
226
+    if (xhttp.status != 200) {
227
+      invalid_upload(icon_obj);
228
+      } 
229
+  };
230
+
231
+  xhttp.onreadystatechange = function() {
232
+    if (xhttp.readyState == 4 && xhttp.status == 200) {
233
+      var response = xhttp.responseText;
234
+      if (response == err_code) {
235
+        invalid_upload(icon_obj);
236
+        return;
237
+      }
238
+      valid_upload(icon_obj);
239
+      return;
240
+    }
241
+  };
242
+  
243
+  xhttp.open('POST', url, true);
244
+  var formData = new FormData();
245
+  for (var i=0; i < files.length; i++){
246
+    formData.append("files", files[i], files[i].name);
247
+  }
248
+  xhttp.send(formData);
249
+}

+ 29
- 0
static/styles/colors.css View File

@@ -0,0 +1,29 @@
1
+/*
2
+* Here are the base color scheme and icon set.
3
+* You can modify it or create your own using the same variables
4
+* and make it loaded after this one but before the fonts.css in
5
+* the HTML header section of the index.html template file.
6
+*/
7
+:root {
8
+    --coloured-bg: #FF5D00;
9
+    --light-coloured-bg: #FFB387;
10
+    --clear-bg: #E5E5E5;
11
+    --mid-bg: #BBBBBB;
12
+    --dark-bg: #2B2B2B;
13
+    --dark-border: #888888;
14
+    --text-color: #555555;
15
+    --white: #FFFFFF;
16
+    --black: #000000;
17
+    --font-normal: url("/static/fonts/RobotoCondensed-Regular.ttf") format("truetype");
18
+    --font-bold: url("/static/fonts/RobotoCondensed-Bold.ttf") format("truetype");
19
+    --banner-logo: url(/static/images/logo.png);
20
+    --add_icon: url(/static/images/add.png);
21
+    --edit_icon: url(/static/images/edit.png);
22
+    --login_icon: url(/static/images/login.png);
23
+    --logout_icon: url(/static/images/logout.png);
24
+    --refresh_icon: url(/static/images/refresh.png);
25
+    --save_icon: url(/static/images/save.png);
26
+    --search_icon: url(/static/images/search.png);
27
+    --trash_icon: url(/static/images/trash.png);
28
+    --upload_icon: url(/static/images/upload.png);
29
+}

+ 20
- 0
static/styles/fonts.css View File

@@ -0,0 +1,20 @@
1
+/*
2
+* Here are the font definitions.
3
+* You can modify it or create your own and make it loaded
4
+* after this one in the HTML header section of the index.html
5
+* template file.
6
+*/
7
+
8
+@font-face {
9
+	font-family: "Roboto Condensed";
10
+	font-style: normal;
11
+	font-weight: 400;
12
+	src: var(--font-normal);
13
+}
14
+
15
+@font-face {
16
+	font-family: "Roboto Condensed";
17
+	font-style: normal;
18
+	font-weight: 700;
19
+	src: var(--font-bold);
20
+}

+ 370
- 0
static/styles/tetawebapp.css View File

@@ -0,0 +1,370 @@
1
+/*
2
+* Do NOT modify this file:
3
+* ------------------------
4
+* If you want to add or modify classes, create a new
5
+* CSS files and make it loaded after this one in the
6
+* HTML header section of the index.html template file.
7
+*/
8
+
9
+* {
10
+ box-sizing: border-box; 
11
+}
12
+
13
+body {
14
+  margin: 10px;
15
+  font-family: "Roboto Condensed";
16
+  background-color:  var(--dark-bg);
17
+}
18
+
19
+div.content {
20
+  display: flex;
21
+  min-height: calc(100vh - 110px);
22
+}
23
+
24
+main > article {
25
+  flex: 1;
26
+  background-color:  var(--clear-bg);
27
+}
28
+
29
+main > section.inline {
30
+  display: flex;
31
+  background-color:  var(--clear-bg);
32
+}
33
+
34
+main > section.inline > article.left {
35
+  flex: 0 0 50%;
36
+  margin-left: 10px;
37
+}
38
+
39
+main > section.inline > article.right {
40
+  flex: 1;
41
+  margin-left: 10px;
42
+}
43
+
44
+div.content > nav.vertical {
45
+  flex: 0 0 200px;
46
+  background-color:  var(--clear-bg);
47
+  border-right-color: var(--mid-bg);
48
+  border-right-style: solid;
49
+  border-right-width: 1px;
50
+}
51
+
52
+div.content > nav.vertical {
53
+  order: -1;
54
+  display: block;
55
+}
56
+
57
+div.content > nav.vertical > a {
58
+  display: block;
59
+  background-color: var(--clear-bg);
60
+  font-size: 20px;
61
+  color: var(--text-color);
62
+  padding: 5px;
63
+  text-decoration: none;
64
+}
65
+
66
+div.content > nav.vertical > a:hover {
67
+  background-color:  var(--coloured-bg);
68
+  color: var(--white);
69
+  cursor: pointer;
70
+}
71
+
72
+div.content > nav.vertical > a.selected {
73
+  background-color:  var(--light-coloured-bg);
74
+}
75
+
76
+main {
77
+  color: var(--text-color);
78
+  background-color: var(--clear-bg);
79
+  width: 100%;
80
+}
81
+
82
+main > div.navbar_container {
83
+  text-align: center;
84
+  padding: 0;
85
+  margin: 0;
86
+}
87
+
88
+main > div.navbar_container > ul.horizontal {
89
+  display: inline-block;
90
+  list-style-type: none;
91
+  margin: 10px;
92
+  padding: 0;
93
+  overflow: hidden;
94
+  background-color: var(--white);
95
+  border-color: var(--coloured-bg);
96
+  border-style: solid;
97
+  border-width: 1px;
98
+  color: var(--text-color);
99
+  border-radius: 2px;
100
+}
101
+
102
+main > div.navbar_container > ul.horizontal > li {
103
+  float: left;
104
+}
105
+
106
+main > div.navbar_container > ul.horizontal > li > a {
107
+  display: block;
108
+  color: var(--text-color);
109
+  text-align: center;
110
+  padding: 5px;
111
+  text-decoration: none;
112
+}
113
+
114
+main > div.navbar_container > ul.horizontal > li > a:hover {
115
+  background-color: var(--coloured-bg);
116
+  color: var(--white);
117
+}
118
+
119
+main > div.navbar_container > ul.horizontal > li > a.right_border {
120
+  border-right-color: var(--coloured-bg);
121
+  border-right-style: solid;
122
+  border-right-width: 1px;
123
+}
124
+
125
+main > div.navbar_container > ul.horizontal > li > a.selected {
126
+  background-color: var(--light-coloured-bg);
127
+}
128
+
129
+main > article {
130
+  padding: 10px;
131
+  color: var(--text-color);
132
+  display: block;
133
+}
134
+
135
+main > article.error,
136
+main > article.error > p {
137
+  padding: 10px;
138
+  color: var(--text-color);
139
+  display: block;
140
+  text-align: center;
141
+}
142
+
143
+main > article > h3,
144
+main > section.inline > article > h3 {
145
+  font-size: 30px;
146
+  color: var(--text-color);
147
+  margin-bottom: 10px;
148
+}
149
+
150
+main > article > p,
151
+main > article > ul,
152
+main > article > ol {
153
+  color: var(--text-color);
154
+  text-align: justify;
155
+  text-justify: distribute;
156
+}
157
+
158
+main > hr {
159
+  border-color: var(--mid-bg);
160
+  border-style: solid;
161
+  border-width: 1px;
162
+}
163
+
164
+main > article > img {
165
+  display:inline-block;
166
+  border-color: var(--mid-bg);
167
+  border-style: solid;
168
+  border-width: 1px;
169
+  border-radius: 4px;
170
+}
171
+
172
+main > article > p > a {
173
+  color: var(--coloured-bg);
174
+}
175
+
176
+main > article > p > a:hover {
177
+  text-decoration: none;
178
+}
179
+
180
+main > article.right > img {
181
+  float: right;
182
+  margin: 0 0 0px 10px;
183
+}
184
+
185
+main > article.left > img {
186
+  float: left;
187
+  margin: 0 10px 0px 0;
188
+}
189
+
190
+header {
191
+  height: 65px;
192
+  font-size: 34px;
193
+  padding: 10px;
194
+  text-align: right;
195
+  color: var(--white);
196
+  background: var(--banner-logo);
197
+  background-repeat: no-repeat;
198
+  background-position: 10px;
199
+  text-shadow: 0 0 1px var(--black);
200
+  border-bottom-color: var(--dark-border);
201
+  border-bottom-style: solid;
202
+  border-bottom-width: 1px;
203
+  border-top-color: var(--white);
204
+  border-top-style: solid;
205
+  border-top-width: 1px;
206
+}
207
+
208
+footer {
209
+  height: 35px;
210
+  font-size: 12px;
211
+  text-align: center;
212
+  padding: 1em;
213
+  border-bottom-color: var(--white);
214
+  border-bottom-style: solid;
215
+  border-bottom-width: 1px;
216
+  border-top-color: var(--dark-border);
217
+  border-top-style: solid;
218
+  border-top-width: 1px;
219
+}
220
+
221
+header,
222
+footer {
223
+  background-color: var(--coloured-bg);
224
+  color: var(--white);
225
+}
226
+
227
+input[type="text"],
228
+input[type="password"],
229
+textarea,
230
+select,
231
+pre {
232
+  border-color: var(--dark-border);
233
+  border-style: solid;
234
+  border-width: 1px;
235
+  background-color: var(--white);
236
+  color: var(--text-color);
237
+  padding: 5px;
238
+  font-family: "Roboto Condensed";
239
+  margin: 5px;
240
+}
241
+
242
+pre {
243
+  border-color: var(--coloured-bg);
244
+}
245
+
246
+button,
247
+input[type="button"],
248
+input[type="submit"] {
249
+  border-color: var(--dark-border);
250
+  border-style: solid;
251
+  border-width: 1px;
252
+  background-color: var(--coloured-bg);
253
+  color: var(--white);
254
+  font-weight: bold;
255
+  padding: 5px;
256
+  font-family: "Roboto Condensed";
257
+  margin: 5px;
258
+  border-radius: 4px;
259
+}
260
+
261
+button:hover,
262
+input[type="button"]:hover,
263
+input[type="submit"]:hover,
264
+input[type="file"]:hover {
265
+  background-color: var(--light-coloured-bg);
266
+  color: var(--text-color);
267
+  cursor: pointer;
268
+}
269
+
270
+div.file_upload {
271
+  display: inline-block;
272
+  position: relative;
273
+  width: 20px;
274
+  height: 20px;
275
+  margin: 0;
276
+  padding: 0;
277
+  border-radius: 2px;
278
+  border-style: solid;
279
+  border-width: 1px;
280
+  border-color: var(--clear-bg);
281
+}
282
+
283
+input[type="file"] {
284
+  position: absolute;
285
+  width: 18px;
286
+  height: 18px;
287
+  left: 0;
288
+  top: 1px;
289
+  opacity: 0;
290
+}
291
+
292
+
293
+input.add,
294
+input.edit,
295
+input.login,
296
+input.logout,
297
+input.refresh,
298
+input.save,
299
+input.search,
300
+input.trash,
301
+input.upload {
302
+  width: 20px;
303
+  height: 20px;
304
+  margin: 0;
305
+  padding: 0;
306
+  border-radius: 2px;
307
+  border-style: none;
308
+}
309
+
310
+input.add:hover,
311
+input.edit:hover,
312
+input.login:hover,
313
+input.logout:hover,
314
+input.refresh:hover,
315
+input.save:hover,
316
+input.search:hover,
317
+input.trash:hover,
318
+input.upload:hover {
319
+  border-color: var(--text-color);
320
+  border-style: solid;
321
+  border-width: 1px;
322
+  background-color: var(--coloured-bg);
323
+  cursor: pointer;
324
+}
325
+
326
+input.add {
327
+  background: var(--add_icon);
328
+  background-repeat: no-repeat;
329
+  background-position: center center;
330
+}
331
+input.edit {
332
+  background: var(--edit_icon);
333
+  background-repeat: no-repeat;
334
+  background-position: center center;
335
+}
336
+input.login {
337
+  background: var(--login_icon);
338
+  background-repeat: no-repeat;
339
+  background-position: center center;
340
+}
341
+input.logout {
342
+  background: var(--logout_icon);
343
+  background-repeat: no-repeat;
344
+  background-position: center center;
345
+}
346
+input.refresh {
347
+  background: var(--refresh_icon);
348
+  background-repeat: no-repeat;
349
+  background-position: center center;
350
+}
351
+input.save {
352
+  background: var(--save_icon);
353
+  background-repeat: no-repeat;
354
+  background-position: center center;
355
+}
356
+input.search {
357
+  background: var(--search_icon);
358
+  background-repeat: no-repeat;
359
+  background-position: center center;
360
+}
361
+input.trash {
362
+  background: var(--trash_icon);
363
+  background-repeat: no-repeat;
364
+  background-position: center center;
365
+}
366
+input.upload {
367
+  background: var(--upload_icon);
368
+  background-repeat: no-repeat;
369
+  background-position: center center;
370
+}

+ 49
- 0
templates/ajax.html View File

@@ -0,0 +1,49 @@
1
+{% extends "index.html" %}
2
+{% block title %}Ajax{% endblock %}
3
+      {% block main %}
4
+      <section class='inline'>
5
+        <article class='left'>
6
+          <h3>Get HTML response from AJAX</h3>
7
+          <p>Click the refresh button to get the HTML response.</p>
8
+          <p>The response may randomly be a voluntary error so you should try it more than once.</p>
9
+          Refresh: <input type='button' class='refresh' value=' '
10
+                          onclick='javascript:get_html_from_ajax(document.getElementById("html_container"), "/get_html_from_ajax");'>
11
+        </article>
12
+        <article class='right'>
13
+          <h3>Upload files with AJAX</h3>
14
+          <p>Select files to upload</p>
15
+          <p>The response may randomly be a voluntary error so you should try it more than once.</p>
16
+          Upload files: 
17
+          <div class='file_upload'>
18
+            <!--
19
+              Input file is a tricky hack (see tetawebapp.css and tetawebapp.js)
20
+            -->
21
+            <input type='button' id='upload_icon_1' class='upload' title='Upload' value=' '/>
22
+            <input type='file' id='upload_input_1' multiple
23
+                   title='Upload'
24
+                   onchange='javascript:upload_file_from_ajax(this, "/upload", "TETA_ERR");'
25
+                   onmouseover='javascript:lit(document.getElementById("upload_icon_1"));'
26
+                   onmouseout='javascript:unlit(document.getElementById("upload_icon_1"));'/>
27
+          </div>
28
+        </article>
29
+      </section>
30
+      <hr/>
31
+      <article class='right' id='html_container'></article>
32
+      <hr/>
33
+      <section class='inline'>
34
+        <article class='left'>
35
+          <h3>Set value via AJAX</h3>
36
+          <p>Send value to the application.</p>
37
+          <p>If value is empty or is "We Make Porn" (case sensitive), an error is raised.</p>
38
+          <input type='text' id='value_sender'>
39
+          <input type='button' value="Try me" onclick='javascript:set_value_from_ajax(document.getElementById("value_sender"), "/set_value_from_ajax", "TETA_ERR");'>
40
+        </article>
41
+        <article class='right'>
42
+          <h3>Get value from AJAX</h3>
43
+          <p>Get a random value from the application.</p>
44
+          <p>Randomly raises a voluntary error so you should try it more than once.</p>
45
+          <input type='text' id='value_receiver'>
46
+          <input type='button' value="Try me" onclick='javascript:get_value_from_ajax(document.getElementById("value_receiver"), "/get_value_from_ajax", "TETA_ERR");'>
47
+        </article>
48
+      </section>
49
+      {% endblock %}

+ 24
- 0
templates/ajax_html.html View File

@@ -0,0 +1,24 @@
1
+<h3>This is the title</h3>
2
+<img src='/static/images/dummy_pic.png' alt='dummy pic' title='dummy pic'/>
3
+<p>
4
+  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
5
+  et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
6
+  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
7
+  dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
8
+  officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit,
9
+  sed do eiusmod tempor incididunt ut labore
10
+</p>
11
+<p>This <a href='/plop.html'>link</a> will lead to an error page</p>
12
+<p>
13
+  et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
14
+  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
15
+  dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
16
+  officia desers unt mollit anim id est laborum.
17
+</p>
18
+<p>
19
+  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
20
+  et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
21
+  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
22
+  dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
23
+  officia deserunt mollit anim id est laborum.
24
+</p>

+ 12
- 0
templates/articles.html View File

@@ -0,0 +1,12 @@
1
+{% extends "index.html" %}
2
+{% block title %}Articles{% endblock %}
3
+      {% block main %}
4
+      <article>
5
+        <h3>Choose your article</h3>
6
+        <p>
7
+          Please select your article
8
+        </p>
9
+      </article>
10
+      <article id='article_receiver'>
11
+      </article>
12
+      {% endblock %}

+ 65
- 0
templates/articles_by_id.html View File

@@ -0,0 +1,65 @@
1
+{% extends "index.html" %}
2
+{% block title %}Articles{% endblock %}
3
+      {% block main %}
4
+      <article class='right'>
5
+        <h3>Article #{{ ID }}</h3>
6
+        <img src='/static/images/dummy_pic.png' alt='dummy pic' title='dummy pic'/>
7
+        <p>
8
+          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
9
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
10
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
11
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
12
+          officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit,
13
+          sed do eiusmod tempor incididunt ut labore
14
+        </p>
15
+        <p>This <a href='/plop.html'>link</a> will lead to an error page</p>
16
+        <p>
17
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
18
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
19
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
20
+          officia desers unt mollit anim id est laborum.
21
+        </p>
22
+        <ul>
23
+          <li>plop</li>
24
+          <li>plap</li>
25
+          <li>plip</li>
26
+        </ul>
27
+        <ol>
28
+          <li>plop</li>
29
+          <li>plap</li>
30
+          <li>plip</li>
31
+        </ol>
32
+        <p>
33
+          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
34
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
35
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
36
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
37
+          officia deserunt mollit anim id est laborum.
38
+        </p>
39
+      </article>
40
+      <article class='left'>
41
+        <h3>Another disposition</h3>
42
+        <img src='/static/images/dummy_pic.png' alt='dummy pic' title='dummy pic'/>
43
+        <p>
44
+          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
45
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
46
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
47
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
48
+          officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit,
49
+          sed do eiusmod tempor incididunt ut labore
50
+        </p>
51
+        <p>
52
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
53
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
54
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
55
+          officia deserunt mollit anim id est laborum.
56
+        </p>
57
+        <p>
58
+          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
59
+          et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
60
+          aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
61
+          dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
62
+          officia deserunt mollit anim id est laborum.
63
+        </p>
64
+      </article>
65
+      {% endblock %}

+ 68
- 0
templates/basics.html View File

@@ -0,0 +1,68 @@
1
+{% extends "index.html" %}
2
+{% block title %}Basics{% endblock %}
3
+      {% block main %}
4
+      <article class='right'>
5
+        <h3>Basics</h3>
6
+        <p>
7
+          Thanks to <a href='http://flask.pocoo.org/'>Python/Flask</a> with <strong>TetaWebApp</strong> most of the output things come to life via
8
+          <a href='http://jinja.pocoo.org/docs/2.10/'>Jinja2 HTML templates</a>
9
+          and is 100% <strong title='Bulshit inside'>HTML5 ready©</strong>.
10
+        </p>
11
+        <p>
12
+          Colors and fonts are managed from separated CSS files letting you easily
13
+          change the default theme to your favorite colors and icon set.
14
+        </p>
15
+        <pre>
16
+/*
17
+* Here are the font definitions.
18
+* You can modify it or create your own and make it loaded
19
+* after this one in the HTML header section of the index.html
20
+* template file.
21
+*/
22
+
23
+@font-face {
24
+	font-family: "Roboto Condensed";
25
+	font-style: normal;
26
+	font-weight: 400;
27
+	src: var(--font-normal);
28
+}
29
+
30
+@font-face {
31
+	font-family: "Roboto Condensed";
32
+	font-style: normal;
33
+	font-weight: 700;
34
+	src: var(--font-bold);
35
+}
36
+        </pre>
37
+        <pre>
38
+/*
39
+* Here are the base color scheme and icon set.
40
+* You can modify it or create your own using the same variables
41
+* and make it loaded after this one but before the fonts.css in
42
+* the HTML header section of the index.html template file.
43
+*/
44
+:root {
45
+    --coloured-bg: #FF5D00;
46
+    --light-coloured-bg: #FFB387;
47
+    --clear-bg: #E5E5E5;
48
+    --mid-bg: #BBBBBB;
49
+    --dark-bg: #2B2B2B;
50
+    --dark-border: #888888;
51
+    --text-color: #555555;
52
+    --white: #FFFFFF;
53
+    --black: #000000;
54
+    --font-normal: url("/static/fonts/RobotoCondensed-Regular.ttf") format("truetype");
55
+    --font-bold: url("/static/fonts/RobotoCondensed-Bold.ttf") format("truetype");
56
+    --banner-logo: url(/static/images/logo.png);
57
+    --add_icon: url(/static/images/add.png);
58
+    --edit_icon: url(/static/images/edit.png);
59
+    --login_icon: url(/static/images/login.png);
60
+    --logout_icon: url(/static/images/logout.png);
61
+    --refresh_icon: url(/static/images/refresh.png);
62
+    --save_icon: url(/static/images/save.png);
63
+    --search_icon: url(/static/images/search.png);
64
+    --trash_icon: url(/static/images/trash.png);
65
+}
66
+        </pre>
67
+      </article>
68
+      {% endblock %}

+ 12
- 0
templates/database.html View File

@@ -0,0 +1,12 @@
1
+{% extends "index.html" %}
2
+{% block title %}Database{% endblock %}
3
+      {% block main %}
4
+      <article class='right'>
5
+        <h3>Accessing database</h3>
6
+        <p>
7
+          Even if using <a href='http://flask-sqlalchemy.pocoo.org/2.3/'>Flask-SQLAlchemy</a> to retrieve data
8
+          stored in <strong>Postgres</strong> databases is the recommended way to use <strong>TetaWebApp</strong>, 
9
+          you're free to use the database connector that suits your need.
10
+        </p>
11
+      </article>
12
+      {% endblock %}

+ 15
- 0
templates/error.html View File

@@ -0,0 +1,15 @@
1
+{% extends "index.html" %}
2
+{% block title %}Erreur{% endblock %}
3
+{% block nav %}{% endblock %}
4
+{% block main %}
5
+    <article class='error'>
6
+      <h3>404 - Not found</h3>
7
+      <p>The page you asked for was not found on this server.<br/>
8
+      It may has been lost, it may has never existed.<br/>
9
+      Maybe we don't care at all...</p>
10
+      <p>
11
+        <input type='button' value='Get me back to business' onclick='javascript:document.location="/";'/>
12
+      </p>
13
+      <img src='/static/images/404.png' alt='404 - Not found' title='404 - Not found'/>
14
+    </article>
15
+{% endblock %}

+ 106
- 0
templates/index.html View File

@@ -0,0 +1,106 @@
1
+<!DOCTYPE html>
2
+<html lang='zxx'>
3
+<head>
4
+  <title>TetaWebApp - {% block title %}Accueil{% endblock %}</title>
5
+  <meta name="viewport" content="initial-scale=1.0" />
6
+  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
7
+  <link rel="stylesheet" type="text/css" href="/static/styles/colors.css" />
8
+  <link rel="stylesheet" type="text/css" href="/static/styles/fonts.css" />
9
+  <link rel="stylesheet" type="text/css" href="/static/styles/tetawebapp.css" />
10
+  <link rel="icon" type="image/png" href="/static/images/favicon.png" />
11
+  <script src="/static/scripts/tetawebapp.js"></script>
12
+</head>
13
+{% block bodyheader %}
14
+<body>
15
+{% endblock %}
16
+  <header>{% block banner %}TetaWebApp{% endblock %}</header>
17
+  <div class='content'>
18
+    {% block nav %}
19
+    <nav class='vertical'>
20
+      {% block menu %}
21
+        {% for item in menu %}
22
+          {% for key in item[1] %}
23
+            {% if item[2] == 1 %}
24
+              <a class='selected' href='{{ key }}'>{{ item[0] }}</a>
25
+            {% else %}
26
+              <a href='{{ key }}'>{{ item[0] }}</a>
27
+            {% endif %}
28
+          {% endfor %}
29
+        {% endfor %}
30
+      {% endblock %}
31
+    </nav>
32
+    {% endblock%}
33
+    <main>
34
+        {% if navbar %}
35
+          <div class='navbar_container'>
36
+            <ul class='horizontal'>
37
+            {% for item in navbar %}
38
+              {% set selected = ['', 'selected'] %}
39
+              {% set last = ['right_border', 'last'] %}
40
+              {% for url in item[1] %}
41
+                <li><a class='{{ last[item[3]] }} {{ selected[item[2]] }}' href='{{ url }}'>{{ item[0] }}</a></li>
42
+              {% endfor %}
43
+            {% endfor %}
44
+            </ul>
45
+          </div>
46
+        {% endif %}
47
+      {% block main %}
48
+      <article class='right'>
49
+        <h3>TetaWebApp demo</h3>
50
+        <p>
51
+          Welcome to the <strong>TetaWebApp</strong> demo
52
+        </p>
53
+        <p>
54
+          TetaWebApp is a basic web application template based on <a href='http://flask.pocoo.org/'>Python/Flask</a>
55
+          and <a href='https://www.w3schools.com/js/js_ajax_intro.asp'>AJAX</a> made by
56
+          <a href='mailto:doug.letough@free.fr'>Doug Le Tough</a> from <a href='https://www.tetalab.org'>Tetalab</a>.
57
+        </p>
58
+        <p>
59
+          The goal of this project is to provide a basic framework to make any web application you need while
60
+          letting you complete freedom on how to use or extend it <strong>without</strong> using any Google,
61
+          Bootstrap or any other piece of <strong>shitty free spyware</strong>.
62
+        </p>
63
+          TetaWebApp will <strong>never</strong> download or upload anything in any way.
64
+        <p>
65
+        </p>
66
+        <p>There is <strong>no</strong> limitation, you can use all or only parts of <strong>TetaWebApp</strong>
67
+        and you can <strong title='bullshit inside'>virtually</strong> do any app you want with TetaWebApp.
68
+        </p>
69
+        <p>
70
+          But be sure that freedom has a cost: You <strong>will</strong> need work to make it work ;-)
71
+        </p>
72
+        <p>
73
+          <strong>TetaWebApp</strong> is released under the only real <strong>free</strong> license: The
74
+          <a href='http://www.wtfpl.net/'><img src='http://www.wtfpl.net/wp-content/uploads/2012/12/wtfpl-badge-2.png'
75
+          title='WTFPL' alt='WTFPL' /></a>.
76
+        </p>
77
+        <pre>
78
+        DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 
79
+                    Version 2, December 2004 
80
+
81
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> 
82
+
83
+ Everyone is permitted to copy and distribute verbatim or modified 
84
+ copies of this license document, and changing it is allowed as long 
85
+ as the name is changed. 
86
+
87
+            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 
88
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 
89
+
90
+  0. You just DO WHAT THE FUCK YOU WANT TO.
91
+        </pre>
92
+        <p>
93
+          Get a copy of <strong>TetaWebApp</strong>:<br/>
94
+          <pre>
95
+git clone git://git.tetalab.org/tetalab/tetawebapp
96
+          </pre>
97
+        </p>
98
+      </article>
99
+      {% endblock %}
100
+    </main>
101
+  </div>
102
+  {% block footer %}
103
+  <footer>© - Tetalab - Le hacker space Toulousaing' putaing' cong' -</footer>
104
+  {% endblock%}
105
+</body>
106
+</html>

+ 53
- 0
templates/inputs.html View File

@@ -0,0 +1,53 @@
1
+{% extends "index.html" %}
2
+{% block title %}Inputs{% endblock %}
3
+      {% block main %}
4
+      <article class='right'>
5
+        <h3>The input collection</h3>
6
+        <p>
7
+          Have a look to the input collection:
8
+        </p>
9
+        <input type='text'/>
10
+        <button>Click me</button>
11
+        <br/>
12
+        <textarea cols='25'></textarea>
13
+        <br/>
14
+        <select>
15
+          {% for item in menu %}
16
+          <option>{{ item[0] }}</option>
17
+          {% endfor %}
18
+        </select>
19
+        <input type='submit' value='Click me too'/>
20
+        <br/>
21
+        <input type='button' value='And me !' />
22
+        <br/>
23
+        <input type='button' class='add' title='Add' value=' '/>
24
+        <input type='button' class='edit' title='Edit' value=' '/>
25
+        <input type='button' class='login' title='Login' value=' '/>
26
+        <input type='button' class='logout' title='Logout' value=' ' onclick='javascript:logout();'/>
27
+        <input type='button' class='refresh' title='Refresh' value=' '/>
28
+        <input type='button' class='save' title='Save' value=' '/>
29
+        <input type='button' class='search' title='Search' value=' '/>
30
+        <input type='button' class='trash' title='Trash' value=' '/>
31
+        <!--
32
+          Input file is a tricky hack (see tetawebapp.css and tetawebapp.js)
33
+        -->
34
+        <div class='file_upload'>
35
+          <input type='button' id='upload_icon_1' class='upload' title='Upload' value=' '/>
36
+          <input type='file' id='upload_input_1' name='files' multiple
37
+                 title='Upload'
38
+                 onchange='javascript:upload_file_from_ajax(this, "/upload", "TETA_ERR");'
39
+                 onmouseover='javascript:lit(document.getElementById("upload_icon_1"));'
40
+                 onmouseout='javascript:unlit(document.getElementById("upload_icon_1"));'/>
41
+        </div>
42
+        <br/>
43
+        <pre>
44
+#!/bin/sh
45
+# This is code sample
46
+while [ 1 ]
47
+do
48
+  echo "Tits or GTFO !"
49
+  sleep .1
50
+done
51
+        </pre>
52
+      </article>
53
+      {% endblock %}

+ 23
- 0
templates/login.html View File

@@ -0,0 +1,23 @@
1
+{% extends "index.html" %}
2
+{% block title %}Login{% endblock %}
3
+{% block nav %}{% endblock %}
4
+{% block main %}
5
+    <article class='login'>
6
+      <h3>Login</h3>
7
+      <p>The demo login is:</p>
8
+      <ul>
9
+        <li>Login: demo</li>
10
+        <li>Password: demo</li>
11
+      </ul>
12
+    </article>
13
+    {% if message != '' %}
14
+      <pre>{{ message }}</pre>
15
+    {% endif %}
16
+    <article class='left'>
17
+        <form method='POST' action='/login'>
18
+        Login: <input id='login' name='login' type='text' />
19
+        Password: <input id='password' name='password' type='password' />
20
+        <input type='submit' value='Log me in' onclick='javascript:return verify_login();'>
21
+      </form>
22
+    </article>
23
+{% endblock %}

+ 18
- 0
templates/todo.html View File

@@ -0,0 +1,18 @@
1
+{% extends "index.html" %}
2
+{% block title %}TODO{% endblock %}
3
+      {% block main %}
4
+      <article class='right'>
5
+        <h3>TODO list</h3>
6
+        <ul>
7
+          <li><strike>Basic menu management</strike></li>
8
+          <li>Installation wizard</li>
9
+          <li>Back office for basic content management</li>
10
+          <li><strike>Basic Ajax support</strike></li>
11
+          <li><strike>Session management</strike></li>
12
+          <li>File upload</li>
13
+          <li>Basic documentation</li>
14
+          <li><strike>Horizontal navbar</strike></li>
15
+          <li><strike>License</strike></li>
16
+        </ul>
17
+      </article>
18
+      {% endblock %}

+ 304
- 0
tetawebapp.py View File

@@ -0,0 +1,304 @@
1
+#!/usr/bin/env python
2
+# -*- coding: utf-8
3
+
4
+# Required modules
5
+import os
6
+import inspect
7
+import random
8
+import binascii
9
+import bcrypt
10
+from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
11
+from functools import wraps
12
+
13
+# Optionnal modules
14
+import psycopg2
15
+from flask_sqlalchemy import SQLAlchemy
16
+
17
+########################################################################
18
+# App settings
19
+########################################################################
20
+app = Flask(__name__)
21
+# Path to static files
22
+app.static_url_path='/static'
23
+# Set debug mode to False for production
24
+app.debug = True
25
+# Various configuration settings belong here (optionnal)
26
+app.config.from_pyfile('config.local.py')
27
+# Generate a new key: head -n 40 /dev/urandom | md5sum | cut -d' ' -f1
28
+app.secret_key = 'ce1d1c9ff0ff388a838b3a1e3207dd27'
29
+# Feel free to use SQLAlchemy (or not)
30
+db = SQLAlchemy(app)
31
+
32
+
33
+########################################################################
34
+# Sample user database
35
+########################################################################
36
+class Tetawebapp_users(db.Model):
37
+  __tablename__ = 'tetawebapp_users'
38
+  id = db.Column(db.Integer, primary_key=True)
39
+  mail = db.Column(db.Text, nullable=False)
40
+  password = db.Column(db.Text, nullable=False)
41
+  name = db.Column(db.Text, nullable=False)
42
+
43
+
44
+########################################################################
45
+# Menu and navigation management
46
+########################################################################
47
+
48
+def get_menu(page):
49
+  """  The main menu is a list of lists in the followin format:
50
+          [unicode caption,
51
+            {unicode URL endpoint: [unicode route, ...]},
52
+            int 0]
53
+        - The URL end point is the URL where to point to (href)
54
+        - One of the routes MUST match the route called by request
55
+        - The int 0 is used to determine which menu entry is actally called.
56
+          The value MUST be 0."""
57
+  menu = [[u'Home', {u'/': [u'/']}, 0],
58
+          [u'Articles', {u'/articles': [u'/articles', u'/articles/<ID>']}, 0],
59
+          [u'Basics', {u'/basics': [u'/basics']}, 0],
60
+          [u'Inputs', {u'/inputs': [u'/inputs']}, 0],
61
+          [u'Ajax', {u'/ajax': [u'/ajax']}, 0],
62
+          [u'Database', {u'/database': [u'/database']}, 0],
63
+          [u'Todo', {u'/todo': [u'/todo']}, 0],
64
+          ]
65
+  for item in menu:
66
+    for url in item[1]:
67
+      for route in item[1][url]:
68
+        if route == page:
69
+          item[2] = 1
70
+          return menu
71
+  # This should never happen
72
+  return menu
73
+
74
+def get_navbar(page, selected):
75
+  """  The horizontal navbar is a list of lists in the followin format:
76
+          [unicode caption, {unicode URL endpoint: [unicode route, ...]}, int 0]
77
+        - The URL end point is the URL where to point to (href)
78
+        - One of the routes MUST match the route called by request
79
+        - The int 0 is used to de """
80
+  navbars = [[u'First article', {u'/articles/1': [u'/articles', u'/articles/<ID>']}, 0, 0],
81
+            [u'Second article', {u'/articles/2': [u'/articles', u'/articles/<ID>']}, 0, 0],
82
+            [u'Third article', {u'/articles/3': [u'/articles', u'/articles/<ID>']}, 0, 0]
83
+            ]
84
+  navbar = []
85
+  for item in navbars:
86
+    for url in item[1]:
87
+      if url == selected:
88
+        item[2] = 1
89
+      for route in item[1][url]:
90
+        if route == page:
91
+          navbar.append(item)
92
+  navbar[len(navbar) - 1][3] = 1
93
+  return navbar
94
+
95
+########################################################################
96
+# Session management
97
+########################################################################
98
+
99
+def sync_session(request, session):
100
+  """ Synchronize cookies with session """
101
+  for key in request.cookies:
102
+    session[key] = request.cookies[key].encode('utf8')
103
+
104
+def sync_cookies(response, session):
105
+  """ Synchronize session with cookies """
106
+  for key in session:
107
+    response.set_cookie(key, value=str(session[key]))
108
+
109
+def check_session(func):
110
+  """ Check if the session has required token cookie set.
111
+      If not, redirects to the login page. """
112
+  @wraps(func)
113
+  def check(*args, **kwargs):
114
+    try:
115
+      if session['token'] == request.cookies['token'] and len(session['token']) > 0:
116
+        return func(*args, **kwargs)
117
+      else:
118
+        session['token'] = ''
119
+        response = app.make_response(render_template('login.html', message=''))
120
+        sync_cookies(response, session)
121
+        return response
122
+    except KeyError:
123
+      return render_template('login.html', message='')
124
+  return check
125
+
126
+def check_login(login, password):
127
+  """ Puts the login verification code here """
128
+  password = password.encode('utf-8')
129
+  hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
130
+  stored_hash = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.password).first()
131
+  if stored_hash:
132
+    if bcrypt.checkpw(password, stored_hash[0].encode('utf-8')):
133
+      return True
134
+  return False
135
+
136
+def gen_token():
137
+  """ Generate a random token to be stored in session and cookie """
138
+  token = binascii.hexlify(os.urandom(42))
139
+  return token
140
+
141
+########################################################################
142
+# Routes:
143
+# -------
144
+# Except for the index function, the function name MUST have the same
145
+# name than the URL endpoint to make the menu work properly
146
+########################################################################
147
+@app.errorhandler(404)
148
+def page_not_found(e):
149
+  """ 404 not found """
150
+  return render_template('error.html'), 404
151
+
152
+@app.route("/login", methods=['GET', 'POST'])
153
+def login():
154
+  login = request.form.get('login')
155
+  password = request.form.get('password')
156
+  if check_login(login, password):
157
+    # Generate and store a token in session
158
+    session['token'] = gen_token()
159
+    # Return user to index page
160
+    page = '/'
161
+    menu = get_menu(page)
162
+    response = app.make_response(render_template('index.html', menu=menu))
163
+    # Push token to cookie
164
+    sync_cookies(response, session)
165
+    return response
166
+  # Credentials are not valid
167
+  response = app.make_response(render_template('login.html', message='Invalid user or password'))
168
+  session['token'] = ''
169
+  sync_cookies(response, session)
170
+  return response
171
+
172
+@app.route("/", methods=['GET', 'POST'])
173
+@check_session
174
+def index():
175
+  """ Index page """
176
+  page = str(request.url_rule)
177
+  menu = get_menu(page)
178
+  return render_template('index.html', menu=menu)
179
+
180
+@app.route("/articles", methods=['GET', 'POST'])
181
+@check_session
182
+def articles():
183
+  """ Arcticles page """
184
+  page = str(request.url_rule)
185
+  menu = get_menu(page)
186
+  navbar = get_navbar(page, '')
187
+  return render_template('articles.html', menu=menu, navbar=navbar)
188
+
189
+@app.route("/articles/<ID>", methods=['GET', 'POST'])
190
+@check_session
191
+def articles_by_id(ID):
192
+  """ Arcticles page """
193
+  page = str(request.url_rule)
194
+  menu = get_menu(page)
195
+  selected = page.replace('<ID>', ID)
196
+  navbar = get_navbar(page, selected)
197
+  return render_template('articles_by_id.html', menu=menu, navbar=navbar, ID=ID)
198
+
199
+@app.route("/basics", methods=['GET', 'POST'])
200
+@check_session
201
+def basics():
202
+  """ Basics page """
203
+  page = str(request.url_rule)
204
+  menu = get_menu(page)
205
+  return render_template('basics.html', menu=menu)
206
+
207
+@app.route("/inputs", methods=['GET', 'POST'])
208
+@check_session
209
+def inputs():
210
+  """ Show the input collection """
211
+  page = str(request.url_rule)
212
+  menu = get_menu(page)
213
+  return render_template('inputs.html', menu=menu)
214
+
215
+@app.route("/ajax", methods=['GET', 'POST'])
216
+@check_session
217
+def ajax():
218
+  """ Propose various AJAX tests """
219
+  page = str(request.url_rule)
220
+  menu = get_menu(page)
221
+  return render_template('ajax.html', menu=menu)
222
+
223
+@app.route("/database", methods=['GET', 'POST'])
224
+@check_session
225
+def database():
226
+  """ A blah on using databases """
227
+  page = str(request.url_rule)
228
+  menu = get_menu(page)
229
+  return render_template('database.html', menu=menu)
230
+
231
+@app.route("/todo", methods=['GET', 'POST'])
232
+@check_session
233
+def todo():
234
+  """ The famous TODO list """
235
+  page = str(request.url_rule)
236
+  menu = get_menu(page)
237
+  return render_template('todo.html', menu=menu)
238
+
239
+########################################################################
240
+# AJAX routes
241
+########################################################################
242
+
243
+@app.route("/get_html_from_ajax", methods=['GET', 'POST'])
244
+@check_session
245
+def get_html_from_ajax():
246
+  """ Return HTML code to an AJAX request
247
+      It may generate a 404 http error for testing purpose """
248
+  if int(random.random()*10) % 2:
249
+    # Randomly generate 404 HTTP response
250
+    return render_template('error.html'), 404
251
+  return render_template('ajax_html.html')
252
+
253
+@app.route("/get_value_from_ajax", methods=['GET', 'POST'])
254
+@check_session
255
+def get_value_from_ajax():
256
+  """ Return a randomly generated value to an AJAX request
257
+      It may return an error code for testing purpose """
258
+  err_code = 'TETA_ERR'
259
+  RND = int(random.random()*10)
260
+  if RND % 2:
261
+    # Randomly generate error
262
+    return err_code
263
+  return str(RND)
264
+
265
+@app.route("/set_value_from_ajax/<value>", methods=['GET', 'POST'])
266
+@check_session
267
+def set_value_from_ajax(value):
268
+  """ Accept a value from an AJAX request
269
+      It may return an error code for testing purpose """
270
+  err_code = 'TETA_ERR'
271
+  if value != 'We Make Porn':
272
+    return 'True'
273
+  return err_code
274
+
275
+@app.route("/upload", methods=['POST'])
276
+@check_session
277
+def upload():
278
+  """ Save a file from AJAX request
279
+      Files are saved in UPLOADED_FILES_DEST (see config.local.py) """
280
+  err_code = 'TETA_ERR'
281
+  RND = int(random.random()*10)
282
+  if RND % 2:
283
+    # Randomly generate error
284
+    print err_code
285
+    return err_code
286
+  uploaded_files = []
287
+  if len(request.files) > 0 and request.files['files']:
288
+    uploaded_files = request.files.getlist("files")
289
+  print "Uploaded files:"
290
+  for f in uploaded_files:
291
+    print '  [+] %s [%s]' % (f.filename, f.content_type)
292
+    # Before saving you should:
293
+    # - Secure the filename
294
+    # - Check file size
295
+    # - Check content type
296
+    f.save(os.path.join(app.config['UPLOADED_FILES_DEST'], f.filename))
297
+    f.close()
298
+  return "OK"
299
+
300
+########################################################################
301
+# Main
302
+########################################################################
303
+if __name__ == '__main__':
304
+  app.run(host='0.0.0.0')

+ 48
- 0
tetawebapp.sql View File

@@ -0,0 +1,48 @@
1
+\echo ******************************
2
+\echo * Dropping database tetawebapp
3
+\echo ******************************
4
+
5
+\c postgres;
6
+drop database tetawebapp;
7
+
8
+\echo **************************
9
+\echo * Dropping role tetawebapp
10
+\echo **************************
11
+drop role tetawebapp;
12
+
13
+\echo ***************************************************
14
+\echo * Creating role tetawebapp with password tetawebapp
15
+\echo ***************************************************
16
+create role tetawebapp with LOGIN ENCRYPTED PASSWORD 'tetawebapp';
17
+
18
+\echo ******************************
19
+\echo * Creating database tetawebapp
20
+\echo ******************************
21
+create database tetawebapp;
22
+
23
+\echo *******************************************
24
+\echo * Giving tetawebapp ownership to tetawebapp
25
+\echo *******************************************
26
+alter database tetawebapp owner to tetawebapp;
27
+
28
+\echo *********************************
29
+\echo * Creating tetawebapp_users table
30
+\echo *********************************
31
+
32
+\c tetawebapp;
33
+CREATE TABLE tetawebapp_users (
34
+  id serial primary key,
35
+  mail text not NULL,
36
+  password text not NULL,
37
+  name text not NULL
38
+);
39
+
40
+\echo *************************************************
41
+\echo * Giving tetawebapp_users ownership to tetawebapp
42
+\echo *************************************************
43
+alter table tetawebapp_users owner to tetawebapp;
44
+
45
+\echo *********************************************************************
46
+\echo * Inserting user demo identified by password demo to tetawebapp_users
47
+\echo *********************************************************************
48
+insert into tetawebapp_users (mail, password, name) values ('demo', '$2b$12$yjv4QMctGJFj2HmmbF6u5uDq9ATIl/Y9Z96MbaqRrcG6AE0CGHKSS', 'demo');

Loading…
Cancel
Save