Small fixes for ais module creation
[ais.git] / bin / djais / views.py
1 # -*- coding: utf-8 -*-
2
3 import os
4 from datetime import *
5 from time import time as get_timestamp
6 import crack
7 import struct
8 import rrdtool
9 from django.http import *
10 from django.template import loader, RequestContext
11 from django import forms
12 from django.shortcuts import render_to_response, get_object_or_404
13 from django.db import IntegrityError
14
15 from decoratedstr import remove_decoration
16
17 from ais.djais.basicauth import http_authenticate
18 from ais.djais.models import *
19 from ais.show_targets_ships import *
20 from ais.common import Nmea, NmeaFeeder, strmmsi_to_mmsi, SHIP_TYPES, STATUS_CODES, AIS_STATUS_NOT_AVAILABLE, AIS_ROT_NOT_AVAILABLE, AIS_LATLON_SCALE, AIS_LON_NOT_AVAILABLE, AIS_LAT_NOT_AVAILABLE, AIS_COG_SCALE, AIS_COG_NOT_AVAILABLE, AIS_NO_HEADING, AIS_SOG_SCALE, AIS_SOG_NOT_AVAILABLE, AIS_SOG_MAX_SPEED, add_nmea1, add_nmea5_partial, load_fleet_to_uset
21 from ais.ntools import datetime_to_timestamp, clean_ais_charset
22 from ais.inputs.stats import STATS_DIR
23
24 def auth(username, raw_password):
25     try:
26         user = User.objects.get(login=username)
27     except User.DoesNotExist:
28         return None
29     if not user.check_password(raw_password):
30         return None
31     return user
32
33
34 @http_authenticate(auth, 'ais')
35 def index(request):
36     return render_to_response('index.html', {}, RequestContext(request))
37
38
39 class VesselSearchForm(forms.Form):
40     mmsi = forms.CharField(max_length=9, required=False)
41     name = forms.CharField(max_length=20, required=False)
42     imo = forms.IntegerField(required=False)
43     callsign = forms.CharField(max_length=7, required=False)
44     destination = forms.CharField(max_length=20, required=False)
45     def clean(self):
46         cleaned_data = self.cleaned_data
47         for value in cleaned_data.values():
48             if value:
49                 return cleaned_data
50         raise forms.ValidationError("You must enter at least one criteria")
51
52
53
54 @http_authenticate(auth, 'ais')
55 def vessel_search(request):
56     if request.method == 'POST' or request.META['QUERY_STRING']:
57         form = VesselSearchForm(request.REQUEST)
58         if form.is_valid():
59             data = form.cleaned_data
60             vessels = Vessel.objects
61             if data['mmsi']:
62                 vessels = vessels.filter(mmsi=strmmsi_to_mmsi(data['mmsi']))
63             if data['name']:
64                 vessels = vessels.filter(name__contains=data['name'].upper())
65             if data['imo']:
66                 vessels = vessels.filter(imo=data['imo'])
67             if data['callsign']:
68                 vessels = vessels.filter(callsign__contains=data['callsign'].upper())
69             if data['destination']:
70                 vessels = vessels.filter(destination__contains=data['destination'].upper())
71             return render_to_response('vessels.html', {'vessels': vessels}, RequestContext(request))
72     else: # GET
73         form = VesselSearchForm()
74
75     return render_to_response('vessel_index.html', {'form': form}, RequestContext(request))
76
77 @http_authenticate(auth, 'ais')
78 def vessel(request, strmmsi):
79     mmsi = strmmsi_to_mmsi(strmmsi)
80     vessel = get_object_or_404(Vessel, pk=mmsi)
81     nmea = Nmea.new_from_lastinfo(strmmsi)
82     #if not nmea.timestamp_1 and not nmea.timestamp_5:
83     #    raise Http404
84     return render_to_response('vessel.html', {'nmea': nmea}, RequestContext(request))
85
86
87 class VesselManualInputForm(forms.Form):
88     timestamp = forms.DateTimeField(label=u'When', help_text=u'When was the observation made in GMT. Use YYYY-MM-DD HH:MM:SS format')
89     imo = forms.IntegerField(required=False, min_value=1000000, max_value=9999999)
90     name = forms.CharField(max_length=20, required=False)
91     callsign = forms.CharField(max_length=7, required=False)
92     type = forms.TypedChoiceField(required=False, choices = [ kv for kv in SHIP_TYPES.iteritems() if 'reserved' not in kv[1].lower()], coerce=int, empty_value=0, initial=0)
93     status = forms.TypedChoiceField(required=False, choices = [ kv for kv in STATUS_CODES.iteritems() if 'reserved' not in kv[1].lower()], coerce=int, empty_value=AIS_STATUS_NOT_AVAILABLE, initial=AIS_STATUS_NOT_AVAILABLE)
94     sog = forms.FloatField(label='Speed', help_text='Over ground, in knots', required=False, min_value=0, max_value=AIS_SOG_MAX_SPEED/AIS_SOG_SCALE)
95     latitude = forms.CharField(required=False)
96     longitude = forms.CharField(required=False)
97     cog = forms.FloatField(label='Course', help_text='Over ground', required=False, min_value=0.0, max_value=359.9)
98     heading = forms.IntegerField(required=False, min_value=0, max_value=359)
99
100     @staticmethod
101     def _clean_ais_charset(ustr):
102         ustr = remove_decoration(ustr) # benign cleaning, but can increase size (œ->oe)
103         ustr = ustr.upper() # benign cleaning
104         str = clean_ais_charset(ustr.encode('ascii', 'replace'))
105         if unicode(str) != ustr:
106             raise forms.ValidationError('Invalid character: AIS alphabet is @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^- !"#$%&\'()*+,-./0123456789:;<=>?')
107         return str
108
109     def clean_timestamp(self):
110         data = self.cleaned_data['timestamp']
111         if data is None:
112             return None
113         if data < datetime.utcnow() - timedelta(365):
114             raise forms.ValidationError('Date is too much is the past.')
115         if data > datetime.utcnow():
116             raise forms.ValidationError('Date is be in the future. This form is only for observed results.')
117         return datetime_to_timestamp(data)
118
119     def clean_imo(self):
120         data = self.cleaned_data['imo']
121         if data is None:
122             return 0
123         return data
124
125     def clean_name(self):
126         name = self.cleaned_data['name']
127         if name is None:
128             return ''
129         name = VesselManualInputForm._clean_ais_charset(name)
130         if len(name)>20:
131             raise forms.ValidationError('Ensure this value has at most 20 characters (it has %s).' % len(name))
132         return name
133
134     def clean_callsign(self):
135         callsign = self.cleaned_data['callsign']
136         if callsign is None:
137             return ''
138         callsign = VesselManualInputForm._clean_ais_charset(callsign)
139         if len(callsign)>7:
140             raise forms.ValidationError('Ensure this value has at most 7 characters (it has %s).' % len(callsign))
141         return callsign
142
143     def clean_sog(self):
144         sog = self.cleaned_data['sog']
145         if sog is None:
146             return AIS_SOG_NOT_AVAILABLE
147         return int(sog*AIS_SOG_SCALE)
148
149     def clean_latitude(self):
150         data = self.cleaned_data['latitude']
151         data = data.replace(u"''", u'"') # commong mistake
152         data = data.replace(u' ', u'') # remove spaces
153         sides = u'SN'
154         if not data:
155             return AIS_LAT_NOT_AVAILABLE
156         tmp, side = data[:-1], data[-1]
157         if side == sides[0]:
158             side = -1
159         elif side == sides[1]:
160             side = 1
161         else:
162             raise forms.ValidationError(u'Last character must be either %s or %s.' % (sides[0], sides[1]))
163         spl = tmp.split(u'°')
164         if len(spl) == 1:
165             raise forms.ValidationError(u'You need to use the ° character.')
166         d, tmp = spl
167         try:
168             d = float(d)
169         except ValueError:
170             raise forms.ValidationError(u'Degrees must be an number. It\'s %s.' % d)
171         spl = tmp.split(u"'", 1)
172         if len(spl) == 1:
173             # no ' sign: ok only if there is nothing but the side after °
174             # we don't accept seconds if there is no minutes:
175             # It might be an entry mistake
176             tmp = spl[0]
177             if len(tmp) == 0:
178                 m = s = 0
179             else:
180                 raise forms.ValidationError(u'You must use the \' character between ° and %s.' % data[-1])
181         else:
182             m, tmp = spl
183             try:
184                 m = float(m)
185             except ValueError:
186                 raise forms.ValidationError(u'Minutes must be an number. It\'s %s.' % m)
187             if len(tmp) == 0:
188                 s = 0
189             else:
190                 if tmp[-1] != '"':
191                     raise forms.ValidationError(u'You must use the " character between seconds and %s.' % data[-1])
192                 s = tmp[:-1]
193                 try:
194                     s = float(s)
195                 except ValueError:
196                     raise forms.ValidationError(u'Seconds must be an number. It\'s %s.' % s)
197         data = side * ( d + m / 60 + s / 3600)
198
199         if data < -90 or data > 90:
200             raise forms.ValidationError(u'%s in not in -90..90 range' % data)
201         return int(data * AIS_LATLON_SCALE)
202
203     def clean_longitude(self):
204         data = self.cleaned_data['longitude']
205         data = data.replace(u"''", u'"') # commong mistake
206         data = data.replace(u' ', u'') # remove spaces
207         sides = u'WE'
208         if not data:
209             return AIS_LON_NOT_AVAILABLE
210         tmp, side = data[:-1], data[-1]
211         if side == sides[0]:
212             side = -1
213         elif side == sides[1]:
214             side = 1
215         else:
216             raise forms.ValidationError(u'Last character must be either %s or %s.' % (sides[0], sides[1]))
217         spl = tmp.split(u'°')
218         if len(spl) == 1:
219             raise forms.ValidationError(u'You need to use the ° character.')
220         d, tmp = spl
221         try:
222             d = float(d)
223         except ValueError:
224             raise forms.ValidationError(u'Degrees must be an number. It\'s %s.' % d)
225         spl = tmp.split(u"'", 1)
226         if len(spl) == 1:
227             # no ' sign: ok only if there is nothing but the side after °
228             # we don't accept seconds if there is no minutes:
229             # It might be an entry mistake
230             tmp = spl[0]
231             if len(tmp) == 0:
232                 m = s = 0
233             else:
234                 raise forms.ValidationError(u'You must use the \' character between ° and %s.' % data[-1])
235         else:
236             m, tmp = spl
237             try:
238                 m = float(m)
239             except ValueError:
240                 raise forms.ValidationError(u'Minutes must be an number. It\'s %s.' % m)
241             if len(tmp) == 0:
242                 s = 0
243             else:
244                 if tmp[-1] != '"':
245                     raise forms.ValidationError(u'You must use the " character between seconds and %s.' % data[-1])
246                 s = tmp[:-1]
247                 try:
248                     s = float(s)
249                 except ValueError:
250                     raise forms.ValidationError(u'Seconds must be an number. It\'s %s.' % s)
251         data = side * ( d + m / 60 + s / 3600)
252
253         if data < -180 or data > 180:
254             raise forms.ValidationError(u'%s in not in -180..180 range' % data)
255         return int(data * AIS_LATLON_SCALE)
256
257     def clean_cog(self):
258         data = self.cleaned_data['cog']
259         if data is None:
260             return AIS_COG_NOT_AVAILABLE
261         return int(data * AIS_COG_SCALE)
262     
263     def clean_heading(self):
264         #raise forms.ValidationError(u'clean_heading called')
265         data = self.cleaned_data['heading']
266         if data is None:
267             return AIS_NO_HEADING
268         return data
269
270     def clean(self):
271         cleaned_data = self.cleaned_data
272         if (cleaned_data.get('latitude', AIS_LAT_NOT_AVAILABLE) == AIS_LAT_NOT_AVAILABLE ) ^ ( cleaned_data.get('longitude', AIS_LON_NOT_AVAILABLE) == AIS_LON_NOT_AVAILABLE):
273             raise forms.ValidationError('It makes no sense to enter just a latitude or a longitude. Enter both or none.')
274         if cleaned_data.get('latitude', AIS_LAT_NOT_AVAILABLE) == AIS_LAT_NOT_AVAILABLE:
275             if cleaned_data.get('status', AIS_STATUS_NOT_AVAILABLE) != AIS_STATUS_NOT_AVAILABLE:
276                 raise forms.ValidationError('It makes no sense to enter a status without coordinates. Please enter latitute and longitude too.')
277             if cleaned_data.get('sog', AIS_SOG_NOT_AVAILABLE) != AIS_SOG_NOT_AVAILABLE:
278                 raise forms.ValidationError('It makes no sense to enter a speed without coordinates. Please enter latitute and longitude too.')
279             if cleaned_data.get('cog', AIS_COG_NOT_AVAILABLE) != AIS_COG_NOT_AVAILABLE:
280                 raise forms.ValidationError('It makes no sense to enter a course without coordinates. Please enter latitute and longitude too.')
281             if cleaned_data.get('heading', AIS_NO_HEADING) != AIS_NO_HEADING:
282                 raise forms.ValidationError('It makes no sense to enter a heading without coordinates. Please enter latitute and longitude too.')
283
284         if cleaned_data.get('timestamp', None) \
285         and cleaned_data.get('imo', 0) == 0 \
286         and cleaned_data.get('name', '') == '' \
287         and cleaned_data.get('callsign', '') == '' \
288         and cleaned_data.get('type', 0) == 0 \
289         and cleaned_data.get('status', AIS_STATUS_NOT_AVAILABLE) == AIS_STATUS_NOT_AVAILABLE \
290         and cleaned_data.get('sog', AIS_SOG_NOT_AVAILABLE) == AIS_SOG_NOT_AVAILABLE \
291         and cleaned_data.get('latitude', AIS_LAT_NOT_AVAILABLE) == AIS_LAT_NOT_AVAILABLE \
292         and cleaned_data.get('longitude', AIS_LON_NOT_AVAILABLE) == AIS_LON_NOT_AVAILABLE \
293         and cleaned_data.get('cog', AIS_COG_NOT_AVAILABLE) == AIS_COG_NOT_AVAILABLE \
294         and cleaned_data.get('heading', AIS_NO_HEADING) == AIS_NO_HEADING:
295             raise forms.ValidationError("You must enter some data, beside when.")
296         return cleaned_data
297
298 @http_authenticate(auth, 'ais')
299 def vessel_manual_input(request, strmmsi):
300     strmmsi = strmmsi.encode('utf-8')
301     nmea = Nmea.new_from_lastinfo(strmmsi)
302     if request.method == 'POST' or request.META['QUERY_STRING']:
303         form = VesselManualInputForm(request.REQUEST)
304         if form.is_valid():
305             data = form.cleaned_data
306             source = 'U' +  struct.pack('<I', request.user.id)[0:3]
307             result = u''
308             if data['imo'] != 0 \
309             or data['name'] != '' \
310             or data['callsign'] != '' \
311             or data['type'] != 0:
312                 toto = (strmmsi, data['timestamp'], data['imo'], data['name'], data['callsign'], data['type'], 0,0,0,0, 0,0,24,60, 0, '', source)
313                 result += 'UPDATING NMEA 5: '+repr(toto)+'<br>'
314                 add_nmea5_partial(*toto)
315             if data['status'] != AIS_STATUS_NOT_AVAILABLE \
316             or data['sog'] != AIS_SOG_NOT_AVAILABLE \
317             or data['latitude'] != AIS_LAT_NOT_AVAILABLE \
318             or data['longitude'] != AIS_LON_NOT_AVAILABLE \
319             or data['cog'] != AIS_COG_NOT_AVAILABLE \
320             or data['heading'] != AIS_NO_HEADING:
321                 
322                 toto = (strmmsi, data['timestamp'], data['status'], AIS_ROT_NOT_AVAILABLE, data['sog'], data['latitude'], data['longitude'], data['cog'], data['heading'], source)
323                 result += 'UPDATING NMEA 1: '+repr(toto)+'<br>'
324                 add_nmea1(*toto)
325             return HttpResponse('Not fully implemented: '+repr(data) + '<br>' + result)
326     else: # GET
327         form = VesselManualInputForm()
328     return render_to_response('vessel_manual_input.html', {'form': form, 'nmea': nmea}, RequestContext(request))
329
330 @http_authenticate(auth, 'ais')
331 def vessel_track(request, strmmsi):
332     ndays = request.REQUEST.get('ndays', 90)
333     try:
334         ndays = int(ndays)
335     except ValueError:
336         ndays = 90
337     grain = request.REQUEST.get('grain', 3600)
338     try:
339         grain = int(grain)
340     except ValueError:
341         grain = 3600
342     date_end = datetime.utcnow()
343     date_start = date_end - timedelta(ndays)
344     nmea_iterator = NmeaFeeder(strmmsi, date_end, date_start, granularity=grain)
345     value = kml_to_kmz(format_boat_track(nmea_iterator))
346     response = HttpResponse(value, mimetype="application/vnd.google-earth.kml")
347     response['Content-Disposition'] = 'attachment; filename=%s.kmz' % strmmsi
348     return response
349
350
351 @http_authenticate(auth, 'ais')
352 def vessel_animation(request, strmmsi):
353     ndays = request.REQUEST.get('ndays', 90)
354     try:
355         ndays = int(ndays)
356     except ValueError:
357         ndays = 90
358     grain = request.REQUEST.get('grain', 3600)
359     try:
360         grain = int(grain)
361     except ValueError:
362         grain = 3600
363     date_end = datetime.utcnow()
364     date_start = date_end - timedelta(ndays)
365     nmea_iterator = NmeaFeeder(strmmsi, date_end, date_start, granularity=grain)
366     value = kml_to_kmz(format_boat_intime(nmea_iterator))
367     response = HttpResponse(value, mimetype="application/vnd.google-earth.kml")
368     response['Content-Disposition'] = 'attachment; filename=%s.kmz' % strmmsi
369     return response
370
371
372 @http_authenticate(auth, 'ais')
373 def fleets(request):
374     fleetusers = request.user.fleetuser_set.all()
375     return render_to_response('fleets.html', {'fleetusers':fleetusers}, RequestContext(request))
376
377
378 @http_authenticate(auth, 'ais')
379 def fleet(request, fleetname):
380     fleet = get_object_or_404(Fleet, pk=fleetname)
381     if not FleetUser.objects.filter(fleet=fleetname, user=request.user.id).all():
382         return HttpResponseForbidden('<h1>Forbidden</h1>')
383     return render_to_response('fleet.html', {'fleet':fleet}, RequestContext(request))
384
385
386 @http_authenticate(auth, 'ais')
387 def fleet_vessels(request, fleetname):
388     fleet = get_object_or_404(Fleet, pk=fleetname)
389     if not FleetUser.objects.filter(fleet=fleetname, user=request.user.id).all():
390         return HttpResponseForbidden('<h1>Forbidden</h1>')
391     vessels = fleet.vessel.all()
392     return render_to_response('fleet_vessels.html', {'fleet':fleet, 'vessels': vessels}, RequestContext(request))
393
394
395 @http_authenticate(auth, 'ais')
396 def fleet_vessel_add(request, fleetname):
397     fleet = get_object_or_404(Fleet, pk=fleetname)
398     if not FleetUser.objects.filter(fleet=fleetname, user=request.user.id).all():
399         return HttpResponseForbidden('<h1>Forbidden</h1>')
400     strmmsi = request.REQUEST['mmsi']
401     mmsi = strmmsi_to_mmsi(strmmsi)
402     try:
403         vessel = Vessel.objects.get(pk=mmsi)
404     except Vessel.DoesNotExist:
405         return HttpResponse('No such vessel')
406     try:
407         FleetVessel(fleet=fleet, vessel=vessel).save()
408     except IntegrityError:
409         return HttpResponse('Integrity error: Is the ship already in that fleet?')
410     return HttpResponse('Done')
411
412
413 class FleetAddVessel(forms.Form):
414     mmsi = forms.CharField(help_text=u'Enter one MMSI per line', required=False, widget=forms.Textarea)
415     #name = forms.CharField(max_length=20, required=False)
416     #imo = forms.IntegerField(required=False)
417     #callsign = forms.CharField(max_length=7, required=False)
418     #destination = forms.CharField(max_length=20, required=False)
419     def clean(self):
420         cleaned_data = self.cleaned_data
421         for value in cleaned_data.values():
422             if value:
423                 return cleaned_data
424         raise forms.ValidationError("You must enter at least one criteria")
425
426 @http_authenticate(auth, 'ais')
427 def fleet_vessel_add2(request, fleetname):
428     fleet = get_object_or_404(Fleet, pk=fleetname)
429     if not FleetUser.objects.filter(fleet=fleetname, user=request.user.id).all():
430         return HttpResponseForbidden('<h1>Forbidden</h1>')
431     if request.method == 'POST' or request.META['QUERY_STRING']:
432         form = FleetAddVessel(request.REQUEST)
433         if form.is_valid():
434             data = form.cleaned_data
435             result = []
436             a_strmmsi = request.REQUEST['mmsi']
437             if a_strmmsi:
438                 for strmmsi in a_strmmsi.split('\n'):
439                     strmmsi = strmmsi.strip('\r')
440                     if not strmmsi:
441                         continue
442                     try:
443                         sqlmmsi = strmmsi_to_mmsi(strmmsi)
444                     except AssertionError:
445                         result.append('Invalid mmsi %s' % strmmsi)
446                         continue
447                     try:
448                         vessel = Vessel.objects.get(pk=sqlmmsi)
449                     except Vessel.DoesNotExist:
450                         result.append('No vessel with MMSI '+strmmsi)
451                         continue
452                     try:
453                         fv = FleetVessel.objects.get(fleet=fleet, vessel=vessel)
454                         result.append('Vessel with MMSI %s is already in that fleet' % strmmsi)
455                     except FleetVessel.DoesNotExist:
456                         FleetVessel(fleet=fleet, vessel=vessel).save()
457                         result.append('Vessel with MMSI %s added' % strmmsi)
458
459             return HttpResponse('<br>'.join(result))
460     else: # GET
461         form = FleetAddVessel()
462
463     return render_to_response('fleet_vessel_add.html', {'form': form, 'fleet': fleet}, RequestContext(request))
464
465
466 @http_authenticate(auth, 'ais')
467 def fleet_users(request, fleetname):
468     fleet = get_object_or_404(Fleet, pk=fleetname)
469     if not FleetUser.objects.filter(fleet=fleetname, user=request.user.id).all():
470         return HttpResponseForbidden('<h1>Forbidden</h1>')
471
472     message = u''
473     if request.method == 'POST' or request.META['QUERY_STRING']:
474         action = request.REQUEST['action']
475         userlogin = request.REQUEST['user']
476         try:
477             user = User.objects.get(login=userlogin)
478         except User.DoesNotExist:
479             message = u'User %s does not exist.' % userlogin
480         else:
481             if action == u'add':
482                 try:
483                     fu = FleetUser.objects.get(fleet=fleet, user=user)
484                     message = u'User %s already has access.' % user.login
485                 except FleetUser.DoesNotExist:
486                     FleetUser(fleet=fleet, user=user).save()
487                     message = u'Granted access to user %s.' % user.login
488             elif action == u'revoke':
489                 try:
490                     fu = FleetUser.objects.get(fleet=fleet, user=user)
491                     fu.delete()
492                     message = u'Revoked access to user %s.' % user.login
493                 except FleetUser.DoesNotExist:
494                     message = u'User %s didn\'t have access.' % user.login
495             else:
496                 message = u'Unknown action %s' % action
497
498     fleetusers = fleet.fleetuser_set.all()
499     otherusers = User.objects.exclude(id__in=[fu.user.id for fu in fleetusers]).order_by('name')
500     return render_to_response('fleet_users.html', {'fleet':fleet, 'fleetusers': fleetusers, 'otherusers': otherusers, 'message': message }, RequestContext(request))
501
502
503 @http_authenticate(auth, 'ais')
504 def fleet_lastpos(request, fleetname):
505     fleet = get_object_or_404(Fleet, pk=fleetname)
506     if not FleetUser.objects.filter(fleet=fleetname, user=request.user.id).all():
507         return HttpResponseForbidden('<h1>Forbidden</h1>')
508     fleet_uset = load_fleet_to_uset(fleetname)
509     # = set([mmsi_to_strmmsi(vessel.mmsi) for vessel in fleet.vessel.all()])
510     value = kml_to_kmz(format_fleet(fleet_uset, document_name=fleetname+' fleet').encode('utf-8'))
511     response = HttpResponse(value, mimetype="application/vnd.google-earth.kml")
512     response['Content-Disposition'] = 'attachment; filename=%s.kmz' % fleetname
513     return response
514
515
516 @http_authenticate(auth, 'ais')
517 def users(request):
518     users = User.objects.all()
519     for user in users:
520         user.admin_ok = user.is_admin_by(request.user.id)
521     return render_to_response('users.html', {'users':users}, RequestContext(request))
522
523
524 class UserEditForm(forms.Form):
525     login = forms.RegexField(regex=r'^[a-zA-Z0-9_]+$', max_length=16,
526         error_message ='Login must only contain letters, numbers and underscores')
527     name = forms.CharField(max_length=50)
528     email = forms.EmailField()
529     def __init__(self, *args, **kargs):
530         forms.Form.__init__(self, *args, **kargs)
531         self.old_login = kargs['initial']['login']
532     def clean_login(self):
533         new_login = self.cleaned_data['login']
534         if new_login != self.old_login:
535             if  User.objects.filter(login=new_login).count():
536                 raise forms.ValidationError("Sorry that login is already in use. Try another one.")
537         return new_login
538
539 @http_authenticate(auth, 'ais')
540 def user_detail(request, login):
541     user = get_object_or_404(User, login=login)
542     user.admin_ok = user.is_admin_by(request.user.id)
543     return render_to_response('user_detail.html', {'auser': user}, RequestContext(request))
544
545 @http_authenticate(auth, 'ais')
546 def user_edit(request, login):
547     initial = {}
548     if login:
549         user = get_object_or_404(User, login=login)
550         if not user.is_admin_by(request.user.id):
551             return HttpResponseForbidden('403 Forbidden')
552     else:
553         user = User()
554         user.father_id = request.user.id;
555     initial['login'] = user.login
556     initial['name'] = user.name
557     initial['email'] = user.email
558     if request.method == 'POST':
559         form = UserEditForm(request.POST, initial=initial)
560         if form.is_valid():
561             user.login = form.cleaned_data['login']
562             user.name = form.cleaned_data['name']
563             user.email = form.cleaned_data['email']
564             user.save()
565             return HttpResponseRedirect('/user/')
566     else: # GET
567         form = UserEditForm(initial=initial)
568
569     return render_to_response('user_edit.html', {'form':form, 'auser': user}, RequestContext(request))
570
571
572 class ChangePasswordForm(forms.Form):
573     new_password = forms.CharField(max_length=16, widget=forms.PasswordInput())
574     new_password_check = forms.CharField(max_length=16, widget=forms.PasswordInput())
575     def clean_generic_password(self, field_name):
576         password = self.cleaned_data[field_name]
577         try:
578             crack.FascistCheck(password)
579         except ValueError, err:
580             raise forms.ValidationError(err.message)
581         return password
582
583     def clean_new_password(self):
584         return self.clean_generic_password('new_password')
585     def clean_new_password_check(self):
586         return self.clean_generic_password('new_password_check')
587     def clean(self):
588         cleaned_data = self.cleaned_data
589         pass1 = cleaned_data.get('new_password')
590         pass2 = cleaned_data.get('new_password_check')
591         if pass1 != pass2:
592             self._errors['new_password_check'] = forms.util.ErrorList(['Passwords check must match'])
593             del cleaned_data['new_password_check']
594         return cleaned_data
595
596
597 @http_authenticate(auth, 'ais')
598 def user_change_password(request, login):
599     user = get_object_or_404(User, login=login)
600     if not user.is_admin_by(request.user.id):
601         return HttpResponseForbidden('403 Forbidden')
602     if request.method == 'POST':
603         form = ChangePasswordForm(request.POST)
604         if form.is_valid():
605             user.set_password(form.cleaned_data['new_password'])
606             user.save()
607             return HttpResponseRedirect('/user/')
608     else: # GET
609         form = ChangePasswordForm()
610     return render_to_response('user_change_password.html', {'form':form, 'auser':user}, RequestContext(request))
611
612
613 @http_authenticate(auth, 'ais')
614 def user_delete(request, login):
615     user = get_object_or_404(User, login=login)
616     if not user.is_admin_by(request.user.id):
617         return HttpResponseForbidden('403 Forbidden')
618     if request.REQUEST.get('confirm', None):
619         user.delete()
620         return HttpResponseRedirect('/user/')
621     return render_to_response('user_delete.html', {'form':None, 'auser':user}, RequestContext(request))
622
623
624 def logout(request):
625     # TODO
626     return HttpResponse('Not implemented')
627     #response = render_to_response('logout.html', {}, RequestContext(request))
628     #return response
629
630 @http_authenticate(auth, 'ais')
631 def sources(request):
632     sources = ( 'NMMT', 'NMKT', 'NMRW', 'NMEZ', 'NMAS', 'NMAH', 'NMBB' )
633     now = int(get_timestamp())
634     periods = ({
635         #'name_tiny': '2h',
636         #'name_long': '2 hours',
637         #'seconds': 2*60*60
638         #}, {
639         'name_tiny': '6h',
640         'name_long': '6 hours',
641         'seconds': 6*60*60,
642         }, {
643         #'name_tiny': '2d',
644         #'name_long': '2 days',
645         #'seconds': 2*24*60*60
646         #}, {
647         'name_tiny': '7d',
648         'name_long': '1 week',
649         'seconds': 7*24*60*60
650         })
651         
652     for source in sources:
653         for period in periods:
654             args = os.path.join(STATS_DIR, source+'-'+period['name_tiny']+'-bytes.png'), \
655                 '--lazy', \
656                 '-l', '0', \
657                 '--title', source+' - Bandwidth usage - '+period['name_long'], \
658                 '--start', '%d' % (now-period['seconds']), \
659                 '--end', '%d' % now, \
660                 '--vertical-label', 'bps', \
661                 'DEF:bytes=%s:bytes:AVERAGE' % os.path.join(STATS_DIR, source+'.rrd'), \
662                 'DEF:rawbytes=%s:rawbytes:AVERAGE' % os.path.join(STATS_DIR, source+'.rrd'), \
663                 'CDEF:bits=bytes,8,*', \
664                 'CDEF:rawbits=rawbytes,8,*', \
665                 'LINE:bits#00FF00:IP payload', \
666                 'LINE:rawbits#FF0000:IP with headers'
667             rrdtool.graph(*args)
668             args = os.path.join(STATS_DIR, source+'-'+period['name_tiny']+'-counts.png'), \
669                 '--lazy', \
670                 '-l', '0', \
671                 '--title', source+' - Packet\'izer stats - '+period['name_long'], \
672                 '--start', '%d' % (now-period['seconds']), \
673                 '--end', '%d' % now, \
674                 '--vertical-label', 'Hz', \
675                 'DEF:packets=%s:packets:AVERAGE' % os.path.join(STATS_DIR, source+'.rrd'), \
676                 'DEF:lines=%s:lines:AVERAGE' % os.path.join(STATS_DIR, source+'.rrd'), \
677                 'LINE:packets#FF0000:input packets', \
678                 'LINE:lines#00FF00:AIVDM lines'
679             rrdtool.graph(*args)
680
681     return render_to_response('sources.html', {'sources':sources, 'periods': periods}, RequestContext(request))