Commit fcfc62001ca075034b035ec620f14809ded9c69f

Authored by Etienne Pallier
1 parent 1a15d30a
Exists in dev

ajout nouvelle commande "pyros.py dbshell" (+ update doc)

@@ -17,6 +17,7 @@ logs/*.log @@ -17,6 +17,7 @@ logs/*.log
17 /.idea/workspace.xml 17 /.idea/workspace.xml
18 images_folder 18 images_folder
19 Comet/ 19 Comet/
  20 +old/
20 21
21 \.idea/ 22 \.idea/
22 23
@@ -67,15 +67,14 @@ This software has been tested and validated with the following configurations : @@ -67,15 +67,14 @@ This software has been tested and validated with the following configurations :
67 -------------------------------------------------------------------------------------------- 67 --------------------------------------------------------------------------------------------
68 ## LAST VERSION 68 ## LAST VERSION
69 69
70 -Date: 28/02/2019 70 +Date: 04/03/2019
71 71
72 Author: E. Pallier 72 Author: E. Pallier
73 73
74 -VERSION: 0.20.3 74 +VERSION: 0.20.4
75 75
76 Comment: 76 Comment:
77 - Agent.py improvements :  
78 - - read_db_commands() : ajout algo 77 + pyros.py : ajout nouvelle commande "dbshell"
79 78
80 79
81 -------------------------------------------------------------------------------------------- 80 --------------------------------------------------------------------------------------------
@@ -234,6 +234,21 @@ def shell(): @@ -234,6 +234,21 @@ def shell():
234 return True 234 return True
235 235
236 236
  237 +@pyros_launcher.command(help="Run a database (Mysql) shell")
  238 +def dbshell():
  239 + print()
  240 + print("Launching a database (mysql) shell")
  241 + print("From this shell, type 'use database pyros;' to select the pyros database")
  242 + print("Then type 'show tables;' to see all the pyros tables")
  243 + print("Then for example, type 'select * from config;' to see the content of the 'config' table")
  244 + print("Type 'exit' to quit")
  245 + print()
  246 + # execProcess("python install.py install")
  247 + if not test_mode(): execProcessFromVenv("src/manage.py dbshell")
  248 + # Go back to the initial dir
  249 + return True
  250 +
  251 +
237 @pyros_launcher.command(help="Install the pyros software") 252 @pyros_launcher.command(help="Install the pyros software")
238 #@global_test_options 253 #@global_test_options
239 def install(): 254 def install():
pyros_old.py deleted
@@ -1,994 +0,0 @@ @@ -1,994 +0,0 @@
1 -#!/usr/bin/env python3  
2 -  
3 -import sys  
4 -import os  
5 -import subprocess  
6 -import platform  
7 -import fileinput  
8 -import argparse  
9 -import time  
10 -import signal  
11 -  
12 -  
13 -DEBUG = True  
14 -SIMULATOR_CONFIG_FILE = "conf.json" #default config file  
15 -  
16 -'''  
17 - Pyros Manager, able to launch processes and handle all project commands  
18 -'''  
19 -  
20 -  
21 -class Utils:  
22 - system = platform.system()  
23 - columns = 100  
24 - row = 1000  
25 - disp = True  
26 -  
27 - def __init__(self):  
28 - if (platform.system() != 'Windows'):  
29 - try:  
30 - rows, columns = os.popen('stty size', 'r').read().split()  
31 - self.columns = int(columns)  
32 - except:  
33 - self.columns = 100  
34 - if DEBUG:  
35 - print("Could not get terminal size")  
36 -  
37 - def printFullTerm(self, color, string):  
38 - value = int(self.columns / 2 - len(string) / 2)  
39 - self.printColor(color, "-" * value, eol='')  
40 - self.printColor(color, string, eol='')  
41 - value += len(string)  
42 - self.printColor(color, "-" * (self.columns - value))  
43 - return 0  
44 -  
45 - def changeDirectory(self, path):  
46 - if DEBUG:  
47 - print("Moving to : " + path)  
48 - os.chdir(path)  
49 - if DEBUG:  
50 - print("Current directory : " + str(os.getcwd()))  
51 - return 0  
52 -  
53 - def replacePatternInFile(self, pattern, replacement, file_path):  
54 - #try:  
55 - with fileinput.FileInput(file_path, inplace=True, backup='.bak') as file:  
56 - for line in file:  
57 - print(line.replace(pattern, replacement), end='')  
58 - '''  
59 - except:  
60 - return 1  
61 - return 0  
62 - '''  
63 - # Now, check that replacement has been done or else ERROR !!!  
64 - FOUND=False  
65 - #with fileinput.FileInput(file_path) as file:  
66 - with open(file_path) as file:  
67 - for line in file:  
68 - #if replacement in line:  
69 - if line.startswith(replacement):  
70 - FOUND = True  
71 - break  
72 - #if FOUND: print("pattern "+pattern+" found in file "+file_path)  
73 - if not FOUND: raise(Exception("pattern "+replacement+" not found in file "+file_path))  
74 -  
75 -  
76 - def printColor(self, color, message, file=sys.stdout, eol=os.linesep, forced=False):  
77 - if (self.disp == False and forced == False):  
78 - return 0  
79 - if (self.system == 'Windows'):  
80 - print(message, file=file, end=eol)  
81 - else:  
82 - print(color + message + Colors.ENDC, file=file, end=eol)  
83 - return 0  
84 -  
85 - def askQuestion(self, message, default = ""):  
86 - self.printColor(Colors.BLUE, message, forced=True)  
87 - self.printColor(Colors.BOLD, "Answer (default="+default+"): ", eol='', forced=True)  
88 - sys.stdout.flush()  
89 - ret = sys.stdin.readline().replace('\n', '').replace('\r', '')  
90 - if ret == "":  
91 - return default  
92 - return ret  
93 -  
94 - def sleep(self, t):  
95 - time.sleep(t)  
96 - return 0  
97 -  
98 -'''  
99 - Manager class : manager of your project  
100 -'''  
101 -  
102 -  
103 -class AManager(Utils):  
104 - path = os.path.realpath(__file__)  
105 - path_dir = os.getcwd()  
106 - path_dir_file = os.path.dirname(os.path.realpath(__file__))  
107 - python_path = sys.executable  
108 - python_version = sys.version_info  
109 -  
110 - bin_dir = ""  
111 - venv_pip = "pip"  
112 - venv_bin = "python"  
113 - wait = True  
114 - current_command = ""  
115 - config = None  
116 - commandMatcher = {}  
117 - commandDescription = {}  
118 - commands = []  
119 - errors = {}  
120 - executed = {}  
121 - subproc = []  
122 -  
123 - def __init__(self, param):  
124 - super(AManager, self).__init__()  
125 - self.wait = param.getWait()  
126 - self.commands = param.getCommandList()  
127 - self.disp = param.getPrint()  
128 - config = param.getConfig()  
129 - self.config = config  
130 - signal.signal(signal.SIGINT, self.signal_handler)  
131 -  
132 - self.changeDirectory(self.path_dir_file)  
133 -  
134 - if self.system == 'Windows':  
135 - self.bin_dir = "Scripts"  
136 - self.bin_name = "python.exe"  
137 - self.pip_name = "pip.exe"  
138 - else:  
139 - self.bin_dir = "bin"  
140 - self.bin_name = "python"  
141 - self.pip_name = "pip"  
142 - self.venv_pip = self.path_dir_file + os.sep + config["path"] + os.sep + config["env"] + os.sep + self.bin_dir + os.sep + self.pip_name  
143 - self.venv_bin = self.path_dir_file + os.sep + config["path"] + os.sep + config["env"] + os.sep + self.bin_dir + os.sep + self.bin_name  
144 -  
145 - def help(self):  
146 - print("This function must be implemented")  
147 - raise(NotImplementedError("Function not implemented"))  
148 -  
149 -  
150 - def set_simulator_mode_to(self, set_it:bool):  
151 - file_path = "pyros/settings.py"  
152 - '''  
153 - if simulator: self.replacePatternInFile("SIMULATOR = False", "SIMULATOR = True", file_path)  
154 - '''  
155 - self.replacePatternInFile("SIMULATOR = "+str(not set_it), "SIMULATOR = "+str(set_it), file_path)  
156 -  
157 -  
158 - def signal_handler(self, signal, frame):  
159 - self.printFullTerm(Colors.WARNING, "Ctrl-c catched")  
160 - self.set_simulator_mode_to(False)  
161 - for p in self.subproc:  
162 - proc, name = p  
163 - self.printColor(Colors.BLUE, "Killing process " + str(name))  
164 - proc.kill()  
165 - self.printFullTerm(Colors.WARNING, "Exiting")  
166 - sys.exit(0)  
167 -  
168 - def addExecuted(self, src, message):  
169 - if (src in self.executed):  
170 - self.executed[src].append(str(message))  
171 - else:  
172 - self.executed[src] = [str(message)]  
173 - return 0  
174 -  
175 - def addError(self, src, message):  
176 - if (src in self.errors):  
177 - self.errors[src].append(str(message))  
178 - else:  
179 - self.errors[src] = [str(message)]  
180 - return 0  
181 -  
182 - def execProcess(self, command):  
183 - self.printFullTerm(Colors.BLUE, "Executing command [" + command + "]")  
184 - process = subprocess.Popen(command, shell=True)  
185 - process.wait()  
186 - if process.returncode == 0:  
187 - self.printFullTerm(Colors.GREEN, "Process executed successfully")  
188 - self.addExecuted(self.current_command, command)  
189 - else:  
190 - self.printFullTerm(Colors.WARNING, "Process execution failed")  
191 - self.addError(self.current_command, command)  
192 - return process.returncode  
193 -  
194 - def execProcessSilent(self, command):  
195 - process = subprocess.Popen(command, shell=True)  
196 - process.wait()  
197 - return process.returncode  
198 -  
199 - def execProcessFromVenv(self, command):  
200 - args = command.split()  
201 - self.printFullTerm(Colors.BLUE, "Executing command from venv [" + str(' '.join(args[1:])) + "]")  
202 - process = subprocess.Popen(args)  
203 - process.wait()  
204 - if process.returncode == 0:  
205 - self.printFullTerm(Colors.GREEN, "Process executed successfully")  
206 - self.addExecuted(self.current_command, str(' '.join(args[1:])))  
207 - else:  
208 - self.printFullTerm(Colors.WARNING, "Process execution failed")  
209 - self.addError(self.current_command, str(' '.join(args[1:])))  
210 - return process.returncode  
211 -  
212 - def execProcessAsync(self, command):  
213 - self.printFullTerm(Colors.BLUE, "Executing command [" + command + "]")  
214 - p = subprocess.Popen(command, shell=True)  
215 - self.subproc.append((p, command))  
216 - self.printFullTerm(Colors.GREEN, "Process launched successfully")  
217 - self.addExecuted(self.current_command, command)  
218 - return p  
219 -  
220 - def execProcessFromVenvAsync(self, command: str):  
221 - args = command.split()  
222 - self.printFullTerm(Colors.BLUE, "Executing command from venv [" + str(' '.join(args[1:])) + "]")  
223 - p = subprocess.Popen(args)  
224 - self.subproc.append((p, ' '.join(args[1:])))  
225 - self.printFullTerm(Colors.GREEN, "Process launched successfully")  
226 - self.addExecuted(self.current_command, str(' '.join(args[1:])))  
227 - return p  
228 -  
229 - def waitProcesses(self):  
230 - if (self.wait):  
231 - for p in self.subproc:  
232 - proc, name = p  
233 - proc.wait()  
234 - return 0  
235 -  
236 - def end(self):  
237 - count = 0  
238 - self.waitProcesses()  
239 - self.printFullTerm(Colors.WARNING, "Summary")  
240 - self.printColor(Colors.GREEN, "Success : ")  
241 - for command, valid in self.executed.items():  
242 - if not valid:  
243 - self.printColor(Colors.BLUE, "\t- Command : " + command + " success !")  
244 - else:  
245 - self.printColor(Colors.WARNING, "\t- In commmand : " + command)  
246 - for exe in valid:  
247 - self.printColor(Colors.GREEN, "\t\t - Command : " + exe + " success !")  
248 - self.printColor(Colors.FAIL, "Errors : ")  
249 - if not self.errors:  
250 - self.printColor(Colors.GREEN, "\tNone")  
251 - for command, items in self.errors.items():  
252 - count += 1  
253 - if (not items):  
254 - self.printColor(Colors.FAIL, "Command : " + command + " failed !")  
255 - else:  
256 - self.printColor(Colors.WARNING, "\t- In commmand : " + command)  
257 - for exe in items:  
258 - self.printColor(Colors.FAIL, "\t\t - Command : " + exe)  
259 - return count  
260 -  
261 - def exec(self):  
262 - if (not self.commands):  
263 - self.commandMatcher["help"]()  
264 - return 0  
265 - for command in self.commands:  
266 - self.current_command = command  
267 - if command in self.commandMatcher:  
268 - self.commandMatcher[command]()  
269 - else:  
270 - self.addError(str(command), "invalid command")  
271 - return self.end()  
272 -  
273 - def logError(self, message):  
274 - self.printColor(Colors.FAIL, "Pyros : An error occurred [" + message + "]", file=sys.stderr)  
275 - return 0  
276 -  
277 -'''  
278 - Config file representation (able to parse and give informations)  
279 -'''  
280 -  
281 -  
282 -class Config:  
283 - __parser = argparse.ArgumentParser("Project Pyros")  
284 - __content = {  
285 - "path": "private",  
286 - "env": "venv_py3_pyros"  
287 - }  
288 - __wait = True  
289 - __print = True  
290 - usage = ""  
291 - __command_list = []  
292 -  
293 - def __init__(self):  
294 - self.__parser.add_argument("command", nargs='*', default="help", help="The command you want to execute (default=help)")  
295 - self.__parser.add_argument("--env", help="Your environment directory name default=venv")  
296 - self.__parser.add_argument("--path", help="Path to the virtual env (from the source file directory) (default=private)")  
297 - self.__parser.add_argument("--nowait", action='store_true', help="Don't wait the end of a program")  
298 - self.__parser.add_argument("--noprint", action='store_true', help="Won't print")  
299 - self.usage = self.__parser.format_usage()  
300 -  
301 - def parse(self):  
302 - try:  
303 - res = self.__parser.parse_args(), self.__parser.format_usage()  
304 - return (res)  
305 - except SystemExit as e:  
306 - # print(e, file=sys.stderr)  
307 - sys.exit(1)  
308 -  
309 - def parseConf(self):  
310 - res, usage = self.parse()  
311 - try:  
312 - if (res.env):  
313 - self.__content["env"] = res.env  
314 - if (res.path):  
315 - self.__content["path"] = res.path  
316 - if (res.nowait):  
317 - self.__wait = False  
318 - if (res.noprint):  
319 - self.__print = False  
320 - self.__command_list.append(res.command[0])  
321 - #return  
322 - return True  
323 - except Exception as e:  
324 - print(e, file=sys.stderr)  
325 - #return 1  
326 - return False  
327 -  
328 - def getPath(self):  
329 - return self.__content["path"]  
330 -  
331 - def getEnv(self):  
332 - return self.__content["env"]  
333 -  
334 - def getWait(self):  
335 - return self.__wait  
336 -  
337 - def getPrint(self):  
338 - return self.__print  
339 -  
340 - def setPrint(self, value):  
341 - self.__print = value  
342 - return 0  
343 -  
344 - def setWait(self, value):  
345 - self.__wait = value  
346 - return 0  
347 -  
348 - def getCommandList(self):  
349 - return self.__command_list  
350 -  
351 - def printUsage(self):  
352 - print(self.usage, file=sys.stderr)  
353 -  
354 - def addConf(self, key, value):  
355 - if isinstance(key, str) and isinstance(value, str):  
356 - self.__content[key] = value  
357 - return 0  
358 - return 1  
359 -  
360 - def setPath(self, path):  
361 - if (os.path.isdir(path)):  
362 - if (path == ""):  
363 - path = "."  
364 - self.__content["path"] = path  
365 - return 0  
366 - return 1  
367 -  
368 - def setEnvName(self, name):  
369 - self.__content["env"] = name  
370 - return 0  
371 -  
372 - def getConfig(self):  
373 - return self.__content  
374 -  
375 -  
376 -'''  
377 - Color class  
378 -'''  
379 -  
380 -  
381 -class Colors:  
382 - HEADER = '\033[95m'  
383 - BLUE = '\033[94m'  
384 - GREEN = '\033[92m'  
385 - WARNING = '\033[93m'  
386 - FAIL = '\033[91m'  
387 - ENDC = '\033[0m'  
388 - BOLD = '\033[1m'  
389 - UNDERLINE = '\033[4m'  
390 -  
391 -  
392 -'''  
393 - Pyros class  
394 -'''  
395 -  
396 -  
397 -class Pyros(AManager):  
398 - help_message = "python neo.py"  
399 - init_fixture = "initial_fixture.json"  
400 -  
401 - def signal_handler(self, signal, frame):  
402 - self.printFullTerm(Colors.WARNING, "Ctrl-c catched")  
403 - for p in self.subproc:  
404 - proc, name = p  
405 - self.printColor(Colors.BLUE, "Killing process " + str(name))  
406 - proc.kill()  
407 - if self.current_command == "simulator" or self.current_command == "simulator_development":  
408 - self.changeDirectory(self.path_dir_file)  
409 - self.kill_simulation()  
410 - self.printFullTerm(Colors.WARNING, "Exiting")  
411 - sys.exit(0)  
412 -  
413 - def install(self):  
414 - if (self.system == "Windows"):  
415 - self.execProcess("python install/install.py install")  
416 - else:  
417 - self.execProcess("python3 install/install.py install")  
418 - return 0  
419 -  
420 - def update(self):  
421 - if (self.system == "Windows"):  
422 - self.execProcess("python install/install.py update")  
423 - else:  
424 - self.execProcess("python3 install/install.py update")  
425 - return 0  
426 -  
427 - def server(self):  
428 - self.changeDirectory("src")  
429 - self.execProcessFromVenvAsync(self.venv_bin + " manage.py runserver")  
430 - self.changeDirectory("..")  
431 - return 0  
432 -  
433 - def clean(self):  
434 - return self.clean_logs()  
435 -  
436 - def clean_logs(self):  
437 - return self.execProcess("rm logs/*.log")  
438 -  
439 - def test(self):  
440 - self.changeDirectory("src")  
441 - ##self.execProcessFromVenv(self.venv_bin + " manage.py test")  
442 - self.execProcessFromVenvAsync(self.venv_bin + " manage.py test")  
443 - self.changeDirectory("..")  
444 - return 0  
445 -  
446 - def migrate(self):  
447 - self.changeDirectory("src")  
448 - self.execProcessFromVenv(self.venv_bin + " manage.py migrate")  
449 - self.changeDirectory("..")  
450 - return 0  
451 -  
452 - def makemigrations(self):  
453 - self.changeDirectory("src")  
454 - self.execProcessFromVenv(self.venv_bin + " manage.py makemigrations")  
455 - self.changeDirectory("..")  
456 - return 0  
457 -  
458 - def help(self):  
459 - count = 0  
460 - self.printFullTerm(Colors.WARNING, "Help Message")  
461 - for command, message in self.commandDescription.items():  
462 - count += 1  
463 - if (self.columns > 100):  
464 - self.printColor(Colors.BLUE, "\t"+str(count)+(' ' if count < 10 else '')+": " + command + ": ", eol='')  
465 - else:  
466 - self.printColor(Colors.BLUE, "-> " + command + ": ", eol='')  
467 - self.printColor(Colors.GREEN, message)  
468 - return 0  
469 -  
470 - def updatedb(self):  
471 - self.makemigrations()  
472 - self.migrate()  
473 - return 0  
474 -  
475 - def reset_config(self):  
476 - self.changeDirectory("src")  
477 - self.set_simulator_mode_to(False)  
478 - self.addExecuted(self.current_command, "reset configuration")  
479 - self.changeDirectory("..")  
480 - return 0  
481 -  
482 - def unittest(self):  
483 - #self.changeDirectory("src")  
484 - apps = ['common', 'scheduler', 'routine_manager', 'user_manager', 'alert_manager.tests.TestStrategyChange']  
485 - for app in apps:  
486 - self.loaddata()  
487 - self.changeDirectory("src")  
488 - self.execProcessFromVenv(self.venv_bin + ' manage.py test ' + app)  
489 - self.changeDirectory("..")  
490 - return 0  
491 -  
492 - def test_all(self):  
493 - self.unittest()  
494 - return 0  
495 -  
496 - #TODO: mettre la fixture en date naive (sans time zone)  
497 - def loaddata(self):  
498 - self.changeDirectory("src")  
499 - self.execProcessFromVenv(self.venv_bin + " manage.py loaddata misc" + os.sep + "fixtures" + os.sep + self.init_fixture)  
500 - self.changeDirectory("..")  
501 - return 0  
502 -  
503 -  
504 - def init_database(self):  
505 - self.makemigrations()  
506 - self.migrate()  
507 - self.loaddata()  
508 - return 0  
509 -  
510 -  
511 - # Start the PyROS software  
512 -  
513 - # Start only the (django) Web server (pyros website)  
514 - def start_web(self):  
515 - self.start("web")  
516 - #self.start(3)  
517 -  
518 - # Start only the agents (all of them)  
519 - def start_agents(self):  
520 - self.start("agents")  
521 -  
522 - # Start only 1 agent  
523 - def start_agent_monitoring(self):  
524 - self.start("monitoring")  
525 - def start_agent_majordome(self):  
526 - self.start("majordome")  
527 - def start_agent_alertmanager(self):  
528 - self.start("alert_manager")  
529 -  
530 - # Start the PyROS software  
531 - # By default (what=None), start everything : all the agents and also the web server (website, web viewing, for global monitoring)  
532 - def start(self, what:str = None):  
533 - #agents = ("alert_manager", "majordome", "monitoring")  
534 - agents = ("majordome", "monitoring")  
535 - '''  
536 - what="monitoring"  
537 - what="majordome"  
538 - what="alert_manager"  
539 - what="all"  
540 - '''  
541 -  
542 - # Go into src/  
543 - self.changeDirectory("src")  
544 - #print("Current directory : " + str(os.getcwd()))  
545 -  
546 - # Start (Django) Web server (pyros website)  
547 - if what=='web' or what is None:  
548 - self.execProcessFromVenvAsync(self.venv_bin + " manage.py runserver")  
549 - #self.changeDirectory("..")  
550 -  
551 - # DJANGO setup  
552 - ''' (optional)  
553 - import sys  
554 - sys.path.append('/PROJECTS/GFT/SOFT/PYROS_SOFT/PYROS201802/src')  
555 - '''  
556 - '''  
557 - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "src.pyros.settings")  
558 - #os.environ['SECRET_KEY'] = 'abc'  
559 - #os.environ['ENVIRONMENT'] = 'production'  
560 - import django  
561 - django.setup()  
562 - '''  
563 -  
564 - # Start Agents  
565 - '''  
566 - if agent=="majordome" or agent=="all":  
567 - from majordome.tasks import Majordome  
568 - Majordome().run()  
569 - if agent=="alert_manager" or agent=="all":  
570 - from alert_manager.tasks import AlertListener  
571 - AlertListener().run()  
572 - '''  
573 - for agent in agents:  
574 - if what==agent or what=="agents" or what is None:  
575 - #os.chdir('src')  
576 - '''  
577 - from monitoring.tasks import Monitoring  
578 - Monitoring().run()  
579 - '''  
580 - #self.changeDirectory("src/monitoring")  
581 - #self.changeDirectory("src")  
582 - self.changeDirectory(agent)  
583 - self.execProcessFromVenvAsync(self.venv_bin + ' start_agent_'+agent+'.py')  
584 - # Go back to src/  
585 - self.changeDirectory('..')  
586 - #os.chdir('..')  
587 - # Go back to root folder (/)  
588 - self.changeDirectory('..')  
589 -  
590 -  
591 -  
592 - # Reset the database content  
593 - def reset_database_sim(self):  
594 - self.changeDirectory("src")  
595 - self.set_simulator_mode_to(True, False)  
596 - '''  
597 - Supprime toutes les données de la base de données  
598 - et réexécute tout gestionnaire de post-synchronisation.  
599 - La table contenant les informations d’application des migrations n’est pas effacée  
600 - '''  
601 - #TODO: remplacer par manage.py --noinput flush pour eviter le "echo yes"...  
602 - self.execProcess( self.venv_bin + " manage.py flush --noinput")  
603 - self.set_simulator_mode_to(True, False)  
604 - self.changeDirectory("..")  
605 -  
606 -  
607 - # Simulation for the scheduler ONLY  
608 - def simulator_development(self):  
609 - self.simulator(False)  
610 -  
611 - '''  
612 - (EP) utile pour le debug avec phpmyadmin pour remettre à zéro la bd pyros_test  
613 - delete from plan;  
614 - delete from album;  
615 - delete from schedule_has_sequences;  
616 - delete from schedule;  
617 - delete from sequence;  
618 - delete from request;  
619 - '''  
620 -  
621 - # Simulation (by default, with ALL simulators)  
622 - def simulator(self, TOTAL=True):  
623 - self.changeDirectory("src")  
624 -  
625 - # Set SIMULATOR mode ON  
626 - self.replacePatternInFile("SIMULATOR = False", "SIMULATOR = True", "pyros/settings.py")  
627 -  
628 - # 0) Reset the database (Empty the database from any data)  
629 - #TODO: je crois qu'on n'utilise plus sqlite... donc ce code est inutile ?  
630 - if self.system == "Windows" : self.execProcess("del /f testdb.sqlite3")  
631 - else : self.execProcess("rm -f testdb.sqlite3")  
632 - self.changeDirectory("..")  
633 - if TOTAL: self.reset_database_sim()  
634 - # EP added:  
635 - if not TOTAL: self.reset_database_sim()  
636 - # Actualise l’état de la DB en accord avec l’ensemble des modèles et des migrations actuels  
637 - self.makemigrations()  
638 - self.migrate()  
639 - # Load fixture initial_fixture.json  
640 - self.loaddata()  
641 -  
642 - #  
643 - # 1) Launch web server  
644 - #  
645 - self.server()  
646 - self.sleep(2)  
647 -  
648 - self.printFullTerm(Colors.WARNING, "SUMMARY")  
649 - self.printColor(Colors.GREEN, "The simulator has been successfully initialised")  
650 - #TODO: (EP) verifier mais je pense que cette info est obsolete, c'est MySQL qui est utilisé, pas sqlite  
651 - #self.printColor(Colors.GREEN, "The simulator runs on a temp database : src/testdb.sqlite3")  
652 - self.printColor(Colors.GREEN, "The simulation will be ended by the task 'simulator herself'")  
653 - self.printColor(Colors.GREEN, "If you want to shutdown the simulation, please run :")  
654 - self.printColor(Colors.GREEN, "CTRL-C or pyros.py kill_simulation")  
655 - self.printColor(Colors.GREEN, "If the simulation isn't correctly killed, please switch the variable")  
656 - self.printColor(Colors.GREEN, "SIMULATOR in src/pyros/settings.py to False")  
657 - self.printFullTerm(Colors.WARNING, "SUMMARY")  
658 -  
659 - #  
660 - # 2) Start simulator(s) :  
661 - #  
662 - self.sims_launch(TOTAL)  
663 -  
664 - '''  
665 - # (DEFAULT NORMAL) TOTAL RUN  
666 - # - Launch ALL simulators  
667 - if TOTAL:  
668 - # Launch ALL simulators and WAIT until finished:  
669 - #self.sims_launch(conf)  
670 - self.sims_launch(TOTAL)  
671 -  
672 - # (DEV) PARTIAL RUN  
673 - # - Launch only USER simulator  
674 - else:  
675 - # EP: bugfix: inutile si TOTAL, car refait dans sims_launch !!!  
676 - self.changeDirectory("simulators/config")  
677 - self.printColor(Colors.BOLD, "Existing simulations : ", eol='')  
678 - sys.stdout.flush()  
679 - if (self.system == "Windows") : self.execProcessSilent("dir /B conf*.json")  
680 - else: self.execProcessSilent("ls conf*.json")  
681 - self.changeDirectory("..")  
682 - conf = self.askQuestion("Which simulation do you want to use", default="conf.json")  
683 - self.changeDirectory("..")  
684 -  
685 - procs = []  
686 - self.changeDirectory("simulators")  
687 - self.changeDirectory("user")  
688 - # Launch only 1 simulator : UserSimulator  
689 - procs.append(self.execProcessFromVenvAsync(self.venv_bin + " userSimulator.py " + conf))  
690 - self.changeDirectory("..")  
691 - for p in procs:  
692 - p.wait()  
693 - self.changeDirectory("..")  
694 - '''  
695 -  
696 - # When simulators are finished:  
697 - #self.kill_simulation()  
698 - return 0  
699 -  
700 -  
701 -  
702 -  
703 - # Test of the Majordome agent  
704 - def test_majordome(self):  
705 - self.changeDirectory("src")  
706 -  
707 - # Set SIMULATOR mode ON  
708 - self.set_simulator_mode_to(True)  
709 -  
710 - # 0) Reset the database (Empty the database from any data)  
711 - self.changeDirectory("..")  
712 - self.reset_database_sim()  
713 - # Actualise l’état de la DB en accord avec l’ensemble des modèles et des migrations actuels  
714 - self.migrate()  
715 - # Load fixture initial_fixture.json  
716 - self.loaddata()  
717 - return  
718 -  
719 - #  
720 - # 1) Launch Django web server  
721 - #  
722 - '''  
723 - self.server()  
724 - self.sleep(2)  
725 - '''  
726 -  
727 - self.printFullTerm(Colors.WARNING, "SUMMARY")  
728 - self.printColor(Colors.GREEN, "The simulation will be ended by the task 'simulator herself'")  
729 - self.printColor(Colors.GREEN, "If you want to shutdown the simulation, please run :")  
730 - self.printColor(Colors.GREEN, "CTRL-C or pyros.py kill_simulation")  
731 - self.printColor(Colors.GREEN, "If the simulation isn't correctly killed, please switch the variable")  
732 - self.printColor(Colors.GREEN, "SIMULATOR in src/pyros/settings.py to False")  
733 - self.printFullTerm(Colors.WARNING, "SUMMARY")  
734 -  
735 - #  
736 - # 2) Start simulator(s) :  
737 - #  
738 - #self.sims_launch(True)  
739 - agent='majordome'  
740 - self.changeDirectory(agent)  
741 - p = self.execProcessFromVenvAsync(self.venv_bin + ' start_agent_'+agent+'.py')  
742 - os.chdir('..')  
743 - time.sleep(5)  
744 -  
745 -  
746 - # Wait for end of simulators :  
747 - #p.wait()  
748 -  
749 - # When simulators are finished:  
750 - #self.kill_simulation()  
751 - #self.changeDirectory("src")  
752 - self.set_simulator_mode_to(False)  
753 - self.changeDirectory("..")  
754 - self.execProcessAsync("ps aux | grep \"start_agent_majordome.py\" | awk '{ print $2 }' | xargs kill")  
755 -  
756 - return 0  
757 -  
758 -  
759 -  
760 -  
761 -  
762 -  
763 -  
764 - def kill_server(self):  
765 - self.execProcessAsync("fuser -k 8000/tcp")  
766 - return 0  
767 -  
768 - def kill_simulation(self):  
769 - self.changeDirectory("src")  
770 - self.set_simulator_mode_to(False)  
771 - self.changeDirectory("..")  
772 - if (self.system == "Windows"):  
773 - self.execProcessAsync("taskkill /f /im python.exe")  
774 - #self.execProcessAsync("del /f testdb.sqlite3") obsolete  
775 - #self.changeDirectory("..") POURQUOI ?  
776 - return 0  
777 - #else: self.execProcessAsync("rm -f testdb.sqlite3")  
778 -  
779 - # Kill web server (processes using TCP port 8000) :  
780 - #self.execProcessAsync("fuser -k 8000/tcp")  
781 -  
782 - self.kill_server()  
783 -  
784 - # Kill all agents :  
785 - self.execProcessAsync("ps aux | grep \"start_agent_alert_manager.py\" | awk '{ print $2 }' | xargs kill")  
786 - self.execProcessAsync("ps aux | grep \"start_agent_monitoring.py\" | awk '{ print $2 }' | xargs kill")  
787 - self.execProcessAsync("ps aux | grep \"start_agent_majordome.py\" | awk '{ print $2 }' | xargs kill")  
788 - #self.execProcessAsync("ps aux | grep \"start_agent_majordome.py\" | awk '{ print $2 }' | xargs kill")  
789 -  
790 - # Kill all simulators :  
791 - self.execProcessAsync("ps aux | grep \" domeSimulator.py\" | awk '{ print $2 }' | xargs kill")  
792 - self.execProcessAsync("ps aux | grep \" userSimulator.py\" | awk '{ print $2 }' | xargs kill")  
793 - self.execProcessAsync("ps aux | grep \" alertSimulator.py\" | awk '{ print $2 }' | xargs kill")  
794 - self.execProcessAsync("ps aux | grep \" plcSimulator.py\" | awk '{ print $2 }' | xargs kill")  
795 - self.execProcessAsync("ps aux | grep \" telescopeSimulator.py\" | awk '{ print $2 }' | xargs kill")  
796 - self.execProcessAsync("ps aux | grep \" cameraNIRSimulator.py\" | awk '{ print $2 }' | xargs kill")  
797 - self.execProcessAsync("ps aux | grep \" cameraVISSimulator.py\" | awk '{ print $2 }' | xargs kill")  
798 - self.changeDirectory("..")  
799 -  
800 - self.printFullTerm(Colors.GREEN, "simulation ended")  
801 - return 0  
802 -  
803 -  
804 -  
805 - # EP 25/7/18  
806 - # Start one, some, or all agents and simulators  
807 -  
808 - def start_agents_and_simulators_for_majordome(self):  
809 - self.start_agents_and_simulators("majordome")  
810 -  
811 - def start_agents_and_simulators(self, agent:str=None):  
812 -  
813 - simulators = ('user', 'plc', 'dome', 'alert', 'cameraVIS', 'cameraNIR', 'telescope')  
814 - #simulators = ('plc', 'user')  
815 - #simulators = ()  
816 -  
817 - agents = ("alert_manager", "majordome", "monitoring")  
818 -  
819 - if agent=="majordome":  
820 - simulators = ("plc",)  
821 - agents = (agent, "monitoring")  
822 -  
823 - # Start simulators  
824 - p_sims = []  
825 - self.changeDirectory("simulators/")  
826 - for simulator in simulators:  
827 - print()  
828 - print("Start simulator", ' '+simulator+"Simulator.py ")  
829 - self.changeDirectory(simulator)  
830 - #p_sims.append( self.execProcessFromVenvAsync(self.venv_bin + ' '+simulator+"Simulator.py " + conf) )  
831 - p_sims.append( self.execProcessFromVenvAsync(self.venv_bin + ' '+simulator+"Simulator.py ") )  
832 - self.changeDirectory("../")  
833 -  
834 - # Start agents  
835 - #self.start_agents()  
836 - p_agents = []  
837 - self.changeDirectory("../src/")  
838 - for agent in agents:  
839 - print()  
840 - print("Start agent", 'start_agent_'+agent+'.py')  
841 - self.changeDirectory(agent)  
842 - p_agents.append( self.execProcessFromVenvAsync(self.venv_bin + ' start_agent_'+agent+'.py') )  
843 - self.changeDirectory('..')  
844 -  
845 -  
846 -  
847 - # Simulators only  
848 - def sims_launch(self, TOTAL=True):  
849 - conf = SIMULATOR_CONFIG_FILE  
850 - # Read simulators scenario file  
851 - self.changeDirectory("simulators/config")  
852 - # if (conf == ""):  
853 - # self.printColor(Colors.BOLD, "Existing simulations : ", eol='')  
854 - # sys.stdout.flush()  
855 - # if (self.system == "Windows"):  
856 - # self.execProcessSilent("dir /B conf*.json")  
857 - # else:  
858 - # self.execProcessSilent("ls conf*.json")  
859 - # conf = self.askQuestion("Which simulation do you want to use ?", default="conf.json")  
860 - if not os.path.isfile(conf):  
861 - self.printColor(Colors.FAIL, "The simulation file " + conf + " does not exist")  
862 - return 1  
863 - # Go back to simulators/  
864 - self.changeDirectory("..")  
865 -  
866 - procs = []  
867 -  
868 - # Launch the User simulator:  
869 - self.changeDirectory("user")  
870 - procs.append(self.execProcessFromVenvAsync(self.venv_bin + " userSimulator.py " + conf))  
871 - self.changeDirectory("..")  
872 -  
873 - # (if TOTAL) Launch all other simulators  
874 - if TOTAL:  
875 -  
876 - self.changeDirectory("dome")  
877 - procs.append(self.execProcessFromVenvAsync(self.venv_bin + " domeSimulator.py " + conf))  
878 - self.changeDirectory("..")  
879 -  
880 - self.changeDirectory("alert")  
881 - procs.append(self.execProcessFromVenvAsync(self.venv_bin + " alertSimulator.py " + conf))  
882 - self.changeDirectory("..")  
883 -  
884 - self.changeDirectory("plc")  
885 - procs.append(self.execProcessFromVenvAsync(self.venv_bin + " plcSimulator.py " + conf))  
886 - self.changeDirectory("..")  
887 -  
888 - self.changeDirectory("camera")  
889 - procs.append(self.execProcessFromVenvAsync(self.venv_bin + " cameraVISSimulator.py " + conf))  
890 - procs.append(self.execProcessFromVenvAsync(self.venv_bin + " cameraNIRSimulator.py " + conf))  
891 - self.changeDirectory("..")  
892 -  
893 - self.changeDirectory("telescope")  
894 - procs.append(self.execProcessFromVenvAsync(self.venv_bin + " telescopeSimulator.py " + conf))  
895 - self.changeDirectory("..")  
896 -  
897 - # Get back to project root folder  
898 - self.changeDirectory("..")  
899 -  
900 - # Launch agents (env monitor, major, alert mgr)  
901 - self.start_agents()  
902 -  
903 - # Wait for end of simulators :  
904 - for p in procs: p.wait()  
905 -  
906 - # Kill all processes  
907 - self.kill_simulation()  
908 - return 0  
909 -  
910 -  
911 - def mysql_on(self):  
912 - self.changeDirectory("src")  
913 - self.replacePatternInFile("MYSQL = False", "MYSQL = True", "pyros/settings.py")  
914 - self.addExecuted(self.current_command, "Switch to mysql")  
915 - self.changeDirectory("..")  
916 - return 0  
917 -  
918 - def mysql_off(self):  
919 - self.changeDirectory("src")  
920 - self.replacePatternInFile("MYSQL = True", "MYSQL = False", "pyros/settings.py")  
921 - self.addExecuted(self.current_command, "Switch to sqlite")  
922 - self.changeDirectory("..")  
923 - return 0  
924 -  
925 - def __init__(self, argv):  
926 - super(Pyros, self).__init__(argv)  
927 - self.commandMatcher = {  
928 - "install": self.install,  
929 - "update": self.update,  
930 - "server": self.server,  
931 - "clean": self.clean,  
932 - "simulator_development": self.simulator_development,  
933 - "clean_logs": self.clean_logs,  
934 - "test": self.test,  
935 - "migrate": self.migrate,  
936 - "mysql_on": self.mysql_on,  
937 - "mysql_off": self.mysql_off,  
938 - "makemigrations": self.makemigrations,  
939 - "updatedb": self.updatedb,  
940 - "unittest": self.unittest,  
941 - "reset_config": self.reset_config,  
942 - "test_all": self.test_all,  
943 - "reset_database_sim": self.reset_database_sim,  
944 - "init_database": self.init_database,  
945 - "kill_server": self.kill_server,  
946 - "loaddata": self.loaddata,  
947 - "start": self.start,  
948 - "start_web": self.start_web,  
949 - "start_agents": self.start_agents,  
950 - "start_agents_and_simulators": self.start_agents_and_simulators,  
951 - "start_agents_and_simulators_for_majordome": self.start_agents_and_simulators_for_majordome,  
952 - "start_agent_monitoring": self.start_agent_monitoring,  
953 - "start_agent_majordome": self.start_agent_majordome,  
954 - "start_agent_alertmanager": self.start_agent_alertmanager,  
955 - "test_majordome": self.test_majordome,  
956 -  
957 - "simulator": self.simulator,  
958 - "kill_simulation": self.kill_simulation,  
959 - "sims_launch": self.sims_launch,  
960 - "help": self.help,  
961 - }  
962 - self.commandDescription = {  
963 - "install": "Launch the server installation",  
964 - "update": "Update the server",  
965 - "server": "Launch the web server",  
966 - "loaddata": "Load the initial fixture in database",  
967 - "clean": "clean the repository",  
968 - "clean_logs": "clean the log directory",  
969 - "test": "launch the server tests",  
970 - "migrate": "execute migrations",  
971 - "mysql_on": "switch the database to be used to MYSQL",  
972 - "mysql_off": "switch the database to be used usage to SQLITE",  
973 - "makemigrations": "create new migrations",  
974 - "reset_config": "Reset the configuration in settings.py",  
975 - "reset_database_sim": "Reset the database content",  
976 - "help": "Help message",  
977 - "updatedb": "Update the database",  
978 - "kill_server": "Kill the web server on port 8000",  
979 - "init_database": "Create a standard initial context for pyros in db",  
980 - "unittest": "Runs the unit tests",  
981 - "test_all": "Run all the existing tests (this command needs to be updated when tests are added in the project",  
982 - "simulator": "Launch a simulation",  
983 - "simulator_development": "Simulation for the scheduler only",  
984 - "kill_simulation": "kill the simulators & web server",  
985 - "sims_launch": "Launch only the simulators",  
986 - }  
987 -  
988 -if __name__ == "__main__":  
989 - if len(sys.argv) == 3 and sys.argv[1].startswith("simulator"):  
990 - SIMULATOR_CONFIG_FILE = sys.argv[2]  
991 - conf = Config()  
992 - if not conf.parseConf(): sys.exit(1)  
993 - pyros = Pyros(conf)  
994 - sys.exit(pyros.exec())  
src/agent/Agent.py
@@ -115,8 +115,11 @@ class Agent: @@ -115,8 +115,11 @@ class Agent:
115 self.config = ConfigPyros(config_filename) 115 self.config = ConfigPyros(config_filename)
116 if self.config.get_last_errno() != self.config.NO_ERROR: 116 if self.config.get_last_errno() != self.config.NO_ERROR:
117 raise Exception(f"Bad config file name '{config_filename}', error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}") 117 raise Exception(f"Bad config file name '{config_filename}', error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}")
118 - tmp = AgentsSurvey.objects.filter(name=self.name)  
119 - if len(tmp) == 0: 118 + #tmp = AgentsSurvey.objects.filter(name=self.name)
  119 + #if len(tmp) == 0:
  120 + #nb_agents = AgentsSurvey.objects.filter(name=self.name).count()
  121 + #if nb_agents == 0:
  122 + if not AgentsSurvey.objects.filter(name=self.name).exists():
120 self._agent_survey = AgentsSurvey.objects.create(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status) 123 self._agent_survey = AgentsSurvey.objects.create(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status)
121 #self._agent_survey = AgentsSurvey(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status) 124 #self._agent_survey = AgentsSurvey(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status)
122 #self._agent_survey.save() 125 #self._agent_survey.save()
@@ -172,11 +175,12 @@ class Agent: @@ -172,11 +175,12 @@ class Agent:
172 175
173 self.load_config() 176 self.load_config()
174 177
175 - self.update_db_survey()  
176 -  
177 - self.read_db_commands() 178 + self.update_survey()
178 179
  180 + # generic cmd in json format
  181 + cmd = self.read_next_command()
179 182
  183 + self.general_process(cmd)
180 ''' 184 '''
181 if self.config.majordome_state == "STOP": 185 if self.config.majordome_state == "STOP":
182 break 186 break
@@ -190,7 +194,7 @@ class Agent: @@ -190,7 +194,7 @@ class Agent:
190 # Sub-level loop (only if ACTIVE) 194 # Sub-level loop (only if ACTIVE)
191 if self.is_active(): 195 if self.is_active():
192 self.set_status(self.STATUS_PROCESS_LOOP) 196 self.set_status(self.STATUS_PROCESS_LOOP)
193 - self.core_process() 197 + self.specific_process(cmd)
194 198
195 self.waitfor(self.mainloop_waittime) 199 self.waitfor(self.mainloop_waittime)
196 200
@@ -200,6 +204,14 @@ class Agent: @@ -200,6 +204,14 @@ class Agent:
200 204
201 205
202 206
  207 + def general_process(self, cmd):
  208 + pass
  209 +
  210 + # @abstract
  211 + # to be implemented by subclasses
  212 + def specific_process(self, cmd):
  213 + raise NotImplemented()
  214 +
203 def waitfor(self, nbsec): 215 def waitfor(self, nbsec):
204 print(f"Now, waiting for {nbsec} seconds...") 216 print(f"Now, waiting for {nbsec} seconds...")
205 time.sleep(nbsec) 217 time.sleep(nbsec)
@@ -330,9 +342,9 @@ class Agent: @@ -330,9 +342,9 @@ class Agent:
330 self._agent_survey.status = self.status 342 self._agent_survey.status = self.status
331 self._agent_survey.save() 343 self._agent_survey.save()
332 344
333 - def read_db_commands(self): 345 + def read_next_command(self):
334 print("Looking for new commands from the database ...") 346 print("Looking for new commands from the database ...")
335 - _agent_commands = AgentsCommand.objects.filter(receiver=self.name) 347 + _agent_commands = AgentsCommand.objects.filter(receiver=self.name, receiver_error_code=1)
336 # Is there any command for me to execute ? 348 # Is there any command for me to execute ?
337 if _agent_commands: 349 if _agent_commands:
338 print(f"I have received {len(_agent_commands)} new command(s) to execute") 350 print(f"I have received {len(_agent_commands)} new command(s) to execute")
src/agent/AgentX.py
@@ -33,17 +33,17 @@ class AgentX(Agent): @@ -33,17 +33,17 @@ class AgentX(Agent):
33 super().load_config() 33 super().load_config()
34 34
35 # @override 35 # @override
36 - def update_db_survey(self): 36 + def update_survey(self):
37 super().update_db_survey() 37 super().update_db_survey()
38 38
39 # @override 39 # @override
40 - def read_db_commands(self):  
41 - super().read_db_commands() 40 + def read_next_command(self):
  41 + return super().read_next_command()
42 42
43 # @override 43 # @override
44 def do_log(self): 44 def do_log(self):
45 super().do_log() 45 super().do_log()
46 46
47 # @override 47 # @override
48 - def core_process(self):  
49 - super().core_process() 48 + def specific_process(self, cmd):
  49 + pass
50 \ No newline at end of file 50 \ No newline at end of file
src/common/models.py
1 -from __future__ import unicode_literals 1 +##from __future__ import unicode_literals
  2 +
  3 +from enum import Enum
2 4
3 from django.contrib.auth.models import AbstractUser 5 from django.contrib.auth.models import AbstractUser
4 from django.db import models 6 from django.db import models
5 -from enum import Enum 7 +from django.core.validators import MaxValueValidator, MinValueValidator
  8 +
6 9
7 10
8 ''' 11 '''
@@ -22,25 +25,24 @@ class PyrosState(Enum): @@ -22,25 +25,24 @@ class PyrosState(Enum):
22 """ 25 """
23 STYLE RULES 26 STYLE RULES
24 =========== 27 ===========
25 -(https://simpleisbetterthancomplex.com/tips/2018/02/10/django-tip-22-designing-better-models.html) 28 +https://simpleisbetterthancomplex.com/tips/2018/02/10/django-tip-22-designing-better-models.html
  29 +https://steelkiwi.com/blog/best-practices-working-django-models-python/
26 30
27 -- The model definition is a class, so always use CapWords convention (no underscores) 31 +- Model name => singular
  32 + Call it Company instead of Companies.
  33 + A model definition is the representation of a single object (the object in this example is a company),
  34 + and not a collection of companies
  35 + The model definition is a class, so always use CapWords convention (no underscores)
28 E.g. User, Permission, ContentType, etc. 36 E.g. User, Permission, ContentType, etc.
29 37
30 - For the model’s attributes use snake_case. 38 - For the model’s attributes use snake_case.
31 E.g. first_name, last_name, etc 39 E.g. first_name, last_name, etc
32 40
33 -- Always name your models using singular.  
34 - Call it Company instead of Companies.  
35 - A model definition is the representation of a single object (the object in this example is a company),  
36 - and not a collection of companies  
37 -  
38 -- Blank and Null Fields 41 +- Blank and Null Fields (https://simpleisbetterthancomplex.com/tips/2016/07/25/django-tip-8-blank-or-null.html)
39 - Null: It is database-related. Defines if a given database column will accept null values or not. 42 - Null: It is database-related. Defines if a given database column will accept null values or not.
40 - Blank: It is validation-related. It will be used during forms validation, when calling form.is_valid(). 43 - Blank: It is validation-related. It will be used during forms validation, when calling form.is_valid().
41 Do not use null=True for text-based fields that are optional. 44 Do not use null=True for text-based fields that are optional.
42 - Otherwise, you will end up having two possible values for “no data”,  
43 - that is: None and an empty string. 45 + Otherwise, you will end up having two possible values for “no data”, that is: None and an empty string.
44 Having two possible values for “no data” is redundant. 46 Having two possible values for “no data” is redundant.
45 The Django convention is to use the empty string, not NULL. 47 The Django convention is to use the empty string, not NULL.
46 Example: 48 Example:
@@ -49,6 +51,16 @@ STYLE RULES @@ -49,6 +51,16 @@ STYLE RULES
49 name = models.CharField(max_length=255) # Mandatory 51 name = models.CharField(max_length=255) # Mandatory
50 bio = models.TextField(max_length=500, blank=True) # Optional (don't put null=True) 52 bio = models.TextField(max_length=500, blank=True) # Optional (don't put null=True)
51 birth_date = models.DateField(null=True, blank=True) # Optional (here you may add null=True) 53 birth_date = models.DateField(null=True, blank=True) # Optional (here you may add null=True)
  54 + The default values of null and blank are False.
  55 + Special case, when you need to accept NULL values for a BooleanField, use NullBooleanField instead.
  56 +
  57 +- Choices : you can use Choices from the model_utils library. Take model Article, for instance:
  58 + from model_utils import Choices
  59 + class Article(models.Model):
  60 + STATUSES = Choices(
  61 + (0, 'draft', _('draft')),
  62 + (1, 'published', _('published')) )
  63 + status = models.IntegerField(choices=STATUSES, default=STATUSES.draft)
52 64
53 - Reverse Relationships 65 - Reverse Relationships
54 66
@@ -203,10 +215,11 @@ class AgentsCommand(models.Model): @@ -203,10 +215,11 @@ class AgentsCommand(models.Model):
203 (l'agent destinataire en profite pour supprimer les commandes périmées qui le concernent) 215 (l'agent destinataire en profite pour supprimer les commandes périmées qui le concernent)
204 """ 216 """
205 #sender = models.CharField(max_length=50, blank=True, null=True, unique=True) 217 #sender = models.CharField(max_length=50, blank=True, null=True, unique=True)
206 - sender = models.CharField(max_length=50, unique=True) 218 + sender = models.CharField(max_length=50, unique=True, help_text='sender agent name')
207 receiver = models.CharField(max_length=50, unique=True) 219 receiver = models.CharField(max_length=50, unique=True)
208 command = models.CharField(max_length=400) 220 command = models.CharField(max_length=400)
209 - validity_duration_sec = models.PositiveIntegerField(default=60) 221 + #validity_duration_sec = models.PositiveIntegerField(default=60)
  222 + validity_duration_sec = models.DurationField(default=60)
210 223
211 # Automatically set at table line creation (line created by the sender) 224 # Automatically set at table line creation (line created by the sender)
212 sender_deposit_time = models.DateTimeField(blank=True, null=True, auto_now_add=True) 225 sender_deposit_time = models.DateTimeField(blank=True, null=True, auto_now_add=True)
@@ -231,6 +244,9 @@ class AgentsSurvey(models.Model): @@ -231,6 +244,9 @@ class AgentsSurvey(models.Model):
231 """ 244 """
232 | id | name | created | updated | validity_duration_sec (default=1mn) | mode (active/idle) | status (launch/init/loop/exit/...) | 245 | id | name | created | updated | validity_duration_sec (default=1mn) | mode (active/idle) | status (launch/init/loop/exit/...) |
233 """ 246 """
  247 +
  248 + #STATUSES = Choices('new', 'verified', 'published')
  249 +
234 # Statuses 250 # Statuses
235 STATUS_LAUNCH = "LAUNCHED" 251 STATUS_LAUNCH = "LAUNCHED"
236 STATUS_INIT = "INITIALIZING" 252 STATUS_INIT = "INITIALIZING"
@@ -255,13 +271,15 @@ class AgentsSurvey(models.Model): @@ -255,13 +271,15 @@ class AgentsSurvey(models.Model):
255 (STATUS_EXIT, "EXITING"), 271 (STATUS_EXIT, "EXITING"),
256 ) 272 )
257 273
258 - name = models.CharField(max_length=50, blank=True, null=True, unique=True) 274 + name = models.CharField(max_length=50, unique=True)
  275 + #name = models.CharField(max_length=50, blank=True, null=True, unique=True)
259 #created = models.DateTimeField(blank=True, null=True, auto_now_add=True) 276 #created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
260 created = models.DateTimeField(blank=True, null=True, auto_now_add=True) 277 created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
261 updated = models.DateTimeField(blank=True, null=True, auto_now=True) 278 updated = models.DateTimeField(blank=True, null=True, auto_now=True)
262 - validity_duration_sec = models.PositiveIntegerField(blank=True, null=True)  
263 - mode = models.CharField('agent mode', max_length=15, blank=True, null=True, choices=MODE_CHOICES)  
264 - status = models.CharField(max_length=15, blank=True, null=True, choices=STATUS_CHOICES) 279 + #validity_duration_sec = models.PositiveIntegerField(blank=True, null=True)
  280 + validity_duration_sec = models.DurationField(default=90)
  281 + mode = models.CharField('agent mode', max_length=15, blank=True, choices=MODE_CHOICES)
  282 + status = models.CharField(max_length=15, blank=True, choices=STATUS_CHOICES)
265 283
266 class Meta: 284 class Meta:
267 managed = True 285 managed = True
@@ -330,9 +348,25 @@ class Config(models.Model): @@ -330,9 +348,25 @@ class Config(models.Model):
330 PYROS_STATE = ["Starting", "Passive", "Standby", "Remote", "Startup", "Scheduler", "Closing" ] 348 PYROS_STATE = ["Starting", "Passive", "Standby", "Remote", "Startup", "Scheduler", "Closing" ]
331 349
332 id = models.IntegerField(default='1', primary_key=True) 350 id = models.IntegerField(default='1', primary_key=True)
333 - latitude = models.FloatField(default=1) 351 + #latitude = models.FloatField(default=1)
  352 + latitude = models.DecimalField(
  353 + max_digits=4, decimal_places=2,
  354 + default=1,
  355 + validators=[
  356 + MaxValueValidator(90),
  357 + MinValueValidator(-90)
  358 + ]
  359 + )
334 local_time_zone = models.FloatField(default=1) 360 local_time_zone = models.FloatField(default=1)
335 - longitude = models.FloatField(default=1) 361 + #longitude = models.FloatField(default=1)
  362 + longitude = models.DecimalField(
  363 + max_digits=5, decimal_places=2,
  364 + default=1,
  365 + validators=[
  366 + MaxValueValidator(360),
  367 + MinValueValidator(-360)
  368 + ]
  369 + )
336 altitude = models.FloatField(default=1) 370 altitude = models.FloatField(default=1)
337 horizon_line = models.FloatField(default=1) 371 horizon_line = models.FloatField(default=1)
338 row_data_save_frequency = models.IntegerField(default='300') 372 row_data_save_frequency = models.IntegerField(default='300')