Commit fcfc62001ca075034b035ec620f14809ded9c69f

Authored by Etienne Pallier
1 parent 1a15d30a
Exists in dev

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

.gitignore
... ... @@ -17,6 +17,7 @@ logs/*.log
17 17 /.idea/workspace.xml
18 18 images_folder
19 19 Comet/
  20 +old/
20 21  
21 22 \.idea/
22 23  
... ...
README.md
... ... @@ -67,15 +67,14 @@ This software has been tested and validated with the following configurations :
67 67 --------------------------------------------------------------------------------------------
68 68 ## LAST VERSION
69 69  
70   -Date: 28/02/2019
  70 +Date: 04/03/2019
71 71  
72 72 Author: E. Pallier
73 73  
74   -VERSION: 0.20.3
  74 +VERSION: 0.20.4
75 75  
76 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 --------------------------------------------------------------------------------------------
... ...
pyros.py
... ... @@ -234,6 +234,21 @@ def shell():
234 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 252 @pyros_launcher.command(help="Install the pyros software")
238 253 #@global_test_options
239 254 def install():
... ...
pyros_old.py deleted
... ... @@ -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 115 self.config = ConfigPyros(config_filename)
116 116 if self.config.get_last_errno() != self.config.NO_ERROR:
117 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 123 self._agent_survey = AgentsSurvey.objects.create(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status)
121 124 #self._agent_survey = AgentsSurvey(name=self.name, validity_duration_sec=60, mode=self.mode, status=self.status)
122 125 #self._agent_survey.save()
... ... @@ -172,11 +175,12 @@ class Agent:
172 175  
173 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 185 if self.config.majordome_state == "STOP":
182 186 break
... ... @@ -190,7 +194,7 @@ class Agent:
190 194 # Sub-level loop (only if ACTIVE)
191 195 if self.is_active():
192 196 self.set_status(self.STATUS_PROCESS_LOOP)
193   - self.core_process()
  197 + self.specific_process(cmd)
194 198  
195 199 self.waitfor(self.mainloop_waittime)
196 200  
... ... @@ -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 215 def waitfor(self, nbsec):
204 216 print(f"Now, waiting for {nbsec} seconds...")
205 217 time.sleep(nbsec)
... ... @@ -330,9 +342,9 @@ class Agent:
330 342 self._agent_survey.status = self.status
331 343 self._agent_survey.save()
332 344  
333   - def read_db_commands(self):
  345 + def read_next_command(self):
334 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 348 # Is there any command for me to execute ?
337 349 if _agent_commands:
338 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 33 super().load_config()
34 34  
35 35 # @override
36   - def update_db_survey(self):
  36 + def update_survey(self):
37 37 super().update_db_survey()
38 38  
39 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 43 # @override
44 44 def do_log(self):
45 45 super().do_log()
46 46  
47 47 # @override
48   - def core_process(self):
49   - super().core_process()
  48 + def specific_process(self, cmd):
  49 + pass
50 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 5 from django.contrib.auth.models import AbstractUser
4 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 """
23 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 36 E.g. User, Permission, ContentType, etc.
29 37  
30 38 - For the modelโ€™s attributes use snake_case.
31 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 42 - Null: It is database-related. Defines if a given database column will accept null values or not.
40 43 - Blank: It is validation-related. It will be used during forms validation, when calling form.is_valid().
41 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 46 Having two possible values for โ€œno dataโ€ is redundant.
45 47 The Django convention is to use the empty string, not NULL.
46 48 Example:
... ... @@ -49,6 +51,16 @@ STYLE RULES
49 51 name = models.CharField(max_length=255) # Mandatory
50 52 bio = models.TextField(max_length=500, blank=True) # Optional (don't put null=True)
51 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 65 - Reverse Relationships
54 66  
... ... @@ -203,10 +215,11 @@ class AgentsCommand(models.Model):
203 215 (l'agent destinataire en profite pour supprimer les commandes pรฉrimรฉes qui le concernent)
204 216 """
205 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 219 receiver = models.CharField(max_length=50, unique=True)
208 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 224 # Automatically set at table line creation (line created by the sender)
212 225 sender_deposit_time = models.DateTimeField(blank=True, null=True, auto_now_add=True)
... ... @@ -231,6 +244,9 @@ class AgentsSurvey(models.Model):
231 244 """
232 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 250 # Statuses
235 251 STATUS_LAUNCH = "LAUNCHED"
236 252 STATUS_INIT = "INITIALIZING"
... ... @@ -255,13 +271,15 @@ class AgentsSurvey(models.Model):
255 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 276 #created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
260 277 created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
261 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 284 class Meta:
267 285 managed = True
... ... @@ -330,9 +348,25 @@ class Config(models.Model):
330 348 PYROS_STATE = ["Starting", "Passive", "Standby", "Remote", "Startup", "Scheduler", "Closing" ]
331 349  
332 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 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 370 altitude = models.FloatField(default=1)
337 371 horizon_line = models.FloatField(default=1)
338 372 row_data_save_frequency = models.IntegerField(default='300')
... ...