Fixed job detail crash when running job is killed
[ais.git] / bin / djais / models.py
1 # -*- coding: utf-8 -*-
2
3 from __future__ import division
4 import os, os.path
5 from datetime import datetime
6 from random import SystemRandom
7 from django.db import models
8 from django.contrib.auth.models import get_hexdigest
9 from django.utils import html
10
11 from ais.common import Nmea, mmsi_to_strmmsi, nice_timedelta_str
12
13 class UserMessageCategory(models.Model):
14     id = models.CharField(max_length=10, primary_key=True)
15     class Meta:
16         db_table = u'user_message_category'
17
18 class UserMessage(models.Model):
19     id = models.AutoField(primary_key=True)
20     user = models.ForeignKey('User')
21     category = models.ForeignKey(UserMessageCategory, db_column='user_message_category_id')
22     txt = models.TextField()
23     class Meta:
24         db_table = u'user_message'
25
26 class User(models.Model):
27     id = models.AutoField(primary_key=True)
28     login = models.CharField(max_length=16, unique=True)
29     password_hash = models.CharField(max_length=75)
30     name = models.CharField(max_length=50)
31     email = models.EmailField()
32     father = models.ForeignKey('User')
33     creation_datetime = models.DateTimeField(auto_now_add=True)
34     phone = models.CharField(max_length=20, blank=True)
35     access_datetime = models.DateTimeField(blank=True, null=True)
36     class Meta:
37         db_table = u'user'
38         ordering = ('id',)
39
40     def __unicode__(self):
41         return self.login
42
43     def set_password(self, raw_password):
44         import random
45         algo = 'sha1'
46         salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
47         hsh = get_hexdigest(algo, salt, raw_password)
48         self.password_hash = '%s$%s$%s' % (algo, salt, hsh)
49         self.info('Password changed') # FIXME
50
51     def check_password(self, raw_password):
52         algo, salt, hsh = self.password_hash.split('$')
53         return hsh == get_hexdigest(algo, salt, raw_password)
54
55
56     def update_access_datetime(self):
57         self.access_datetime = datetime.utcnow()
58         self.save()
59
60     def get_and_delete_messages(self):
61         return None
62
63     def is_admin_by(self, user_id):
64         if self.id == user_id:
65             return True
66         if self.father_id is None:
67             return False
68         return self.father.is_admin_by(user_id)
69
70     def get_messages(self):
71         messages = UserMessage.objects.filter(user__id = self.id)
72         messages_dict = \
73             [ {'category': message.category, \
74                'txt': message.txt} \
75             for message in messages \
76             ]
77         messages.delete()
78         return messages_dict
79     
80     def info(self, message):
81         UserMessage(user_id = self.id, category_id=u'info', txt=html.escape(message)).save()
82
83     def error(self, message):
84         UserMessage(user_id = self.id, category_id=u'error', txt=html.escape(message)).save()
85
86     def check_sandbox_access(self, source_user=None):
87         SANDBOX_FLEET = 1
88         try:
89             FleetUser.objects.get(fleet = SANDBOX_FLEET, user = self)
90         except FleetUser.DoesNotExist:
91             fu = FleetUser()
92             fu.user_id = self.id
93             fu.fleet_id = SANDBOX_FLEET
94             fu.save()
95             if source_user:
96                 source_user.info("%s was granted access to 'sandbox' fleet" % self.login)
97
98
99 class Vessel(models.Model):
100     mmsi = models.IntegerField(primary_key=True)
101     name = models.CharField(max_length=20)
102     imo = models.IntegerField()
103     callsign = models.CharField(max_length=7)
104     type = models.IntegerField(default=0)
105     destination = models.CharField(max_length=20, blank=True, null=True)
106     updated = models.DateTimeField()
107     source = models.CharField(max_length=8)
108     dim_bow = models.IntegerField(default=0)
109     dim_stern = models.IntegerField(default=0)
110     dim_port = models.IntegerField(default=0)
111     dim_starboard = models.IntegerField(default=0)
112     eta = models.CharField(max_length=8, default='00002460') # format MMDDhhmm
113     class Meta:
114         db_table = u'vessel'
115     def __unicode__(self):
116         return unicode(self.mmsi) # FIXME
117     def get_last_nmea(self):
118         strmmsi = mmsi_to_strmmsi(self.mmsi)
119         return Nmea.new_from_lastinfo(strmmsi)
120
121
122 class Fleet(models.Model):
123     id = models.AutoField(primary_key=True)
124     name = models.CharField(max_length=50)
125     vessel = models.ManyToManyField(Vessel, through='FleetVessel')
126     description = models.TextField()
127     class Meta:
128         db_table = u'fleet'
129     def __unicode__(self):
130         return self.name
131     def vessel_count(self):
132         return FleetVessel.objects.filter(fleet=self.id).count()
133     def user_count(self):
134         return FleetUser.objects.filter(fleet=self.id).count()
135     def job_count(self):
136         if os.path.exists('/var/lib/ais/cron/fleets/%s.cron' % self.name):
137             return 1
138         else:
139             return 0
140
141 class FleetUser(models.Model):
142     id = models.AutoField(primary_key=True)
143     fleet = models.ForeignKey(Fleet) #, db_column='fleet_id', to_field='id')
144     user = models.ForeignKey(User)
145     class Meta:
146         db_table = u'fleet_user'
147
148 class FleetVessel(models.Model):
149     id = models.AutoField(primary_key=True)
150     fleet = models.ForeignKey(Fleet, db_column='fleet_id', to_field='id')
151     vessel = models.ForeignKey(Vessel, db_column='mmsi', to_field='mmsi')
152     class Meta:
153         db_table = u'fleet_vessel'
154     
155 ## manual input source
156 #class MiSource(models.Model):
157 #    id = models.IntegerField(primary_key=True)
158 #    userid = models.IntegerField()
159 #    name = models.TextField(unique=True)
160 #    class Meta:
161 #        db_table = u'mi_source'
162 #
163 ## manual input vessel
164 #class MiVessel(models.Model):
165 #    mmsi_txt = models.TextField(primary_key=True) # This field type is a guess.
166 #    class Meta:
167 #        db_table = u'mi_vessel'
168
169
170 # Plane plotter
171 #class Ppuser(models.Model):
172 #    usr = models.TextField(primary_key=True) # This field type is a guess.
173 #    lat = models.FloatField()
174 #    lon = models.FloatField()
175 #    class Meta:
176 #        db_table = u'ppuser'
177 #
178 #class Plane(models.Model):
179 #    flight = models.CharField(max_length=8)
180 #    reg = models.CharField(max_length=8)
181 #    ads = models.CharField(max_length=8)
182 #    type = models.CharField(max_length=4)
183 #    usr = models.TextField() # This field type is a guess.
184 #    updated = models.DateTimeField()
185 #    class Meta:
186 #        db_table = u'plane'
187
188
189
190 class News(models.Model):
191     id = models.AutoField(primary_key=True)
192     created = models.DateTimeField()
193     updated = models.DateTimeField()
194     title = models.TextField()
195     txt = models.TextField()
196     class Meta:
197         db_table = u'news'
198
199
200 class Job(models.Model):
201     def make_unique_job_id():
202         def make_id():
203             rnd = SystemRandom()
204             source = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
205             result = u''
206             for i in range(8):
207                 result += source[int(rnd.random()*len(source))]
208             return result
209         return make_id() # TODO check it's unique
210     
211     id = models.CharField(primary_key=True, max_length=8, default=make_unique_job_id)
212     user = models.ForeignKey(User)
213     queue_time = models.DateTimeField(auto_now_add=True)
214     start_time = models.DateTimeField(blank=True, null=True)
215     finish_time = models.DateTimeField(blank=True, null=True)
216     command = models.TextField()
217     friendly_filename = models.CharField(max_length=255)
218     pid = models.IntegerField(blank=True, null=True)
219     result = models.IntegerField(blank=True, null=True)
220     archive_time = models.DateTimeField(blank=True, null=True)
221     notify = models.CharField(max_length=1, blank=True, null=True)
222
223     def queue_rank(self):
224         return Job.objects.filter(queue_time__lt=self.queue_time).filter(start_time__isnull=True).count() + 1
225
226     @staticmethod
227     def queue_size():
228         return Job.objects.filter(start_time__isnull=True).count()
229
230     def process_time(self):
231         dt = self.finish_time - self.start_time
232         return nice_timedelta_str(dt)
233
234     def running_time(self):
235         dt = datetime.utcnow() - self.start_time
236         return nice_timedelta_str(dt)
237
238     def get_stats(self):
239         result = {}
240         try:
241             strstats = file('/proc/%s/stat' % self.pid).read()
242         except:
243             return
244         # man 5 proc
245         # TODO:
246         # "getconf CLK_TCK" = 100 -> 1 tick = 1/100 seconds
247         strstats = strstats.rstrip('\n').split(' ')
248         for i, key in enumerate(('pid', 'comm', 'state', 'ppid', 'pgrp', 'session', 'tty_nr', 'tpgid', 'flags', 'minflt', 'cminflt', 'majflt', 'cmajflt', 'utime', 'stime', 'cutime', 'cstime', 'priority', 'nice', 'num_threads', 'itrealvalue', 'starttime', 'vsize', 'rss', 'rsslim', 'startcode', 'endcode', 'startstack', 'kstkesp', 'kstkeip', 'signal', 'blocked', 'sigignore', 'sigcatch', 'wchan', 'nswap', 'cnswap', 'exit_signal', 'processor', 'rt_priority', 'policy', 'delayacct_blkio_ticks', 'guest_time', 'cguest_time')):
249             result[key] = strstats[i]
250         return result
251     
252     def get_sucess_size(self):
253         extension = os.path.splitext(self.friendly_filename)[-1]
254         filename = '/var/lib/ais/jobs/%s%s' % (self.id, extension)
255         return os.path.getsize(filename)
256
257     class Meta:
258         db_table = u'job'
259         ordering = ('queue_time',)