diff --git a/admin.py b/admin.py index 71199d1..c8cc9ee 100644 --- a/admin.py +++ b/admin.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, unused-argument + from django.contrib import admin from purple_robot_app.models import PurpleRobotConfiguration, PurpleRobotPayload, \ @@ -7,13 +9,15 @@ PurpleRobotDevice, PurpleRobotAlert, \ PurpleRobotDeviceNote + class PurpleRobotConfigurationAdmin(admin.ModelAdmin): list_display = ('name', 'slug', 'added') - list_filter = ['added',] + list_filter = ['added', ] search_fields = ['name', 'slug', 'contents'] admin.site.register(PurpleRobotConfiguration, PurpleRobotConfigurationAdmin) + class PurpleRobotPayloadAdmin(admin.ModelAdmin): list_display = ('user_id', 'added', 'process_tags',) list_filter = ['added', 'user_id'] @@ -21,6 +25,7 @@ class PurpleRobotPayloadAdmin(admin.ModelAdmin): admin.site.register(PurpleRobotPayload, PurpleRobotPayloadAdmin) + class PurpleRobotEventAdmin(admin.ModelAdmin): list_display = ('event', 'event_name', 'logged', 'user_id') list_filter = ['event', 'logged', 'user_id'] @@ -28,6 +33,7 @@ class PurpleRobotEventAdmin(admin.ModelAdmin): admin.site.register(PurpleRobotEvent, PurpleRobotEventAdmin) + class PurpleRobotReadingAdmin(admin.ModelAdmin): list_display = ('probe_name', 'user_id', 'guid', 'logged', 'size', 'attachment',) list_filter = ['probe', 'user_id', 'logged'] @@ -35,27 +41,31 @@ class PurpleRobotReadingAdmin(admin.ModelAdmin): admin.site.register(PurpleRobotReading, PurpleRobotReadingAdmin) + class PurpleRobotReportAdmin(admin.ModelAdmin): list_display = ('probe', 'user_id', 'generated', 'mime_type') list_filter = ['probe', 'user_id', 'generated', 'mime_type'] admin.site.register(PurpleRobotReport, PurpleRobotReportAdmin) + class PurpleRobotTestAdmin(admin.ModelAdmin): list_display = ('probe', 'user_id', 'slug', 'frequency', 'last_updated', 'active') list_filter = ['probe', 'user_id', 'last_updated', 'active'] - + admin.site.register(PurpleRobotTest, PurpleRobotTestAdmin) + class PurpleRobotExportJobAdmin(admin.ModelAdmin): list_display = ('start_date', 'end_date', 'destination', 'state') list_filter = ['start_date', 'end_date', 'state'] - + admin.site.register(PurpleRobotExportJob, PurpleRobotExportJobAdmin) + class PurpleRobotDeviceGroupAdmin(admin.ModelAdmin): - list_display = ('group_id', 'configuration',) - + list_display = ('name', 'group_id', 'configuration',) + admin.site.register(PurpleRobotDeviceGroup, PurpleRobotDeviceGroupAdmin) @@ -66,6 +76,7 @@ def clear_performance_metadata(modeladmin, request, queryset): clear_performance_metadata.description = 'Reset cached metadata' + def mute_alerts(modeladmin, request, queryset): for device in queryset: device.mute_alerts = True @@ -73,6 +84,7 @@ def mute_alerts(modeladmin, request, queryset): mute_alerts.description = 'Mute alerts' + def unmute_alerts(modeladmin, request, queryset): for device in queryset: device.mute_alerts = False @@ -81,21 +93,40 @@ def unmute_alerts(modeladmin, request, queryset): unmute_alerts.description = 'Unmute alerts' +def mark_testing(modeladmin, request, queryset): + for device in queryset: + device.test_device = True + device.save() + +mark_testing.description = 'Mark as test device' + + +def unmark_testing(modeladmin, request, queryset): + for device in queryset: + device.test_device = False + device.save() + +unmark_testing.description = 'Mark as regular device' + + class PurpleRobotDeviceAdmin(admin.ModelAdmin): - list_display = ('device_id', 'name', 'configuration', 'config_last_fetched', 'config_last_user_agent', 'hash_key', 'mute_alerts') - list_filter = ['device_group', 'configuration', 'mute_alerts'] - - actions = [ clear_performance_metadata, mute_alerts, unmute_alerts ] - + list_display = ('device_id', 'name', 'device_group', 'configuration', 'config_last_fetched', 'config_last_user_agent', 'hash_key', 'mute_alerts', 'test_device', ) # 'earliest_reading_date', 'latest_reading_date',) + list_filter = ['device_group', 'test_device', 'configuration', 'mute_alerts'] + search_fields = ['device_id', 'name', 'hash_key'] + + actions = [clear_performance_metadata, mute_alerts, unmute_alerts, mark_testing, unmark_testing] + admin.site.register(PurpleRobotDevice, PurpleRobotDeviceAdmin) + class PurpleRobotDeviceNoteAdmin(admin.ModelAdmin): list_display = ('device', 'added',) list_filter = ['added'] search_fields = ['note'] - + admin.site.register(PurpleRobotDeviceNote, PurpleRobotDeviceNoteAdmin) + class PurpleRobotAlertAdmin(admin.ModelAdmin): list_display = ('user_id', 'probe', 'tags', 'message', 'severity', 'generated', 'dismissed', 'manually_dismissed',) diff --git a/device_info.py b/device_info.py new file mode 100644 index 0000000..54afb94 --- /dev/null +++ b/device_info.py @@ -0,0 +1,45 @@ +# pylint: disable=line-too-long + +DEVICE_PROBES = { + 'LGE: LGLS740': { + 'name': 'LG Volt (LGLS740)', + 'missing_probes': [ + 'edu.northwestern.cbits.purple_robot_manager.probes.sensors.LightSensorProbe', + ] + }, + 'LGE: LGL34C': { + 'name': 'Optimus Fuel (LGL34C)', + 'missing_probes': [ + 'edu.northwestern.cbits.purple_robot_manager.probes.sensors.LightSensorProbe', + ] + }, + 'LGE: LGLS660': { + 'name': 'LG Tribute (LGLS660)', + 'missing_probes': [ + 'edu.northwestern.cbits.purple_robot_manager.probes.sensors.LightSensorProbe', + ] + }, + 'LGE: LGL41C': { + 'name': 'LG Ultimate 2 (LGL41C)', + 'missing_probes': [ + 'edu.northwestern.cbits.purple_robot_manager.probes.sensors.LightSensorProbe', + ] + } +} + + +def can_sense(manufacturer, model, probe): + if manufacturer is None or model is None or probe is None: + return None + + # Until contrary evidence surfaces, assume that all devices have all sensors. + + key = manufacturer + ': ' + model + + if key in DEVICE_PROBES: + device = DEVICE_PROBES[key] + + if probe in device['missing_probes']: + return False + + return True diff --git a/formatters/builtin_applicationlaunchprobe.py b/formatters/builtin_applicationlaunchprobe.py index 7523f01..28d533b 100644 --- a/formatters/builtin_applicationlaunchprobe.py +++ b/formatters/builtin_applicationlaunchprobe.py @@ -1,35 +1,37 @@ -import math +# pylint: disable=line-too-long, unused-argument + import json -from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) app = item['CURRENT_APP_NAME'] category = item['CURRENT_CATEGORY'] - + return app + ' (' + category + ')' + def visualize(probe_name, readings): return '' -''' - report = [] - - for reading in readings: - payload = json.loads(reading.payload) - - timestamp = payload['TIMESTAMP'] - device_active = payload['DEVICE_ACTIVE'] - - if device_active == True: - device_active = 1 - elif device_active == False: - device_active = 0 - - rep_dict = {} - rep_dict["y"] = device_active - rep_dict["x"] = timestamp - report.append(rep_dict) - - return render_to_string('visualization_device.html', { 'probe_name' : probe_name, 'readings' : readings, 'device_report' : json.dumps(report) }) -''' \ No newline at end of file + +# +# report = [] +# +# for reading in readings: +# payload = json.loads(reading.payload) +# +# timestamp = payload['TIMESTAMP'] +# device_active = payload['DEVICE_ACTIVE'] +# +# if device_active == True: +# device_active = 1 +# elif device_active == False: +# device_active = 0 +# +# rep_dict = {} +# rep_dict["y"] = device_active +# rep_dict["x"] = timestamp +# report.append(rep_dict) +# +# return render_to_string('visualization_device.html', { 'probe_name' : probe_name, 'readings' : readings, 'device_report' : json.dumps(report) }) diff --git a/formatters/builtin_batteryprobe.py b/formatters/builtin_batteryprobe.py index 1382ad8..e7a008d 100644 --- a/formatters/builtin_batteryprobe.py +++ b/formatters/builtin_batteryprobe.py @@ -1,20 +1,24 @@ +# pylint: disable=line-too-long, unused-argument + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + status = 'Unknown' - + if item['status'] == 2: status = 'Charging' elif item['status'] == 3: status = 'Discharging' elif item['status'] == 5: status = 'Full' - + return str(item['level']) + '% (' + status + ')' + def visualize(probe_name, readings): - return render_to_string('visualization_probe.html', { 'probe_name': probe_name, 'readings': readings }) + return render_to_string('visualization_probe.html', {'probe_name': probe_name, 'readings': readings}) diff --git a/formatters/builtin_callstateprobe.py b/formatters/builtin_callstateprobe.py index a1bc5cf..ac013a7 100644 --- a/formatters/builtin_callstateprobe.py +++ b/formatters/builtin_callstateprobe.py @@ -1,33 +1,36 @@ -import math +# pylint: disable=line-too-long, unused-argument + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) state = item['CALL_STATE'] - + return state + def visualize(probe_name, readings): report = [] - + for reading in readings: payload = json.loads(reading.payload) - + timestamp = payload['TIMESTAMP'] call_state = payload['CALL_STATE'] - + if call_state == "Off-Hook": call_state = 2 elif call_state == "Ringing": call_state = 1 elif call_state == "Idle": call_state = 0 - + rep_dict = {} rep_dict["y"] = call_state rep_dict["x"] = timestamp report.append(rep_dict) - - return render_to_string('visualization_callstate.html', { 'probe_name' : probe_name, 'readings' : readings, 'callstate_report' : json.dumps(report) }) + + return render_to_string('visualization_callstate.html', {'probe_name': probe_name, 'readings': readings, 'callstate_report': json.dumps(report)}) diff --git a/formatters/builtin_communicationeventprobe.py b/formatters/builtin_communicationeventprobe.py index db4f92e..d8073b7 100644 --- a/formatters/builtin_communicationeventprobe.py +++ b/formatters/builtin_communicationeventprobe.py @@ -1,71 +1,70 @@ +# pylint: disable=line-too-long, unused-argument + import datetime import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + comm_type = item["COMMUNICATION_TYPE"] comm_direction = item["COMMUNICATION_DIRECTION"] comm_timestamp = datetime.datetime.fromtimestamp((item["COMM_TIMESTAMP"]/1000)) - + if comm_type == "PHONE": duration = item["DURATION"] return "{0} - {1}: {2} - Duration: {3}".format(comm_timestamp, comm_direction.capitalize(), comm_type.capitalize(), duration) else: return "{0} - {1}: {2}".format(comm_timestamp, comm_direction.capitalize(), comm_type) -def visualize(probe_name, readings): + +def visualize(probe_name, readings): phone_out_report = [] phone_in_report = [] sms_out_report = [] sms_in_report = [] - + comm_subtype = 0 - + for reading in readings: payload = json.loads(reading.payload) - + timestamp = (payload["COMM_TIMESTAMP"])/1000 comm_type = payload["COMMUNICATION_TYPE"] comm_direct = payload["COMMUNICATION_DIRECTION"] - + subtype_dict = {} subtype_dict["x"] = timestamp - + if comm_type == "PHONE" and comm_direct == "OUTGOING": comm_type = 1 comm_subtype = 3 subtype_dict["y"] = comm_subtype phone_out_report.append(subtype_dict) - elif comm_type == "PHONE" and comm_direct == "INCOMING": comm_type = 1 comm_subtype = 2 subtype_dict["y"] = comm_subtype phone_in_report.append(subtype_dict) - elif comm_type == "SMS" and comm_direct == "OUTGOING": comm_type = 0 comm_subtype = 1 subtype_dict["y"] = comm_subtype sms_out_report.append(subtype_dict) - elif comm_type == "SMS" and comm_direct == "INCOMING": comm_type = 0 comm_subtype = 0 subtype_dict["y"] = comm_subtype sms_in_report.append(subtype_dict) - - return render_to_string( - 'visualization_communicationevent.html', - { - 'probe_name' : probe_name, - 'readings' : readings, - 'phone_out_report' : json.dumps(phone_out_report), - 'phone_in_report' : json.dumps(phone_in_report), - 'sms_out_report' : json.dumps(sms_out_report), - 'sms_in_report' : json.dumps(sms_in_report) - } - ) + + context = {'probe_name': probe_name, + 'readings': readings, + 'phone_out_report': json.dumps(phone_out_report), + 'phone_in_report': json.dumps(phone_in_report), + 'sms_out_report': json.dumps(sms_out_report), + 'sms_in_report': json.dumps(sms_in_report) + } + + return render_to_string('visualization_communicationevent.html', context) diff --git a/formatters/builtin_fusedlocationprobe.py b/formatters/builtin_fusedlocationprobe.py index 93ba18e..fa89c41 100644 --- a/formatters/builtin_fusedlocationprobe.py +++ b/formatters/builtin_fusedlocationprobe.py @@ -1,53 +1,57 @@ +# pylint: disable=line-too-long, unused-argument + import math import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + provider = item['PROVIDER'] - + if item['PROVIDER'] == 'fused': provider = 'Fused Provider' - + return provider + ': ' + str(item['LATITUDE']) + ', ' + str(item['LONGITUDE']) + def visualize(probe_name, readings): point_counter = {} - + for reading in readings: payload = json.loads(reading.payload) - + longitude = payload['LONGITUDE'] latitude = payload['LATITUDE'] - + key = str(round(longitude, 5)) + ',' + str(round(latitude, 5)) - + points = [] - + try: points = point_counter[key] except KeyError: pass - + points.append(payload) - + point_counter[key] = points - + report = [] - - for k,v in point_counter.iteritems(): - count = len(point_counter[k]) - latitude = round(point_counter[k][0]['LATITUDE'], 5) - longitude = round(point_counter[k][0]['LONGITUDE'], 5) - + + for key in point_counter.keys(): + count = len(point_counter[key]) + latitude = round(point_counter[key][0]['LATITUDE'], 5) + longitude = round(point_counter[key][0]['LONGITUDE'], 5) + report_item = {} - + report_item['count'] = 1 + math.log(count, 100) report_item['lat'] = latitude report_item['lng'] = longitude - + report.append(report_item) - - return render_to_string('visualization_fusedlocationprobe.html', { 'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) + + return render_to_string('visualization_fusedlocationprobe.html', {'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) diff --git a/formatters/builtin_locationprobe.py b/formatters/builtin_locationprobe.py index 5106b0d..1f98232 100644 --- a/formatters/builtin_locationprobe.py +++ b/formatters/builtin_locationprobe.py @@ -1,57 +1,61 @@ +# pylint: disable=line-too-long, unused-argument + import math import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + provider = item['PROVIDER'] - + if item['PROVIDER'] == 'fused': provider = 'Fused Provider' elif item['PROVIDER'] == 'network': provider = 'Network Provider' elif item['PROVIDER'] == 'gps': provider = 'GPS Provider' - + return provider + ': ' + str(item['LATITUDE']) + ', ' + str(item['LONGITUDE']) + def visualize(probe_name, readings): point_counter = {} - + for reading in readings: payload = json.loads(reading.payload) - + longitude = payload['LONGITUDE'] latitude = payload['LATITUDE'] - + key = str(round(longitude, 5)) + ',' + str(round(latitude, 5)) - + points = [] - + try: points = point_counter[key] except KeyError: pass - + points.append(payload) - + point_counter[key] = points - + report = [] - - for k,v in point_counter.iteritems(): - count = len(point_counter[k]) - latitude = round(point_counter[k][0]['LATITUDE'], 5) - longitude = round(point_counter[k][0]['LONGITUDE'], 5) - + + for key in point_counter.keys(): + count = len(point_counter[key]) + latitude = round(point_counter[key][0]['LATITUDE'], 5) + longitude = round(point_counter[key][0]['LONGITUDE'], 5) + report_item = {} - + report_item['count'] = 1 + math.log(count, 100) report_item['lat'] = latitude report_item['lng'] = longitude - + report.append(report_item) - - return render_to_string('visualization_fusedlocationprobe.html', { 'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) + + return render_to_string('visualization_fusedlocationprobe.html', {'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) diff --git a/formatters/builtin_rawlocationprobe.py b/formatters/builtin_rawlocationprobe.py index 93ba18e..fa89c41 100644 --- a/formatters/builtin_rawlocationprobe.py +++ b/formatters/builtin_rawlocationprobe.py @@ -1,53 +1,57 @@ +# pylint: disable=line-too-long, unused-argument + import math import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + provider = item['PROVIDER'] - + if item['PROVIDER'] == 'fused': provider = 'Fused Provider' - + return provider + ': ' + str(item['LATITUDE']) + ', ' + str(item['LONGITUDE']) + def visualize(probe_name, readings): point_counter = {} - + for reading in readings: payload = json.loads(reading.payload) - + longitude = payload['LONGITUDE'] latitude = payload['LATITUDE'] - + key = str(round(longitude, 5)) + ',' + str(round(latitude, 5)) - + points = [] - + try: points = point_counter[key] except KeyError: pass - + points.append(payload) - + point_counter[key] = points - + report = [] - - for k,v in point_counter.iteritems(): - count = len(point_counter[k]) - latitude = round(point_counter[k][0]['LATITUDE'], 5) - longitude = round(point_counter[k][0]['LONGITUDE'], 5) - + + for key in point_counter.keys(): + count = len(point_counter[key]) + latitude = round(point_counter[key][0]['LATITUDE'], 5) + longitude = round(point_counter[key][0]['LONGITUDE'], 5) + report_item = {} - + report_item['count'] = 1 + math.log(count, 100) report_item['lat'] = latitude report_item['lng'] = longitude - + report.append(report_item) - - return render_to_string('visualization_fusedlocationprobe.html', { 'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) + + return render_to_string('visualization_fusedlocationprobe.html', {'probe_name': probe_name, 'readings': readings, 'heat_report': json.dumps(report, indent=2)}) diff --git a/formatters/builtin_screenprobe.py b/formatters/builtin_screenprobe.py index 549779e..4200339 100644 --- a/formatters/builtin_screenprobe.py +++ b/formatters/builtin_screenprobe.py @@ -1,36 +1,39 @@ -import math +# pylint: disable=line-too-long, unused-argument + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) status = item['SCREEN_ACTIVE'] - + if item['SCREEN_ACTIVE'] is True: status = "Active" elif item['SCREEN_ACTIVE'] is False: status = "Inactive" - + return status + def visualize(probe_name, readings): report = [] - + for reading in readings: payload = json.loads(reading.payload) - + timestamp = payload['TIMESTAMP'] screen_active = payload['SCREEN_ACTIVE'] - - if screen_active == True: + + if screen_active is True: screen_active = 1 - elif screen_active == False: + elif screen_active is False: screen_active = 0 - + rep_dict = {} rep_dict["y"] = screen_active rep_dict["x"] = timestamp report.append(rep_dict) - - return render_to_string('visualization_screen.html', { 'probe_name' : probe_name, 'readings' : readings, 'screen_report' : json.dumps(report) }) + + return render_to_string('visualization_screen.html', {'probe_name': probe_name, 'readings': readings, 'screen_report': json.dumps(report)}) diff --git a/formatters/features_deviceinusefeature.py b/formatters/features_deviceinusefeature.py index 8635ee4..509d8d8 100644 --- a/formatters/features_deviceinusefeature.py +++ b/formatters/features_deviceinusefeature.py @@ -1,36 +1,39 @@ -import math +# pylint: disable=line-too-long, unused-argument + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) status = item['DEVICE_ACTIVE'] - + if item['DEVICE_ACTIVE'] is True: status = "Active" elif item['DEVICE_ACTIVE'] is False: status = "Inactive" - + return status + def visualize(probe_name, readings): report = [] - + for reading in readings: payload = json.loads(reading.payload) - + timestamp = payload['TIMESTAMP'] device_active = payload['DEVICE_ACTIVE'] - - if device_active == True: + + if device_active is True: device_active = 1 - elif device_active == False: + elif device_active is False: device_active = 0 - + rep_dict = {} rep_dict["y"] = device_active rep_dict["x"] = timestamp report.append(rep_dict) - - return render_to_string('visualization_device.html', { 'probe_name' : probe_name, 'readings' : readings, 'device_report' : json.dumps(report) }) + + return render_to_string('visualization_device.html', {'probe_name': probe_name, 'readings': readings, 'device_report': json.dumps(report)}) diff --git a/formatters/features_sunrisesunsetfeature.py b/formatters/features_sunrisesunsetfeature.py index 67ddd05..6990c9d 100644 --- a/formatters/features_sunrisesunsetfeature.py +++ b/formatters/features_sunrisesunsetfeature.py @@ -1,58 +1,60 @@ +# pylint: disable=line-too-long, unused-argument + import datetime import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) timestamp = datetime.datetime.fromtimestamp(item["TIMESTAMP"]) sunrise = datetime.datetime.fromtimestamp((item["SUNRISE"]/1000)) sunset = datetime.datetime.fromtimestamp((item["SUNSET"]/1000)) - + day_duration = (item["DAY_DURATION"]/1000)/3600.0 - + if item["IS_DAY"] == True: sunlight_hrs = (timestamp - sunrise).seconds / 3600.0 return "{0} -> {1}, Daylight hours: {2:.1f} - Hours after sunrise: {3:.1f}".format(str(sunrise)[10:], str(sunset)[10:], day_duration, sunlight_hrs) else: night_hrs = (timestamp - sunset).seconds / 3600.0 return "{0} -> {1}, Daylight hours: {2:.1f} - Hours after sunset: {3:.1f}".format(str(sunrise)[10:], str(sunset)[10:], day_duration, night_hrs) - + + def visualize(probe_name, readings): sun_report = [] daylight_report = [] - + for reading in readings: payload = json.loads(reading.payload) - + timestamp = payload["TIMESTAMP"] sunrise = datetime.datetime.fromtimestamp((payload["SUNRISE"]/1000)) sunset = datetime.datetime.fromtimestamp((payload["SUNSET"]/1000)) day_duration = (payload["DAY_DURATION"]/1000)/3600 - + sun_ref = 0 if payload["IS_DAY"] == True: sun_ref = (datetime.datetime.fromtimestamp(timestamp) - sunrise).seconds / 3600.0 else: sun_ref = -(datetime.datetime.fromtimestamp(timestamp) - sunset).seconds / 3600.0 - + sun_dict = {} sun_dict["y"] = sun_ref sun_dict["x"] = timestamp sun_report.append(sun_dict) - + daylight_dict = {} daylight_dict["y"] = day_duration daylight_dict["x"] = timestamp daylight_report.append(daylight_dict) - return render_to_string( - 'visualization_sunrisesunset.html', - { - 'probe_name' : probe_name, - 'readings' : readings, - 'sun_report' : json.dumps(sun_report), - 'daylight_report' : json.dumps(daylight_report) - } - ) + context = {'probe_name': probe_name, + 'readings': readings, + 'sun_report': json.dumps(sun_report), + 'daylight_report': json.dumps(daylight_report) + } + + return render_to_string('visualization_sunrisesunset.html', context) diff --git a/formatters/probe.py b/formatters/probe.py index 71deb90..9eb2516 100644 --- a/formatters/probe.py +++ b/formatters/probe.py @@ -1,14 +1,18 @@ +# pylint: disable=line-too-long + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + if 'PROBE_DISPLAY_MESSAGE' in item: return item['PROBE_DISPLAY_MESSAGE'] - + return probe_name + ': TODO' + def visualize(probe_name, readings): - return render_to_string('visualization_probe.html', { 'probe_name': probe_name, 'readings': readings }) + return render_to_string('visualization_probe.html', {'probe_name': probe_name, 'readings': readings}) diff --git a/formatters/services_fitbitbetaprobe.py b/formatters/services_fitbitbetaprobe.py index 4eb25ea..5744c7b 100644 --- a/formatters/services_fitbitbetaprobe.py +++ b/formatters/services_fitbitbetaprobe.py @@ -1,17 +1,20 @@ +# pylint: disable=line-too-long, unused-argument + import json from django.template.loader import render_to_string -def format(probe_name, json_payload): + +def format_reading(probe_name, json_payload): item = json.loads(json_payload) - + heart = '?' steps = '?' distance = '?' calories = '?' floors = '?' elevation = '?' - + if 'HEART' in item: heart = str(len(item['HEART'])) @@ -30,7 +33,8 @@ def format(probe_name, json_payload): if 'ELEVATION' in item: elevation = str(len(item['ELEVATION'])) - return 'Heart: ' + heart + ' Elevation: ' + elevation + ' Distance: ' + distance + ' Steps: ' + steps + ' Floors: ' + floors + ' Calories: ' + calories + return 'Heart: ' + heart + ' Elevation: ' + elevation + ' Distance: ' + distance + ' Steps: ' + steps + ' Floors: ' + floors + ' Calories: ' + calories + def visualize(probe_name, readings): heart = [] @@ -39,44 +43,44 @@ def visualize(probe_name, readings): calories = [] floors = [] elevation = [] - + for reading in readings: item = json.loads(reading.payload) - + if 'HEART' in item: for i in range(0, len(item['HEART'])): - plot = { 'x': item['HEART_TIMESTAMPS'][i] / 1000, 'y': item['HEART'][i] } - + plot = {'x': item['HEART_TIMESTAMPS'][i] / 1000, 'y': item['HEART'][i]} + heart.append(plot) if 'STEPS' in item: for i in range(0, len(item['STEPS'])): - plot = { 'x': item['STEP_TIMESTAMPS'][i] / 1000, 'y': item['STEPS'][i] } - + plot = {'x': item['STEP_TIMESTAMPS'][i] / 1000, 'y': item['STEPS'][i]} + steps.append(plot) if 'FLOORS' in item: for i in range(0, len(item['FLOORS'])): - plot = { 'x': item['FLOORS_TIMESTAMPS'][i] / 1000, 'y': item['FLOORS'][i] } - + plot = {'x': item['FLOORS_TIMESTAMPS'][i] / 1000, 'y': item['FLOORS'][i]} + floors.append(plot) if 'ELEVATION' in item: for i in range(0, len(item['ELEVATION'])): - plot = { 'x': item['ELEVATION_TIMESTAMPS'][i] / 1000, 'y': item['ELEVATION'][i] } - + plot = {'x': item['ELEVATION_TIMESTAMPS'][i] / 1000, 'y': item['ELEVATION'][i]} + elevation.append(plot) if 'CALORIES' in item: for i in range(0, len(item['CALORIES'])): - plot = { 'x': item['CALORIES_TIMESTAMPS'][i] / 1000, 'y': item['CALORIES'][i] } - + plot = {'x': item['CALORIES_TIMESTAMPS'][i] / 1000, 'y': item['CALORIES'][i]} + calories.append(plot) if 'DISTANCE' in item: for i in range(0, len(item['DISTANCE'])): - plot = { 'x': item['DISTANCE_TIMESTAMPS'][i] / 1000, 'y': item['DISTANCE'][i] } - + plot = {'x': item['DISTANCE_TIMESTAMPS'][i] / 1000, 'y': item['DISTANCE'][i]} + distance.append(plot) heart.sort(key=lambda o: o['x']) @@ -85,5 +89,5 @@ def visualize(probe_name, readings): elevation.sort(key=lambda o: o['x']) calories.sort(key=lambda o: o['x']) distance.sort(key=lambda o: o['x']) - - return render_to_string('visualization_fitbit.html', { 'probe_name': probe_name, 'distance': json.dumps(distance), 'heart': json.dumps(heart), 'steps': json.dumps(steps), 'floors': json.dumps(floors), 'elevation': json.dumps(elevation), 'calories': json.dumps(calories) }) + + return render_to_string('visualization_fitbit.html', {'probe_name': probe_name, 'distance': json.dumps(distance), 'heart': json.dumps(heart), 'steps': json.dumps(steps), 'floors': json.dumps(floors), 'elevation': json.dumps(elevation), 'calories': json.dumps(calories)}) diff --git a/forms.py b/forms.py index 8b8d4d7..513d3f5 100644 --- a/forms.py +++ b/forms.py @@ -1,21 +1,23 @@ +# pylint: disable=line-too-long, no-member + from django import forms -from models import PurpleRobotReading +from purple_robot_app.models import PurpleRobotReading + class ExportJobForm(forms.Form): start_date = forms.DateTimeField() end_date = forms.DateTimeField() destination = forms.EmailField() - + def __init__(self, *args, **kwargs): super(ExportJobForm, self).__init__(*args, **kwargs) - + probes = [] - + for probe in PurpleRobotReading.objects.order_by('probe').values_list('probe', flat=True).distinct(): tokens = probe.split('.') - probes.append((probe, tokens[-1])) self.fields['probes'] = forms.MultipleChoiceField(choices=probes, widget=forms.CheckboxSelectMultiple()) diff --git a/management/commands/analytics/__init__.py b/management/commands/analytics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/management/commands/analytics/generic_probe.py b/management/commands/analytics/generic_probe.py new file mode 100644 index 0000000..0d79585 --- /dev/null +++ b/management/commands/analytics/generic_probe.py @@ -0,0 +1,14 @@ +# pylint: disable=line-too-long + +import datetime + +from django.utils import timezone + +from purple_robot_app.performance import append_performance_sample + + +def log_reading(reading): + yesterday = timezone.now() - datetime.timedelta(days=1) + + if reading.logged > yesterday: + append_performance_sample(reading.user_id, reading.probe, reading.logged, {'sample_count': 1}) diff --git a/management/commands/check_user_data.py b/management/commands/check_user_data.py new file mode 100644 index 0000000..9e7462c --- /dev/null +++ b/management/commands/check_user_data.py @@ -0,0 +1,37 @@ +# pylint: disable=line-too-long, no-member + +import hashlib + +from django.core.management.base import BaseCommand + +from purple_robot_app.models import PurpleRobotReading + + +class Command(BaseCommand): + def handle(self, *args, **options): + user_file = open(args[0], 'r') + + for line in user_file: + user_id = line.strip() + + if len(user_id) > 0: + # readings = PurpleRobotReading.objects.filter(user_id=user_id) + # print(user_id + ': ' + str(readings.count())) + + md5_hash = hashlib.md5() + md5_hash.update(user_id) + + md5_id = md5_hash.hexdigest() + + readings = PurpleRobotReading.objects.filter(user_id=md5_id) + + print user_id + ' (MD5 - ' + md5_id + '): ' + str(readings.count()) + + # m = hashlib.sha256() + # m.update(user_id) + # + # sha256_id = m.hexdigest() + # + # readings = PurpleRobotReading.objects.filter(user_id=sha256_id) + # + # print(user_id + ' (SHA-256 - ' + sha256_id + '): ' + str(readings.count())) diff --git a/management/commands/clean_database.py b/management/commands/clean_database.py index 74325bc..8aede47 100644 --- a/management/commands/clean_database.py +++ b/management/commands/clean_database.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime from django.core.management.base import BaseCommand @@ -8,11 +10,12 @@ DAYS_KEPT = 21 + class Command(BaseCommand): def handle(self, *args, **options): now = timezone.now() start = now - datetime.timedelta(DAYS_KEPT) - + PurpleRobotReading.objects.filter(logged__lte=start).delete() PurpleRobotEvent.objects.filter(logged__lte=start).delete() PurpleRobotPayload.objects.filter(added__lte=start).delete() diff --git a/management/commands/clean_old_reports.py b/management/commands/clean_old_reports.py index f706231..c8895dd 100644 --- a/management/commands/clean_old_reports.py +++ b/management/commands/clean_old_reports.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime from django.core.management.base import BaseCommand @@ -7,9 +9,10 @@ PROBE_NAME = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.SignificantMotionProbe' + class Command(BaseCommand): def handle(self, *args, **options): now = timezone.now() start = now - datetime.timedelta(1) - + PurpleRobotReport.objects.filter(generated__lte=start).delete() diff --git a/management/commands/clear_payload_skip_tags.py b/management/commands/clear_payload_skip_tags.py new file mode 100644 index 0000000..343b715 --- /dev/null +++ b/management/commands/clear_payload_skip_tags.py @@ -0,0 +1,60 @@ +# pylint: disable=line-too-long, no-member + +import datetime +import requests +import os + +from django.core.management.base import BaseCommand + +from purple_robot_app.models import PurpleRobotPayload + +PRINT_PROGRESS = False + +SKIP_TAG = 'extracted_into_database_skip' + + +def touch(fname, mode=0o666): + flags = os.O_CREAT | os.O_APPEND + + if os.fdopen(os.open(fname, flags, mode)) is not None: + os.utime(fname, None) + + +class Command(BaseCommand): + def handle(self, *args, **options): + if os.access('/tmp/clear_payload_skip_tags.lock', os.R_OK): + timestamp = os.path.getmtime('/tmp/clear_payload_skip_tags.lock') + created = datetime.datetime.fromtimestamp(timestamp) + + if (datetime.datetime.now() - created).total_seconds() > 300: + print 'clear_payload_skip_tags: Stale lock - removing...' + os.remove('/tmp/clear_payload_skip_tags.lock') + else: + return + + touch('/tmp/clear_payload_skip_tags.lock') + + requests.packages.urllib3.disable_warnings() + + payloads = list(PurpleRobotPayload.objects.filter(process_tags__contains=SKIP_TAG).order_by('-added')[:250]) + + count = 0 + + while len(payloads) > 0 and count < 20: + count += 1 + + for payload in payloads: + payload.process_tags = payload.process_tags.replace(SKIP_TAG, '') + + while payload.process_tags.find(' ') != -1: + payload.process_tags = payload.process_tags.replace(' ', ' ') + + payload.process_tags = payload.process_tags.strip() + + touch('/tmp/clear_payload_skip_tags.lock') + + payload.save() + + payloads = list(PurpleRobotPayload.objects.filter(process_tags__contains=SKIP_TAG).order_by('-added')[:250]) + + os.remove('/tmp/clear_payload_skip_tags.lock') diff --git a/management/commands/compile_accelerometer_report.py b/management/commands/compile_accelerometer_report.py index 1db8130..e75a966 100644 --- a/management/commands/compile_accelerometer_report.py +++ b/management/commands/compile_accelerometer_report.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import gzip import json @@ -14,10 +16,10 @@ PROBE_NAME = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.AccelerometerProbe' + class Command(BaseCommand): def handle(self, *args, **options): hashes = REPORT_DEVICES - # start = datetime.datetime.now() - datetime.timedelta(days=120) start_ts = datetime.datetime(2015, 7, 3, 5, 0, 0, 0, tzinfo=pytz.timezone('US/Central')) end_ts = start_ts + datetime.timedelta(hours=1) @@ -27,95 +29,95 @@ def handle(self, *args, **options): for user_hash in hashes: payloads = PurpleRobotReading.objects.filter(user_id=user_hash, probe=PROBE_NAME, logged__gte=start_ts, logged__lt=end_ts).order_by('logged') - + count = payloads.count() - + if count > 0: temp_file = tempfile.TemporaryFile() gzf = gzip.GzipFile(mode='wb', fileobj=temp_file) gzf.write('User ID\tSensor Timestamp\tNormalized Timestamp\tCPU Timestamp\tX\tY\tZ\n') - + index = 0 - + last_sensor = sys.maxint base_ts = 0 - + while index < count: end = index + 100 - + if end > count: end = count - + last_sensor = sys.maxint base_ts = 0 - + for payload in payloads[index:end]: reading_json = json.loads(payload.payload) - - ns = [] - ss = [] - ts = [] - xs = [] - ys = [] - zs = [] - + + normal_times = [] + sensor_times = [] + cpu_times = [] + x_readings = [] + y_readings = [] + z_readings = [] + has_sensor = False - + if 'SENSOR_TIMESTAMP' in reading_json: has_sensor = True - - for s in reading_json['SENSOR_TIMESTAMP']: - ss.append(s) - for t in reading_json['EVENT_TIMESTAMP']: - ts.append(t) - + for sensor_time in reading_json['SENSOR_TIMESTAMP']: + sensor_times.append(sensor_time) + + for event_time in reading_json['EVENT_TIMESTAMP']: + cpu_times.append(event_time) + if has_sensor is False: - ss.append(-1) - ns.append(-1) - + sensor_times.append(-1) + normal_times.append(-1) + if has_sensor: - for i in range(0, len(ss)): - sensor_ts = float(ss[i]) - - normalized_ts = sensor_ts / (1000 * 1000 * 1000) - + for i in range(0, len(sensor_times)): + sensor_ts = float(sensor_times[i]) + + normalized_ts = sensor_ts / (1000 * 1000 * 1000) + if normalized_ts < last_sensor: - cpu_time = ts[i] - + cpu_time = cpu_times[i] + base_ts = cpu_time - normalized_ts - - ns.append(base_ts + normalized_ts) - + + normal_times.append(base_ts + normalized_ts) + last_sensor = normalized_ts - - for x in reading_json['X']: - xs.append(x) - - for y in reading_json['Y']: - ys.append(y) - - for z in reading_json['Z']: - zs.append(z) - - for i in range(0, len(ts)): - x = xs[i] - y = ys[i] - z = zs[i] - t = ts[i] - s = ss[i] - n = ns[i] - - gzf.write(user_hash + '\t' + str(s) + '\t' + str(n) + '\t' + str(t) + '\t' + str(x) + '\t' + str(y) + '\t' + str(z) + '\n') - + + for x_reading in reading_json['X']: + x_readings.append(x_reading) + + for y_reading in reading_json['Y']: + y_readings.append(y_reading) + + for z_reading in reading_json['Z']: + z_readings.append(z_reading) + + for i in range(0, len(cpu_times)): + x_reading = x_readings[i] + y_reading = y_readings[i] + z_reading = z_readings[i] + cpu_time = cpu_times[i] + sensor_time = sensor_times[i] + normal_time = normal_times[i] + + gzf.write(user_hash + '\t' + str(sensor_time) + '\t' + str(normal_time) + '\t' + str(cpu_time) + '\t' + str(x_reading) + '\t' + str(y_reading) + '\t' + str(z_reading) + '\n') + index += 100 - + gzf.flush() gzf.close() - + temp_file.seek(0) - + report = PurpleRobotReport(generated=timezone.now(), mime_type='application/x-gzip', probe=PROBE_NAME, user_id=user_hash) report.save() report.report_file.save(user_hash + '-accelerometer.txt.gz', File(temp_file)) diff --git a/management/commands/compile_barometer_report.py b/management/commands/compile_barometer_report.py index 53da195..6ab902a 100644 --- a/management/commands/compile_barometer_report.py +++ b/management/commands/compile_barometer_report.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import gzip import json @@ -14,9 +16,10 @@ PROBE_NAME = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.PressureProbe' + class Command(BaseCommand): def handle(self, *args, **options): - hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() + hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() # start = datetime.datetime.now() - datetime.timedelta(days=120) start_ts = datetime.datetime(2015, 7, 3, 5, 0, 0, 0, tzinfo=pytz.timezone('US/Central')) @@ -31,81 +34,81 @@ def handle(self, *args, **options): if count > 0: temp_file = tempfile.TemporaryFile() gzf = gzip.GzipFile(mode='wb', fileobj=temp_file) - + gzf.write('User ID\tSensor Timestamp\ttNormalized Timestamp\tCPU Timestamp\tAltitude\tPressure\n') - + index = 0 last_sensor = sys.maxint base_ts = 0 - + while index < count: end = index + 100 - + if end > count: end = count - + for payload in payloads[index:end]: reading_json = json.loads(payload.payload) - - ns = [] - ss = [] - ts = [] - xs = [] - ys = [] - + + normal_times = [] + sensor_times = [] + cpu_times = [] + x_readings = [] + y_readings = [] + has_sensor = False - + if 'SENSOR_TIMESTAMP' in reading_json: has_sensor = True - - for s in reading_json['SENSOR_TIMESTAMP']: - ss.append(s) - for t in reading_json['EVENT_TIMESTAMP']: - ts.append(t) - + for sensor_time in reading_json['SENSOR_TIMESTAMP']: + sensor_times.append(sensor_time) + + for event_time in reading_json['EVENT_TIMESTAMP']: + cpu_times.append(event_time) + if has_sensor is False: - ss.append(-1) - ns.append(-1) + sensor_times.append(-1) + normal_times.append(-1) if has_sensor: - for i in range(0, len(ss)): - sensor_ts = float(ss[i]) - - normalized_ts = sensor_ts / (1000 * 1000 * 1000) - + for i in range(0, len(sensor_times)): + sensor_ts = float(sensor_times[i]) + + normalized_ts = sensor_ts / (1000 * 1000 * 1000) + if normalized_ts < last_sensor: - cpu_time = ts[i] - + cpu_time = cpu_times[i] + base_ts = cpu_time - normalized_ts - - ns.append(base_ts + normalized_ts) - + + normal_times.append(base_ts + normalized_ts) + last_sensor = normalized_ts - - for x in reading_json['ALTITUDE']: - xs.append(x) - - for y in reading_json['PRESSURE']: - ys.append(y) - - for i in range(0, len(ts)): - x = xs[i] - y = ys[i] - t = ts[i] - s = ss[i] - n = ns[i] - - gzf.write(user_hash + '\t' + str(s) + '\t' + str(n) + '\t' + str(t) + '\t' + str(x) + '\t' + str(y) + '\n') - + + for x_reading in reading_json['ALTITUDE']: + x_readings.append(x_reading) + + for y_reading in reading_json['PRESSURE']: + y_readings.append(y_reading) + + for i in range(0, len(cpu_times)): + x_reading = x_readings[i] + y_reading = y_readings[i] + cpu_time = cpu_times[i] + sensor_time = sensor_times[i] + normal_time = normal_times[i] + + gzf.write(user_hash + '\t' + str(sensor_time) + '\t' + str(normal_time) + '\t' + str(cpu_time) + '\t' + str(x_reading) + '\t' + str(y_reading) + '\n') + index += 100 - + gzf.flush() gzf.close() - + temp_file.seek(0) - + report = PurpleRobotReport(generated=timezone.now(), mime_type='application/x-gzip', probe=PROBE_NAME, user_id=user_hash) report.save() report.report_file.save(user_hash + '-barometer.txt.gz', File(temp_file)) diff --git a/management/commands/compile_gyroscope_report.py b/management/commands/compile_gyroscope_report.py index 28b561d..e339b7f 100644 --- a/management/commands/compile_gyroscope_report.py +++ b/management/commands/compile_gyroscope_report.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import gzip import json @@ -14,6 +16,7 @@ PROBE_NAME = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.GyroscopeProbe' + class Command(BaseCommand): def handle(self, *args, **options): hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() @@ -26,92 +29,90 @@ def handle(self, *args, **options): # hash = hash['user_id'] payloads = PurpleRobotReading.objects.filter(user_id=user_hash, probe=PROBE_NAME, logged__gte=start_ts, logged__lt=end_ts).order_by('logged') - count = payloads.count() if count > 0: temp_file = tempfile.TemporaryFile() gzf = gzip.GzipFile(mode='wb', fileobj=temp_file) gzf.write('User ID\tSensor Timestamp\tNormalized Timestamp\tCPU Timestamp\tX\tY\tZ\n') - + index = 0 - + last_sensor = sys.maxint base_ts = 0 while index < count: end = index + 100 - + if end > count: end = count - - + for payload in payloads[index:end]: reading_json = json.loads(payload.payload) - - ns = [] - ss = [] - ts = [] - xs = [] - ys = [] - zs = [] - + + normal_times = [] + sensor_times = [] + cpu_times = [] + x_readings = [] + y_readings = [] + z_readings = [] + has_sensor = False - + if 'SENSOR_TIMESTAMP' in reading_json: has_sensor = True - - for s in reading_json['SENSOR_TIMESTAMP']: - ss.append(s) - for t in reading_json['EVENT_TIMESTAMP']: - ts.append(t) - + for sensor_time in reading_json['SENSOR_TIMESTAMP']: + sensor_times.append(sensor_times) + + for event_time in reading_json['EVENT_TIMESTAMP']: + cpu_times.append(event_time) + if has_sensor is False: - ss.append(-1) - ns.append(-1) - + sensor_times.append(-1) + normal_times.append(-1) + if has_sensor: - for i in range(0, len(ss)): - sensor_ts = float(ss[i]) - - normalized_ts = sensor_ts / (1000 * 1000 * 1000) - + for i in range(0, len(sensor_times)): + sensor_ts = float(sensor_times[i]) + + normalized_ts = sensor_ts / (1000 * 1000 * 1000) + if normalized_ts < last_sensor: - cpu_time = ts[i] - + cpu_time = cpu_times[i] + base_ts = cpu_time - normalized_ts - - ns.append(base_ts + normalized_ts) - + + normal_times.append(base_ts + normalized_ts) + last_sensor = normalized_ts - - for x in reading_json['X']: - xs.append(x) - - for y in reading_json['Y']: - ys.append(y) - - for z in reading_json['Z']: - zs.append(z) - - for i in range(0, len(ts)): - x = xs[i] - y = ys[i] - z = zs[i] - t = ts[i] - s = ss[i] - n = ns[i] - - gzf.write(user_hash + '\t' + str(s) + '\t' + str(n) + '\t' + str(t) + '\t' + str(x) + '\t' + str(y) + '\t' + str(z) + '\n') - + + for x_reading in reading_json['X']: + x_readings.append(x_reading) + + for y_reading in reading_json['Y']: + y_readings.append(y_reading) + + for z_reading in reading_json['Z']: + z_readings.append(z_reading) + + for i in range(0, len(cpu_times)): + x_reading = x_readings[i] + y_reading = y_readings[i] + z_reading = z_readings[i] + cpu_time = cpu_times[i] + sensor_time = sensor_times[i] + normal_time = normal_times[i] + + gzf.write(user_hash + '\t' + str(sensor_time) + '\t' + str(normal_time) + '\t' + str(cpu_time) + '\t' + str(x_reading) + '\t' + str(y_reading) + '\t' + str(z_reading) + '\n') + index += 100 - + gzf.flush() gzf.close() - + temp_file.seek(0) - + report = PurpleRobotReport(generated=timezone.now(), mime_type='application/x-gzip', probe=PROBE_NAME, user_id=user_hash) report.save() report.report_file.save(user_hash + '-gyroscope.txt.gz', File(temp_file)) diff --git a/management/commands/compile_labels_report.py b/management/commands/compile_labels_report.py index 43b872f..f9de932 100644 --- a/management/commands/compile_labels_report.py +++ b/management/commands/compile_labels_report.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import gzip import json @@ -10,25 +12,23 @@ from django.utils.text import slugify from purple_robot_app.models import PurpleRobotReading, PurpleRobotReport - from purple_robot.settings import REPORT_DEVICES + class Command(BaseCommand): def handle(self, *args, **options): - hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() + hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() # start = datetime.datetime.now() - datetime.timedelta(days=120) start_ts = datetime.datetime(2015, 11, 10, 0, 0, 0, 0, tzinfo=pytz.timezone('US/Central')) end_ts = start_ts + datetime.timedelta(days=1) labels = PurpleRobotReading.objects.exclude(probe__startswith='edu.northwestern').values('probe').distinct() - + for user_hash in hashes: for label in labels: slug_label = slugify(label['probe']) - payloads = PurpleRobotReading.objects.filter(user_id=user_hash, probe=label['probe'], logged__gte=start_ts, logged__lt=end_ts).order_by('logged') - count = payloads.count() if count > 0: @@ -36,27 +36,27 @@ def handle(self, *args, **options): gzf = gzip.GzipFile(mode='wb', fileobj=temp_file) gzf.write('User ID\tTimestamp\tValue\n') - + index = 0 - + while index < count: end = index + 100 - + if end > count: end = count - + for payload in payloads[index:end]: reading_json = json.loads(payload.payload) gzf.write(user_hash + '\t' + str(reading_json['TIMESTAMP']) + '\t' + reading_json['FEATURE_VALUE'] + '\n') - + index += 100 - + gzf.flush() gzf.close() - + temp_file.seek(0) - + report = PurpleRobotReport(generated=timezone.now(), mime_type='application/x-gzip', probe=slug_label, user_id=hash) report.save() report.report_file.save(user_hash + '-' + slug_label + '.txt.gz', File(temp_file)) diff --git a/management/commands/compile_significant_motion_report.py b/management/commands/compile_significant_motion_report.py index e1bdee2..8eb1d2d 100644 --- a/management/commands/compile_significant_motion_report.py +++ b/management/commands/compile_significant_motion_report.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import gzip import json @@ -13,6 +15,7 @@ PROBE_NAME = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.SignificantMotionProbe' + class Command(BaseCommand): def handle(self, *args, **options): hashes = REPORT_DEVICES # PurpleRobotPayload.objects.order_by().values('user_id').distinct() @@ -28,29 +31,29 @@ def handle(self, *args, **options): if count > 0: temp_file = tempfile.TemporaryFile() gzf = gzip.GzipFile(mode='wb', fileobj=temp_file) - + gzf.write('User ID\tTimestamp\n') - + index = 0 - + while index < count: end = index + 100 - + if end > count: end = count - + for payload in payloads[index:end]: reading_json = json.loads(payload.payload) - + gzf.write(hash + '\t' + str(reading_json['TIMESTAMP']) + '\n') - + index += 100 - + gzf.flush() gzf.close() - + temp_file.seek(0) - + report = PurpleRobotReport(generated=timezone.now(), mime_type='application/x-gzip', probe=PROBE_NAME, user_id=user_hash) report.save() report.report_file.save(hash + '-significant-motion.txt.gz', File(temp_file)) diff --git a/management/commands/delete_duplicate_readings.py b/management/commands/delete_duplicate_readings.py index 7201e75..b1712a1 100644 --- a/management/commands/delete_duplicate_readings.py +++ b/management/commands/delete_duplicate_readings.py @@ -1,48 +1,45 @@ +# pylint: disable=line-too-long, no-member + import datetime -import json import os from django.core.management.base import BaseCommand from purple_robot_app.models import PurpleRobotReading, PurpleRobotDevice -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): if True: - return # Disable this command! - + return # Disable this command! + if os.access('/tmp/delete_duplicate_readings.lock', os.R_OK): - t = os.path.getmtime('/tmp/delete_duplicate_readings.lock') - created = datetime.datetime.fromtimestamp(t) - + timestamp = os.path.getmtime('/tmp/delete_duplicate_readings.lock') + created = datetime.datetime.fromtimestamp(timestamp) + if (datetime.datetime.now() - created).total_seconds() > 120: - print('delete_duplicate_readings: Stale lock - removing...') + print 'delete_duplicate_readings: Stale lock - removing...' os.remove('/tmp/delete_duplicate_readings.lock') else: return touch('/tmp/delete_duplicate_readings.lock') - - print('1') + for device in PurpleRobotDevice.objects.all().order_by('device_id'): - print('2 ' + device.device_id) guids = PurpleRobotReading.objects.filter(user_id=device.hash_key).order_by('guid').values_list('guid', flat=True).distinct() - print('3 ' + device.device_id + ' -- ' + str(len(guids))) - for guid in guids: - if guid != None: + if guid is not None: count = PurpleRobotReading.objects.filter(guid=guid, user_id=device.hash_key).count() - - if count > 1: - print('4 ' + guid + ' -- ' + str(count)) + if count > 1: for match in PurpleRobotReading.objects.filter(guid=guid)[1:]: match.delete() diff --git a/management/commands/delete_older_data.py b/management/commands/delete_older_data.py index 8730833..2b11bc2 100644 --- a/management/commands/delete_older_data.py +++ b/management/commands/delete_older_data.py @@ -1,35 +1,38 @@ +# pylint: disable=line-too-long, no-member + import datetime -import json import os from django.core.management.base import BaseCommand from purple_robot_app.models import PurpleRobotReading, PurpleRobotPayload, PurpleRobotEvent -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/delete_older_data.lock', os.R_OK): - t = os.path.getmtime('/tmp/delete_older_data.lock') - created = datetime.datetime.fromtimestamp(t) - + timestamp = os.path.getmtime('/tmp/delete_older_data.lock') + created = datetime.datetime.fromtimestamp(timestamp) + if (datetime.datetime.now() - created).total_seconds() > 120: - print('delete_older_data: Stale lock - removing...') + print 'delete_older_data: Stale lock - removing...' os.remove('/tmp/delete_older_data.lock') else: return touch('/tmp/delete_older_data.lock') - + end = datetime.datetime.now() - datetime.timedelta(days=14) - + PurpleRobotPayload.objects.filter(added__lte=end).delete() PurpleRobotReading.objects.filter(logged__lte=end).delete() PurpleRobotEvent.objects.filter(logged__lte=end).delete() - + os.remove('/tmp/delete_older_data.lock') diff --git a/management/commands/extract_guids.py b/management/commands/extract_guids.py index cdc1425..db2e77a 100644 --- a/management/commands/extract_guids.py +++ b/management/commands/extract_guids.py @@ -1,13 +1,16 @@ +# pylint: disable=line-too-long, no-member + import json from django.core.management.base import BaseCommand from purple_robot_app.models import PurpleRobotReading + class Command(BaseCommand): def handle(self, *args, **options): readings = PurpleRobotReading.objects.filter(guid=None)[:5000] - + for reading in readings: payload = json.loads(reading.payload) reading.guid = payload['GUID'] diff --git a/management/commands/extract_into_database.py b/management/commands/extract_into_database.py index ddfc172..b70ee7e 100644 --- a/management/commands/extract_into_database.py +++ b/management/commands/extract_into_database.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import json import importlib @@ -5,19 +7,26 @@ from django.conf import settings from django.core.management.base import BaseCommand +from django.utils import timezone from django.utils.text import slugify -from purple_robot_app.models import PurpleRobotReading, PurpleRobotPayload +from purple_robot_app.models import PurpleRobotPayload +from purple_robot_app.performance import append_performance_sample + +EXTRACTORS = {} + def my_slugify(str_obj): return slugify(str_obj.replace('.', ' ')).replace('-', '_') -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): try: @@ -26,105 +35,187 @@ def handle(self, *args, **options): return if os.access('/tmp/extract_into_database.lock', os.R_OK): - t = os.path.getmtime('/tmp/extract_into_database.lock') - created = datetime.datetime.fromtimestamp(t) - - if (datetime.datetime.now() - created).total_seconds() > 60 * 60 * 3: - print('extract_into_database: Stale lock - removing...') + timestamp = os.path.getmtime('/tmp/extract_into_database.lock') + created = datetime.datetime.fromtimestamp(timestamp) + + if (datetime.datetime.now() - created).total_seconds() > 60 * 60 * 8: + print 'extract_into_database: Stale lock - removing...' os.remove('/tmp/extract_into_database.lock') else: return - + touch('/tmp/extract_into_database.lock') tag = 'extracted_into_database' skip_tag = 'extracted_into_database_skip' - - payloads = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).order_by('-added')[:100] - - index = 0 - - while payloads.count() > 0 and index < 50: - index += 1 - + + start = timezone.now() + payloads = list(PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).order_by('-added')[:250]) + end = timezone.now() + + query_time = (end - start).total_seconds() + + extractor_times = {} + extractor_counts = {} + + local_db = 0.0 + remote_db = 0.0 + local_app = 0.0 + + while len(payloads) > 0: + touch('/tmp/extract_into_database.lock') + + extractor_times = {} + extractor_counts = {} + + local_db = query_time + remote_db = 0.0 + local_app = 0.0 + + start = timezone.now() + for payload in payloads: - # print('PAYLOAD ' + str(payload.added) + ' / ' + str(payload.pk)) - + cpu_start = datetime.datetime.now() + items = json.loads(payload.payload) - + has_all_extractors = True missing_extractors = [] for item in items: if 'PROBE' in item and 'GUID' in item: probe_name = my_slugify(item['PROBE']).replace('edu_northwestern_cbits_purple_robot_manager_probes_', '') - + found = False - - for app in settings.INSTALLED_APPS: - try: - importlib.import_module(app + '.management.commands.extractors.' + probe_name) - + + if (probe_name in EXTRACTORS) is True: + if EXTRACTORS[probe_name] is not None: found = True - except ImportError: - pass - - if found == False: + else: + found = False + else: + EXTRACTORS[probe_name] = None + + for app in settings.INSTALLED_APPS: + try: + probe = importlib.import_module(app + '.management.commands.extractors.' + probe_name) + + EXTRACTORS[probe_name] = probe + + found = True + except ImportError: + pass + + if found is False: has_all_extractors = False - + if (probe_name in missing_extractors) == False: missing_extractors.append(probe_name) else: has_all_extractors = False missing_extractors.append('Unknown Probe') - + if has_all_extractors: for item in items: if 'PROBE' in item and 'GUID' in item: - probe = None - probe_name = my_slugify(item['PROBE']).replace('edu_northwestern_cbits_purple_robot_manager_probes_', '') - - for app in settings.INSTALLED_APPS: - try: - if probe == None: - probe = importlib.import_module(app + '.management.commands.extractors.' + probe_name) - - found = True - except ImportError: - pass - - if probe != None and probe.exists(settings.PURPLE_ROBOT_FLAT_MIRROR, payload.user_id, item) == False: - # print('PROBE: ' + probe_name) - probe.insert(settings.PURPLE_ROBOT_FLAT_MIRROR, payload.user_id, item) + + probe = EXTRACTORS[probe_name] + + write_start = datetime.datetime.now() + local_app += (write_start - cpu_start).total_seconds() + + exists = True + + if settings.PURPLE_ROBOT_DISABLE_DATA_CHECKS: + exists = False + else: + exists = probe.exists(settings.PURPLE_ROBOT_FLAT_MIRROR, payload.user_id, item) + + cpu_start = datetime.datetime.now() + remote_db += (cpu_start - write_start).total_seconds() + + if EXTRACTORS[probe_name] is not None and exists is False: + write_start = datetime.datetime.now() + local_app += (write_start - cpu_start).total_seconds() + + EXTRACTORS[probe_name].insert(settings.PURPLE_ROBOT_FLAT_MIRROR, payload.user_id, item, check_exists=(settings.PURPLE_ROBOT_DISABLE_DATA_CHECKS is False)) + + cpu_start = datetime.datetime.now() + remote_db += (cpu_start - write_start).total_seconds() + + duration = 0.0 + + if probe_name in extractor_times: + duration = extractor_times[probe_name] + + duration += (cpu_start - write_start).total_seconds() + + extractor_times[probe_name] = duration + + count = 0.0 + + if probe_name in extractor_counts: + count = extractor_counts[probe_name] + + count += 1 + + extractor_counts[probe_name] = count tags = payload.process_tags - + if tags is None or tags.find(tag) == -1: if tags is None or len(tags) == 0: tags = tag else: tags += ' ' + tag - + payload.process_tags = tags - + + read_start = datetime.datetime.now() + local_app += (read_start - cpu_start).total_seconds() + payload.save() + + cpu_start = datetime.datetime.now() + local_db += (cpu_start - read_start).total_seconds() else: tags = payload.process_tags - + if tags is None or tags.find(skip_tag) == -1: if tags is None or len(tags) == 0: tags = skip_tag else: tags += ' ' + skip_tag - + payload.process_tags = tags - + + read_start = datetime.datetime.now() + local_app += (read_start - cpu_start).total_seconds() + payload.save() - + + cpu_start = datetime.datetime.now() + local_db += (cpu_start - read_start).total_seconds() + if len(missing_extractors) > 0: - print('MISSING EXTRACTORS: ' + str(missing_extractors)) - - payloads = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).order_by('-added')[:100] - + print 'MISSING EXTRACTORS: ' + str(missing_extractors) + + end = timezone.now() + + perf_values = {} + perf_values['num_mirrored'] = len(payloads) + perf_values['query_time'] = query_time + perf_values['local_db'] = local_db + perf_values['remote_db'] = remote_db + perf_values['local_app'] = local_app + perf_values['extraction_time'] = (end - start).total_seconds() + + append_performance_sample('system', 'reading_mirror_performance', end, perf_values) + + start = timezone.now() + payloads = list(PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).order_by('-added')[:250]) + end = timezone.now() + query_time = (end - start).total_seconds() + os.remove('/tmp/extract_into_database.lock') diff --git a/management/commands/extract_readings.py b/management/commands/extract_readings.py index de3e15b..7167512 100644 --- a/management/commands/extract_readings.py +++ b/management/commands/extract_readings.py @@ -1,26 +1,30 @@ +# pylint: disable=line-too-long, no-member + import datetime -import json import os -import pytz from django.core.management.base import BaseCommand +from django.utils import timezone from purple_robot_app.models import PurpleRobotReading, PurpleRobotPayload +from purple_robot_app.performance import append_performance_sample + -def touch(fname, mode=0o666, dir_fd=None, **kwargs): +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/extract_readings.lock', os.R_OK): - t = os.path.getmtime('/tmp/extract_readings.lock') - created = datetime.datetime.fromtimestamp(t) - + timestamp = os.path.getmtime('/tmp/extract_readings.lock') + created = datetime.datetime.fromtimestamp(timestamp) + if (datetime.datetime.now() - created).total_seconds() > 4 * 60 * 60: - print('extract_readings: Stale lock - removing...') + print 'extract_readings: Stale lock - removing...' os.remove('/tmp/extract_readings.lock') else: return @@ -28,18 +32,40 @@ def handle(self, *args, **options): touch('/tmp/extract_readings.lock') tag = 'extracted_readings' - - payloads = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).order_by('-added')[:250] - - while payloads.count() > 0: + + start = timezone.now() + payloads = list(PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains='ingest_error')[:500]) + end = timezone.now() + + query_time = (end - start).total_seconds() + + while len(payloads) > 0: + touch('/tmp/extract_readings.lock') + + start = timezone.now() + for payload in payloads: payload.ingest_readings() - - payloads = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains='ingest_error').order_by('-added')[:250] - + + end = timezone.now() + + perf_values = {} + perf_values['num_extracted'] = len(payloads) + perf_values['query_time'] = query_time + perf_values['extraction_time'] = (end - start).total_seconds() + + append_performance_sample('system', 'reading_ingestion_performance', end, perf_values) + + start = timezone.now() + payloads = list(PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains='ingest_error')[:500]) + end = timezone.now() + query_time = (end - start).total_seconds() + readings = PurpleRobotReading.objects.filter(guid=None)[:250] - + for reading in readings: reading.update_guid() + readings = PurpleRobotReading.objects.filter(guid=None)[:250] + os.remove('/tmp/extract_readings.lock') diff --git a/management/commands/extractors/builtin_accelerometerprobe.py b/management/commands/extractors/builtin_accelerometerprobe.py index ab026d3..411806b 100644 --- a/management/commands/extractors/builtin_accelerometerprobe.py +++ b/management/commands/extractors/builtin_accelerometerprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,63 +14,65 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON builtin_accelerometerprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_accelerometerprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - if probe_table_exists(conn) == False or reading_table_exists(conn) == False: + if probe_table_exists(conn) is False or reading_table_exists(conn) is False: conn.close() return False cursor.execute('SELECT id FROM builtin_accelerometerprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_accelerometerprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_accelerometerprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_accelerometerprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -81,29 +84,28 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + readings_len = len(reading['EVENT_TIMESTAMP']) - + has_sensor = ('SENSOR_TIMESTAMP' in reading) has_normalized = ('NORMALIZED_TIMESTAMP' in reading) - + reading_cursor = conn.cursor() - + for i in range(0, readings_len): reading_cmd = 'INSERT INTO builtin_accelerometerprobe_reading(user_id, ' + \ 'reading_id, ' + \ @@ -118,15 +120,15 @@ def insert(connection_str, user_id, reading): 'y, ' + \ 'z, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(reading['EVENT_TIMESTAMP'][i]) values.append(datetime.datetime.fromtimestamp(reading['EVENT_TIMESTAMP'][i], tz=pytz.utc)) - + if has_sensor: values.append(reading['SENSOR_TIMESTAMP'][i]) - + try: values.append(datetime.datetime.fromtimestamp((reading['SENSOR_TIMESTAMP'][i] / 1000), tz=pytz.utc)) except ValueError: @@ -141,15 +143,15 @@ def insert(connection_str, user_id, reading): else: values.append(None) values.append(None) - + values.append(reading['X'][i]) values.append(reading['Y'][i]) values.append(reading['Z'][i]) values.append(reading['ACCURACY'][i]) - + reading_cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_activitydetectionprobe.py b/management/commands/extractors/builtin_activitydetectionprobe.py index 5c465a4..d7eba00 100644 --- a/management/commands/extractors/builtin_activitydetectionprobe.py +++ b/management/commands/extractors/builtin_activitydetectionprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,77 +14,79 @@ CREATE_ACTIVITY_READING_ID_INDEX = 'CREATE INDEX ON builtin_activitydetectionprobe_activity(reading_id);' CREATE_ACTIVITY_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_activitydetectionprobe_activity(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False or activity_table_exists(conn) == False: + if probe_table_exists(conn) is False or activity_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_activitydetectionprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_activitydetectionprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists + + return table_exists + def activity_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_activitydetectionprobe_activity\')') - + activities_table_exists = (cursor.rowcount > 0) - + cursor.close() - + return activities_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if activity_table_exists(conn) == False: + + if check_exists and activity_table_exists(conn) is False: cursor.execute(CREATE_ACTIVITY_TABLE_SQL) cursor.execute(CREATE_ACTIVITY_USER_ID_INDEX) cursor.execute(CREATE_ACTIVITY_READING_ID_INDEX) cursor.execute(CREATE_ACTIVITY_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_activitydetectionprobe(user_id, guid, timestamp, utc_logged, activity_count, most_probable_activity, most_probable_confidence) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id;' - + cursor.execute(reading_cmd, (user_id, reading['GUID'], reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['ACTIVITY_COUNT'], reading['MOST_PROBABLE_ACTIVITY'], reading['MOST_PROBABLE_CONFIDENCE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + for activity in reading['ACTIVITIES']: activity_cmd = 'INSERT INTO builtin_activitydetectionprobe_activity(user_id, reading_id, utc_logged, activity, confidence) VALUES (%s, %s, %s, %s, %s);' - + cursor.execute(activity_cmd, (user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), activity['ACTIVITY_TYPE'], activity['ACTIVITY_CONFIDENCE'])) - + conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_applicationlaunchprobe.py b/management/commands/extractors/builtin_applicationlaunchprobe.py index 8a19e33..effe99c 100644 --- a/management/commands/extractors/builtin_applicationlaunchprobe.py +++ b/management/commands/extractors/builtin_applicationlaunchprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_applicationlaunchprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_applicationlaunchprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_applicationlaunchprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_applicationlaunchprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_applicationlaunchprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -57,16 +59,14 @@ def insert(connection_str, user_id, reading): 'current_app_pkg, ' + \ 'current_app_name, ' + \ 'current_category) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['CURRENT_APP_PKG'], \ - reading['CURRENT_APP_NAME'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['CURRENT_APP_PKG'], + reading['CURRENT_APP_NAME'], reading['CURRENT_CATEGORY'])) - conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_audiofeaturesprobe.py b/management/commands/extractors/builtin_audiofeaturesprobe.py index 7376bb6..6f9d0c6 100644 --- a/management/commands/extractors/builtin_audiofeaturesprobe.py +++ b/management/commands/extractors/builtin_audiofeaturesprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -20,49 +21,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_audiofeaturesprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_audiofeaturesprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_audiofeaturesprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_audiofeaturesprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_audiofeaturesprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -73,21 +74,20 @@ def insert(connection_str, user_id, reading): 'power, ' + \ 'frequency, ' + \ 'normalized_avg_magnitude) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - data = (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SAMPLES_RECORDED'], \ - reading['SAMPLE_RATE'], \ - reading['SAMPLE_BUFFER_SIZE'], \ - reading['POWER'], \ - reading['FREQUENCY'], \ + data = (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SAMPLES_RECORDED'], + reading['SAMPLE_RATE'], + reading['SAMPLE_BUFFER_SIZE'], + reading['POWER'], + reading['FREQUENCY'], reading['NORMALIZED_AVG_MAGNITUDE']) - + cursor.execute(reading_cmd, data) - + conn.commit() - + cursor.close() - conn.close() \ No newline at end of file + conn.close() diff --git a/management/commands/extractors/builtin_batteryprobe.py b/management/commands/extractors/builtin_batteryprobe.py index 7341581..8ae36fb 100644 --- a/management/commands/extractors/builtin_batteryprobe.py +++ b/management/commands/extractors/builtin_batteryprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_batteryprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_batteryprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_batteryprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_batteryprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_batteryprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -64,23 +66,21 @@ def insert(connection_str, user_id, reading): 'technology, ' + \ 'present, ' + \ 'temperature) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['status'], \ - reading['scale'], \ - reading['invalid_charger'], \ - reading['level'], \ - reading['plugged'], \ - reading['health'], \ - reading['voltage'], \ - reading['technology'], \ - reading['present'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['status'], + reading['scale'], + reading['invalid_charger'], + reading['level'], + reading['plugged'], + reading['health'], + reading['voltage'], + reading['technology'], + reading['present'], reading['temperature'])) - conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_bluetoothdevicesprobe.py b/management/commands/extractors/builtin_bluetoothdevicesprobe.py new file mode 100644 index 0000000..c521ebe --- /dev/null +++ b/management/commands/extractors/builtin_bluetoothdevicesprobe.py @@ -0,0 +1,141 @@ +# pylint: disable=line-too-long + +import datetime +import psycopg2 +import pytz + +CREATE_PROBE_TABLE_SQL = 'CREATE TABLE builtin_bluetoothdevicesprobe(id SERIAL PRIMARY KEY, user_id TEXT, guid TEXT, timestamp BIGINT, utc_logged TIMESTAMP, device_count BIGINT);' +CREATE_PROBE_USER_ID_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe(user_id);' +CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe(guid);' +CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe(utc_logged);' + +CREATE_DEVICE_TABLE_SQL = 'CREATE TABLE builtin_bluetoothdevicesprobe_device(id SERIAL PRIMARY KEY, user_id TEXT, reading_id BIGINT, utc_logged TIMESTAMP, name TEXT, major_class TEXT, minor_class TEXT, bond_state TEXT, address TEXT);' +CREATE_DEVICE_USER_ID_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe_device(user_id);' +CREATE_DEVICE_READING_ID_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe_device(reading_id);' +CREATE_DEVICE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_bluetoothdevicesprobe_device(utc_logged);' + + +def exists(connection_str, user_id, reading): + conn = psycopg2.connect(connection_str) + + if probe_table_exists(conn) is False or device_table_exists(conn) is False: + conn.close() + return False + + cursor = conn.cursor() + + cursor.execute('SELECT id FROM builtin_bluetoothdevicesprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) + + row_exists = (cursor.rowcount > 0) + + cursor.close() + conn.close() + + return row_exists + + +def probe_table_exists(conn): + cursor = conn.cursor() + cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_bluetoothdevicesprobe\')') + + table_exists = (cursor.rowcount > 0) + + cursor.close() + + return table_exists + + +def device_table_exists(conn): + cursor = conn.cursor() + cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_bluetoothdevicesprobe_device\')') + + table_exists = (cursor.rowcount > 0) + + cursor.close() + + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): + check_exists = True + + conn = psycopg2.connect(connection_str) + cursor = conn.cursor() + + if check_exists and probe_table_exists(conn) is False: + cursor.execute(CREATE_PROBE_TABLE_SQL) + cursor.execute(CREATE_PROBE_USER_ID_INDEX) + cursor.execute(CREATE_PROBE_GUID_INDEX) + cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) + + if check_exists and device_table_exists(conn) is False: + cursor.execute(CREATE_DEVICE_TABLE_SQL) + cursor.execute(CREATE_DEVICE_USER_ID_INDEX) + cursor.execute(CREATE_DEVICE_READING_ID_INDEX) + cursor.execute(CREATE_DEVICE_UTC_LOGGED_INDEX) + + conn.commit() + + reading_cmd = 'INSERT INTO builtin_bluetoothdevicesprobe(user_id, ' + \ + 'guid, ' + \ + 'timestamp, ' + \ + 'utc_logged, ' + \ + 'device_count) VALUES (%s, %s, %s, %s, %s) RETURNING id;' + device_count = None + + if 'DEVICE_COUNT' in reading: + device_count = reading['DEVICE_COUNT'] + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + device_count)) + + for row in cursor.fetchall(): + reading_id = row[0] + + device_cursor = conn.cursor() + + for device in reading['DEVICES']: + device_cmd = 'INSERT INTO builtin_bluetoothdevicesprobe_device(user_id, ' + \ + 'reading_id, ' + \ + 'utc_logged, ' + \ + 'name, ' + \ + 'major_class, ' + \ + 'minor_class, ' + \ + 'bond_state, ' + \ + 'address) VALUES (%s, %s, %s, %s, %s, %s, %s, %s);' + major = None + minor = None + name = None + bond = None + address = None + + if 'DEVICE MAJOR CLASS' in device: + major = device['DEVICE MAJOR CLASS'] + + if 'DEVICE MINOR CLASS' in device: + minor = device['DEVICE MINOR CLASS'] + + if 'BLUETOOTH_NAME' in device: + name = device['BLUETOOTH_NAME'] + + if 'BOND_STATE' in device: + bond = device['BOND_STATE'] + + if 'BLUETOOTH_ADDRESS' in device: + address = device['BLUETOOTH_ADDRESS'] + + device_cursor.execute(device_cmd, (user_id, + reading_id, + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + name, + major, + minor, + bond, + address)) + conn.commit() + + cursor.close() + conn.close() diff --git a/management/commands/extractors/builtin_callstateprobe.py b/management/commands/extractors/builtin_callstateprobe.py index 5ec7772..c2daacd 100644 --- a/management/commands/extractors/builtin_callstateprobe.py +++ b/management/commands/extractors/builtin_callstateprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,59 +9,60 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_callstateprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_callstateprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_callstateprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_callstateprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_callstateprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged, ' + \ 'call_state) VALUES (%s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['CALL_STATE'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_communicationeventprobe.py b/management/commands/extractors/builtin_communicationeventprobe.py index e9f3e1a..12e572e 100644 --- a/management/commands/extractors/builtin_communicationeventprobe.py +++ b/management/commands/extractors/builtin_communicationeventprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,49 +9,50 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_communicationeventprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_communicationeventprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() - + return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_communicationeventprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_communicationeventprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_communicationeventprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -62,18 +64,18 @@ def insert(connection_str, user_id, reading): 'comm_timestamp, ' + \ 'comm_timestamp_ts) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['NAME'], \ - reading['NUMBER'], \ - reading['COMMUNICATION_TYPE'], \ - reading['COMMUNICATION_DIRECTION'], \ - reading['COMM_TIMESTAMP'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['NAME'], + reading['NUMBER'], + reading['COMMUNICATION_TYPE'], + reading['COMMUNICATION_DIRECTION'], + reading['COMM_TIMESTAMP'], datetime.datetime.fromtimestamp((reading['COMM_TIMESTAMP'] / 1000), tz=pytz.utc))) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_communicationlogprobe.py b/management/commands/extractors/builtin_communicationlogprobe.py index 082aa29..8406de8 100644 --- a/management/commands/extractors/builtin_communicationlogprobe.py +++ b/management/commands/extractors/builtin_communicationlogprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,64 +14,66 @@ CREATE_CALL_READING_ID_INDEX = 'CREATE INDEX ON builtin_communicationlogprobe_call(reading_id);' CREATE_CALL_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_communicationlogprobe_call(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False or call_table_exists(conn) == False: + + if probe_table_exists(conn) is False or call_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_communicationlogprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_communicationlogprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists + + return table_exists + def call_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_communicationlogprobe_call\')') - - activities_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return activities_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if call_table_exists(conn) == False: + + if check_exists and call_table_exists(conn) is False: cursor.execute(CREATE_CALL_TABLE_SQL) cursor.execute(CREATE_CALL_USER_ID_INDEX) cursor.execute(CREATE_CALL_READING_ID_INDEX) cursor.execute(CREATE_CALL_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_communicationlogprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -85,65 +88,58 @@ def insert(connection_str, user_id, reading): 'recent_time_utc, ' + \ 'sms_outgoing_count, ' + \ 'sms_incoming_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - recent_caller = None recent_number = None recent_time = None recent_datetime = None - + if 'RECENT_CALLER' in reading: recent_caller = reading['RECENT_CALLER'] recent_number = reading['RECENT_NUMBER'] recent_time = reading['RECENT_TIME'] recent_datetime = datetime.datetime.fromtimestamp((reading['RECENT_TIME'] / 1000), tz=pytz.utc) - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['CALL_TOTAL_COUNT'], \ - reading['CALL_INCOMING_COUNT'], \ - reading['CALL_OUTGOING_COUNT'], \ - reading['CALL_MISSED_COUNT'], \ - recent_caller, \ - recent_number, \ - recent_time, \ - recent_datetime, \ - reading['SMS_OUTGOING_COUNT'], \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['CALL_TOTAL_COUNT'], + reading['CALL_INCOMING_COUNT'], + reading['CALL_OUTGOING_COUNT'], + reading['CALL_MISSED_COUNT'], + recent_caller, + recent_number, + recent_time, + recent_datetime, + reading['SMS_OUTGOING_COUNT'], reading['SMS_INCOMING_COUNT'])) - + for row in cursor.fetchall(): reading_id = row[0] - + for call in reading['PHONE_CALLS']: - existing_query = 'SELECT id FROM builtin_communicationlogprobe_call WHERE (user_id = %s AND number_name = %s AND call_timestamp = %s);' - values = (user_id, call['NUMBER_NAME'], call['CALL_TIMESTAMP']) - call_cursor = conn.cursor() - call_cursor.execute(existing_query, values) - - if call_cursor.rowcount == 0: - call_cmd = 'INSERT INTO builtin_communicationlogprobe_call(user_id, ' + \ - 'reading_id, ' + \ - 'utc_logged, ' + \ - 'number_name, ' + \ - 'number_label, ' + \ - 'call_duration, ' + \ - 'number_type, ' + \ - 'call_timestamp, ' + \ - 'call_timestamp_utc) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s);' - - call_cursor.execute(call_cmd, (user_id, \ - reading_id, - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), - call['NUMBER_NAME'], - call['NUMBER_LABEL'], - call['CALL_DURATION'], - call['NUMBER_TYPE'], - call['CALL_TIMESTAMP'], - datetime.datetime.fromtimestamp((call['CALL_TIMESTAMP'] / 1000), tz=pytz.utc))) + call_cmd = 'INSERT INTO builtin_communicationlogprobe_call(user_id, ' + \ + 'reading_id, ' + \ + 'utc_logged, ' + \ + 'number_name, ' + \ + 'number_label, ' + \ + 'call_duration, ' + \ + 'number_type, ' + \ + 'call_timestamp, ' + \ + 'call_timestamp_utc) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s);' + + call_cursor.execute(call_cmd, (user_id, + reading_id, + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + call['NUMBER_NAME'], + call['NUMBER_LABEL'], + call['CALL_DURATION'], + call['NUMBER_TYPE'], + call['CALL_TIMESTAMP'], + datetime.datetime.fromtimestamp((call['CALL_TIMESTAMP'] / 1000), tz=pytz.utc))) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_datecalendarprobe.py b/management/commands/extractors/builtin_datecalendarprobe.py index 7623676..71e842d 100644 --- a/management/commands/extractors/builtin_datecalendarprobe.py +++ b/management/commands/extractors/builtin_datecalendarprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_datecalendarprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_datecalendarprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_datecalendarprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_datecalendarprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_datecalendarprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -64,22 +66,21 @@ def insert(connection_str, user_id, reading): 'hour_of_day, ' + \ 'minute, ' + \ 'dst_offset) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['MONTH'], \ - reading['DAY_OF_YEAR'], \ - reading['WEEK_OF_YEAR'], \ - reading['WEEK_OF_MONTH'], \ - reading['DAY_OF_MONTH'], \ - reading['DAY_OF_WEEK_IN_MONTH'], \ - reading['DAY_OF_WEEK'], \ - reading['HOUR_OF_DAY'], \ - reading['MINUTE'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['MONTH'], + reading['DAY_OF_YEAR'], + reading['WEEK_OF_YEAR'], + reading['WEEK_OF_MONTH'], + reading['DAY_OF_MONTH'], + reading['DAY_OF_WEEK_IN_MONTH'], + reading['DAY_OF_WEEK'], + reading['HOUR_OF_DAY'], + reading['MINUTE'], reading['DST_OFFSET'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_default.py b/management/commands/extractors/builtin_default.py index 1487fe2..8cb6d9c 100644 --- a/management/commands/extractors/builtin_default.py +++ b/management/commands/extractors/builtin_default.py @@ -1,10 +1,11 @@ -import datetime +# pylint: disable=line-too-long, unused-argument + import json -import psycopg2 -import pytz -def exists(connection_str, user_id, reading): + +def exists(connection_str, user_id, reading, check_exists=True): return False + def insert(connection_str, user_id, reading): - print(json.dumps(reading, indent=2)) + print json.dumps(reading, indent=2) diff --git a/management/commands/extractors/builtin_fusedlocationprobe.py b/management/commands/extractors/builtin_fusedlocationprobe.py index 2b5229b..f542bf0 100644 --- a/management/commands/extractors/builtin_fusedlocationprobe.py +++ b/management/commands/extractors/builtin_fusedlocationprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_fusedlocationprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_fusedlocationprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_fusedlocationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_fusedlocationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_fusedlocationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -59,13 +61,12 @@ def insert(connection_str, user_id, reading): 'altitude, ' + \ 'accuracy, ' + \ 'provider) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - latitude = None longitude = None timestamp = None provider = None accuracy = None - + if 'LATITUDE' in reading: latitude = reading['LATITUDE'] @@ -74,29 +75,29 @@ def insert(connection_str, user_id, reading): if 'TIMESTAMP' in reading: timestamp = reading['TIMESTAMP'] - + if 'PROVIDER' in reading: provider = reading['PROVIDER'] if 'ACCURACY' in reading: accuracy = reading['ACCURACY'] - - if timestamp != None: - values = [ user_id, reading['GUID'], timestamp, datetime.datetime.fromtimestamp(timestamp, tz=pytz.utc), latitude, longitude ] + + if timestamp is not None: + values = [user_id, reading['GUID'], timestamp, datetime.datetime.fromtimestamp(timestamp, tz=pytz.utc), latitude, longitude] else: - values = [ user_id, reading['GUID'], None, None, latitude, longitude ] - + values = [user_id, reading['GUID'], None, None, latitude, longitude] + if 'ALTITUDE' in reading: values.append(reading['ALTITUDE']) else: values.append(None) - + values.append(accuracy) values.append(provider) - + cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_gyroscopeprobe.py b/management/commands/extractors/builtin_gyroscopeprobe.py index f1e1f6a..8a31b70 100644 --- a/management/commands/extractors/builtin_gyroscopeprobe.py +++ b/management/commands/extractors/builtin_gyroscopeprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,64 +14,66 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON builtin_gyroscopeprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_gyroscopeprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_gyroscopeprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_gyroscopeprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_gyroscopeprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_gyroscopeprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -82,29 +85,28 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + readings_len = len(reading['EVENT_TIMESTAMP']) - + has_sensor = ('SENSOR_TIMESTAMP' in reading) has_normalized = ('NORMALIZED_TIMESTAMP' in reading) - + reading_cursor = conn.cursor() - + for i in range(0, readings_len): reading_cmd = 'INSERT INTO builtin_gyroscopeprobe_reading(user_id, ' + \ 'reading_id, ' + \ @@ -119,15 +121,15 @@ def insert(connection_str, user_id, reading): 'y, ' + \ 'z, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(reading['EVENT_TIMESTAMP'][i]) values.append(datetime.datetime.fromtimestamp(reading['EVENT_TIMESTAMP'][i], tz=pytz.utc)) - + if has_sensor: values.append(reading['SENSOR_TIMESTAMP'][i]) - + try: values.append(datetime.datetime.fromtimestamp((reading['SENSOR_TIMESTAMP'][i] / 1000), tz=pytz.utc)) except ValueError: @@ -142,15 +144,15 @@ def insert(connection_str, user_id, reading): else: values.append(None) values.append(None) - + values.append(reading['X'][i]) values.append(reading['Y'][i]) values.append(reading['Z'][i]) values.append(reading['ACCURACY'][i]) - + reading_cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_hardwareinformationprobe.py b/management/commands/extractors/builtin_hardwareinformationprobe.py index e9d32aa..6c83d8b 100644 --- a/management/commands/extractors/builtin_hardwareinformationprobe.py +++ b/management/commands/extractors/builtin_hardwareinformationprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,49 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_hardwareinformationprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_hardwareinformationprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() - return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_hardwareinformationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_hardwareinformationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_hardwareinformationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -70,42 +71,40 @@ def insert(connection_str, user_id, reading): 'display, ' + \ 'device_id, ' + \ 'manufacturer) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - mobile_id = None bluetooth_mac = None - + if 'MOBILE_ID' in reading: mobile_id = reading['MOBILE_ID'] if 'BLUETOOTH_MAC' in reading: bluetooth_mac = reading['BLUETOOTH_MAC'] - + wifi_mac = None - + if 'WIFI_MAC' in reading: wifi_mac = reading['WIFI_MAC'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['PRODUCT'], \ - reading['BOOTLOADER'], \ - reading['BRAND'], \ - mobile_id, \ - reading['HARDWARE'], \ - reading['HOST'], \ - bluetooth_mac, \ - reading['BOARD'], \ - reading['FINGERPRINT'], \ - reading['DEVICE'], \ - reading['MODEL'], \ - wifi_mac, \ - reading['DISPLAY'], \ - reading['ID'], \ - reading['MANUFACTURER'])) + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['PRODUCT'], + reading['BOOTLOADER'], + reading['BRAND'], + mobile_id, + reading['HARDWARE'], + reading['HOST'], + bluetooth_mac, + reading['BOARD'], + reading['FINGERPRINT'], + reading['DEVICE'], + reading['MODEL'], + wifi_mac, + reading['DISPLAY'], + reading['ID'], + reading['MANUFACTURER'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_lightprobe.py b/management/commands/extractors/builtin_lightprobe.py index 3a19ef2..fa71b18 100644 --- a/management/commands/extractors/builtin_lightprobe.py +++ b/management/commands/extractors/builtin_lightprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,64 +14,66 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON builtin_lightprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_lightprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_lightprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_lightprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_lightprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_lightprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -82,29 +85,28 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + readings_len = len(reading['EVENT_TIMESTAMP']) - + has_sensor = ('SENSOR_TIMESTAMP' in reading) has_normalized = ('NORMALIZED_TIMESTAMP' in reading) - + reading_cursor = conn.cursor() - + for i in range(0, readings_len): reading_cmd = 'INSERT INTO builtin_lightprobe_reading(user_id, ' + \ 'reading_id, ' + \ @@ -117,15 +119,15 @@ def insert(connection_str, user_id, reading): 'normalized_timestamp_utc, ' + \ 'lux, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(reading['EVENT_TIMESTAMP'][i]) values.append(datetime.datetime.fromtimestamp(reading['EVENT_TIMESTAMP'][i], tz=pytz.utc)) - + if has_sensor: values.append(reading['SENSOR_TIMESTAMP'][i]) - + try: values.append(datetime.datetime.fromtimestamp((reading['SENSOR_TIMESTAMP'][i] / 1000), tz=pytz.utc)) except ValueError: @@ -140,13 +142,13 @@ def insert(connection_str, user_id, reading): else: values.append(None) values.append(None) - + values.append(reading['LUX'][i]) values.append(reading['ACCURACY'][i]) - + reading_cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_locationprobe.py b/management/commands/extractors/builtin_locationprobe.py index 6564d62..17c4183 100644 --- a/management/commands/extractors/builtin_locationprobe.py +++ b/management/commands/extractors/builtin_locationprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_locationprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_locationprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_locationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_locationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_locationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -62,14 +64,14 @@ def insert(connection_str, user_id, reading): 'network_available, ' + \ 'gps_available, ' + \ 'cluster) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - values = [ user_id, reading['GUID'], reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['LATITUDE'], reading['LONGITUDE'] ] - + + values = [user_id, reading['GUID'], reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['LATITUDE'], reading['LONGITUDE']] + if 'ALTITUDE' in reading: values.append(reading['ALTITUDE']) else: values.append(None) - + values.append(reading['ACCURACY']) values.append(reading['PROVIDER']) values.append(reading['NETWORK_AVAILABLE']) @@ -79,10 +81,10 @@ def insert(connection_str, user_id, reading): values.append(reading['CLUSTER']) else: values.append(None) - + cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_networkprobe.py b/management/commands/extractors/builtin_networkprobe.py index af14c82..3b7dc18 100644 --- a/management/commands/extractors/builtin_networkprobe.py +++ b/management/commands/extractors/builtin_networkprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,49 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_networkprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_networkprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_networkprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_networkprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print('USER: '+ user_id) -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_networkprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -59,36 +60,36 @@ def insert(connection_str, user_id, reading): 'interface_name, ' + \ 'ip_address, ' + \ 'interface_display) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - + interface_name = None - + if 'INTERFACE_NAME' in reading: interface_name = reading['INTERFACE_NAME'] ip_address = None - + if 'IP_ADDRESS' in reading: ip_address = reading['IP_ADDRESS'] hostname = None - + if 'HOSTNAME' in reading: hostname = reading['HOSTNAME'] interface_display = None - + if 'INTERFACE_DISPLAY' in reading: interface_display = reading['INTERFACE_DISPLAY'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - hostname, \ - interface_name, \ - ip_address, \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + hostname, + interface_name, + ip_address, interface_display)) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_pressureprobe.py b/management/commands/extractors/builtin_pressureprobe.py index b244986..dbe3842 100644 --- a/management/commands/extractors/builtin_pressureprobe.py +++ b/management/commands/extractors/builtin_pressureprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,64 +14,66 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON builtin_pressureprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_pressureprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_pressureprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_pressureprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_pressureprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_pressureprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -82,29 +85,28 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + readings_len = len(reading['EVENT_TIMESTAMP']) - + has_sensor = ('SENSOR_TIMESTAMP' in reading) has_normalized = ('NORMALIZED_TIMESTAMP' in reading) - + reading_cursor = conn.cursor() - + for i in range(0, readings_len): reading_cmd = 'INSERT INTO builtin_pressureprobe_reading(user_id, ' + \ 'reading_id, ' + \ @@ -118,15 +120,15 @@ def insert(connection_str, user_id, reading): 'pressure, ' + \ 'altitude, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(reading['EVENT_TIMESTAMP'][i]) values.append(datetime.datetime.fromtimestamp(reading['EVENT_TIMESTAMP'][i], tz=pytz.utc)) - + if has_sensor: values.append(reading['SENSOR_TIMESTAMP'][i]) - + try: values.append(datetime.datetime.fromtimestamp((reading['SENSOR_TIMESTAMP'][i] / 1000), tz=pytz.utc)) except ValueError: @@ -141,14 +143,14 @@ def insert(connection_str, user_id, reading): else: values.append(None) values.append(None) - + values.append(reading['PRESSURE'][i]) values.append(reading['ALTITUDE'][i]) values.append(reading['ACCURACY'][i]) - + reading_cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_rawlocationprobe.py b/management/commands/extractors/builtin_rawlocationprobe.py index e4dfb13..dade607 100644 --- a/management/commands/extractors/builtin_rawlocationprobe.py +++ b/management/commands/extractors/builtin_rawlocationprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_rawlocationprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_rawlocationprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_rawlocationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_rawlocationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_rawlocationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -61,17 +63,17 @@ def insert(connection_str, user_id, reading): 'provider, ' + \ 'network_available, ' + \ 'gps_available) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - values = [ user_id, reading['GUID'], reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['LATITUDE'], reading['LONGITUDE'] ] - + + values = [user_id, reading['GUID'], reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['LATITUDE'], reading['LONGITUDE']] + if 'ALTITUDE' in reading: values.append(reading['ALTITUDE']) else: values.append(None) - + values.append(reading['ACCURACY']) values.append(reading['PROVIDER']) - + if 'NETWORK_AVAILABLE' in reading: values.append(reading['NETWORK_AVAILABLE']) else: @@ -81,10 +83,10 @@ def insert(connection_str, user_id, reading): values.append(reading['GPS_AVAILABLE']) else: values.append(None) - + cursor.execute(reading_cmd, values) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_rawlocationprobeeventlog.py b/management/commands/extractors/builtin_rawlocationprobeeventlog.py index 0f5ca55..932df27 100644 --- a/management/commands/extractors/builtin_rawlocationprobeeventlog.py +++ b/management/commands/extractors/builtin_rawlocationprobeeventlog.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,49 +9,50 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_rawlocationprobeeventlog(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_rawlocationprobeeventlog(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() - + return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_rawlocationprobeeventlog WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_rawlocationprobeeventlog\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_rawlocationprobeeventlog(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -60,22 +62,21 @@ def insert(connection_str, user_id, reading): 'satellites) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id;' provider_status = None satellites = None - + if 'PROVIDER_STATUS' in reading: provider_status = reading['PROVIDER_STATUS'] if 'satellites' in reading: satellites = reading['satellites'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - provider_status, \ - reading['LOG_EVENT'], \ - satellites)) + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + provider_status, + reading['LOG_EVENT'], + satellites)) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_robothealthprobe.py b/management/commands/extractors/builtin_robothealthprobe.py index 81f1982..4c757a4 100644 --- a/management/commands/extractors/builtin_robothealthprobe.py +++ b/management/commands/extractors/builtin_robothealthprobe.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long + import datetime import json import psycopg2 @@ -54,80 +56,83 @@ CREATE_TRIGGER_READING_ID_INDEX = 'CREATE INDEX ON builtin_robothealthprobe_trigger(reading_id);' CREATE_TRIGGER_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_robothealthprobe_trigger(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False or warning_table_exists(conn) == False or trigger_table_exists(conn) == False or trigger_table_exists(conn) == False: + if probe_table_exists(conn) is False or warning_table_exists(conn) is False or trigger_table_exists(conn) is False or trigger_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_robothealthprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_robothealthprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def warning_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_robothealthprobe_warning\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def trigger_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_robothealthprobe_trigger\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if warning_table_exists(conn) == False: + + if check_exists and warning_table_exists(conn) is False: cursor.execute(CREATE_WARNING_TABLE_SQL) cursor.execute(CREATE_WARNING_USER_ID_INDEX) cursor.execute(CREATE_WARNING_READING_ID_INDEX) cursor.execute(CREATE_WARNING_UTC_LOGGED_INDEX) - if trigger_table_exists(conn) == False: + if check_exists and trigger_table_exists(conn) is False: cursor.execute(CREATE_TRIGGER_TABLE_SQL) cursor.execute(CREATE_TRIGGER_USER_ID_INDEX) cursor.execute(CREATE_TRIGGER_READING_ID_INDEX) cursor.execute(CREATE_TRIGGER_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_robothealthprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -150,57 +155,56 @@ def insert(connection_str, user_id, reading): 'app_version_name, ' + \ 'json_config, ' + \ 'scheme_config) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - external_total = None - + if 'EXTERNAL_TOTAL'in reading: external_total = reading['EXTERNAL_TOTAL'] external_free = None - + if 'EXTERNAL_FREE'in reading: external_free = reading['EXTERNAL_FREE'] - data = (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['LAST_BOOT'], \ - reading['LAST_HALT'], \ - reading['ACTIVE_RUNTIME'], \ - reading['ROOT_TOTAL'], \ - reading['ROOT_FREE'], \ - external_total, \ - external_free, \ - reading['PENDING_SIZE'], \ - reading['PENDING_COUNT'], \ - reading['CLEAR_TIME'], \ - reading['TIME_OFFSET_MS'], \ - reading['THROUGHPUT'], \ - reading['CPU_USAGE'], \ - reading['MEASURE_TIME'], \ - str(reading['APP_VERSION_CODE']), \ - reading['APP_VERSION_NAME'], \ - reading['JSON_CONFIG'], \ + data = (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['LAST_BOOT'], + reading['LAST_HALT'], + reading['ACTIVE_RUNTIME'], + reading['ROOT_TOTAL'], + reading['ROOT_FREE'], + external_total, + external_free, + reading['PENDING_SIZE'], + reading['PENDING_COUNT'], + reading['CLEAR_TIME'], + reading['TIME_OFFSET_MS'], + reading['THROUGHPUT'], + reading['CPU_USAGE'], + reading['MEASURE_TIME'], + str(reading['APP_VERSION_CODE']), + reading['APP_VERSION_NAME'], + reading['JSON_CONFIG'], reading['SCHEME_CONFIG']) - + cursor.execute(reading_cmd, data) - + for row in cursor.fetchall(): reading_id = row[0] - + for trigger in reading['TRIGGERS']: trigger_cmd = 'INSERT INTO builtin_robothealthprobe_trigger(user_id, reading_id, utc_logged, trigger_json) VALUES (%s, %s, %s, %s);' - + cursor.execute(trigger_cmd, (user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), json.dumps(trigger))) - + if 'CHECK_WARNINGS' in reading: for warning in reading['CHECK_WARNINGS']: warning_cmd = 'INSERT INTO builtin_robothealthprobe_warning(user_id, reading_id, utc_logged, warning) VALUES (%s, %s, %s, %s);' - + cursor.execute(warning_cmd, (user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), warning)) - + conn.commit() - + cursor.close() - conn.close() \ No newline at end of file + conn.close() diff --git a/management/commands/extractors/builtin_runningsoftwareprobe.py b/management/commands/extractors/builtin_runningsoftwareprobe.py index ef9ad04..8246441 100644 --- a/management/commands/extractors/builtin_runningsoftwareprobe.py +++ b/management/commands/extractors/builtin_runningsoftwareprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,86 +14,88 @@ CREATE_TASK_READING_ID_INDEX = 'CREATE INDEX ON builtin_runningsoftwareprobe_runningtask(reading_id);' CREATE_TASK_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_runningsoftwareprobe_runningtask(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_runningsoftwareprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_runningsoftwareprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def task_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_runningsoftwareprobe_runningtask\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if task_table_exists(conn) == False: + + if check_exists and task_table_exists(conn) is False: cursor.execute(CREATE_TASK_TABLE_SQL) cursor.execute(CREATE_TASK_USER_ID_INDEX) cursor.execute(CREATE_TASK_READING_ID_INDEX) cursor.execute(CREATE_TASK_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_runningsoftwareprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged, ' + \ 'running_task_count) VALUES (%s, %s, %s, %s, %s) RETURNING id;' running_count = -1 - + if 'RUNNING_TASK_COUNT' in reading: running_count = reading['RUNNING_TASK_COUNT'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), running_count)) - + if 'RUNNING_TASKS' in reading: for row in cursor.fetchall(): reading_id = row[0] - + task_cursor = conn.cursor() - + for task in reading['RUNNING_TASKS']: task_cmd = 'INSERT INTO builtin_runningsoftwareprobe_runningtask(user_id, ' + \ 'reading_id, ' + \ @@ -100,16 +103,15 @@ def insert(connection_str, user_id, reading): 'package_name, ' + \ 'package_category, ' + \ 'task_stack_index) VALUES (%s, %s, %s, %s, %s, %s);' - - task_cursor.execute(task_cmd, (user_id, \ - reading_id, - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), - task['PACKAGE_NAME'], - task['PACKAGE_CATEGORY'], + task_cursor.execute(task_cmd, (user_id, + reading_id, + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + task['PACKAGE_NAME'], + task['PACKAGE_CATEGORY'], task['TASK_STACK_INDEX'])) task_cursor.close() conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_screenprobe.py b/management/commands/extractors/builtin_screenprobe.py index 87061ce..0535e3c 100644 --- a/management/commands/extractors/builtin_screenprobe.py +++ b/management/commands/extractors/builtin_screenprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,59 +9,60 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_screenprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_screenprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_screenprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_screenprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_screenprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged, ' + \ 'screen_active) VALUES (%s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['SCREEN_ACTIVE'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_softwareinformationprobe.py b/management/commands/extractors/builtin_softwareinformationprobe.py index 81da979..e9e5f88 100644 --- a/management/commands/extractors/builtin_softwareinformationprobe.py +++ b/management/commands/extractors/builtin_softwareinformationprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,65 +14,66 @@ CREATE_APP_READING_ID_INDEX = 'CREATE INDEX ON builtin_softwareinformationprobe_app(reading_id);' CREATE_APP_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_softwareinformationprobe_app(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() - return False - + cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_softwareinformationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_softwareinformationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists + + return table_exists + def app_point_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_softwareinformationprobe_app\')') - - activities_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return activities_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if app_point_table_exists(conn) == False: + + if check_exists and app_point_table_exists(conn) is False: cursor.execute(CREATE_APP_TABLE_SQL) cursor.execute(CREATE_APP_USER_ID_INDEX) cursor.execute(CREATE_APP_READING_ID_INDEX) cursor.execute(CREATE_APP_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_softwareinformationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -81,22 +83,21 @@ def insert(connection_str, user_id, reading): 'incremental, ' + \ 'codename, ' + \ 'installed_app_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SDK_INT'], \ - reading['RELEASE'], \ - reading['INCREMENTAL'], \ - reading['CODENAME'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SDK_INT'], + reading['RELEASE'], + reading['INCREMENTAL'], + reading['CODENAME'], reading['INSTALLED_APP_COUNT'])) - + for row in cursor.fetchall(): reading_id = row[0] - + app_cursor = conn.cursor() - + for app in reading['INSTALLED_APPS']: app_cmd = 'INSERT INTO builtin_softwareinformationprobe_app(user_id, ' + \ 'reading_id, ' + \ @@ -104,14 +105,14 @@ def insert(connection_str, user_id, reading): 'name, ' + \ 'package) VALUES (%s, %s, %s, %s, %s);' - app_cursor.execute(app_cmd, (user_id, \ - reading_id, - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), - app['APP_NAME'], - app['PACKAGE_NAME'])) + app_cursor.execute(app_cmd, (user_id, + reading_id, + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + app['APP_NAME'], + app['PACKAGE_NAME'])) app_cursor.close() conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_telephonyprobe.py b/management/commands/extractors/builtin_telephonyprobe.py index b418b33..18c523e 100644 --- a/management/commands/extractors/builtin_telephonyprobe.py +++ b/management/commands/extractors/builtin_telephonyprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_telephonyprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_telephonyprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_telephonyprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_telephonyprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_telephonyprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -95,28 +97,28 @@ def insert(connection_str, user_id, reading): if 'GSM_ERROR_RATE' in reading: gsm_error = reading['GSM_ERROR_RATE'] - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['NETWORK_COUNTRY_ISO'], \ - reading['PHONE_TYPE'], \ - reading['SIM_STATE'], \ - cid, \ - reading['NETWORK_OPERATOR'], \ - reading['HAS_ICC_CARD'], \ - reading['SIM_COUNTRY_ISO'], \ - lac, \ - reading['CALL_STATE'], \ - gsm_strength, \ - reading['SIM_OPERATOR_NAME'], \ - psc, \ - device_software_version, \ - gsm_error, \ - reading['NETWORK_TYPE'], \ - reading['SERVICE_STATE'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['NETWORK_COUNTRY_ISO'], + reading['PHONE_TYPE'], + reading['SIM_STATE'], + cid, + reading['NETWORK_OPERATOR'], + reading['HAS_ICC_CARD'], + reading['SIM_COUNTRY_ISO'], + lac, + reading['CALL_STATE'], + gsm_strength, + reading['SIM_OPERATOR_NAME'], + psc, + device_software_version, + gsm_error, + reading['NETWORK_TYPE'], + reading['SERVICE_STATE'], reading['IS_FORWARDING'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_toucheventsprobe.py b/management/commands/extractors/builtin_toucheventsprobe.py index 12189e0..b14247c 100644 --- a/management/commands/extractors/builtin_toucheventsprobe.py +++ b/management/commands/extractors/builtin_toucheventsprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,61 +9,62 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_toucheventsprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_toucheventsprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_toucheventsprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_toucheventsprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_toucheventsprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged, ' + \ 'last_touch_delay, ' + \ 'touch_count) VALUES (%s, %s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['LAST_TOUCH_DELAY'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['LAST_TOUCH_DELAY'], reading['TOUCH_COUNT'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_wakelockinformationprobe.py b/management/commands/extractors/builtin_wakelockinformationprobe.py index feec454..f0ac6ff 100644 --- a/management/commands/extractors/builtin_wakelockinformationprobe.py +++ b/management/commands/extractors/builtin_wakelockinformationprobe.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long + import datetime import json import psycopg2 @@ -8,49 +10,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON builtin_wakelockinformationprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_wakelockinformationprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM builtin_wakelockinformationprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_wakelockinformationprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print('USER: '+ user_id) -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_wakelockinformationprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -63,20 +65,19 @@ def insert(connection_str, user_id, reading): 'dim_count, ' + \ 'partial_locks, ' + \ 'partial_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - json.dumps(reading['FULL_LOCKS']), \ - reading['FULL_COUNT'], \ - json.dumps(reading['BRIGHT_LOCKS']), \ - reading['BRIGHT_COUNT'], \ - json.dumps(reading['DIM_LOCKS']), \ - reading['DIM_COUNT'], \ - json.dumps(reading['PARTIAL_LOCKS']), \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + json.dumps(reading['FULL_LOCKS']), + reading['FULL_COUNT'], + json.dumps(reading['BRIGHT_LOCKS']), + reading['BRIGHT_COUNT'], + json.dumps(reading['DIM_LOCKS']), + reading['DIM_COUNT'], + json.dumps(reading['PARTIAL_LOCKS']), reading['PARTIAL_COUNT'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/builtin_wifiaccesspointsprobe.py b/management/commands/extractors/builtin_wifiaccesspointsprobe.py index d40e939..2016287 100644 --- a/management/commands/extractors/builtin_wifiaccesspointsprobe.py +++ b/management/commands/extractors/builtin_wifiaccesspointsprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -13,64 +14,66 @@ CREATE_ACCESS_POINT_READING_ID_INDEX = 'CREATE INDEX ON builtin_wifiaccesspointsprobe_accesspoint(reading_id);' CREATE_ACCESS_POINT_UTC_LOGGED_INDEX = 'CREATE INDEX ON builtin_wifiaccesspointsprobe_accesspoint(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - if probe_table_exists(conn) == False or access_point_table_exists(conn) == False: + if probe_table_exists(conn) is False or access_point_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() cursor.execute('SELECT id FROM builtin_wifiaccesspointsprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_wifiaccesspointsprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists + + return table_exists + def access_point_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'builtin_wifiaccesspointsprobe_accesspoint\')') - - activities_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return activities_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if access_point_table_exists(conn) == False: + + if check_exists and access_point_table_exists(conn) is False: cursor.execute(CREATE_ACCESS_POINT_TABLE_SQL) cursor.execute(CREATE_ACCESS_POINT_USER_ID_INDEX) cursor.execute(CREATE_ACCESS_POINT_READING_ID_INDEX) cursor.execute(CREATE_ACCESS_POINT_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO builtin_wifiaccesspointsprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -80,39 +83,38 @@ def insert(connection_str, user_id, reading): 'current_rssi, ' + \ 'current_link_speed, ' + \ 'access_point_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - current_ssid = None current_bssid = None current_link_speed = None current_rssi = None - + if 'CURRENT_SSID' in reading: current_ssid = reading['CURRENT_SSID'] - + if 'CURRENT_BSSID' in reading: current_bssid = reading['CURRENT_BSSID'] - + if 'CURRENT_LINK_SPEED' in reading: current_link_speed = reading['CURRENT_LINK_SPEED'] if 'CURRENT_RSSI' in reading: current_rssi = reading['CURRENT_RSSI'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - current_ssid, \ - current_bssid, \ - current_rssi, \ - current_link_speed, \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + current_ssid, + current_bssid, + current_rssi, + current_link_speed, reading['ACCESS_POINT_COUNT'])) - + for row in cursor.fetchall(): reading_id = row[0] - + ap_cursor = conn.cursor() - + for point in reading['ACCESS_POINTS']: point_cmd = 'INSERT INTO builtin_wifiaccesspointsprobe_accesspoint(user_id, ' + \ 'reading_id, ' + \ @@ -122,17 +124,15 @@ def insert(connection_str, user_id, reading): 'capabilities, ' + \ 'frequency, ' + \ 'level) VALUES (%s, %s, %s, %s, %s, %s, %s, %s);' - - ap_cursor.execute(point_cmd, (user_id, \ - reading_id, - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), - point['SSID'], - point['BSSID'], - point['CAPABILITIES'], - point['FREQUENCY'], + ap_cursor.execute(point_cmd, (user_id, + reading_id, + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + point['SSID'], + point['BSSID'], + point['CAPABILITIES'], + point['FREQUENCY'], point['LEVEL'])) - conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/features_callhistoryfeature.py b/management/commands/extractors/features_callhistoryfeature.py index f54a9ad..ea614df 100644 --- a/management/commands/extractors/features_callhistoryfeature.py +++ b/management/commands/extractors/features_callhistoryfeature.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,6 +9,7 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON features_callhistoryfeature(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON features_callhistoryfeature(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) @@ -18,38 +20,38 @@ def exists(connection_str, user_id, reading): cursor = conn.cursor() cursor.execute('SELECT id FROM features_callhistoryfeature WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'features_callhistoryfeature\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) == False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO features_callhistoryfeature(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -70,31 +72,29 @@ def insert(connection_str, user_id, reading): 'acquiantance_count, ' + \ 'stranger_count, ' + \ 'acquiantance_ratio) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - for window in reading['WINDOWS']: - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], - reading['TIMESTAMP'], - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), - window['WINDOW_SIZE'], - window['INCOMING_COUNT'], - window['OUTGOING_COUNT'], - window['TOTAL'], - window['TOTAL_DURATION'], - window['MIN_DURATION'], - window['MAX_DURATION'], - window['STD_DEVIATION'], - window['AVG_DURATION'], - window['INCOMING_RATIO'], - window['ACK_RATIO'], - window['NEW_COUNT'], - window['ACK_COUNT'], - window['ACQUIANTANCE_COUNT'], - window['STRANGER_COUNT'], + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + window['WINDOW_SIZE'], + window['INCOMING_COUNT'], + window['OUTGOING_COUNT'], + window['TOTAL'], + window['TOTAL_DURATION'], + window['MIN_DURATION'], + window['MAX_DURATION'], + window['STD_DEVIATION'], + window['AVG_DURATION'], + window['INCOMING_RATIO'], + window['ACK_RATIO'], + window['NEW_COUNT'], + window['ACK_COUNT'], + window['ACQUIANTANCE_COUNT'], + window['STRANGER_COUNT'], window['ACQUAINTANCE_RATIO'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/features_deviceinusefeature.py b/management/commands/extractors/features_deviceinusefeature.py index 24901c5..58458c0 100644 --- a/management/commands/extractors/features_deviceinusefeature.py +++ b/management/commands/extractors/features_deviceinusefeature.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,59 +9,60 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON features_deviceinusefeature(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON features_deviceinusefeature(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - + if probe_table_exists(conn) == False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM features_deviceinusefeature WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'features_deviceinusefeature\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) == False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO features_deviceinusefeature(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged, ' + \ 'device_active) VALUES (%s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), reading['DEVICE_ACTIVE'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/features_sunrisesunsetfeature.py b/management/commands/extractors/features_sunrisesunsetfeature.py index 59dd745..1a100cc 100644 --- a/management/commands/extractors/features_sunrisesunsetfeature.py +++ b/management/commands/extractors/features_sunrisesunsetfeature.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,49 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON features_sunrisesunsetfeature(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON features_sunrisesunsetfeature(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM features_sunrisesunsetfeature WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'features_sunrisesunsetfeature\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print('USER: '+ user_id) -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO features_sunrisesunsetfeature(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -63,19 +64,19 @@ def insert(connection_str, user_id, reading): 'sunrise_distance, ' + \ 'sunset_distance, ' + \ 'is_day) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['LATITUDE'], \ - reading['LONGITUDE'], \ - reading['SUNRISE'], \ - reading['SUNSET'], \ - reading['DAY_DURATION'], \ - reading['SUNRISE_DISTANCE'], \ - reading['SUNSET_DISTANCE'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['LATITUDE'], + reading['LONGITUDE'], + reading['SUNRISE'], + reading['SUNSET'], + reading['DAY_DURATION'], + reading['SUNRISE_DISTANCE'], + reading['SUNSET_DISTANCE'], reading['IS_DAY'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/features_weatherundergroundfeature.py b/management/commands/extractors/features_weatherundergroundfeature.py index 8ffca12..817f6f6 100644 --- a/management/commands/extractors/features_weatherundergroundfeature.py +++ b/management/commands/extractors/features_weatherundergroundfeature.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -8,48 +9,49 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON features_weatherundergroundfeature(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON features_weatherundergroundfeature(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - - if probe_table_exists(conn) == False: + + if probe_table_exists(conn) is False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM features_weatherundergroundfeature WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'features_weatherundergroundfeature\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO features_weatherundergroundfeature(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -69,25 +71,25 @@ def insert(connection_str, user_id, reading): 'visibility, ' + \ 'wind_dir) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['WEATHER'], \ - reading['LOCATION'], \ - reading['STATION_ID'], \ - reading['OBS_TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['OBS_TIMESTAMP'], tz=pytz.utc), \ - reading['TEMPERATURE'], \ - reading['PRESSURE'], \ - reading['PRESSURE_TREND'], \ - reading['WIND_SPEED'], \ - reading['GUST_SPEED'], \ - reading['WIND_DEGREES'], \ - reading['DEWPOINT'], \ - reading['VISIBILITY'], \ + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['WEATHER'], + reading['LOCATION'], + reading['STATION_ID'], + reading['OBS_TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['OBS_TIMESTAMP'], tz=pytz.utc), + reading['TEMPERATURE'], + reading['PRESSURE'], + reading['PRESSURE_TREND'], + reading['WIND_SPEED'], + reading['GUST_SPEED'], + reading['WIND_DEGREES'], + reading['DEWPOINT'], + reading['VISIBILITY'], reading['WIND_DIR'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/media_audiocaptureprobe.py b/management/commands/extractors/media_audiocaptureprobe.py index 293b5d6..53308dc 100644 --- a/management/commands/extractors/media_audiocaptureprobe.py +++ b/management/commands/extractors/media_audiocaptureprobe.py @@ -1,11 +1,9 @@ +# pylint: disable=line-too-long + import datetime -import json -import msgpack import psycopg2 import pytz -from purple_robot_app.models import PurpleRobotReading - CREATE_PROBE_TABLE_SQL = 'CREATE TABLE media_audiocaptureprobe(id SERIAL PRIMARY KEY, user_id TEXT, guid TEXT, timestamp DOUBLE PRECISION, utc_logged TIMESTAMP, media_content_type TEXT, media_url TEXT, recording_duration BIGINT);' CREATE_PROBE_USER_ID_INDEX = 'CREATE INDEX ON media_audiocaptureprobe(user_id);' CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON media_audiocaptureprobe(guid);' @@ -16,43 +14,43 @@ def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False cursor.execute('SELECT id FROM media_audiocaptureprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'media_audiocaptureprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists - -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO media_audiocaptureprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -60,15 +58,15 @@ def insert(connection_str, user_id, reading): 'media_content_type, ' + \ 'media_url, ' + \ 'recording_duration) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['media_content_type'], \ - reading['media_url'], \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['media_content_type'], + reading['media_url'], reading['RECORDING_DURATION'])) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/sensors_accelerometersensorprobe.py b/management/commands/extractors/sensors_accelerometersensorprobe.py index dac37bc..c235d12 100644 --- a/management/commands/extractors/sensors_accelerometersensorprobe.py +++ b/management/commands/extractors/sensors_accelerometersensorprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long, no-member + import datetime -import json import msgpack import psycopg2 import pytz @@ -16,67 +17,69 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON sensors_accelerometersensorprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON sensors_accelerometersensorprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False - if reading_table_exists(conn) == False: + if reading_table_exists(conn) is False: conn.close() return False cursor.execute('SELECT id FROM sensors_accelerometersensorprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'sensors_accelerometersensorprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'sensors_accelerometersensorprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO sensors_accelerometersensorprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -88,27 +91,27 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + reading_cursor = conn.cursor() pr_reading = PurpleRobotReading.objects.filter(guid=reading['GUID']).first() - - if pr_reading != None and pr_reading.attachment != None: + + if pr_reading is not None and pr_reading.attachment is not None: reading_cmd = 'INSERT INTO sensors_accelerometersensorprobe_reading(user_id, ' + \ 'reading_id, ' + \ 'utc_logged, ' + \ @@ -122,18 +125,18 @@ def insert(connection_str, user_id, reading): 'y, ' + \ 'z, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - + readings_file = None - + try: readings_file = pr_reading.attachment except ValueError: readings_file = None - - if readings_file != None: + + if readings_file is not None: try: content = list(msgpack.Unpacker(readings_file)) - + if len(content) == 8: time_buffer = content[1] sensor_time_buffer = content[2] @@ -142,13 +145,13 @@ def insert(connection_str, user_id, reading): x_buffer = content[5] y_buffer = content[6] z_buffer = content[7] - + for i in range(0, len(time_buffer)): - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(time_buffer[i]) values.append(datetime.datetime.fromtimestamp(time_buffer[i], tz=pytz.utc)) - + values.append(sensor_time_buffer[i]) try: values.append(datetime.datetime.fromtimestamp((sensor_time_buffer[i] / 1000), tz=pytz.utc)) @@ -161,12 +164,12 @@ def insert(connection_str, user_id, reading): values.append(y_buffer[i]) values.append(z_buffer[i]) values.append(accuracy_buffer[i]) - + reading_cursor.execute(reading_cmd, values) except ValueError: pass conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/sensors_lightsensorprobe.py b/management/commands/extractors/sensors_lightsensorprobe.py index ef08c6a..e0c02cc 100644 --- a/management/commands/extractors/sensors_lightsensorprobe.py +++ b/management/commands/extractors/sensors_lightsensorprobe.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long, no-member + import datetime -import json import msgpack import psycopg2 import pytz @@ -16,67 +17,69 @@ CREATE_READING_READING_ID_INDEX = 'CREATE INDEX ON sensors_lightsensorprobe_reading(reading_id);' CREATE_READING_UTC_LOGGED_INDEX = 'CREATE INDEX ON sensors_lightsensorprobe_reading(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - if probe_table_exists(conn) == False: + if probe_table_exists(conn) is False: conn.close() return False - if reading_table_exists(conn) == False: + if reading_table_exists(conn) is False: conn.close() return False cursor.execute('SELECT id FROM sensors_lightsensorprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'sensors_lightsensorprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def reading_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'sensors_lightsensorprobe_reading\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if reading_table_exists(conn) == False: + + if check_exists and reading_table_exists(conn) is False: cursor.execute(CREATE_READING_TABLE_SQL) cursor.execute(CREATE_READING_USER_ID_INDEX) cursor.execute(CREATE_READING_READING_ID_INDEX) cursor.execute(CREATE_READING_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO sensors_lightsensorprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -88,27 +91,27 @@ def insert(connection_str, user_id, reading): 'sensor_version, ' + \ 'sensor_resolution, ' + \ 'sensor_maximum_range) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - reading['SENSOR']['VENDOR'], \ - reading['SENSOR']['NAME'], \ - reading['SENSOR']['POWER'], \ - reading['SENSOR']['TYPE'], \ - reading['SENSOR']['VERSION'], \ - reading['SENSOR']['RESOLUTION'], \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + reading['SENSOR']['VENDOR'], + reading['SENSOR']['NAME'], + reading['SENSOR']['POWER'], + reading['SENSOR']['TYPE'], + reading['SENSOR']['VERSION'], + reading['SENSOR']['RESOLUTION'], reading['SENSOR']['MAXIMUM_RANGE'])) - + for row in cursor.fetchall(): reading_id = row[0] - + reading_cursor = conn.cursor() pr_reading = PurpleRobotReading.objects.filter(guid=reading['GUID']).first() - - if pr_reading != None and pr_reading.attachment != None: + + if pr_reading is not None and pr_reading.attachment is not None: reading_cmd = 'INSERT INTO sensors_lightsensorprobe_reading(user_id, ' + \ 'reading_id, ' + \ 'utc_logged, ' + \ @@ -120,32 +123,33 @@ def insert(connection_str, user_id, reading): 'normalized_timestamp_utc, ' + \ 'lux, ' + \ 'accuracy) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);' - + readings_file = None - + try: readings_file = pr_reading.attachment except ValueError: readings_file = None - - if readings_file != None: + + if readings_file is not None: try: content = list(msgpack.Unpacker(readings_file)) - + if len(content) == 6: time_buffer = content[1] sensor_time_buffer = content[2] normal_time_buffer = content[3] accuracy_buffer = content[4] lux_buffer = content[5] - + for i in range(0, len(time_buffer)): - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc) ] - + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc)] + values.append(time_buffer[i]) values.append(datetime.datetime.fromtimestamp(time_buffer[i], tz=pytz.utc)) - + values.append(sensor_time_buffer[i]) + try: values.append(datetime.datetime.fromtimestamp((sensor_time_buffer[i] / 1000), tz=pytz.utc)) except ValueError: @@ -155,12 +159,13 @@ def insert(connection_str, user_id, reading): values.append(datetime.datetime.fromtimestamp(normal_time_buffer[i], tz=pytz.utc)) values.append(lux_buffer[i]) values.append(accuracy_buffer[i]) - + reading_cursor.execute(reading_cmd, values) except ValueError: pass - + except TypeError: + pass conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/services_fitbitbetaprobe copy.py b/management/commands/extractors/services_fitbitbetaprobe copy.py index ba36ec0..4c7f60a 100644 --- a/management/commands/extractors/services_fitbitbetaprobe copy.py +++ b/management/commands/extractors/services_fitbitbetaprobe copy.py @@ -1,5 +1,6 @@ +# pylint: disable=line-too-long + import datetime -import json import psycopg2 import pytz @@ -38,163 +39,169 @@ CREATE_ELEVATION_ID_INDEX = 'CREATE INDEX ON services_fitbitbetaprobe_elevation(reading_id);' CREATE_ELEVATION_UTC_LOGGED_INDEX = 'CREATE INDEX ON services_fitbitbetaprobe_elevation(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - if probe_table_exists(conn) == False or heart_table_exists(conn) == False or distance_table_exists(conn) == False or calories_table_exists(conn) == False or steps_table_exists(conn) == False or floors_table_exists(conn) == False or elevation_table_exists(conn) == False: + if probe_table_exists(conn) is False or heart_table_exists(conn) is False or distance_table_exists(conn) is False or calories_table_exists(conn) is False or steps_table_exists(conn) is False or floors_table_exists(conn) is False or elevation_table_exists(conn) is False: conn.close() return False cursor.execute('SELECT id FROM services_fitbitbetaprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def heart_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_heart\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def steps_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_steps\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def distance_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_distance\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def calories_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_calories\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def floors_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_floors\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists + def elevation_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_fitbitbetaprobe_elevation\')') - - exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return exists + + return table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) is False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - - if heart_table_exists(conn) == False: + + if check_exists and heart_table_exists(conn) is False: cursor.execute(CREATE_HEART_TABLE_SQL) cursor.execute(CREATE_HEART_USER_ID_INDEX) cursor.execute(CREATE_HEART_ID_INDEX) cursor.execute(CREATE_HEART_UTC_LOGGED_INDEX) - - if distance_table_exists(conn) == False: + + if check_exists and distance_table_exists(conn) is False: cursor.execute(CREATE_DISTANCE_TABLE_SQL) cursor.execute(CREATE_DISTANCE_USER_ID_INDEX) cursor.execute(CREATE_DISTANCE_ID_INDEX) cursor.execute(CREATE_DISTANCE_UTC_LOGGED_INDEX) - - if calories_table_exists(conn) == False: + + if check_exists and calories_table_exists(conn) is False: cursor.execute(CREATE_CALORIES_TABLE_SQL) cursor.execute(CREATE_CALORIES_USER_ID_INDEX) cursor.execute(CREATE_CALORIES_ID_INDEX) cursor.execute(CREATE_CALORIES_UTC_LOGGED_INDEX) - - if steps_table_exists(conn) == False: + + if check_exists and steps_table_exists(conn) is False: cursor.execute(CREATE_STEPS_TABLE_SQL) cursor.execute(CREATE_STEPS_USER_ID_INDEX) cursor.execute(CREATE_STEPS_ID_INDEX) cursor.execute(CREATE_STEPS_UTC_LOGGED_INDEX) - - if floors_table_exists(conn) == False: + + if check_exists and floors_table_exists(conn) is False: cursor.execute(CREATE_FLOORS_TABLE_SQL) cursor.execute(CREATE_FLOORS_USER_ID_INDEX) cursor.execute(CREATE_FLOORS_ID_INDEX) cursor.execute(CREATE_FLOORS_UTC_LOGGED_INDEX) - - if elevation_table_exists(conn) == False: + + if check_exists and elevation_table_exists(conn) is False: cursor.execute(CREATE_ELEVATION_TABLE_SQL) cursor.execute(CREATE_ELEVATION_USER_ID_INDEX) cursor.execute(CREATE_ELEVATION_ID_INDEX) cursor.execute(CREATE_ELEVATION_UTC_LOGGED_INDEX) - + conn.commit() - + reading_cmd = 'INSERT INTO services_fitbitbetaprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ 'utc_logged) VALUES (%s, %s, %s, %s) RETURNING id;' - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc))) - + for row in cursor.fetchall(): reading_id = row[0] - + reading_cursor = conn.cursor() - + if 'DISTANCE' in reading: for i in range(0, len(reading['DISTANCE'])): value = reading['DISTANCE'][i] - ts = reading['DISTANCE_TIMESTAMPS'][i] + timestamp = reading['DISTANCE_TIMESTAMPS'][i] reading_cmd = 'INSERT INTO services_fitbitbetaprobe_distance(user_id, ' + \ 'reading_id, ' + \ @@ -202,31 +209,31 @@ def insert(connection_str, user_id, reading): 'sensor_timestamp, ' + \ 'sensor_timestamp_utc, ' + \ 'distance) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + reading_cursor.execute(reading_cmd, values) if 'CALORIES' in reading: - for i in range(0, len(reading['CALORIES'])): - value = reading['CALORIES'][i] - ts = reading['CALORIES_TIMESTAMPS'][i] - - reading_cmd = 'INSERT INTO services_fitbitbetaprobe_calories(user_id, ' + \ - 'reading_id, ' + \ - 'utc_logged, ' + \ - 'sensor_timestamp, ' + \ - 'sensor_timestamp_utc, ' + \ - 'calories) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - - reading_cursor.execute(reading_cmd, values) - + for i in range(0, len(reading['CALORIES'])): + value = reading['CALORIES'][i] + timestamp = reading['CALORIES_TIMESTAMPS'][i] + + reading_cmd = 'INSERT INTO services_fitbitbetaprobe_calories(user_id, ' + \ + 'reading_id, ' + \ + 'utc_logged, ' + \ + 'sensor_timestamp, ' + \ + 'sensor_timestamp_utc, ' + \ + 'calories) VALUES (%s, %s, %s, %s, %s, %s);' + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + + reading_cursor.execute(reading_cmd, values) + if 'STEPS' in reading: for i in range(0, len(reading['STEPS'])): value = reading['STEPS'][i] - ts = reading['STEP_TIMESTAMPS'][i] + timestamp = reading['STEP_TIMESTAMPS'][i] reading_cmd = 'INSERT INTO services_fitbitbetaprobe_steps(user_id, ' + \ 'reading_id, ' + \ @@ -234,15 +241,15 @@ def insert(connection_str, user_id, reading): 'sensor_timestamp, ' + \ 'sensor_timestamp_utc, ' + \ 'steps) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + reading_cursor.execute(reading_cmd, values) - + if 'FLOORS' in reading: for i in range(0, len(reading['FLOORS'])): value = reading['FLOORS'][i] - ts = reading['FLOORS_TIMESTAMPS'][i] + timestamp = reading['FLOORS_TIMESTAMPS'][i] reading_cmd = 'INSERT INTO services_fitbitbetaprobe_floors(user_id, ' + \ 'reading_id, ' + \ @@ -250,46 +257,46 @@ def insert(connection_str, user_id, reading): 'sensor_timestamp, ' + \ 'sensor_timestamp_utc, ' + \ 'floors) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + reading_cursor.execute(reading_cmd, values) if 'HEART' in reading: - for i in range(0, len(reading['HEART'])): - value = reading['HEART'][i] - ts = reading['HEART_TIMESTAMPS'][i] - - reading_cmd = 'INSERT INTO services_fitbitbetaprobe_heart(user_id, ' + \ - 'reading_id, ' + \ - 'utc_logged, ' + \ - 'sensor_timestamp, ' + \ - 'sensor_timestamp_utc, ' + \ - 'avg_heartrate) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - - reading_cursor.execute(reading_cmd, values) - + for i in range(0, len(reading['HEART'])): + value = reading['HEART'][i] + timestamp = reading['HEART_TIMESTAMPS'][i] + + reading_cmd = 'INSERT INTO services_fitbitbetaprobe_heart(user_id, ' + \ + 'reading_id, ' + \ + 'utc_logged, ' + \ + 'sensor_timestamp, ' + \ + 'sensor_timestamp_utc, ' + \ + 'avg_heartrate) VALUES (%s, %s, %s, %s, %s, %s);' + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + + reading_cursor.execute(reading_cmd, values) + if 'ELEVATION' in reading: - for i in range(0, len(reading['ELEVATION'])): - value = reading['ELEVATION'][i] - ts = reading['ELEVATION_TIMESTAMPS'][i] - - reading_cmd = 'INSERT INTO services_fitbitbetaprobe_elevation(user_id, ' + \ - 'reading_id, ' + \ - 'utc_logged, ' + \ - 'sensor_timestamp, ' + \ - 'sensor_timestamp_utc, ' + \ - 'elevation) VALUES (%s, %s, %s, %s, %s, %s);' - - values = [ user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), ts, datetime.datetime.fromtimestamp(ts / 1000, tz=pytz.utc), value ] - - reading_cursor.execute(reading_cmd, values) - + for i in range(0, len(reading['ELEVATION'])): + value = reading['ELEVATION'][i] + timestamp = reading['ELEVATION_TIMESTAMPS'][i] + + reading_cmd = 'INSERT INTO services_fitbitbetaprobe_elevation(user_id, ' + \ + 'reading_id, ' + \ + 'utc_logged, ' + \ + 'sensor_timestamp, ' + \ + 'sensor_timestamp_utc, ' + \ + 'elevation) VALUES (%s, %s, %s, %s, %s, %s);' + + values = [user_id, reading_id, datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), timestamp, datetime.datetime.fromtimestamp(timestamp / 1000, tz=pytz.utc), value] + + reading_cursor.execute(reading_cmd, values) + reading_cursor.close() conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/extractors/services_googleplacesprobe.py b/management/commands/extractors/services_googleplacesprobe.py index 9347e0d..d1f88ca 100644 --- a/management/commands/extractors/services_googleplacesprobe.py +++ b/management/commands/extractors/services_googleplacesprobe.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long + import datetime import json import psycopg2 @@ -8,54 +10,55 @@ CREATE_PROBE_GUID_INDEX = 'CREATE INDEX ON services_googleplacesprobe(guid);' CREATE_PROBE_UTC_LOGGED_INDEX = 'CREATE INDEX ON services_googleplacesprobe(utc_logged);' + def exists(connection_str, user_id, reading): conn = psycopg2.connect(connection_str) - + if probe_table_exists(conn) == False: conn.close() return False cursor = conn.cursor() - + cursor.execute('SELECT id FROM services_googleplacesprobe WHERE (user_id = %s AND guid = %s);', (user_id, reading['GUID'])) - - exists = (cursor.rowcount > 0) - + + row_exists = (cursor.rowcount > 0) + cursor.close() conn.close() - - return exists + + return row_exists + def probe_table_exists(conn): cursor = conn.cursor() cursor.execute('SELECT table_name FROM information_schema.tables WHERE (table_schema = \'public\' AND table_name = \'services_googleplacesprobe\')') - - probe_table_exists = (cursor.rowcount > 0) - + + table_exists = (cursor.rowcount > 0) + cursor.close() - - return probe_table_exists -def insert(connection_str, user_id, reading): -# print(json.dumps(reading, indent=2)) - + return table_exists + + +def insert(connection_str, user_id, reading, check_exists=True): conn = psycopg2.connect(connection_str) cursor = conn.cursor() - - if probe_table_exists(conn) == False: + + if check_exists and probe_table_exists(conn) == False: cursor.execute(CREATE_PROBE_TABLE_SQL) cursor.execute(CREATE_PROBE_USER_ID_INDEX) cursor.execute(CREATE_PROBE_GUID_INDEX) cursor.execute(CREATE_PROBE_UTC_LOGGED_INDEX) - + conn.commit() - + places_only = {} - - for k,v in reading.iteritems(): - if k != 'MOST_LIKELY_PLACE_ID' and k != 'MOST_LIKELY_PLACE' and k != 'MOST_LIKELY_PLACE_LIKELIHOOD' and k != 'GUID' and k != 'TIMESTAMP' and k != 'PROBE': - places_only[k] = v - + + for key, value in reading.iteritems(): + if key != 'MOST_LIKELY_PLACE_ID' and key != 'MOST_LIKELY_PLACE' and key != 'MOST_LIKELY_PLACE_LIKELIHOOD' and key != 'GUID' and key != 'TIMESTAMP' and key != 'PROBE': + places_only[key] = value + reading_cmd = 'INSERT INTO services_googleplacesprobe(user_id, ' + \ 'guid, ' + \ 'timestamp, ' + \ @@ -64,31 +67,31 @@ def insert(connection_str, user_id, reading): 'most_likely_place, ' + \ 'most_likely_place_likelihood, ' + \ 'places_json) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) RETURNING id;' - + most_likely_id = None - + if 'MOST_LIKELY_PLACE_ID' in reading: - most_likely_id = reading['MOST_LIKELY_PLACE_ID'] + most_likely_id = reading['MOST_LIKELY_PLACE_ID'] most_likely_place = None - + if 'MOST_LIKELY_PLACE' in reading: - most_likely_place = reading['MOST_LIKELY_PLACE'] + most_likely_place = reading['MOST_LIKELY_PLACE'] most_likely_likelihood = None - + if 'MOST_LIKELY_PLACE_LIKELIHOOD' in reading: - most_likely_likelihood = reading['MOST_LIKELY_PLACE_LIKELIHOOD'] - - cursor.execute(reading_cmd, (user_id, \ - reading['GUID'], \ - reading['TIMESTAMP'], \ - datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), \ - most_likely_id, \ - most_likely_place, \ - most_likely_likelihood, \ + most_likely_likelihood = reading['MOST_LIKELY_PLACE_LIKELIHOOD'] + + cursor.execute(reading_cmd, (user_id, + reading['GUID'], + reading['TIMESTAMP'], + datetime.datetime.fromtimestamp(reading['TIMESTAMP'], tz=pytz.utc), + most_likely_id, + most_likely_place, + most_likely_likelihood, json.dumps(places_only))) conn.commit() - + cursor.close() conn.close() diff --git a/management/commands/fitbit_beta_check.py b/management/commands/fitbit_beta_check.py index 8dda689..7e8bfda 100644 --- a/management/commands/fitbit_beta_check.py +++ b/management/commands/fitbit_beta_check.py @@ -1,37 +1,36 @@ +# pylint: disable=line-too-long, no-member + import json from django.core.management.base import BaseCommand -from django.conf import settings -from django.utils import timezone from purple_robot_app.models import PurpleRobotReading + class Command(BaseCommand): def handle(self, *args, **options): seen = {} - - keys = [ 'HEART', 'FLOORS', 'ELEVATION', 'DISTANCE', 'CALORIES', 'STEPS' ] - + + keys = ['HEART', 'FLOORS', 'ELEVATION', 'DISTANCE', 'CALORIES', 'STEPS'] + for reading in PurpleRobotReading.objects.filter(user_id='073adb668d74a6aaa7d452eb9f804c99', probe='edu.northwestern.cbits.purple_robot_manager.probes.services.FitbitBetaProbe').order_by('logged'): payload = json.loads(reading.payload) - + for key in keys: values = payload[key] - + if key == 'STEPS': key = 'STEP' - + times = payload[key + '_TIMESTAMPS'] - + for i in range(0, len(values)): value = values[i] time = times[i] - + seen_key = key + '_' + str(time) - + if seen_key in seen: - print('DUPE VALUE: ' + seen_key + ' - O: ' + str(seen[seen_key]) + ' N: ' + str(value) + ' --> ' + reading.guid) - + print 'DUPE VALUE: ' + seen_key + ' - O: ' + str(seen[seen_key]) + ' N: ' + str(value) + ' --> ' + reading.guid + seen[seen_key] = value - - diff --git a/management/commands/gather_statistics.py b/management/commands/gather_statistics.py new file mode 100644 index 0000000..eb332e0 --- /dev/null +++ b/management/commands/gather_statistics.py @@ -0,0 +1,129 @@ +# pylint: disable=line-too-long, no-member + +import arrow +import datetime +import os +import pytz + +from django.core.management.base import BaseCommand +from django.conf import settings +from django.utils import timezone + +from purple_robot_app.models import PurpleRobotPayload +from purple_robot_app.performance import append_performance_sample + + +def touch(fname, mode=0o666): + flags = os.O_CREAT | os.O_APPEND + + if os.fdopen(os.open(fname, flags, mode)) is not None: + os.utime(fname, None) + + +class Command(BaseCommand): + def handle(self, *args, **options): + if os.access('/tmp/gather_statistics.lock', os.R_OK): + timestamp = os.path.getmtime('/tmp/gather_statistics.lock') + created = datetime.datetime.fromtimestamp(timestamp) + + if (datetime.datetime.now() - created).total_seconds() > 6 * 60 * 60: + print 'gather_statistics: Stale lock - removing...' + os.remove('/tmp/gather_statistics.lock') + else: + return + + touch('/tmp/gather_statistics.lock') + + here_tz = pytz.timezone(settings.TIME_ZONE) + now = arrow.get(timezone.now().astimezone(here_tz)) + + start_today = now.replace(hour=0, minute=0, second=0, microsecond=0).datetime + start_hour = now.datetime - datetime.timedelta(hours=1) + + count = PurpleRobotPayload.objects.filter(added__gte=start_hour).count() + append_performance_sample('system', 'uploads_hour', timezone.now(), {'count': count}) + + count = PurpleRobotPayload.objects.filter(added__gte=start_today).count() + append_performance_sample('system', 'uploads_today', timezone.now(), {'count': count}) + + tag = 'extracted_into_database' + skip_tag = 'extracted_into_database_skip' + + count = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + append_performance_sample('system', 'pending_mirror_payloads', timezone.now(), {'count': count}) + + count = PurpleRobotPayload.objects.filter(process_tags__contains=skip_tag).count() + append_performance_sample('system', 'skipped_mirror_payloads', timezone.now(), {'count': count}) + + week = now.datetime - datetime.timedelta(days=7) + day = now.datetime - datetime.timedelta(days=1) + half_day = now.datetime - datetime.timedelta(hours=12) + quarter_day = now.datetime - datetime.timedelta(hours=6) + hour = now.datetime - datetime.timedelta(hours=1) + + week_count = PurpleRobotPayload.objects.filter(added__lt=week).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + day_count = PurpleRobotPayload.objects.filter(added__gte=week, added__lt=day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + half_day_count = PurpleRobotPayload.objects.filter(added__gte=day, added__lt=half_day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + quarter_day_count = PurpleRobotPayload.objects.filter(added__gte=half_day, added__lt=quarter_day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + hour_count = PurpleRobotPayload.objects.filter(added__gte=quarter_day, added__lt=hour).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + less_hour_count = PurpleRobotPayload.objects.filter(added__gte=hour).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + + append_performance_sample('system', 'pending_mirror_ages', timezone.now(), { + 'week_count': week_count, + 'day_count': day_count, + 'half_day_count': half_day_count, + 'quarter_day_count': quarter_day_count, + 'hour_count': hour_count, + 'less_hour_count': less_hour_count + }) + + tag = 'extracted_readings' + skip_tag = 'ingest_error' + + count = PurpleRobotPayload.objects.exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + append_performance_sample('system', 'pending_ingest_payloads', timezone.now(), {'count': count}) + + count = PurpleRobotPayload.objects.filter(process_tags__contains=skip_tag).count() + append_performance_sample('system', 'skipped_ingest_payloads', timezone.now(), {'count': count}) + + week_count = PurpleRobotPayload.objects.filter(added__lt=week).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + day_count = PurpleRobotPayload.objects.filter(added__gte=week, added__lt=day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + half_day_count = PurpleRobotPayload.objects.filter(added__gte=day, added__lt=half_day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + quarter_day_count = PurpleRobotPayload.objects.filter(added__gte=half_day, added__lt=quarter_day).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + hour_count = PurpleRobotPayload.objects.filter(added__gte=quarter_day, added__lt=hour).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + less_hour_count = PurpleRobotPayload.objects.filter(added__gte=hour).exclude(process_tags__contains=tag).exclude(process_tags__contains=skip_tag).count() + + append_performance_sample('system', 'pending_ingest_ages', timezone.now(), { + 'week_count': week_count, + 'day_count': day_count, + 'half_day_count': half_day_count, + 'quarter_day_count': quarter_day_count, + 'hour_count': hour_count, + 'less_hour_count': less_hour_count + }) + + uptime = os.popen("/usr/bin/uptime").read() + uptime = uptime.split('load average: ')[1].split(" ") + + load_minute = float(uptime[0].replace(',', '').strip()) + load_five = float(uptime[1].replace(',', '').strip()) + load_fifteen = float(uptime[2].replace(',', '').strip()) + + append_performance_sample('system', 'server_performance', timezone.now(), {'load_minute': load_minute, 'load_five': load_five, 'load_fifteen': load_fifteen}) + + counts = [] + + index_time = start_today + + while (index_time + datetime.timedelta(seconds=(15 * 60))) < timezone.now(): + end = index_time + datetime.timedelta(seconds=(30 * 60)) + + plot = index_time + datetime.timedelta(seconds=(15 * 60)) + + counts.append({'date': plot.isoformat(), 'count': PurpleRobotPayload.objects.filter(added__gte=index_time, added__lt=end).count()}) + + index_time = end + + append_performance_sample('system', 'payload_uploads', timezone.now(), {'counts': counts}) + + os.remove('/tmp/gather_statistics.lock') diff --git a/management/commands/generate_md5_hash.py b/management/commands/generate_md5_hash.py new file mode 100644 index 0000000..fba1722 --- /dev/null +++ b/management/commands/generate_md5_hash.py @@ -0,0 +1,15 @@ +# pylint: disable=line-too-long + +import hashlib + +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + def handle(self, *args, **options): + md5_hash = hashlib.md5() + md5_hash.update(args[0]) + + user_id = md5_hash.hexdigest() + + print 'Hash for ' + args[0] + ': ' + user_id diff --git a/management/commands/generate_probe_tests.py b/management/commands/generate_probe_tests.py index 24126d7..f3a4e03 100644 --- a/management/commands/generate_probe_tests.py +++ b/management/commands/generate_probe_tests.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + from django.core.management.base import BaseCommand from django.utils import timezone from django.utils.text import slugify @@ -6,15 +8,16 @@ REPORT_DAYS = 1 + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.all(): user_id = device.user_hash() - + for reading in device.last_readings(omit_readings=True): probe = reading['full_probe_name'] - + if PurpleRobotTest.objects.filter(user_id=user_id, probe=probe).count() == 0: PurpleRobotTest(user_id=user_id, probe=probe, active=True, last_updated=timezone.now(), slug=slugify(device.device_id + '-' + reading['name'])).save() - - print('Added test ' + device.device_id + '/' + reading['name']) + + print 'Added test ' + device.device_id + '/' + reading['name'] diff --git a/management/commands/migrate_user.py b/management/commands/migrate_user.py new file mode 100644 index 0000000..ce4f88c --- /dev/null +++ b/management/commands/migrate_user.py @@ -0,0 +1,18 @@ +# pylint: disable=line-too-long, no-member + +from django.core.management.base import BaseCommand + +from purple_robot_app.models import PurpleRobotReading + + +class Command(BaseCommand): + def handle(self, *args, **options): + print 'Rename ' + args[0] + ' to ' + args[1] + '...' + + readings = PurpleRobotReading.objects.filter(user_id=args[0]) + + print 'Original reading count: ' + str(readings.count()) + + updated = readings.update(user_id=args[1]) + + print 'Updated reading count: ' + str(updated) diff --git a/management/commands/pr_status_check_device_payload_reading_gap.py b/management/commands/off_pr_status_check_device_payload_reading_gap.py similarity index 65% rename from management/commands/pr_status_check_device_payload_reading_gap.py rename to management/commands/off_pr_status_check_device_payload_reading_gap.py index f17638e..b396d2f 100644 --- a/management/commands/pr_status_check_device_payload_reading_gap.py +++ b/management/commands/off_pr_status_check_device_payload_reading_gap.py @@ -1,33 +1,35 @@ +# pylint: disable=line-too-long, no-member + import datetime from django.core.management.base import BaseCommand -from django.utils import timezone from purple_robot_app.models import PurpleRobotDevice, PurpleRobotPayload, PurpleRobotReading from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert START_DAYS = 7 + class Command(BaseCommand): def handle(self, *args, **options): start = datetime.datetime.now() - datetime.timedelta(days=START_DAYS) - + for device in PurpleRobotDevice.objects.filter(mute_alerts=False): payload = PurpleRobotPayload.objects.filter(user_id=device.hash_key, added__gte=start).order_by('-added').first() reading = PurpleRobotReading.objects.filter(user_id=device.hash_key, logged__gte=start).order_by('-logged').first() - - if payload != None and reading != None: + + if payload is not None and reading is not None: diff = payload.added - reading.logged - + seconds = diff.total_seconds() - - if seconds < (10 * 60): + + if seconds < (10 * 60): cancel_alert(tags='device_payload_reading_gap', user_id=device.hash_key) - elif seconds < (30 * 60): + elif seconds < (30 * 60): log_alert(message='{0:.2f}'.format(seconds / 60) + ' minute gap between payload and reading.', severity=1, tags='device_payload_reading_gap', user_id=device.hash_key) else: log_alert(message='{0:.2f}'.format(seconds / 60) + ' minute gap between payload and reading.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key) - elif payload == None: - log_alert(message='Unable to determine reading-payload gap. No payloads uploaded in the last week.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key) - elif reading == None: - log_alert(message='Unable to determine reading-payload gap. No readings uploaded in the last week.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key) + elif payload is None: + log_alert(message='Unable to determine reading-payload gap. No payloads uploaded in the last week.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key) + elif reading is None: + log_alert(message='Unable to determine reading-payload gap. No readings uploaded in the last week.', severity=2, tags='device_payload_reading_gap', user_id=device.hash_key) diff --git a/management/commands/pr_check_status.py b/management/commands/pr_check_status.py index 0b71435..bcf4215 100644 --- a/management/commands/pr_check_status.py +++ b/management/commands/pr_check_status.py @@ -1,7 +1,9 @@ +# pylint: disable=line-too-long, no-member + import os import datetime -from django.core.management import call_command, find_management_module, find_commands, load_command_class +from django.core.management import call_command, find_management_module, find_commands from django.core.management.base import BaseCommand from django.conf import settings from django.db.models import Q @@ -9,50 +11,53 @@ from purple_robot_app.models import PurpleRobotAlert, PurpleRobotDevice -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + def fetch_query(message=None, severity=None, tags=None, user_id=None, probe=None, dismissed=False): - q = Q(dismissed=None) - + query = Q(dismissed=None) + if dismissed: - q = Q(dismissed__lte=timezone.now()) - - if message != None: - q = q & Q(message=message) - - if severity != None: - q = q & Q(severity=severity) - - if tags != None: - q = q & Q(tags__contains=tags) - - if user_id != None: - q = q & Q(user_id=user_id) - - if probe != None: - q = q & Q(probe=probe) - - return q + query = Q(dismissed__lte=timezone.now()) + + if message is not None: + query = query & Q(message=message) + + if severity is not None: + query = query & Q(severity=severity) + + if tags is not None: + query = query & Q(tags__contains=tags) + + if user_id is not None: + query = query & Q(user_id=user_id) + + if probe is not None: + query = query & Q(probe=probe) + + return query + def log_alert(message=None, severity=None, tags=None, user_id=None, probe=None): - q = fetch_query(None, None, tags, user_id, probe) - + query = fetch_query(None, None, tags, user_id, probe) + now = timezone.now() - - alerts = PurpleRobotAlert.objects.filter(q) - + + alerts = PurpleRobotAlert.objects.filter(query) + if alerts.count() > 0: for alert in alerts: - if message != None: + if message is not None: alert.message = message - - if severity != None: + + if severity is not None: alert.severity = severity - + alert.save() else: alert = PurpleRobotAlert(message=message, severity=severity, tags=tags, probe=probe, user_id=user_id, generated=now) @@ -60,47 +65,47 @@ def log_alert(message=None, severity=None, tags=None, user_id=None, probe=None): def cancel_alert(message=None, severity=None, tags=None, user_id=None, probe=None): - q = fetch_query(message, severity, tags, user_id, probe) - + query = fetch_query(message, severity, tags, user_id, probe) + now = timezone.now() - - for alert in PurpleRobotAlert.objects.filter(q): + + for alert in PurpleRobotAlert.objects.filter(query): alert.dismissed = now alert.manually_dismissed = True - + alert.save() + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/check_status.lock', os.R_OK): - t = os.path.getmtime('/tmp/check_status.lock') - created = datetime.datetime.fromtimestamp(t) - - if (datetime.datetime.now() - created).total_seconds() > 60 * 60: - print('check_status: Stale lock - removing...') + timestamp = os.path.getmtime('/tmp/check_status.lock') + created = datetime.datetime.fromtimestamp(timestamp) + + if (datetime.datetime.now() - created).total_seconds() > 6 * 60 * 60: + print 'check_status: Stale lock - removing...' os.remove('/tmp/check_status.lock') else: return - + touch('/tmp/check_status.lock') for app in settings.INSTALLED_APPS: if app.startswith('django') == False: - try: + try: command_names = find_commands(find_management_module(app)) - + for command_name in command_names: if command_name.startswith('pr_status_check_'): - # print('Running: ' + command_name) call_command(command_name) - + touch('/tmp/check_status.lock') except ImportError: pass - + for device in PurpleRobotDevice.objects.filter(mute_alerts=True): for alert in PurpleRobotAlert.objects.filter(user_id=device.hash_key, dismissed=None): - alert.dismissed = timezone.now() - alert.save() + alert.dismissed = timezone.now() + alert.save() os.remove('/tmp/check_status.lock') diff --git a/management/commands/pr_status_check_consistent_hardware.py b/management/commands/pr_status_check_consistent_hardware.py index 79b5ee9..a3defcd 100644 --- a/management/commands/pr_status_check_consistent_hardware.py +++ b/management/commands/pr_status_check_consistent_hardware.py @@ -1,32 +1,36 @@ +# pylint: disable=line-too-long, no-member + import datetime import json from django.core.management.base import BaseCommand from django.utils import timezone -from purple_robot_app.models import PurpleRobotDevice, PurpleRobotReading, my_slugify +from purple_robot_app.models import PurpleRobotDevice, PurpleRobotReading from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert HARDWARE_PROBE = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe' TAG = 'device_hardware_changed' + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): start = timezone.now() - datetime.timedelta(days=3) - + mac = None same_mac = True - + for reading in PurpleRobotReading.objects.filter(user_id=device.hash_key, probe=HARDWARE_PROBE, logged__gte=start): payload = json.loads(reading.payload) - - if mac == None: - mac = payload['WIFI_MAC'] - elif mac != payload['WIFI_MAC']: - same_mac = False - + + if 'WIFI_MAC' in payload: + if mac is None: + mac = payload['WIFI_MAC'] + elif mac is not payload['WIFI_MAC']: + same_mac = False + if same_mac: cancel_alert(tags=TAG, user_id=device.hash_key) else: diff --git a/management/commands/pr_status_check_consistent_hardware_sensors.py b/management/commands/pr_status_check_consistent_hardware_sensors.py index 6d0b23e..762bba1 100644 --- a/management/commands/pr_status_check_consistent_hardware_sensors.py +++ b/management/commands/pr_status_check_consistent_hardware_sensors.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + import datetime import json @@ -10,32 +12,33 @@ HARDWARE_SENSORS = ( u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.AccelerometerProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.GyroscopeProbe', -# u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.LightProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.PressureProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.ProximityProbe', -# u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.sensors.AccelerometerSensorProbe', u'edu.northwestern.cbits.purple_robot_manager.probes.sensors.LightSensorProbe', + # u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe', + # u'edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe', ) + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): for sensor in HARDWARE_SENSORS: last = device.most_recent_reading(sensor) - - if last != None: + + if last is not None: hardwares = set() - + start = timezone.now() - datetime.timedelta(days=3) - + for reading in PurpleRobotReading.objects.filter(user_id=device.hash_key, probe=sensor, logged__gte=start): payload = json.loads(reading.payload) - + if 'SENSOR' in payload: hardwares.add(payload['SENSOR']['NAME']) - + if len(hardwares) < 2: cancel_alert(tags='device_sensor_changed_' + my_slugify(sensor.replace('edu.northwestern.cbits.purple_robot_manager.', '')), user_id=device.hash_key) else: diff --git a/management/commands/pr_status_check_device_last_battery.py b/management/commands/pr_status_check_device_last_battery.py index 13c1df4..56b25c7 100644 --- a/management/commands/pr_status_check_device_last_battery.py +++ b/management/commands/pr_status_check_device_last_battery.py @@ -1,17 +1,19 @@ +# pylint: disable=line-too-long, no-member + from django.core.management.base import BaseCommand -from django.utils import timezone from purple_robot_app.models import PurpleRobotDevice from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): level = device.last_battery() - - if level >= 33: + + if level >= 33: cancel_alert(tags='device_last_battery', user_id=device.hash_key) - elif level >= 25: + elif level >= 25: log_alert(message='Battery level is less than 33%.', severity=1, tags='device_last_battery', user_id=device.hash_key) else: log_alert(message='Battery level is less than 25%.', severity=2, tags='device_last_battery', user_id=device.hash_key) diff --git a/management/commands/pr_status_check_device_last_upload.py b/management/commands/pr_status_check_device_last_upload.py index 0d619a7..1c0e50a 100644 --- a/management/commands/pr_status_check_device_last_upload.py +++ b/management/commands/pr_status_check_device_last_upload.py @@ -1,17 +1,20 @@ +# pylint: disable=line-too-long, no-member + from django.core.management.base import BaseCommand from django.utils import timezone from purple_robot_app.models import PurpleRobotDevice, PurpleRobotPayload from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): payload = PurpleRobotPayload.objects.filter(user_id=device.hash_key).order_by('-added').first() - + now = timezone.now() - - if payload == None: + + if payload is None: log_alert(message='No payloads have been uploaded yet.', severity=1, tags='device_last_upload', user_id=device.hash_key) elif (now - payload.added).total_seconds() > (60 * 60 * 12): log_alert(message='No payloads have been uploaded in the last 12 hours.', severity=2, tags='device_last_upload', user_id=device.hash_key) diff --git a/management/commands/pr_status_check_device_missing_probe.py b/management/commands/pr_status_check_device_missing_probe.py index a1d7700..a6420f2 100644 --- a/management/commands/pr_status_check_device_missing_probe.py +++ b/management/commands/pr_status_check_device_missing_probe.py @@ -1,26 +1,32 @@ +# pylint: disable=line-too-long, no-member + import datetime import os -from sexpdata import * +from sexpdata import Symbol, Quoted, car, cdr, loads from django.core.management.base import BaseCommand from django.utils import timezone -from purple_robot_app.models import PurpleRobotDevice, PurpleRobotConfiguration, PurpleRobotReading +from purple_robot_app.models import PurpleRobotDevice, PurpleRobotConfiguration from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert +from purple_robot_app.device_info import can_sense + TAG = 'expected_probe_missing' START_DAYS = 7 -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + def enabled_probes(contents): probes = [] - + if isinstance(contents, Symbol): pass elif isinstance(contents, basestring): @@ -32,77 +38,94 @@ def enabled_probes(contents): elif car(contents) == Symbol('pr-update-probe'): probe_name = None probe_enabled = False - + for item in cdr(contents): if isinstance(item, Quoted): item = item.value() - + for child in item: if car(child) == 'name': probe_name = cdr(child) elif car(child) == 'enabled': probe_enabled = cdr(child) - - if probe_name != None and probe_enabled and (probe_name in probes) == False: + + if probe_name is not None and probe_enabled and (probe_name in probes) is False: probes.append(probe_name) else: for item in contents: found_probes = enabled_probes(item) - + for found in found_probes: - if (found in probes) == False: + if (found in probes) is False: probes.append(found) - + return probes + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/expected_probe_missing_check.lock', os.R_OK): - t = os.path.getmtime('/tmp/expected_probe_missing_check.lock') - created = datetime.datetime.fromtimestamp(t) - + timestamp = os.path.getmtime('/tmp/expected_probe_missing_check.lock') + created = datetime.datetime.fromtimestamp(timestamp) + if (datetime.datetime.now() - created).total_seconds() > 60 * 60 * 4: - print('expected_probe_missing_check: Stale lock - removing...') + print 'expected_probe_missing_check: Stale lock - removing...' os.remove('/tmp/expected_probe_missing_check.lock') else: return - + touch('/tmp/expected_probe_missing_check.lock') start = timezone.now() - datetime.timedelta(days=START_DAYS) - + for device in PurpleRobotDevice.objects.filter(mute_alerts=False).order_by('device_id'): + model = device.last_model() + mfgr = device.last_manufacturer() + config = None - + default = PurpleRobotConfiguration.objects.filter(slug='default').first() - - if device.configuration != None: + + if device.configuration is not None: config = device.configuration - elif device.device_group != None and device.device_group.configuration != None: + elif device.device_group is not None and device.device_group.configuration is not None: config = device.device_group.configuration - elif config == None: + elif config is None: config = default - - if config == None: + + if config is None: log_alert(message='No configuration associated with ' + device.device_id + '.', severity=2, tags=TAG, user_id=device.hash_key) else: config_probes = enabled_probes(loads(config.contents, true='#t', false='#f')) - + + touch('/tmp/expected_probe_missing_check.lock') + missing_probes = [] - + for probe in config_probes: - found = device.most_recent_reading(probe) - - if found == None or found.logged < start: - missing_probes.append(probe.split('.')[-1]) - -# if found == None: -# print(device.device_id + ' ' + str(config) + ': ' + probe) - - + if can_sense(mfgr, model, probe): + found = device.most_recent_reading(probe) + + if found is None or found.logged < start: + missing_probes.append(probe.split('.')[-1]) + + platform = device.last_platform() + + if platform is not None and platform.startswith('Android 5'): + if 'ApplicationLaunchProbe' in missing_probes: + missing_probes.remove('ApplicationLaunchProbe') + + if 'RunningSoftwareProbe' in missing_probes: + missing_probes.remove('RunningSoftwareProbe') + if len(missing_probes) == 0: cancel_alert(tags=TAG, user_id=device.hash_key) else: - log_alert(message='Missing data from ' + str(len(missing_probes)) + ' probe(s). Absent probes: ' + ', '.join(missing_probes), severity=2, tags=TAG, user_id=device.hash_key) + missing_probes_str = ', '.join(missing_probes[:4]) + + if len(missing_probes) > 4: + missing_probes_str = missing_probes_str + ', and ' + str(len(missing_probes) - 4) + ' more' + + log_alert(message='Missing data from ' + str(len(missing_probes)) + ' probe(s). Absent probes: ' + missing_probes_str, severity=2, tags=TAG, user_id=device.hash_key) os.remove('/tmp/expected_probe_missing_check.lock') diff --git a/management/commands/pr_status_check_device_pending_files.py b/management/commands/pr_status_check_device_pending_files.py index d8a9dc9..b951187 100644 --- a/management/commands/pr_status_check_device_pending_files.py +++ b/management/commands/pr_status_check_device_pending_files.py @@ -1,14 +1,16 @@ +# pylint: disable=line-too-long, no-member + from django.core.management.base import BaseCommand -from django.utils import timezone from purple_robot_app.models import PurpleRobotDevice from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): pending = device.last_pending_count() - + if pending < 250: cancel_alert(tags='device_pending_files', user_id=device.hash_key) elif pending < 1000: diff --git a/management/commands/pr_status_check_device_projected_battery_life.py b/management/commands/pr_status_check_device_projected_battery_life.py index 8e031a7..74a1bbf 100644 --- a/management/commands/pr_status_check_device_projected_battery_life.py +++ b/management/commands/pr_status_check_device_projected_battery_life.py @@ -1,19 +1,26 @@ +# pylint: disable=line-too-long, no-member + from django.core.management.base import BaseCommand -from django.utils import timezone from purple_robot_app.models import PurpleRobotDevice -from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert +from purple_robot_app.management.commands.pr_check_status import cancel_alert + class Command(BaseCommand): def handle(self, *args, **options): for device in PurpleRobotDevice.objects.filter(mute_alerts=False): - lifetime = device.projected_battery_lifetime() / (60 * 60) - - if lifetime == -1: - log_alert(message='Unable to project device lifetime.', severity=1, tags='device_projected_lifetime', user_id=device.hash_key) - elif lifetime > 8: - cancel_alert(tags='device_projected_lifetime', user_id=device.hash_key) - elif lifetime > 6: - log_alert(message='Device lifetime is estimated to be {0:.2f} hours.'.format(lifetime), severity=1, tags='device_projected_lifetime', user_id=device.hash_key) - else: - log_alert(message='Device lifetime is estimated to be {0:.2f} hours.'.format(lifetime), severity=2, tags='device_projected_lifetime', user_id=device.hash_key) + cancel_alert(tags='device_projected_lifetime', user_id=device.hash_key) + +# lifetime = device.projected_battery_lifetime() / (60 * 60) +# +# if lifetime == -1: +# log_alert(message='Unable to project device lifetime.', severity=1, +# tags='device_projected_lifetime', user_id=device.hash_key) +# elif lifetime > 8: +# cancel_alert(tags='device_projected_lifetime', user_id=device.hash_key) +# elif lifetime > 6: +# log_alert(message='Device lifetime is estimated to be {0:.2f} hours.'.format(lifetime), +# severity=1, tags='device_projected_lifetime', user_id=device.hash_key) +# else: +# log_alert(message='Device lifetime is estimated to be {0:.2f} hours.'.format(lifetime), +# severity=2, tags='device_projected_lifetime', user_id=device.hash_key) diff --git a/management/commands/pr_status_check_latest_version.py b/management/commands/pr_status_check_latest_version.py index 1deaf55..3473e0b 100644 --- a/management/commands/pr_status_check_latest_version.py +++ b/management/commands/pr_status_check_latest_version.py @@ -1,57 +1,54 @@ -import datetime -import json +# pylint: disable=line-too-long, no-member + import requests from BeautifulSoup import BeautifulSoup from django.core.management.base import BaseCommand -from django.utils import timezone -from purple_robot_app.models import PurpleRobotDevice, PurpleRobotReading, my_slugify +from purple_robot_app.models import PurpleRobotDevice from purple_robot_app.management.commands.pr_check_status import log_alert, cancel_alert TAG = 'running_latest_version' + class Command(BaseCommand): def handle(self, *args, **options): play_url = 'https://play.google.com/store/apps/details?id=edu.northwestern.cbits.purple_robot_manager' - - r = requests.get(play_url) - - soup = BeautifulSoup(r.text) - - if soup != None: + + request = requests.get(play_url) + + soup = BeautifulSoup(request.text) + + if soup is not None: changelog = '' - - changes = soup.findAll('div', { 'class': 'recent-change' }) - + + changes = soup.findAll('div', {'class': 'recent-change'}) + for change in changes: if len(changelog) > 0: changelog += '\n' - + changelog += change.contents[0].strip() - - version_html = soup.find('div', { 'class': 'content', 'itemprop': 'softwareVersion' }) - + + version_html = soup.find('div', {'class': 'content', 'itemprop': 'softwareVersion'}) + version = version_html.contents[0].strip() - - version_code = None - version_code_html = soup.findAll('button', { 'class': 'dropdown-child' }) - - for div in version_code_html: - if div.contents[0].strip() == 'Latest Version': - version_code = float(div['data-dropdown-value']) + + # version_code_html = soup.findAll('button', {'class': 'dropdown-child'}) + + # for div in version_code_html: + # if div.contents[0].strip() == 'Latest Version': + # version_code = float(div['data-dropdown-value']) for device in PurpleRobotDevice.objects.filter(mute_alerts=False): device_version = device.config_last_user_agent - - if device_version == None: + + if device_version is None: log_alert(message='Unable to determine installed version.', severity=1, tags=TAG, user_id=device.hash_key) elif device_version.endswith(str(version)): cancel_alert(tags=TAG, user_id=device.hash_key) else: log_alert(message='Running an older version on Purple Robot: ' + device_version + '.', severity=1, tags=TAG, user_id=device.hash_key) else: - print('Unable to fetch Play Store metadata.') - - + print 'Unable to fetch Play Store metadata.' diff --git a/management/commands/reflect_payloads.py b/management/commands/reflect_payloads.py index 66e9bd2..cb6b0fe 100644 --- a/management/commands/reflect_payloads.py +++ b/management/commands/reflect_payloads.py @@ -1,16 +1,15 @@ +# pylint: disable=line-too-long, no-member + import datetime import hashlib import json import requests import os -import sys -import time from requests.exceptions import ConnectionError, ReadTimeout from django.conf import settings from django.core.management.base import BaseCommand -from django.db import transaction from purple_robot_app.models import PurpleRobotPayload, PurpleRobotReading @@ -18,110 +17,115 @@ # Via http://stackoverflow.com/questions/1158076/implement-touch-using-python -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): try: settings.PR_REDIRECT_ENDPOINT_MAP except AttributeError: print 'PR_REDIRECT_ENDPOINT_MAP not defined in settings.py. Exiting...' - + return if os.access('/tmp/reflected_payload.lock', os.R_OK): - t = os.path.getmtime('/tmp/reflected_payload.lock') - created = datetime.datetime.fromtimestamp(t) - - if (datetime.datetime.now() - created).total_seconds() > 120: - print('reflect_payloads: Stale lock - removing...') + timestamp = os.path.getmtime('/tmp/reflected_payload.lock') + created = datetime.datetime.fromtimestamp(timestamp) + + if (datetime.datetime.now() - created).total_seconds() > 60 * 60: + print 'reflect_payloads: Stale lock - removing...' os.remove('/tmp/reflected_payload.lock') else: return - + touch('/tmp/reflected_payload.lock') - + tag = 'reflected_payload' requests.packages.urllib3.disable_warnings() - - reflected_payloads = [] - + for config in settings.PR_REDIRECT_ENDPOINT_MAP: - payloads = PurpleRobotPayload.objects.filter(user_id=config['hash']).exclude(process_tags__contains=tag)[:50] - - if payloads.count() > 0: + payloads = list(PurpleRobotPayload.objects.filter(user_id=config['hash']).exclude(process_tags__contains=tag)[:50]) + + while len(payloads) > 0: + reflected_payloads = [] + if PRINT_PROGRESS: - print('') - + print 'COUNT: ' + str(len(payloads)) + + index = 0 + for pr_payload in payloads: + index += 1 + if PRINT_PROGRESS: + print ' PAYLOAD: ' + str(index) + ' -- ' + str(pr_payload.pk) + payload = {} payload['Payload'] = pr_payload.payload payload['Operation'] = 'SubmitProbes' - m = hashlib.md5() - m.update(config['new_id'].encode('utf-8')) - payload['UserHash'] = m.hexdigest() - - m = hashlib.md5() - m.update((payload['UserHash'] + payload['Operation'] + payload['Payload']).encode('utf-8')) - payload['Checksum'] = m.hexdigest() + md5_hash = hashlib.md5() + md5_hash.update(config['new_id'].encode('utf-8')) + payload['UserHash'] = md5_hash.hexdigest() + + md5_hash = hashlib.md5() + md5_hash.update((payload['UserHash'] + payload['Operation'] + payload['Payload']).encode('utf-8')) + payload['Checksum'] = md5_hash.hexdigest() + + data = {'json': json.dumps(payload, indent=2)} - data = { 'json': json.dumps(payload, indent=2) } - files = {} - + if 'media_url' in pr_payload.payload: readings = json.loads(pr_payload.payload) - + for reading in readings: if 'media_url' in reading: pr_reading = PurpleRobotReading.objects.filter(guid=reading['GUID']).first() - - if pr_reading != None and pr_reading.attachment != None: + + if pr_reading is not None and pr_reading.attachment is not None: reading_json = json.loads(pr_reading.payload) - + filename = pr_reading.attachment.name.split('/')[-1] files[pr_reading.guid] = (filename, pr_reading.attachment, reading_json['media_content_type'],) - try: response = requests.post(config['endpoint'], data=data, verify=False, timeout=120.0, files=files) - + response_obj = json.loads(response.text) - + if response_obj['Status'] == 'success': reflected_payloads.append(pr_payload.pk) - - touch('/tmp/reflected_payload.lock') except ValueError: # Missing attachment error - continue on... - reflected_payloads.append(pr_payload.pk) - - touch('/tmp/reflected_payload.lock') except ConnectionError: pass except ReadTimeout: pass - for pk in reflected_payloads: - pr_payload = PurpleRobotPayload.objects.get(pk=pk) - - tags = pr_payload.process_tags - - if tags is None or tags.find(tag) == -1: - if tags is None or len(tags) == 0: - tags = tag - else: - tags += ' ' + tag - - pr_payload.process_tags = tags - - pr_payload.save() - + touch('/tmp/reflected_payload.lock') + + for primary_key in reflected_payloads: + pr_payload = PurpleRobotPayload.objects.get(pk=primary_key) + + tags = pr_payload.process_tags + + if tags is None or tags.find(tag) == -1: + if tags is None or len(tags) == 0: + tags = tag + else: + tags += ' ' + tag + + pr_payload.process_tags = tags + + pr_payload.save() + + payloads = list(PurpleRobotPayload.objects.filter(user_id=config['hash']).exclude(process_tags__contains=tag)[:50]) + os.remove('/tmp/reflected_payload.lock') - diff --git a/management/commands/refresh_device_pages.py b/management/commands/refresh_device_pages.py index 8ea6161..3d26eb3 100644 --- a/management/commands/refresh_device_pages.py +++ b/management/commands/refresh_device_pages.py @@ -1,7 +1,6 @@ -import datetime -import json +# pylint: disable=line-too-long, no-member, invalid-name + import os -import pytz from collections import namedtuple @@ -11,20 +10,21 @@ from purple_robot_app.models import PurpleRobotDevice from purple_robot_app.views import pr_device + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/refresh_device_pages.lock', os.R_OK): return - - open('/tmp/refresh_device_pages.lock', 'wa').close() - + + open('/tmp/refresh_device_pages.lock', 'w').close() + FakeRequest = namedtuple('FakeRequest', ['is_active', 'is_staff']) - + factory = RequestFactory() - + request = factory.get('/pr/devices/foobar') request.user = FakeRequest(is_active=True, is_staff=True) - + for device in PurpleRobotDevice.objects.all(): pr_device(request, device.device_id) diff --git a/management/commands/run_export_jobs.py b/management/commands/run_export_jobs.py index 041b188..e441789 100644 --- a/management/commands/run_export_jobs.py +++ b/management/commands/run_export_jobs.py @@ -18,6 +18,7 @@ from ...models import PurpleRobotExportJob, PurpleRobotReading + class Command(BaseCommand): def handle(self, *args, **options): processing = PurpleRobotExportJob.objects.filter(state='processing') diff --git a/management/commands/send_alert_digest.py b/management/commands/send_alert_digest.py index 2eed086..84e47d0 100644 --- a/management/commands/send_alert_digest.py +++ b/management/commands/send_alert_digest.py @@ -1,3 +1,5 @@ +# pylint: disable=line-too-long, no-member + from django.conf import settings from django.contrib.auth.models import Group from django.core.mail import send_mail @@ -7,43 +9,45 @@ from purple_robot_app.models import PurpleRobotAlert, PurpleRobotDevice + class Command(BaseCommand): def handle(self, *args, **options): group = Group.objects.filter(name=settings.PURPLE_ROBOT_ADMIN_GROUP).first() - - if group != None: + + if group is not None: users = group.user_set.all() - + recipient_list = [] - + for user in users: recipient_list.append(user.email) - + alerts = PurpleRobotAlert.objects.filter(dismissed=None).order_by('user_id', '-severity') - - c = Context() - c['alerts'] = alerts + + context = Context() + context['alerts'] = alerts if alerts.count() > 0 and len(recipient_list) > 0: from_addr = settings.PURPLE_ROBOT_EMAIL_SENDER - + devices = {} - + for alert in alerts: - device = PurpleRobotDevice.objects.get(hash_key=alert.user_id) - - device_alerts = [] - - if device.device_id in devices: - device_alerts = devices[device.device_id] - else: - devices[device.device_id] = device_alerts - - device_alerts.append([alert, device]) - - c['devices'] = devices - - message = render_to_string('email_admin_alert_content.txt', c) - subject = render_to_string('email_admin_alert_subject.txt', c) + device = PurpleRobotDevice.objects.filter(hash_key=alert.user_id).first() + + if device is not None: + device_alerts = [] + + if device.device_id in devices: + device_alerts = devices[device.device_id] + else: + devices[device.device_id] = device_alerts + + device_alerts.append([alert, device]) + + context['devices'] = devices + + message = render_to_string('email_admin_alert_content.txt', context) + subject = render_to_string('email_admin_alert_subject.txt', context) send_mail(subject, message, from_addr, recipient_list, fail_silently=False) diff --git a/management/commands/size_readings.py b/management/commands/size_readings.py index c940e0b..02d1a5d 100644 --- a/management/commands/size_readings.py +++ b/management/commands/size_readings.py @@ -1,9 +1,8 @@ +# pylint: disable=line-too-long, no-member + import datetime -import hashlib import json -import requests import os -import sys import time from django.core.management.base import BaseCommand @@ -12,53 +11,71 @@ from purple_robot_app.models import PurpleRobotReading, PurpleRobotDevice -def touch(fname, mode=0o666, dir_fd=None, **kwargs): + +def touch(fname, mode=0o666): flags = os.O_CREAT | os.O_APPEND - - with os.fdopen(os.open(fname, flags, mode)) as f: + + if os.fdopen(os.open(fname, flags, mode)) is not None: os.utime(fname, None) + class Command(BaseCommand): def handle(self, *args, **options): if os.access('/tmp/size_readings.lock', os.R_OK): - t = os.path.getmtime('/tmp/size_readings.lock') - created = datetime.datetime.fromtimestamp(t) - + timestamp = os.path.getmtime('/tmp/size_readings.lock') + created = datetime.datetime.fromtimestamp(timestamp) + if (datetime.datetime.now() - created).total_seconds() > 4 * 60 * 60: - print('size_readings: Stale lock - removing...') + print 'size_readings: Stale lock - removing...' os.remove('/tmp/size_readings.lock') else: return touch('/tmp/size_readings.lock') - - readings = PurpleRobotReading.objects.filter(size=0).order_by('-logged')[:2000] - - for reading in readings: - reading.size = len(reading.payload) - - if reading.attachment != None: - try: - reading.size += reading.attachment.size - except ValueError: - pass # No attachment... - - reading.save() - + + readings = list(PurpleRobotReading.objects.filter(size=0)[:1000]) + count = PurpleRobotReading.objects.filter(size=0).count() + + while count > 0: + for reading in readings: + reading.size = len(reading.payload) + + if reading.attachment is not None: + try: + reading.size += reading.attachment.size + except ValueError: + pass # No attachment... + + reading.save() + touch('/tmp/size_readings.lock') - - for device in PurpleRobotDevice.objects.all(): - total_size = PurpleRobotReading.objects.filter(user_id=device.hash_key).aggregate(Sum('size')) - + count -= len(readings) + readings = list(PurpleRobotReading.objects.filter(size=0)[:1000]) + + for device in PurpleRobotDevice.objects.order_by('device_id'): + now = timezone.now() + metadata = json.loads(device.performance_metadata) - - metadata['total_readings_size'] = total_size['size__sum'] - + + last_readings_size = 0 + + if 'last_readings_size' in metadata: + last_readings_size = metadata['last_readings_size'] + + last_readings_size = datetime.datetime.fromtimestamp(last_readings_size, tz=now.tzinfo) + + if (now - last_readings_size).total_seconds() > 60 * 60 * 4: + if 'total_readings_size' in metadata: + del metadata['total_readings_size'] + + if ('total_readings_size' in metadata) == False: + total_size = PurpleRobotReading.objects.filter(user_id=device.hash_key).aggregate(Sum('size')) + metadata['total_readings_size'] = total_size['size__sum'] + + metadata['last_readings_size'] = time.mktime(now.timetuple()) + device.performance_metadata = json.dumps(metadata, indent=2) - device.save() - touch('/tmp/size_readings.lock') - os.remove('/tmp/size_readings.lock') diff --git a/management/commands/update_test_reports.py b/management/commands/update_test_reports.py index 1c1b15e..ca8dd22 100644 --- a/management/commands/update_test_reports.py +++ b/management/commands/update_test_reports.py @@ -1,10 +1,14 @@ +# pylint: disable=line-too-long + from django.core.management.base import BaseCommand -from purple_robot_app.models import PurpleRobotTest +# from purple_robot_app.models import PurpleRobotTest REPORT_DAYS = 1 + class Command(BaseCommand): def handle(self, *args, **options): - for test in PurpleRobotTest.objects.all().order_by('last_updated')[:1]: - test.update(days=REPORT_DAYS) + pass + # test = PurpleRobotTest.objects.all().order_by('last_updated').first() + # test.update(days=REPORT_DAYS) diff --git a/management/commands/verify_user_data_present.py b/management/commands/verify_user_data_present.py new file mode 100644 index 0000000..d1a8462 --- /dev/null +++ b/management/commands/verify_user_data_present.py @@ -0,0 +1,29 @@ +# pylint: disable=line-too-long, no-member + +import hashlib + +from django.core.management.base import BaseCommand + +from purple_robot_app.models import PurpleRobotReading + + +class Command(BaseCommand): + def handle(self, *args, **options): + md5_hash = hashlib.md5() + md5_hash.update(args[0]) + + user_id = md5_hash.hexdigest() + + count = PurpleRobotReading.objects.filter(user_id=user_id).count() + + print 'Readings for ' + args[0] + ': ' + str(count) + + if count == 0: + md5_hash = hashlib.md5() + md5_hash.update(args[0].lower()) + + user_id = md5_hash.hexdigest() + + count = PurpleRobotReading.objects.filter(user_id=user_id).count() + + print 'Readings for ' + args[0].lower() + ': ' + str(count) diff --git a/migrations/0036_auto__add_field_purplerobotdevice_test_device.py b/migrations/0036_auto__add_field_purplerobotdevice_test_device.py new file mode 100644 index 0000000..8e17f9e --- /dev/null +++ b/migrations/0036_auto__add_field_purplerobotdevice_test_device.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'PurpleRobotDevice.test_device' + db.add_column(u'purple_robot_app_purplerobotdevice', 'test_device', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'PurpleRobotDevice.test_device' + db.delete_column(u'purple_robot_app_purplerobotdevice', 'test_device') + + + models = { + u'purple_robot_app.purplerobotalert': { + 'Meta': {'object_name': 'PurpleRobotAlert'}, + 'action_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'dismissed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'manually_dismissed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'severity': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tags': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotconfiguration': { + 'Meta': {'object_name': 'PurpleRobotConfiguration'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'contents': ('django.db.models.fields.TextField', [], {'max_length': '1048576'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevice': { + 'Meta': {'object_name': 'PurpleRobotDevice'}, + 'config_last_fetched': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'config_last_user_agent': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'device_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotDeviceGroup']"}), + 'device_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256', 'db_index': 'True'}), + 'hash_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mute_alerts': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'performance_metadata': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'max_length': '1048576'}), + 'test_device': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + u'purple_robot_app.purplerobotdevicegroup': { + 'Meta': {'object_name': 'PurpleRobotDeviceGroup'}, + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'groups'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'group_id': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevicenote': { + 'Meta': {'object_name': 'PurpleRobotDeviceNote'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notes'", 'to': u"orm['purple_robot_app.PurpleRobotDevice']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotevent': { + 'Meta': {'object_name': 'PurpleRobotEvent'}, + 'event': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotexportjob': { + 'Meta': {'object_name': 'PurpleRobotExportJob'}, + 'destination': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateField', [], {}), + 'export_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'probes': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {}), + 'state': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '512'}), + 'users': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotpayload': { + 'Meta': {'object_name': 'PurpleRobotPayload'}, + 'added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'errors': ('django.db.models.fields.TextField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'process_tags': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreading': { + 'Meta': {'object_name': 'PurpleRobotReading', 'index_together': "[['probe', 'user_id'], ['logged', 'user_id'], ['probe', 'logged', 'user_id']]"}, + 'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'probe': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'size': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreport': { + 'Meta': {'object_name': 'PurpleRobotReport'}, + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobottest': { + 'Meta': {'object_name': 'PurpleRobotTest'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'frequency': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + } + } + + complete_apps = ['purple_robot_app'] \ No newline at end of file diff --git a/migrations/0037_auto__add_field_purplerobotdevice_first_reading_timestamp.py b/migrations/0037_auto__add_field_purplerobotdevice_first_reading_timestamp.py new file mode 100644 index 0000000..6b597c5 --- /dev/null +++ b/migrations/0037_auto__add_field_purplerobotdevice_first_reading_timestamp.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'PurpleRobotDevice.first_reading_timestamp' + db.add_column(u'purple_robot_app_purplerobotdevice', 'first_reading_timestamp', + self.gf('django.db.models.fields.BigIntegerField')(default=0), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'PurpleRobotDevice.first_reading_timestamp' + db.delete_column(u'purple_robot_app_purplerobotdevice', 'first_reading_timestamp') + + + models = { + u'purple_robot_app.purplerobotalert': { + 'Meta': {'object_name': 'PurpleRobotAlert'}, + 'action_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'dismissed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'manually_dismissed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'severity': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tags': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotconfiguration': { + 'Meta': {'object_name': 'PurpleRobotConfiguration'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'contents': ('django.db.models.fields.TextField', [], {'max_length': '1048576'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevice': { + 'Meta': {'object_name': 'PurpleRobotDevice'}, + 'config_last_fetched': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'config_last_user_agent': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'device_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotDeviceGroup']"}), + 'device_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256', 'db_index': 'True'}), + 'first_reading_timestamp': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + 'hash_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mute_alerts': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'performance_metadata': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'max_length': '1048576'}), + 'test_device': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + u'purple_robot_app.purplerobotdevicegroup': { + 'Meta': {'object_name': 'PurpleRobotDeviceGroup'}, + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'groups'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'group_id': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevicenote': { + 'Meta': {'object_name': 'PurpleRobotDeviceNote'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notes'", 'to': u"orm['purple_robot_app.PurpleRobotDevice']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotevent': { + 'Meta': {'object_name': 'PurpleRobotEvent'}, + 'event': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotexportjob': { + 'Meta': {'object_name': 'PurpleRobotExportJob'}, + 'destination': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateField', [], {}), + 'export_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'probes': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {}), + 'state': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '512'}), + 'users': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotpayload': { + 'Meta': {'object_name': 'PurpleRobotPayload'}, + 'added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'errors': ('django.db.models.fields.TextField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'process_tags': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreading': { + 'Meta': {'object_name': 'PurpleRobotReading', 'index_together': "[['probe', 'user_id'], ['logged', 'user_id'], ['probe', 'logged', 'user_id']]"}, + 'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'probe': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'size': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreport': { + 'Meta': {'object_name': 'PurpleRobotReport'}, + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobottest': { + 'Meta': {'object_name': 'PurpleRobotTest'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'frequency': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + } + } + + complete_apps = ['purple_robot_app'] \ No newline at end of file diff --git a/migrations/0038_auto__add_index_purplerobotevent_logged.py b/migrations/0038_auto__add_index_purplerobotevent_logged.py new file mode 100644 index 0000000..c9b7153 --- /dev/null +++ b/migrations/0038_auto__add_index_purplerobotevent_logged.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding index on 'PurpleRobotEvent', fields ['logged'] + db.create_index(u'purple_robot_app_purplerobotevent', ['logged']) + + + def backwards(self, orm): + # Removing index on 'PurpleRobotEvent', fields ['logged'] + db.delete_index(u'purple_robot_app_purplerobotevent', ['logged']) + + + models = { + u'purple_robot_app.purplerobotalert': { + 'Meta': {'object_name': 'PurpleRobotAlert'}, + 'action_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'dismissed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'manually_dismissed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'severity': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tags': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotconfiguration': { + 'Meta': {'object_name': 'PurpleRobotConfiguration'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'contents': ('django.db.models.fields.TextField', [], {'max_length': '1048576'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevice': { + 'Meta': {'object_name': 'PurpleRobotDevice'}, + 'config_last_fetched': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'config_last_user_agent': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'device_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotDeviceGroup']"}), + 'device_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256', 'db_index': 'True'}), + 'first_reading_timestamp': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + 'hash_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mute_alerts': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'performance_metadata': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'max_length': '1048576'}), + 'test_device': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + u'purple_robot_app.purplerobotdevicegroup': { + 'Meta': {'object_name': 'PurpleRobotDeviceGroup'}, + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'groups'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'group_id': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevicenote': { + 'Meta': {'object_name': 'PurpleRobotDeviceNote'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notes'", 'to': u"orm['purple_robot_app.PurpleRobotDevice']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotevent': { + 'Meta': {'object_name': 'PurpleRobotEvent'}, + 'event': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotexportjob': { + 'Meta': {'object_name': 'PurpleRobotExportJob'}, + 'destination': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateField', [], {}), + 'export_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'probes': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {}), + 'state': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '512'}), + 'users': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotpayload': { + 'Meta': {'object_name': 'PurpleRobotPayload'}, + 'added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'errors': ('django.db.models.fields.TextField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'process_tags': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreading': { + 'Meta': {'object_name': 'PurpleRobotReading', 'index_together': "[['probe', 'user_id'], ['logged', 'user_id'], ['probe', 'logged', 'user_id']]"}, + 'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'probe': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'size': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreport': { + 'Meta': {'object_name': 'PurpleRobotReport'}, + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobottest': { + 'Meta': {'object_name': 'PurpleRobotTest'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'frequency': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + } + } + + complete_apps = ['purple_robot_app'] \ No newline at end of file diff --git a/migrations/0039_auto__add_field_purplerobotdevice_last_reading_timestamp.py b/migrations/0039_auto__add_field_purplerobotdevice_last_reading_timestamp.py new file mode 100644 index 0000000..cc0d7e0 --- /dev/null +++ b/migrations/0039_auto__add_field_purplerobotdevice_last_reading_timestamp.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'PurpleRobotDevice.last_reading_timestamp' + db.add_column(u'purple_robot_app_purplerobotdevice', 'last_reading_timestamp', + self.gf('django.db.models.fields.BigIntegerField')(default=0), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'PurpleRobotDevice.last_reading_timestamp' + db.delete_column(u'purple_robot_app_purplerobotdevice', 'last_reading_timestamp') + + + models = { + u'purple_robot_app.purplerobotalert': { + 'Meta': {'object_name': 'PurpleRobotAlert'}, + 'action_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'dismissed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'manually_dismissed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'severity': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tags': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotconfiguration': { + 'Meta': {'object_name': 'PurpleRobotConfiguration'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'contents': ('django.db.models.fields.TextField', [], {'max_length': '1048576'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevice': { + 'Meta': {'object_name': 'PurpleRobotDevice'}, + 'config_last_fetched': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'config_last_user_agent': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'device_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'devices'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotDeviceGroup']"}), + 'device_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256', 'db_index': 'True'}), + 'first_reading_timestamp': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + 'hash_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_reading_timestamp': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + 'mute_alerts': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'performance_metadata': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'max_length': '1048576'}), + 'test_device': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + u'purple_robot_app.purplerobotdevicegroup': { + 'Meta': {'object_name': 'PurpleRobotDeviceGroup'}, + 'configuration': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'groups'", 'null': 'True', 'to': u"orm['purple_robot_app.PurpleRobotConfiguration']"}), + 'description': ('django.db.models.fields.TextField', [], {'max_length': '1048576', 'null': 'True', 'blank': 'True'}), + 'group_id': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotdevicenote': { + 'Meta': {'object_name': 'PurpleRobotDeviceNote'}, + 'added': ('django.db.models.fields.DateTimeField', [], {}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notes'", 'to': u"orm['purple_robot_app.PurpleRobotDevice']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'note': ('django.db.models.fields.TextField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobotevent': { + 'Meta': {'object_name': 'PurpleRobotEvent'}, + 'event': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotexportjob': { + 'Meta': {'object_name': 'PurpleRobotExportJob'}, + 'destination': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateField', [], {}), + 'export_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'probes': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {}), + 'state': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '512'}), + 'users': ('django.db.models.fields.TextField', [], {'max_length': '8196', 'null': 'True', 'blank': 'True'}) + }, + u'purple_robot_app.purplerobotpayload': { + 'Meta': {'object_name': 'PurpleRobotPayload'}, + 'added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'errors': ('django.db.models.fields.TextField', [], {'max_length': '65536', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'process_tags': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreading': { + 'Meta': {'object_name': 'PurpleRobotReading', 'index_together': "[['probe', 'user_id'], ['logged', 'user_id'], ['probe', 'logged', 'user_id']]"}, + 'attachment': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'guid': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logged': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'payload': ('django.db.models.fields.TextField', [], {'max_length': '8388608'}), + 'probe': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'size': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'db_index': 'True'}) + }, + u'purple_robot_app.purplerobotreport': { + 'Meta': {'object_name': 'PurpleRobotReport'}, + 'generated': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mime_type': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + }, + u'purple_robot_app.purplerobottest': { + 'Meta': {'object_name': 'PurpleRobotTest'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'frequency': ('django.db.models.fields.FloatField', [], {'default': '1.0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {}), + 'probe': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + 'report': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '1024'}) + } + } + + complete_apps = ['purple_robot_app'] \ No newline at end of file diff --git a/models.py b/models.py index ab9dd09..19aa66a 100644 --- a/models.py +++ b/models.py @@ -1,3 +1,6 @@ +# pylint: disable=line-too-long, no-member, old-style-class, no-init, unused-argument, too-many-lines, too-many-public-methods, too-few-public-methods + +import arrow import calendar import datetime import importlib @@ -9,7 +12,7 @@ import time import traceback -from django.core.cache import cache +from django.conf import settings from django.core.urlresolvers import reverse from django.db import models, connection from django.db.models import Sum @@ -19,9 +22,32 @@ from django.utils.safestring import SafeString from django.utils.text import slugify +from purple_robot_app.performance import fetch_performance_samples + +PROBE_USER_TIME_CACHE = {} + +PROBE_CACHE_MAP = { + 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe': [ + 'last_pending_count' + ], + 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe': [ + 'last_model' + ], + 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe': [ + 'last_platform' + ], + 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe': [ + 'last_battery' + ], +} + +PAYLOAD_PERFORMANCE_CACHE = {} + + def my_slugify(str_obj): return slugify(str_obj.replace('.', ' ')).replace('-', '_') + class PurpleRobotConfiguration(models.Model): name = models.CharField(max_length=1024) slug = models.SlugField(max_length=1024, unique=True, db_index=True) @@ -30,24 +56,25 @@ class PurpleRobotConfiguration(models.Model): def __unicode__(self): return self.name - + def device_count(self): identifiers = set() - + for group in self.groups.all(): for device in group.devices.all(): identifiers.add(device.device_id) - + for device in self.devices.all(): identifiers.add(device.device_id) - + return len(identifiers) - + def format(self): if self.contents.lower().startswith('(begin'): return 'Scheme' - - return 'JSON'; + + return 'JSON' + class PurpleRobotDeviceGroup(models.Model): name = models.CharField(max_length=1024) @@ -58,6 +85,7 @@ class PurpleRobotDeviceGroup(models.Model): def __unicode__(self): return self.group_id + class PurpleRobotDevice(models.Model): name = models.CharField(max_length=1024) device_id = models.CharField(max_length=256, unique=True, db_index=True) @@ -67,37 +95,85 @@ class PurpleRobotDevice(models.Model): config_last_fetched = models.DateTimeField(null=True, blank=True) config_last_user_agent = models.CharField(max_length=1024, null=True, blank=True) hash_key = models.CharField(max_length=128, null=True, blank=True) - + + first_reading_timestamp = models.BigIntegerField(default=0) + last_reading_timestamp = models.BigIntegerField(default=0) + mute_alerts = models.BooleanField(default=False) - + test_device = models.BooleanField(default=False) + performance_metadata = models.TextField(max_length=1048576, default='{}') def __unicode__(self): return self.device_id - + def init_hash(self): - if self.hash_key == None or len(self.hash_key) < 32: - m = hashlib.md5() - m.update(self.device_id.encode('utf-8')) - - self.hash_key = m.hexdigest() + if self.hash_key is None or len(self.hash_key) < 32: + md5_hash = hashlib.md5() + md5_hash.update(self.device_id.encode('utf-8')) + + self.hash_key = md5_hash.hexdigest() self.save() - def user_hash(self, start=None, end=None): + def user_hash(self): self.init_hash() - + return self.hash_key - + + def earliest_reading_date(self): + self.init_hash() + + if self.first_reading_timestamp != 0: + if self.first_reading_timestamp > 0: + return datetime.datetime.utcfromtimestamp(self.first_reading_timestamp).replace(tzinfo=pytz.utc) + else: + return None + + first = PurpleRobotReading.objects.filter(user_id=self.hash_key).order_by('logged').first() + + if first is not None: + self.first_reading_timestamp = int(time.mktime(first.logged.timetuple())) + self.save() + + return first.logged + else: + self.first_reading_timestamp = -1 + self.save() + + return None + + def latest_reading_date(self): + self.init_hash() + + if self.last_reading_timestamp != 0: + if self.last_reading_timestamp > 0: + return datetime.datetime.utcfromtimestamp(self.last_reading_timestamp).replace(tzinfo=pytz.utc) + else: + return None + + first = PurpleRobotReading.objects.filter(user_id=self.hash_key).order_by('-logged').first() + + if first is not None: + self.last_reading_timestamp = int(time.mktime(first.logged.timetuple())) + self.save() + + return first.logged + else: + self.last_reading_timestamp = -1 + self.save() + + return None + def fetch_reading_count(self, probe): perf_data = json.loads(self.performance_metadata) - + if ('reading_counts' in perf_data) and (probe in perf_data['reading_counts']): return perf_data['reading_counts'][probe] - if ('reading_counts' in perf_data) == False: + if ('reading_counts' in perf_data) is False: perf_data['reading_counts'] = {} - - if (probe in perf_data['reading_counts']) == False: + + if (probe in perf_data['reading_counts']) is False: cursor = connection.cursor() cursor.execute("SELECT COUNT(logged) FROM \"purple_robot_app_purplerobotreading\" WHERE (\"purple_robot_app_purplerobotreading\".\"probe\" = '%s' AND \"purple_robot_app_purplerobotreading\".\"user_id\" = '%s' );" % (probe, self.hash_key)) row = cursor.fetchone() @@ -107,38 +183,54 @@ def fetch_reading_count(self, probe): self.save() return perf_data['reading_counts'][probe] - + def set_most_recent_reading(self, new_reading): + key = str(self.pk) + '-' + new_reading.probe + perf_data = json.loads(self.performance_metadata) - old_reading = None + old_reading_date = None updated = False - - if ('latest_readings' in perf_data) == False: + + if ('latest_readings' in perf_data) is False: perf_data['latest_readings'] = {} - elif (new_reading.probe in perf_data['latest_readings']): - old_reading = PurpleRobotReading.objects.filter(pk=perf_data['latest_readings'][new_reading.probe]).first() + elif new_reading.probe in perf_data['latest_readings']: + if (key in PROBE_USER_TIME_CACHE) is False: + old_reading = PurpleRobotReading.objects.filter(pk=perf_data['latest_readings'][new_reading.probe]).first() - if ('reading_counts' in perf_data) == False: + if old_reading is not None: + PROBE_USER_TIME_CACHE[key] = old_reading.logged + + if key in PROBE_USER_TIME_CACHE: + old_reading_date = PROBE_USER_TIME_CACHE[key] + + if ('reading_counts' in perf_data) is False: perf_data['reading_counts'] = {} - - if (new_reading.probe in perf_data['reading_counts']) == False: + + if (new_reading.probe in perf_data['reading_counts']) is False: perf_data['reading_counts'][new_reading.probe] = self.fetch_reading_count(new_reading.probe) - - perf_data['reading_counts'][new_reading.probe] = perf_data['reading_counts'][new_reading.probe] + 1 - if ('probes' in perf_data) == False: + perf_data['reading_counts'][new_reading.probe] += 1 + + if ('probes' in perf_data) is False: perf_data['probes'] = [] - - if (new_reading.probe in perf_data['probes']) == False: + + if (new_reading.probe in perf_data['probes']) is False: perf_data['probes'].append(new_reading.probe) updated = True - - if old_reading == None or new_reading.logged > old_reading.logged: + + if old_reading_date is None or new_reading.logged > old_reading_date: perf_data['latest_readings'][new_reading.probe] = new_reading.pk + PROBE_USER_TIME_CACHE[key] = new_reading.logged + updated = True - + if updated: + if new_reading.probe in PROBE_CACHE_MAP: + for key in PROBE_CACHE_MAP[new_reading.probe]: + if key in perf_data: + del perf_data[key] + self.performance_metadata = json.dumps(perf_data, indent=2) self.save() @@ -146,42 +238,37 @@ def set_most_recent_payload(self, new_payload): perf_data = json.loads(self.performance_metadata) old_payload = None - - if ('latest_payload' in perf_data) == False: - perf_data['latest_payload'] = -1 - else: + + if 'latest_payload' in perf_data: old_payload = PurpleRobotPayload.objects.filter(pk=perf_data['latest_payload']).first() - - if old_payload != None and new_payload.added > old_payload.added: + + if (old_payload is not None and new_payload.added > old_payload.added) or old_payload is None: perf_data['latest_payload'] = new_payload.pk - + self.performance_metadata = json.dumps(perf_data, indent=2) self.save() + self.set_performance_info('last_upload', new_payload.added.isoformat()) + def most_recent_payload(self): perf_data = json.loads(self.performance_metadata) - - if 'latest_payload' in perf_data: + + if 'latest_payload' in perf_data and perf_data['latest_payload'] != -1: return PurpleRobotPayload.objects.filter(pk=perf_data['latest_payload']).first() - else: - perf_data['latest_payload'] = -1 payload = PurpleRobotPayload.objects.filter(user_id=self.hash_key).order_by('-added').first() - - if payload != None: + + if payload is not None: perf_data['latest_payload'] = payload.pk - else: - perf_data['latest_payload'] = -1 - + self.performance_metadata = json.dumps(perf_data, indent=2) self.save() - + return payload - def most_recent_reading(self, probe_name): perf_data = json.loads(self.performance_metadata) - + if 'latest_readings' in perf_data: if probe_name in perf_data['latest_readings']: return PurpleRobotReading.objects.filter(pk=perf_data['latest_readings'][probe_name]).first() @@ -189,392 +276,526 @@ def most_recent_reading(self, probe_name): perf_data['latest_readings'] = {} reading = PurpleRobotReading.objects.filter(user_id=self.hash_key, probe=probe_name).order_by('-logged').first() - - if reading != None: - perf_data['latest_readings'][probe_name] = reading.pk + + if reading is not None: + self.set_most_recent_reading(reading) else: perf_data['latest_readings'][probe_name] = -1 - - self.performance_metadata = json.dumps(perf_data, indent=2) - self.save() - + self.performance_metadata = json.dumps(perf_data, indent=2) + self.save() + return reading + def earliest_reading(self, probe_name): + perf_data = json.loads(self.performance_metadata) + + if 'earliest_readings' in perf_data: + if probe_name in perf_data['earliest_readings']: + return PurpleRobotReading.objects.filter(pk=perf_data['earliest_readings'][probe_name]).first() + else: + perf_data['earliest_readings'] = {} + + reading = PurpleRobotReading.objects.filter(user_id=self.hash_key, probe=probe_name).order_by('logged').first() + + if reading is not None: + self.set_earliest_reading(reading) + self.set_latest_reading(reading) + + return reading + + def set_earliest_reading(self, new_reading): + perf_data = json.loads(self.performance_metadata) + + old_reading = None + updated = False + + if ('earliest_readings' in perf_data) is False: + perf_data['earliest_readings'] = {} + elif new_reading.probe in perf_data['earliest_readings']: + old_reading = PurpleRobotReading.objects.filter(pk=perf_data['earliest_readings'][new_reading.probe]).first() + + if old_reading is None or new_reading.logged < old_reading.logged: + perf_data['earliest_readings'][new_reading.probe] = new_reading.pk + updated = True + + if updated: + if new_reading.probe in PROBE_CACHE_MAP: + for key in PROBE_CACHE_MAP[new_reading.probe]: + if key in perf_data: + del perf_data[key] + + self.performance_metadata = json.dumps(perf_data, indent=2) + self.save() + +# Redundant? (set_most_recent_reading) +# def set_latest_reading(self, new_reading): +# perf_data = json.loads(self.performance_metadata) +# +# old_reading = None +# updated = False +# +# if ('latest_readings' in perf_data) is False: +# perf_data['latest_readings'] = {} +# elif (new_reading.probe in perf_data['latest_readings']): +# old_reading = PurpleRobotReading.objects.filter(pk=perf_data['latest_readings'][new_reading.probe]).first() +# +# if old_reading is None or new_reading.logged > old_reading.logged: +# perf_data['latest_readings'][new_reading.probe] = new_reading.pk +# updated = True +# +# if updated: +# if new_reading.probe in PROBE_CACHE_MAP: +# for key in PROBE_CACHE_MAP[new_reading.probe]: +# if key in perf_data: +# del perf_data[key] +# +# self.performance_metadata = json.dumps(perf_data, indent=2) +# self.save() + def clear_most_recent_reading(self, probe_name, new_pk=None): perf_data = json.loads(self.performance_metadata) - + if 'latest_readings' in perf_data: if probe_name in perf_data['latest_readings']: - if new_pk == None: + if new_pk is None: del perf_data['latest_readings'][probe_name] else: perf_data['latest_readings'][probe_name] = new_pk - + self.performance_metadata = json.dumps(perf_data, indent=2) self.save() - + def last_upload(self): self.init_hash() - + + last_upload = self.get_performance_info('last_upload') + + if last_upload is not None: + if last_upload == '': + return None + + last_upload = arrow.get(last_upload).datetime + + return last_upload + payload = self.most_recent_payload() - - if payload != None: - return payload.added - - return None - + + if payload is not None: + last_upload = payload.added + + self.set_performance_info('last_upload', last_upload.isoformat()) + else: + self.set_performance_info('last_upload', '') + + return last_upload + def last_upload_status(self): upload = self.last_upload() - + now = timezone.now() - + diff = now - upload - + if diff.days > 0: return "danger" elif diff.seconds > (8 * 60 * 60): return "warning" - + return "ok" def config_last_fetched_status(self): upload = self.config_last_fetched - + now = timezone.now() - + diff = now - upload - + if diff.days > 1: return "danger" elif diff.days > 0: return "warning" - + return "ok" def battery_history(self, start=None, end=None): self.init_hash() - + now = timezone.now() - - if start == None: + + if start is None: start = now - datetime.timedelta(days=1) - - if end == None: + + if end is None: end = now - + readings = [] - + for reading in PurpleRobotReading.objects.filter(user_id=self.hash_key, probe='edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe', logged__gte=start, logged__lte=end).order_by('logged'): data = json.loads(reading.payload) - - timestamp = calendar.timegm(reading.logged.timetuple()) - + + timestamp = calendar.timegm(reading.logged.timetuple()) + if len(readings) == 0 or readings[-1]['level'] != data['level'] or (timestamp - readings[-1]['timestamp']) > (30 * 60): - item = { 'level': data['level'], 'timestamp': timestamp } - + item = {'level': data['level'], 'timestamp': timestamp} + readings.append(item) - + return readings def last_battery(self): self.init_hash() - data = cache.get(self.hash_key + '__last_battery') - - if data != None: - return data['level'] - + battery = self.get_performance_info('last_battery') + + if battery is not None: + if battery == -1: + return None + + return battery + reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe') - - if reading != None: + + if reading is not None: data = json.loads(reading.payload) - - cache.set(self.hash_key + '__last_battery', data, 15 * 60) - - return data['level'] - - return None + + battery = data['level'] + + self.set_performance_info('last_battery', battery) + else: + self.set_performance_info('last_battery', -1) + + return battery def last_battery_status(self): battery = self.last_battery() - + if battery <= 25: return "danger" elif battery <= 33: return "warning" - + return "ok" def projected_battery_lifetime(self): last_level = None last_timestamp = None - + deltas = [] drain_count = 0 - plug_count = 0 - count = 0 - + start = 0 - + for reading in PurpleRobotReading.objects.filter(user_id=self.hash_key, probe='edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe').order_by('-logged')[start:(start+250)]: - count += 1 data = json.loads(reading.payload) - - timestamp = calendar.timegm(reading.logged.timetuple()) - + + timestamp = calendar.timegm(reading.logged.timetuple()) + plugged = (data['plugged'] != 0) - - if plugged == False: + + if plugged is False: drain_count += 1 - if last_level == None or last_timestamp == None: + if last_level is None or last_timestamp is None: last_level = float(data['level']) last_timestamp = timestamp else: this_level = float(data['level']) this_timestamp = timestamp - + if this_level - last_level > 0 and (last_timestamp - this_timestamp) < (60 * 60) and this_level <= 98: delta = (float(last_timestamp - this_timestamp) / float(this_level - last_level)) - + deltas.append(delta) last_level = this_level last_timestamp = this_timestamp - else: - plug_count += 1 - + if drain_count > 100: break - + start += 250 - + if len(deltas) > 0: return numpy.mean(deltas) * 100 - - return -1 - - + return -1 def status(self): - statuses = [] - + if self.mute_alerts: + return 'ok' + severity = self.alert_severity() - + if severity > 1: return 'danger' if severity > 0: return 'warning' - + return 'ok' def pending_history(self, start=None, end=None): self.init_hash() - + now = timezone.now() - - if start == None: + + if start is None: start = now - datetime.timedelta(days=1) - - if end == None: + + if end is None: end = now - + readings = [] - + for reading in PurpleRobotReading.objects.filter(user_id=self.hash_key, probe='edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe', logged__gte=start, logged__lte=end).order_by('logged'): data = json.loads(reading.payload) - - timestamp = calendar.timegm(reading.logged.timetuple()) - + + timestamp = calendar.timegm(reading.logged.timetuple()) + if len(readings) == 0 or readings[-1]['count'] != data['PENDING_COUNT'] or (timestamp - readings[-1]['timestamp']) > (30 * 60): - item = { 'count': data['PENDING_COUNT'], 'timestamp': timestamp } - + item = {'count': data['PENDING_COUNT'], 'timestamp': timestamp} + readings.append(item) - + return readings + def get_performance_info(self, key): + perf_data = json.loads(self.performance_metadata) + + if key in perf_data: + return perf_data[key] + + return None + + def set_performance_info(self, key, value): + perf_data = json.loads(self.performance_metadata) + + perf_data[key] = value + + self.performance_metadata = json.dumps(perf_data, indent=2) + self.save() + def last_pending_count(self): + count = self.get_performance_info('last_pending_count') + + if count is not None: + return count + data = self.last_robot_health() - - if data != None: - return data['PENDING_COUNT'] - - return None + + if data is not None: + count = data['PENDING_COUNT'] + + self.set_performance_info('last_pending_count', count) + else: + self.set_performance_info('last_pending_count', 0) + + return count def triggers(self): data = self.last_robot_health() - - if data != None: + + if data is not None: return data['TRIGGERS'] - + return None def last_hardware_info(self): self.init_hash() - - data = cache.get(self.hash_key + '__last_hardware_info') - - if data != None: - return data - + + data = None + reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.HardwareInformationProbe') - - if reading != None: + + if reading is not None: data = json.loads(reading.payload) - - cache.set(self.hash_key + '__last_hardware_info', data) - + return data def last_model(self): + model = self.get_performance_info('last_model') + + if model is not None: + return model + data = self.last_hardware_info() - - if data != None: - return data['MODEL'] - - return None + + if data is not None: + model = data['MODEL'] + + self.set_performance_info('last_model', model) + else: + self.set_performance_info('last_model', 'Unknown') + + return model + + def last_manufacturer(self): + mfgr = self.get_performance_info('last_manufacturer') + + if mfgr is not None: + return mfgr + + data = self.last_hardware_info() + + if data is not None: + mfgr = data['MANUFACTURER'] + + self.set_performance_info('last_manufacturer', mfgr) + else: + self.set_performance_info('last_manufacturer', 'Unknown') + + return mfgr def last_robot_health(self): self.init_hash() - + reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe') - + data = None - - if reading != None: + + if reading is not None: data = json.loads(reading.payload) - + return data - + def last_software_info(self): self.init_hash() - - data = cache.get(self.hash_key + '__last_software_info') - - if data != None: - return data - + reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe') - - if reading != None: + + data = None + + if reading is not None: data = json.loads(reading.payload) - - cache.set(self.hash_key + '__last_software_info', data) - + return data def last_platform(self): + platform = self.get_performance_info('last_platform') + + if platform is not None: + return platform + data = self.last_software_info() - - if data != None: - return 'Android ' + data['RELEASE'] - - return None + + if data is not None: + platform = 'Android ' + data['RELEASE'] + self.set_performance_info('last_platform', platform) + else: + self.set_performance_info('last_platform', 'Unknown') + + return platform def last_location(self): self.init_hash() - location = cache.get(self.hash_key + '__last_robot_location') - - if location != None: - return location - reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.LocationProbe') - - if reading != None: + + if reading is not None: data = json.loads(reading.payload) - - location = { 'latitude': data['LATITUDE'], 'longitude': data['LONGITUDE'] } - - cache.set(self.hash_key + '__last_robot_location', location) - return location + location = {'latitude': data['LATITUDE'], 'longitude': data['LONGITUDE']} + + return location + + return None def installed_apps(self): self.init_hash() reading = self.most_recent_reading('edu.northwestern.cbits.purple_robot_manager.probes.builtin.SoftwareInformationProbe') - - if reading != None: + + if reading is not None: data = json.loads(reading.payload) - return sorted(data['INSTALLED_APPS'], key=lambda item: item['APP_NAME'].lower()) - + return [] - + def probes(self): perf_data = json.loads(self.performance_metadata) - if ('probes' in perf_data): + if 'probes' in perf_data: return perf_data['probes'] perf_data['probes'] = [] probe_readings = PurpleRobotReading.objects.values('probe').distinct() - + for probe_reading in probe_readings: item = self.most_recent_reading(probe_reading['probe']) - - if item != None: + + if item is not None: perf_data['probes'].append(item.probe) - + self.performance_metadata = json.dumps(perf_data, indent=2) self.save() - + return perf_data['probes'] - - + def last_readings(self, omit_readings=False): self.init_hash() readings = [] - + + start = timezone.now() - datetime.timedelta(days=1) + for probe_name in self.probes(): item = self.most_recent_reading(probe_name) - - if item != None: + + if item is not None: reading = {} - + reading['name'] = item.probe.split('.')[-1] reading['full_probe_name'] = item.probe reading['last_update'] = item.logged - - if omit_readings == False: -# cursor = connection.cursor() -# cursor.execute("SELECT COUNT(logged) FROM \"purple_robot_app_purplerobotreading\" WHERE (\"purple_robot_app_purplerobotreading\".\"probe\" = '%s' AND \"purple_robot_app_purplerobotreading\".\"user_id\" = '%s' );" % (item.probe, self.hash_key)) -# row = cursor.fetchone() - reading['num_readings'] = self.fetch_reading_count(probe_name) -# reading['num_readings'] = 0 -# reading['num_readings'] = PurpleRobotReading.objects.filter(user_id=self.hash_key, probe=item.probe).count() + + if omit_readings is False: + # cursor = connection.cursor() + # cursor.execute("SELECT COUNT(logged) FROM \"purple_robot_app_purplerobotreading\" WHERE (\"purple_robot_app_purplerobotreading\".\"probe\" = '%s' AND \"purple_robot_app_purplerobotreading\".\"user_id\" = '%s' );" % (item.probe, self.hash_key)) + # row = cursor.fetchone() + # reading['num_readings'] = self.fetch_reading_count(probe_name) + # reading['num_readings'] = 0 + # reading['num_readings'] = PurpleRobotReading.objects.filter(user_id=self.hash_key, probe=item.probe).count() + reading['status'] = 'TODO' reading['frequency'] = 'Unknown' - - for test in PurpleRobotTest.objects.filter(probe=item.probe, user_id=self.hash_key): - reading['frequency'] = test.average_frequency() - + reading['num_readings'] = 'None' + + samples = fetch_performance_samples(self.hash_key, item.probe, start=start) + + count = 0 + + for sample in samples: + count += sample['sample_count'] + + if count > 0: + reading['frequency'] = count / datetime.timedelta(days=1).total_seconds() + + reading['num_readings'] = count + + # for test in PurpleRobotTest.objects.filter(probe=item.probe, user_id=self.hash_key): + # reading['frequency'] = test.average_frequency() + readings.append(reading) - - readings = sorted(readings, key=lambda k: k['name']) - + + readings = sorted(readings, key=lambda k: k['name']) + return readings - + def total_readings_size(self): metadata = json.loads(self.performance_metadata) - + if 'total_readings_size' in metadata: return metadata['total_readings_size'] - + return -1 - def data_size_history(self, unit='day', count=None): now = timezone.localtime(timezone.now()) - + bin_size = None count = 0 - + if unit == 'day': bin_size = datetime.timedelta(days=1) end = datetime.datetime(now.year, now.month, now.day, 0, 0, 0, 0, now.tzinfo) @@ -583,110 +804,155 @@ def data_size_history(self, unit='day', count=None): elif unit == 'hour': bin_size = datetime.timedelta(seconds=(60 * 60)) end = datetime.datetime(now.year, now.month, now.day, now.hour, 0, 0, 0, now.tzinfo) - end = end + in_size + end = end + bin_size count = 48 elif unit == 'minute': bin_size = datetime.timedelta(seconds=60) end = datetime.datetime(now.year, now.month, now.day, now.hour, now.minute, 0, 0, now.tzinfo) end = end + datetime.timedelta(seconds=60) count = 240 - + size_history = [] - + while count > 0: start = end - bin_size - -# size = 0 -# -# readings = PurpleRobotReading.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lt=end, size=0)[:250] -# -# while readings.count() > 0: -# for reading in readings: -# reading.size = len(reading.payload) -# reading.save() -# -# readings = PurpleRobotReading.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lt=end, size=0)[:250] - + + # size = 0 + # + # readings = PurpleRobotReading.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lt=end, size=0)[:250] + # + # while readings.count() > 0: + # for reading in readings: + # reading.size = len(reading.payload) + # reading.save() + # + # readings = PurpleRobotReading.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lt=end, size=0)[:250] + size = PurpleRobotReading.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lt=end).aggregate(Sum('size')) - + start_ts = time.mktime(start.timetuple()) end_ts = time.mktime(end.timetuple()) - + total_size = size['size__sum'] - - if total_size == None: + + if total_size is None: total_size = 0 - - history_point = { 'size': total_size, 'timestamp': ((start_ts + end_ts) / 2) } - + + history_point = {'size': total_size, 'timestamp': ((start_ts + end_ts) / 2)} + size_history.append(history_point) - + end = start count -= 1 - - + return reversed(size_history) def events(self, start=None, end=None): self.init_hash() - + now = timezone.now() - - if start == None: + + if start is None: start = now - datetime.timedelta(days=7) - - if end == None: + + if end is None: end = now - + return PurpleRobotEvent.objects.filter(user_id=self.hash_key, logged__gte=start, logged__lte=end).order_by('-logged') - + def alerts(self): self.init_hash() - + return PurpleRobotAlert.objects.filter(dismissed=None, user_id=self.hash_key) - + def alert_count(self): return self.alerts().count() - + def alert_severity(self): severity = 0 - + for alert in self.alerts(): if alert.severity > severity: severity = alert.severity - + return severity - + def visualization_for_probe(self, probe_name): formatter_name = my_slugify(probe_name).replace('edu_northwestern_cbits_purple_robot_manager_probes_', '') - + try: formatter = importlib.import_module('purple_robot_app.formatters.' + formatter_name) except ImportError: formatter = importlib.import_module('purple_robot_app.formatters.probe') - + readings = PurpleRobotReading.objects.filter(user_id=self.user_hash, probe=probe_name).order_by('-logged')[:500] - + return formatter.visualize(probe_name, readings) - + def sanity_messages(self): data = self.last_robot_health() - - if data == None: + + if data is None: return None - + messages = [] - + if 'CHECK_ERRORS' in data: for error in data['CHECK_ERRORS']: messages.append((error, "error",)) - + if 'CHECK_WARNINGS' in data: for warning in data['CHECK_WARNINGS']: messages.append((warning, "warning",)) - + return messages - + + def fetch_gap_status(self, probe_name, start=None, end=None): + if end is None: + end = timezone.now() + + if start is None: + start = end - datetime.timedelta(days=3) + + samples = fetch_performance_samples(self.hash_key, probe_name, start=start, end=end) + + status = {} + + status['num_samples'] = len(samples) + + if len(samples) > 0: + readings_start = arrow.get(samples[0]['sample_date']).datetime + readings_end = arrow.get(samples[-1]['sample_date']).datetime + + duration = float((readings_end - readings_start).total_seconds()) + + if duration > 0: + status['frequency'] = len(samples) / duration + else: + status['frequency'] = 0.0 + + gaps = [] + + largest_gap = 0.0 + + for i in range(0, len(samples) - 1): + one = samples[i] + two = samples[i + 1] + + gap = float((arrow.get(two['sample_date']).datetime - arrow.get(one['sample_date']).datetime).total_seconds()) + + gaps.append(gap) + + if gap > largest_gap: + largest_gap = gap + + # status['average_gap'] = numpy.mean(gaps) + status['largest_gap'] = largest_gap + # status['std_dev_gap'] = numpy.std(gaps) + + return status + + class PurpleRobotDeviceNote(models.Model): device = models.ForeignKey(PurpleRobotDevice, related_name='notes') note = models.TextField(max_length=1024) @@ -695,6 +961,7 @@ class PurpleRobotDeviceNote(models.Model): def __unicode__(self): return str(self.device) + ': ' + str(self.added) + class PurpleRobotPayload(models.Model): added = models.DateTimeField(auto_now_add=True) payload = models.TextField(max_length=8388608) @@ -702,17 +969,17 @@ class PurpleRobotPayload(models.Model): user_id = models.CharField(max_length=1024, db_index=True) errors = models.TextField(max_length=65536, null=True, blank=True) - + def ingest_readings(self): tags = self.process_tags - + tag = 'extracted_readings' - + items = json.loads(self.payload) - + device = PurpleRobotDevice.objects.filter(hash_key=self.user_id).first() - if device != None: + if device is not None: device.set_most_recent_payload(self) for item in items: @@ -721,68 +988,105 @@ def ingest_readings(self): reading.payload = json.dumps(item, indent=2) reading.logged = datetime.datetime.utcfromtimestamp(item['TIMESTAMP']).replace(tzinfo=pytz.utc) reading.guid = item['GUID'] + reading.size = len(reading.payload) + if reading.attachment is not None: + try: + reading.size += reading.attachment.size + except ValueError: + pass # No attachment... + reading.save() - - if device != None: + + if device is not None: device.set_most_recent_reading(reading) - except KeyError as e: - print('Missing Key: ' + json.dumps(item, indent=2)) - print(traceback.format_exc()) - + device.set_earliest_reading(reading) + # device.set_latest_reading(reading) + + if settings.PURPLE_ROBOT_DISABLE_DATA_CHECKS is False: + probe_name = my_slugify(item['PROBE']).replace('edu_northwestern_cbits_purple_robot_manager_probes_', '') + + found = False + probe = None + + if probe_name in PAYLOAD_PERFORMANCE_CACHE: + probe = PAYLOAD_PERFORMANCE_CACHE[probe_name] + else: + for app in settings.INSTALLED_APPS: + if found is False: + try: + probe = importlib.import_module(app + '.management.commands.analytics.' + probe_name) + found = True + except ImportError: + pass + + if found is False: + probe = importlib.import_module('purple_robot_app.management.commands.analytics.generic_probe') + + PAYLOAD_PERFORMANCE_CACHE[probe_name] = probe + probe.log_reading(reading) + + except KeyError: + print 'Missing Key: ' + json.dumps(item, indent=2) + print traceback.format_exc() + if tags is None or len(tags) == 0: tags = 'ingest_error' elif tags.find('ingest_error') == -1: tags += ' ingest_error' - + if tags is None or tags.find(tag) == -1: if tags is None or len(tags) == 0: tags = tag else: tags += ' ' + tag - + self.process_tags = tags - + self.save() - + class PurpleRobotEvent(models.Model): event = models.CharField(max_length=1024) name = models.CharField(max_length=1024, null=True, blank=True, db_index=True) - logged = models.DateTimeField() + logged = models.DateTimeField(db_index=True) user_id = models.CharField(max_length=1024, db_index=True) payload = models.TextField(max_length=(1024 * 1024 * 8), null=True, blank=True) - + def event_name(self): if self.name is None: return self.name - + if self.event == 'java_exception': payload = json.loads(self.payload) - + tokens = payload['content_object']['stacktrace'].split(':') - + self.name = tokens[0] self.save() - + return self.name - + def description(self): payload = json.loads(self.payload) - + if self.event == 'pr_script_log_message': return payload['message'] elif self.event == 'set_user_id': - return SafeString(payload['old_id'] + ' → ' + payload['new_id']) + if 'new_id' in payload: + return SafeString(payload['old_id'] + ' → ' + payload['new_id']) + else: + return SafeString(payload['old_id'] + ' → ?') elif self.event == 'java_exception': return payload['stacktrace'].split('\n')[0] elif self.event == 'broadcast_message': return payload['message'] - + return self.event + ' (' + str(self.pk) + ')' + class PurpleRobotReading(models.Model): probe = models.CharField(max_length=1024, null=True, blank=True, db_index=True) user_id = models.CharField(max_length=1024, db_index=True) @@ -790,9 +1094,9 @@ class PurpleRobotReading(models.Model): logged = models.DateTimeField(db_index=True) guid = models.CharField(max_length=1024, db_index=True, null=True, blank=True) size = models.IntegerField(default=0, db_index=True) - + attachment = models.FileField(upload_to='reading_attachments', null=True, blank=True) - + class Meta: index_together = [ ['probe', 'user_id'], @@ -802,33 +1106,35 @@ class Meta: def probe_name(self): return self.probe.replace('edu.northwestern.cbits.purple_robot_manager.probes.', '') - + def update_guid(self): - if self.guid != None: + if self.guid is not None: return - + reading_json = json.loads(self.payload) - + self.guid = reading_json['GUID'] self.save() - + def fetch_summary(self): probe_name = my_slugify(self.probe).replace('edu_northwestern_cbits_purple_robot_manager_probes_', '') - + try: formatter = importlib.import_module('purple_robot_app.formatters.' + probe_name) except ImportError: formatter = importlib.import_module('purple_robot_app.formatters.probe') - - return formatter.format(probe_name, self.payload) - + + return formatter.format_reading(probe_name, self.payload) + def payload_value(self): return json.loads(self.payload) + @receiver(pre_delete, sender=PurpleRobotReading) -def purplerobotreport_delete(sender, instance, **kwargs): +def purplerobotreading_delete(sender, instance, **kwargs): instance.attachment.delete(False) - + + EXPORT_JOB_STATE_CHOICES = ( ('pending', 'Pending'), ('processing', 'Processing'), @@ -836,6 +1142,7 @@ def purplerobotreport_delete(sender, instance, **kwargs): ('error', 'Error'), ) + class PurpleRobotExportJob(models.Model): probes = models.TextField(max_length=8196, null=True, blank=True) users = models.TextField(max_length=8196, null=True, blank=True) @@ -845,12 +1152,13 @@ class PurpleRobotExportJob(models.Model): export_file = models.FileField(blank=True, upload_to='export_files') destination = models.EmailField(null=True, blank=True) - + state = models.CharField(max_length=512, choices=EXPORT_JOB_STATE_CHOICES, default='pending') - + def export_file_url(self): return reverse('fetch_export_file', args=[str(self.pk)]) + @receiver(pre_delete, sender=PurpleRobotExportJob) def purplerobotexportjob_delete(sender, instance, **kwargs): instance.export_file.delete(False) @@ -866,10 +1174,12 @@ class PurpleRobotReport(models.Model): def __unicode__(self): return string.split(self.probe, '.')[-1] + @receiver(pre_delete, sender=PurpleRobotReport) def purplerobotreport_delete(sender, instance, **kwargs): instance.report_file.delete(False) + class PurpleRobotTest(models.Model): active = models.BooleanField(default=False) slug = models.SlugField(unique=True) @@ -877,118 +1187,112 @@ class PurpleRobotTest(models.Model): user_id = models.CharField(max_length=1024) frequency = models.FloatField(default=1.0) report = models.TextField(default='{}') - + last_updated = models.DateTimeField() - + def updated_ago(self): delta = timezone.now() - self.last_updated - + return delta.total_seconds() - + def update(self, days=1): report = json.loads(self.report) - + report_end = time.time() report_start = report_end - (days * 24 * 60 * 60) - + date_start = pytz.utc.localize(datetime.datetime.utcfromtimestamp(report_start)) date_end = pytz.utc.localize(datetime.datetime.utcfromtimestamp(report_end)) - + batteries = [] - + battery_probe = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.BatteryProbe' - - battery_readings = PurpleRobotReading.objects.filter(logged__gte=date_start, \ - logged__lte=date_end, \ - probe=battery_probe, \ + + battery_readings = PurpleRobotReading.objects.filter(logged__gte=date_start, + logged__lte=date_end, + probe=battery_probe, user_id=self.user_id).order_by('logged') - + for battery_reading in battery_readings: payload = json.loads(battery_reading.payload) - - reading = [ payload['TIMESTAMP'], payload['level'] ] - + reading = [payload['TIMESTAMP'], payload['level']] batteries.append(reading) - batteries = [b for b in batteries if (b[0] >= report_start and b[0] <= report_end)] + batteries = [b for b in batteries if b[0] >= report_start and b[0] <= report_end] batteries.append([report_start, 0]) batteries.append([report_end, 0]) batteries.sort(key=lambda reading: reading[0]) - + report['battery'] = batteries - + pending_files = [] - + health_probe = 'edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe' - - health_readings = PurpleRobotReading.objects.filter(logged__gte=date_start, \ - logged__lte=date_end, \ - probe=health_probe, \ + + health_readings = PurpleRobotReading.objects.filter(logged__gte=date_start, + logged__lte=date_end, + probe=health_probe, user_id=self.user_id).order_by('logged') - + for health_reading in health_readings: payload = json.loads(health_reading.payload) - - reading = [ payload['TIMESTAMP'], payload['PENDING_COUNT'], payload['ACTIVE_RUNTIME'] ] - + reading = [payload['TIMESTAMP'], payload['PENDING_COUNT'], payload['ACTIVE_RUNTIME']] pending_files.append(reading) - pending_files = [p for p in pending_files if (p[0] >= report_start and p[0] <= report_end)] + pending_files = [p for p in pending_files if p[0] >= report_start and p[0] <= report_end] pending_files.append([report_start, 0, 0]) pending_files.append([report_end, 0, 0]) pending_files.sort(key=lambda reading: reading[0]) - + report['pending_files'] = pending_files - - if ('target' in report) == False: + + if ('target' in report) is False: report['target'] = [] timestamps = [] - + target_readings = PurpleRobotReading.objects.filter(probe=self.probe, user_id=self.user_id, logged__gte=date_start).order_by('logged') - + total_readings = target_readings.count() start_index = 0 while start_index < total_readings: end_index = start_index + 100 - + for reading in target_readings[start_index:end_index]: payload = json.loads(reading.payload) - + if 'EVENT_TIMESTAMP' in payload: - for ts in payload['EVENT_TIMESTAMP']: - if ts > 1000000000000: - ts = ts / 1000 - - if ts >= report_start and ts <= report_end: - timestamps.append(ts) + for timestamp in payload['EVENT_TIMESTAMP']: + if timestamp > 1000000000000: + timestamp = timestamp / 1000 + + if timestamp >= report_start and timestamp <= report_end: + timestamps.append(timestamp) elif 'SENSOR_TIMESTAMP' in payload: - sensor_time = payload['TIMESTAMP'] - - for ts in payload['SENSOR_TIMESTAMP']: - if ts > 1000000000000: - ts = ts / 1000 - - if ts >= report_start and ts <= report_end: - timestamps.append(ts) + for timestamp in payload['SENSOR_TIMESTAMP']: + if timestamp > 1000000000000: + timestamp = timestamp / 1000 + + if timestamp >= report_start and timestamp <= report_end: + timestamps.append(timestamp) else: - ts = payload['TIMESTAMP'] - - if ts >= report_start and ts <= report_end: - timestamps.append(ts) - + timestamp = payload['TIMESTAMP'] + + if timestamp >= report_start and timestamp <= report_end: + timestamps.append(timestamp) + start_index = end_index - + timestamps.sort() start = report_start end = start + (60 * 15) - - counts = [ [start, 0] ] - + + counts = [[start, 0]] + count = 0 - + for timestamp in timestamps: if timestamp < report_start or timestamp > report_end: pass @@ -1000,165 +1304,164 @@ def update(self, days=1): start = end end = start + (60 * 15) count = 0 - + count += 1 counts.append([start, count]) counts.append([report_end, None]) report['target'] = counts - + self.report = json.dumps(report, indent=1) self.last_updated = timezone.now() self.save() - - + def average_frequency(self): report = json.loads(self.report) - + try: readings = report['target'] - + count = 0.0 - + for reading in readings: if reading[1] is not None: count += reading[1] - + first = float(readings[0][0]) last = float(readings[-1][0]) - + return float(count / (last - first)) except KeyError: return -1 - + def passes(self): return self.average_frequency() > self.frequency def max_gap_size(self): report = json.loads(self.report) - + if 'target' in report: readings = report['target'] - + timestamps = [] - + for reading in readings: timestamps.append(reading[0]) - + if len(timestamps) <= 1: return 0 - + timestamps.sort() - + max_gap = 0 - + for i in range(0, len(timestamps) - 1): one = timestamps[i] two = timestamps[i + 1] - + gap = two - one - + if gap > max_gap: max_gap = gap - + return max_gap - + return -1 - + def frequency_graph_json(self, indent=0): report = json.loads(self.report) - + output = [] - + if 'target' in report: timestamps = report['target'] - + for timestamp in timestamps: if len(timestamp) > 1: - output.append({ 'x': timestamp[0], 'y': timestamp[1] }) + output.append({'x': timestamp[0], 'y': timestamp[1]}) else: - output.append({ 'x': timestamp[0], 'y': None }) - + output.append({'x': timestamp[0], 'y': None}) + return json.dumps(output, indent=indent) - + return '[]' def battery_graph_json(self, indent=0): report = json.loads(self.report) - + now = time.time() start = now - (24 * 60 * 60) - - measurements = [ { 'x': start, 'y': None }] - + + measurements = [{'x': start, 'y': None}] + for record in report['battery']: timestamp = record[0] - + if timestamp < start: pass elif timestamp > now: pass else: - measurements.append({ 'x': timestamp, 'y': record[1] }) + measurements.append({'x': timestamp, 'y': record[1]}) - measurements.append({ 'x': now, 'y': None }) + measurements.append({'x': now, 'y': None}) return json.dumps(measurements, indent=indent) def pending_files_graph_json(self, indent=0): report = json.loads(self.report) - + now = time.time() start = now - (24 * 60 * 60) - - measurements = [ { 'x': start, 'y': None }] - + + measurements = [{'x': start, 'y': None}] + for record in report['pending_files']: timestamp = record[0] - + if timestamp < start: pass elif timestamp > now: pass else: - measurements.append({ 'x': timestamp, 'y': record[1] }) + measurements.append({'x': timestamp, 'y': record[1]}) - measurements.append({ 'x': now, 'y': None }) + measurements.append({'x': now, 'y': None}) return json.dumps(measurements, indent=indent) - + def last_recorded_sample(self): report = json.loads(self.report) - + if 'target' in report: readings = report['target'] - + timestamps = [] - + for reading in readings: timestamps.append(reading[0]) - + if len(timestamps) > 0: return datetime.datetime.fromtimestamp(timestamps[-1]) - + return None - + def probe_name(self): - return string.replace(self.probe, 'edu.northwestern.cbits.purple_robot_manager.probes.', '') - + return string.replace(self.probe, 'edu.northwestern.cbits.purple_robot_manager.probes.', '') + class PurpleRobotAlert(models.Model): severity = models.IntegerField(default=0) message = models.CharField(max_length=2048) tags = models.CharField(max_length=2048, null=True, blank=True) - + action_url = models.URLField(max_length=1024, null=True, blank=True) - + probe = models.CharField(max_length=1024, null=True, blank=True) user_id = models.CharField(max_length=1024, null=True, blank=True) generated = models.DateTimeField() dismissed = models.DateTimeField(null=True, blank=True) - + manually_dismissed = models.BooleanField(default=False) diff --git a/performance.py b/performance.py new file mode 100644 index 0000000..508c233 --- /dev/null +++ b/performance.py @@ -0,0 +1,95 @@ +# pylint: disable=line-too-long, bare-except + +import arrow +import datetime +# import msgpack +import os + +import cPickle as pickle + +from django.conf import settings +from django.utils import timezone + + +def append_performance_sample(user, item, detail_date=timezone.now(), value=''): + os.umask(000) + + today = datetime.date.today() + + folder = settings.MEDIA_ROOT + '/purple_robot_analytics/' + user + '/' + today.isoformat() + + if not os.path.exists(folder): + os.makedirs(folder) + + item_path = folder + '/' + item + '.pickle' + + content = {} + + if os.path.exists(item_path): + pickle_file = open(item_path, 'rb') + + try: + content = pickle.load(pickle_file) + except: + pass + + pickle_file.close() + + content[detail_date.isoformat()] = value + + pickle.dump(content, open(item_path, 'wb')) + + +def fetch_performance_samples(user, item, start=None, end=None): + if end is None: + end = timezone.now() + + if start is None: + today = datetime.date.today() + start = datetime.datetime(today.year, today.month, today.day, 0, 0, 0, 0, tzinfo=end.tzinfo) + + start_date = start.date() + end_date = end.date() + + samples = [] + + while start_date <= end_date: + item_path = settings.MEDIA_ROOT + '/purple_robot_analytics/' + user + '/' + start_date.isoformat() + '/' + item + '.pickle' + + if os.path.exists(item_path): + pickle_file = open(item_path, 'rb') + + content = pickle.load(pickle_file) + + for key, value in content.iteritems(): + sample_date = arrow.get(key).datetime + + if sample_date >= start and sample_date <= end: + value['sample_date'] = key + samples.append(value) + + pickle_file.close() + + start_date += datetime.timedelta(days=1) + + samples.sort(key=lambda sample: sample['sample_date']) + + return samples + + +def fetch_performance_users(): + items_path = settings.MEDIA_ROOT + '/purple_robot_analytics/' + + users = {} + + for item in os.listdir(items_path): + if item != 'system': + dates = os.listdir(items_path + '/' + item) + + last = sorted(dates, reverse=True)[0] + + toks = last.split('-') + + users[item] = datetime.date(int(toks[0]), int(toks[1]), int(toks[2])) + + return users diff --git a/requirements.txt b/requirements.txt index 69b9d32..92338da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,14 @@ +BeautifulSoup==3.2.1 Django==1.6.11 PyYAML==3.11 South==1.0 argparse==1.2.1 +arrow==0.7.0 astroid==1.3.6 datadiff==1.1.6 django-debug-toolbar==1.3.2 dodgy==0.1.7 +hockeyapp==0.4.0 logilab-common==0.63.2 mccabe==0.3 msgpack-python==0.4.6 @@ -26,7 +29,7 @@ pytz==2014.10 requests==2.6.0 requirements-detector==0.4 setoptconf==0.2.0 +sexpdata==0.0.3 six==1.9.0 sqlparse==0.1.15 wsgiref==0.1.2 -sexpdata diff --git a/static/bootstrap3/css/bootstrap-datepicker.css b/static/bootstrap3/css/bootstrap-datepicker.css new file mode 100755 index 0000000..5fc431d --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker.css @@ -0,0 +1,472 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker { + padding: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #999999; + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid #999999; +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} +.datepicker > div { + display: none; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.day.focused { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.highlighted { + background: #d9edf7; + border-radius: 0; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: -moz-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -ms-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); + background-image: -webkit-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -o-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: linear-gradient(to bottom, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000; +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.today:hover:hover { + color: #000; +} +.datepicker table tr td.today.active:hover { + color: #fff; +} +.datepicker table tr td.range, +.datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:hover { + background: #eeeeee; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today, +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:hover { + background-color: #f3d17a; + background-image: -moz-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -ms-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); + background-image: -webkit-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -o-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: linear-gradient(to bottom, #f3c17a, #f3e97a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); + border-color: #f3e97a #f3e97a #edde34; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:hover:hover, +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today.disabled:hover:hover, +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today:hover.disabled, +.datepicker table tr td.range.today.disabled.disabled, +.datepicker table tr td.range.today.disabled:hover.disabled, +.datepicker table tr td.range.today[disabled], +.datepicker table tr td.range.today:hover[disabled], +.datepicker table tr td.range.today.disabled[disabled], +.datepicker table tr td.range.today.disabled:hover[disabled] { + background-color: #f3e97a; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active { + background-color: #efe24b \9; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected.disabled:hover { + background-color: #9e9e9e; + background-image: -moz-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -ms-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080)); + background-image: -webkit-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -o-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: linear-gradient(to bottom, #b3b3b3, #808080); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); + border-color: #808080 #808080 #595959; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected:hover:hover, +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.disabled:hover:hover, +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected:hover.disabled, +.datepicker table tr td.selected.disabled.disabled, +.datepicker table tr td.selected.disabled:hover.disabled, +.datepicker table tr td.selected[disabled], +.datepicker table tr td.selected:hover[disabled], +.datepicker table tr td.selected.disabled[disabled], +.datepicker table tr td.selected.disabled:hover[disabled] { + background-color: #808080; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active { + background-color: #666666 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999999; +} +.datepicker .datepicker-switch { + width: 145px; +} +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-append.date .add-on, +.input-prepend.date .add-on { + cursor: pointer; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + margin-top: 3px; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-daterange .add-on { + display: inline-block; + width: auto; + min-width: 16px; + height: 18px; + padding: 4px 5px; + font-weight: normal; + line-height: 18px; + text-align: center; + text-shadow: 0 1px 0 #ffffff; + vertical-align: middle; + background-color: #eeeeee; + border: 1px solid #ccc; + margin-left: -5px; + margin-right: -5px; +} diff --git a/static/bootstrap3/css/bootstrap-datepicker.min.css b/static/bootstrap3/css/bootstrap-datepicker.min.css new file mode 100755 index 0000000..b342245 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker.min.css @@ -0,0 +1,8 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker{padding:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker.datepicker-rtl{direction:rtl}.datepicker.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #999;border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid #999}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker>div{display:none}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker td,.datepicker th{text-align:center;width:20px;height:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.day:hover,.datepicker table tr td.day.focused{background:#eee;cursor:pointer}.datepicker table tr td.old,.datepicker table tr td.new{color:#999}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td.highlighted{background:#d9edf7;border-radius:0}.datepicker table tr td.today,.datepicker table tr td.today:hover,.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:hover{background-color:#fde19a;background-image:-moz-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-ms-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdd49a),to(#fdf59a));background-image:-webkit-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-o-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:linear-gradient(to bottom,#fdd49a,#fdf59a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);border-color:#fdf59a #fdf59a #fbed50;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#000}.datepicker table tr td.today:hover,.datepicker table tr td.today:hover:hover,.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today.disabled:hover:hover,.datepicker table tr td.today:active,.datepicker table tr td.today:hover:active,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today.active,.datepicker table tr td.today:hover.active,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled:hover.active,.datepicker table tr td.today.disabled,.datepicker table tr td.today:hover.disabled,.datepicker table tr td.today.disabled.disabled,.datepicker table tr td.today.disabled:hover.disabled,.datepicker table tr td.today[disabled],.datepicker table tr td.today:hover[disabled],.datepicker table tr td.today.disabled[disabled],.datepicker table tr td.today.disabled:hover[disabled]{background-color:#fdf59a}.datepicker table tr td.today:active,.datepicker table tr td.today:hover:active,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today.active,.datepicker table tr td.today:hover.active,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled:hover.active{background-color:#fbf069 \9}.datepicker table tr td.today:hover:hover{color:#000}.datepicker table tr td.today.active:hover{color:#fff}.datepicker table tr td.range,.datepicker table tr td.range:hover,.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:hover{background:#eee;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today,.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:hover{background-color:#f3d17a;background-image:-moz-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-ms-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f3c17a),to(#f3e97a));background-image:-webkit-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-o-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:linear-gradient(to bottom,#f3c17a,#f3e97a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);border-color:#f3e97a #f3e97a #edde34;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today:hover:hover,.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today.disabled:hover:hover,.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover:active,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today.active,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled:hover.active,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today:hover.disabled,.datepicker table tr td.range.today.disabled.disabled,.datepicker table tr td.range.today.disabled:hover.disabled,.datepicker table tr td.range.today[disabled],.datepicker table tr td.range.today:hover[disabled],.datepicker table tr td.range.today.disabled[disabled],.datepicker table tr td.range.today.disabled:hover[disabled]{background-color:#f3e97a}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover:active,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today.active,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled:hover.active{background-color:#efe24b \9}.datepicker table tr td.selected,.datepicker table tr td.selected:hover,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected.disabled:hover{background-color:#9e9e9e;background-image:-moz-linear-gradient(to bottom,#b3b3b3,gray);background-image:-ms-linear-gradient(to bottom,#b3b3b3,gray);background-image:-webkit-gradient(linear,0 0,0 100%,from(#b3b3b3),to(gray));background-image:-webkit-linear-gradient(to bottom,#b3b3b3,gray);background-image:-o-linear-gradient(to bottom,#b3b3b3,gray);background-image:linear-gradient(to bottom,#b3b3b3,gray);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);border-color:gray #808080 #595959;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected:hover,.datepicker table tr td.selected:hover:hover,.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.disabled:hover:hover,.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover:active,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled:hover.active,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected:hover.disabled,.datepicker table tr td.selected.disabled.disabled,.datepicker table tr td.selected.disabled:hover.disabled,.datepicker table tr td.selected[disabled],.datepicker table tr td.selected:hover[disabled],.datepicker table tr td.selected.disabled[disabled],.datepicker table tr td.selected.disabled:hover[disabled]{background-color:gray}.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover:active,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled:hover.active{background-color:#666 \9}.datepicker table tr td.active,.datepicker table tr td.active:hover,.datepicker table tr td.active.disabled,.datepicker table tr td.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active:hover,.datepicker table tr td.active:hover:hover,.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.disabled:hover:hover,.datepicker table tr td.active:active,.datepicker table tr td.active:hover:active,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active.active,.datepicker table tr td.active:hover.active,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled:hover.active,.datepicker table tr td.active.disabled,.datepicker table tr td.active:hover.disabled,.datepicker table tr td.active.disabled.disabled,.datepicker table tr td.active.disabled:hover.disabled,.datepicker table tr td.active[disabled],.datepicker table tr td.active:hover[disabled],.datepicker table tr td.active.disabled[disabled],.datepicker table tr td.active.disabled:hover[disabled]{background-color:#04c}.datepicker table tr td.active:active,.datepicker table tr td.active:hover:active,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active.active,.datepicker table tr td.active:hover.active,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled:hover.active{background-color:#039 \9}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active:hover,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover:hover,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active:hover.disabled,.datepicker table tr td span.active.disabled.disabled,.datepicker table tr td span.active.disabled:hover.disabled,.datepicker table tr td span.active[disabled],.datepicker table tr td span.active:hover[disabled],.datepicker table tr td span.active.disabled[disabled],.datepicker table tr td span.active.disabled:hover[disabled]{background-color:#04c}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active{background-color:#039 \9}.datepicker table tr td span.old,.datepicker table tr td span.new{color:#999}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .prev,.datepicker .next,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .prev:hover,.datepicker .next:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-append.date .add-on,.input-prepend.date .add-on{cursor:pointer}.input-append.date .add-on i,.input-prepend.date .add-on i{margin-top:3px}.input-daterange input{text-align:center}.input-daterange input:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-daterange input:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-daterange .add-on{display:inline-block;width:auto;min-width:16px;height:18px;padding:4px 5px;font-weight:400;line-height:18px;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:1px solid #ccc;margin-left:-5px;margin-right:-5px} \ No newline at end of file diff --git a/static/bootstrap3/css/bootstrap-datepicker.standalone.css b/static/bootstrap3/css/bootstrap-datepicker.standalone.css new file mode 100755 index 0000000..fc642a1 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker.standalone.css @@ -0,0 +1,505 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker { + padding: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #999999; + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid #999999; +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} +.datepicker > div { + display: none; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker td, +.datepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.day.focused { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.highlighted { + background: #d9edf7; + border-radius: 0; +} +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { + background-color: #fde19a; + background-image: -moz-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -ms-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a)); + background-image: -webkit-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: -o-linear-gradient(to bottom, #fdd49a, #fdf59a); + background-image: linear-gradient(to bottom, #fdd49a, #fdf59a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); + border-color: #fdf59a #fdf59a #fbed50; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000; +} +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { + background-color: #fdf59a; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { + background-color: #fbf069 \9; +} +.datepicker table tr td.today:hover:hover { + color: #000; +} +.datepicker table tr td.today.active:hover { + color: #fff; +} +.datepicker table tr td.range, +.datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:hover { + background: #eeeeee; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today, +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:hover { + background-color: #f3d17a; + background-image: -moz-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -ms-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); + background-image: -webkit-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: -o-linear-gradient(to bottom, #f3c17a, #f3e97a); + background-image: linear-gradient(to bottom, #f3c17a, #f3e97a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); + border-color: #f3e97a #f3e97a #edde34; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:hover:hover, +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today.disabled:hover:hover, +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today:hover.disabled, +.datepicker table tr td.range.today.disabled.disabled, +.datepicker table tr td.range.today.disabled:hover.disabled, +.datepicker table tr td.range.today[disabled], +.datepicker table tr td.range.today:hover[disabled], +.datepicker table tr td.range.today.disabled[disabled], +.datepicker table tr td.range.today.disabled:hover[disabled] { + background-color: #f3e97a; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active { + background-color: #efe24b \9; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected.disabled:hover { + background-color: #9e9e9e; + background-image: -moz-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -ms-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080)); + background-image: -webkit-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: -o-linear-gradient(to bottom, #b3b3b3, #808080); + background-image: linear-gradient(to bottom, #b3b3b3, #808080); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); + border-color: #808080 #808080 #595959; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected:hover:hover, +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.disabled:hover:hover, +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected:hover.disabled, +.datepicker table tr td.selected.disabled.disabled, +.datepicker table tr td.selected.disabled:hover.disabled, +.datepicker table tr td.selected[disabled], +.datepicker table tr td.selected:hover[disabled], +.datepicker table tr td.selected.disabled[disabled], +.datepicker table tr td.selected.disabled:hover[disabled] { + background-color: #808080; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active { + background-color: #666666 \9; +} +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + background-color: #006dcc; + background-image: -moz-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -ms-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); + background-image: -webkit-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: -o-linear-gradient(to bottom, #0088cc, #0044cc); + background-image: linear-gradient(to bottom, #0088cc, #0044cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); + border-color: #0044cc #0044cc #002a80; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { + background-color: #0044cc; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { + background-color: #003399 \9; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999999; +} +.datepicker .datepicker-switch { + width: 145px; +} +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-append.date .add-on, +.input-prepend.date .add-on { + cursor: pointer; +} +.input-append.date .add-on i, +.input-prepend.date .add-on i { + margin-top: 3px; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + -webkit-border-radius: 3px 0 0 3px; + -moz-border-radius: 3px 0 0 3px; + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + -webkit-border-radius: 0 3px 3px 0; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; +} +.input-daterange .add-on { + display: inline-block; + width: auto; + min-width: 16px; + height: 20px; + padding: 4px 5px; + font-weight: normal; + line-height: 20px; + text-align: center; + text-shadow: 0 1px 0 #ffffff; + vertical-align: middle; + background-color: #eeeeee; + border: 1px solid #ccc; + margin-left: -5px; + margin-right: -5px; +} +.datepicker.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + float: left; + display: none; + min-width: 160px; + list-style: none; + background-color: #ffffff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + *border-right-width: 2px; + *border-bottom-width: 2px; + color: #333333; + font-size: 13px; + line-height: 20px; +} +.datepicker.dropdown-menu th, +.datepicker.datepicker-inline th, +.datepicker.dropdown-menu td, +.datepicker.datepicker-inline td { + padding: 4px 5px; +} diff --git a/static/bootstrap3/css/bootstrap-datepicker.standalone.min.css b/static/bootstrap3/css/bootstrap-datepicker.standalone.min.css new file mode 100755 index 0000000..f5db74a --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker.standalone.min.css @@ -0,0 +1,8 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker{padding:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker.datepicker-rtl{direction:rtl}.datepicker.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #999;border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid #999}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker>div{display:none}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker td,.datepicker th{text-align:center;width:20px;height:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.day:hover,.datepicker table tr td.day.focused{background:#eee;cursor:pointer}.datepicker table tr td.old,.datepicker table tr td.new{color:#999}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td.highlighted{background:#d9edf7;border-radius:0}.datepicker table tr td.today,.datepicker table tr td.today:hover,.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:hover{background-color:#fde19a;background-image:-moz-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-ms-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdd49a),to(#fdf59a));background-image:-webkit-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-o-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:linear-gradient(to bottom,#fdd49a,#fdf59a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);border-color:#fdf59a #fdf59a #fbed50;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#000}.datepicker table tr td.today:hover,.datepicker table tr td.today:hover:hover,.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today.disabled:hover:hover,.datepicker table tr td.today:active,.datepicker table tr td.today:hover:active,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today.active,.datepicker table tr td.today:hover.active,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled:hover.active,.datepicker table tr td.today.disabled,.datepicker table tr td.today:hover.disabled,.datepicker table tr td.today.disabled.disabled,.datepicker table tr td.today.disabled:hover.disabled,.datepicker table tr td.today[disabled],.datepicker table tr td.today:hover[disabled],.datepicker table tr td.today.disabled[disabled],.datepicker table tr td.today.disabled:hover[disabled]{background-color:#fdf59a}.datepicker table tr td.today:active,.datepicker table tr td.today:hover:active,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today.active,.datepicker table tr td.today:hover.active,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled:hover.active{background-color:#fbf069 \9}.datepicker table tr td.today:hover:hover{color:#000}.datepicker table tr td.today.active:hover{color:#fff}.datepicker table tr td.range,.datepicker table tr td.range:hover,.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:hover{background:#eee;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today,.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:hover{background-color:#f3d17a;background-image:-moz-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-ms-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f3c17a),to(#f3e97a));background-image:-webkit-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-o-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:linear-gradient(to bottom,#f3c17a,#f3e97a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);border-color:#f3e97a #f3e97a #edde34;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today:hover:hover,.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today.disabled:hover:hover,.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover:active,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today.active,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled:hover.active,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today:hover.disabled,.datepicker table tr td.range.today.disabled.disabled,.datepicker table tr td.range.today.disabled:hover.disabled,.datepicker table tr td.range.today[disabled],.datepicker table tr td.range.today:hover[disabled],.datepicker table tr td.range.today.disabled[disabled],.datepicker table tr td.range.today.disabled:hover[disabled]{background-color:#f3e97a}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover:active,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today.active,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled:hover.active{background-color:#efe24b \9}.datepicker table tr td.selected,.datepicker table tr td.selected:hover,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected.disabled:hover{background-color:#9e9e9e;background-image:-moz-linear-gradient(to bottom,#b3b3b3,gray);background-image:-ms-linear-gradient(to bottom,#b3b3b3,gray);background-image:-webkit-gradient(linear,0 0,0 100%,from(#b3b3b3),to(gray));background-image:-webkit-linear-gradient(to bottom,#b3b3b3,gray);background-image:-o-linear-gradient(to bottom,#b3b3b3,gray);background-image:linear-gradient(to bottom,#b3b3b3,gray);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);border-color:gray #808080 #595959;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected:hover,.datepicker table tr td.selected:hover:hover,.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.disabled:hover:hover,.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover:active,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled:hover.active,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected:hover.disabled,.datepicker table tr td.selected.disabled.disabled,.datepicker table tr td.selected.disabled:hover.disabled,.datepicker table tr td.selected[disabled],.datepicker table tr td.selected:hover[disabled],.datepicker table tr td.selected.disabled[disabled],.datepicker table tr td.selected.disabled:hover[disabled]{background-color:gray}.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover:active,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled:hover.active{background-color:#666 \9}.datepicker table tr td.active,.datepicker table tr td.active:hover,.datepicker table tr td.active.disabled,.datepicker table tr td.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active:hover,.datepicker table tr td.active:hover:hover,.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.disabled:hover:hover,.datepicker table tr td.active:active,.datepicker table tr td.active:hover:active,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active.active,.datepicker table tr td.active:hover.active,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled:hover.active,.datepicker table tr td.active.disabled,.datepicker table tr td.active:hover.disabled,.datepicker table tr td.active.disabled.disabled,.datepicker table tr td.active.disabled:hover.disabled,.datepicker table tr td.active[disabled],.datepicker table tr td.active:hover[disabled],.datepicker table tr td.active.disabled[disabled],.datepicker table tr td.active.disabled:hover[disabled]{background-color:#04c}.datepicker table tr td.active:active,.datepicker table tr td.active:hover:active,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active.active,.datepicker table tr td.active:hover.active,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled:hover.active{background-color:#039 \9}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active:hover,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover:hover,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active:hover.disabled,.datepicker table tr td span.active.disabled.disabled,.datepicker table tr td span.active.disabled:hover.disabled,.datepicker table tr td span.active[disabled],.datepicker table tr td span.active:hover[disabled],.datepicker table tr td span.active.disabled[disabled],.datepicker table tr td span.active.disabled:hover[disabled]{background-color:#04c}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active{background-color:#039 \9}.datepicker table tr td span.old,.datepicker table tr td span.new{color:#999}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .prev,.datepicker .next,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .prev:hover,.datepicker .next:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-append.date .add-on,.input-prepend.date .add-on{cursor:pointer}.input-append.date .add-on i,.input-prepend.date .add-on i{margin-top:3px}.input-daterange input{text-align:center}.input-daterange input:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-daterange input:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-daterange .add-on{display:inline-block;width:auto;min-width:16px;height:20px;padding:4px 5px;font-weight:400;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:1px solid #ccc;margin-left:-5px;margin-right:-5px}.datepicker.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;float:left;display:none;min-width:160px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;*border-right-width:2px;*border-bottom-width:2px;color:#333;font-size:13px;line-height:20px}.datepicker.dropdown-menu th,.datepicker.datepicker-inline th,.datepicker.dropdown-menu td,.datepicker.datepicker-inline td{padding:4px 5px} \ No newline at end of file diff --git a/static/bootstrap3/css/bootstrap-datepicker3.css b/static/bootstrap3/css/bootstrap-datepicker3.css new file mode 100755 index 0000000..06837c8 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker3.css @@ -0,0 +1,791 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker { + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; + padding: 4px; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid rgba(0, 0, 0, 0.15); + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid rgba(0, 0, 0, 0.15); +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} +.datepicker > div { + display: none; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker table tr td, +.datepicker table tr th { + text-align: center; + width: 30px; + height: 30px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.focused { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.highlighted { + color: #000000; + background-color: #d9edf7; + border-color: #85c5e5; + border-radius: 0; +} +.datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted.focus { + color: #000000; + background-color: #afd9ee; + border-color: #298fc2; +} +.datepicker table tr td.highlighted:hover { + color: #000000; + background-color: #afd9ee; + border-color: #52addb; +} +.datepicker table tr td.highlighted:active, +.datepicker table tr td.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.highlighted { + color: #000000; + background-color: #afd9ee; + border-color: #52addb; +} +.datepicker table tr td.highlighted:active:hover, +.datepicker table tr td.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.highlighted:hover, +.datepicker table tr td.highlighted:active:focus, +.datepicker table tr td.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted:active.focus, +.datepicker table tr td.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.highlighted.focus { + color: #000000; + background-color: #91cbe8; + border-color: #298fc2; +} +.datepicker table tr td.highlighted:active, +.datepicker table tr td.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.highlighted { + background-image: none; +} +.datepicker table tr td.highlighted.disabled:hover, +.datepicker table tr td.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.highlighted:hover, +.datepicker table tr td.highlighted.disabled:focus, +.datepicker table tr td.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted.disabled.focus, +.datepicker table tr td.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.highlighted.focus { + background-color: #d9edf7; + border-color: #85c5e5; +} +.datepicker table tr td.highlighted.focused { + background: #afd9ee; +} +.datepicker table tr td.highlighted.disabled, +.datepicker table tr td.highlighted.disabled:active { + background: #d9edf7; + color: #999999; +} +.datepicker table tr td.today { + color: #000000; + background-color: #ffdb99; + border-color: #ffb733; +} +.datepicker table tr td.today:focus, +.datepicker table tr td.today.focus { + color: #000000; + background-color: #ffc966; + border-color: #b37400; +} +.datepicker table tr td.today:hover { + color: #000000; + background-color: #ffc966; + border-color: #f59e00; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today.active, +.open > .dropdown-toggle.datepicker table tr td.today { + color: #000000; + background-color: #ffc966; + border-color: #f59e00; +} +.datepicker table tr td.today:active:hover, +.datepicker table tr td.today.active:hover, +.open > .dropdown-toggle.datepicker table tr td.today:hover, +.datepicker table tr td.today:active:focus, +.datepicker table tr td.today.active:focus, +.open > .dropdown-toggle.datepicker table tr td.today:focus, +.datepicker table tr td.today:active.focus, +.datepicker table tr td.today.active.focus, +.open > .dropdown-toggle.datepicker table tr td.today.focus { + color: #000000; + background-color: #ffbc42; + border-color: #b37400; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today.active, +.open > .dropdown-toggle.datepicker table tr td.today { + background-image: none; +} +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today[disabled]:hover, +fieldset[disabled] .datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled:focus, +.datepicker table tr td.today[disabled]:focus, +fieldset[disabled] .datepicker table tr td.today:focus, +.datepicker table tr td.today.disabled.focus, +.datepicker table tr td.today[disabled].focus, +fieldset[disabled] .datepicker table tr td.today.focus { + background-color: #ffdb99; + border-color: #ffb733; +} +.datepicker table tr td.today.focused { + background: #ffc966; +} +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:active { + background: #ffdb99; + color: #999999; +} +.datepicker table tr td.range { + color: #000000; + background-color: #eeeeee; + border-color: #bbbbbb; + border-radius: 0; +} +.datepicker table tr td.range:focus, +.datepicker table tr td.range.focus { + color: #000000; + background-color: #d5d5d5; + border-color: #7c7c7c; +} +.datepicker table tr td.range:hover { + color: #000000; + background-color: #d5d5d5; + border-color: #9d9d9d; +} +.datepicker table tr td.range:active, +.datepicker table tr td.range.active, +.open > .dropdown-toggle.datepicker table tr td.range { + color: #000000; + background-color: #d5d5d5; + border-color: #9d9d9d; +} +.datepicker table tr td.range:active:hover, +.datepicker table tr td.range.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range:hover, +.datepicker table tr td.range:active:focus, +.datepicker table tr td.range.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range:focus, +.datepicker table tr td.range:active.focus, +.datepicker table tr td.range.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.focus { + color: #000000; + background-color: #c3c3c3; + border-color: #7c7c7c; +} +.datepicker table tr td.range:active, +.datepicker table tr td.range.active, +.open > .dropdown-toggle.datepicker table tr td.range { + background-image: none; +} +.datepicker table tr td.range.disabled:hover, +.datepicker table tr td.range[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled:focus, +.datepicker table tr td.range[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range:focus, +.datepicker table tr td.range.disabled.focus, +.datepicker table tr td.range[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.focus { + background-color: #eeeeee; + border-color: #bbbbbb; +} +.datepicker table tr td.range.focused { + background: #d5d5d5; +} +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:active { + background: #eeeeee; + color: #999999; +} +.datepicker table tr td.range.highlighted { + color: #000000; + background-color: #e4eef3; + border-color: #9dc1d3; +} +.datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted.focus { + color: #000000; + background-color: #c1d7e3; + border-color: #4b88a6; +} +.datepicker table tr td.range.highlighted:hover { + color: #000000; + background-color: #c1d7e3; + border-color: #73a6c0; +} +.datepicker table tr td.range.highlighted:active, +.datepicker table tr td.range.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted { + color: #000000; + background-color: #c1d7e3; + border-color: #73a6c0; +} +.datepicker table tr td.range.highlighted:active:hover, +.datepicker table tr td.range.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted:hover, +.datepicker table tr td.range.highlighted:active:focus, +.datepicker table tr td.range.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted:active.focus, +.datepicker table tr td.range.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted.focus { + color: #000000; + background-color: #a8c8d8; + border-color: #4b88a6; +} +.datepicker table tr td.range.highlighted:active, +.datepicker table tr td.range.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted { + background-image: none; +} +.datepicker table tr td.range.highlighted.disabled:hover, +.datepicker table tr td.range.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range.highlighted:hover, +.datepicker table tr td.range.highlighted.disabled:focus, +.datepicker table tr td.range.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted.disabled.focus, +.datepicker table tr td.range.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.highlighted.focus { + background-color: #e4eef3; + border-color: #9dc1d3; +} +.datepicker table tr td.range.highlighted.focused { + background: #c1d7e3; +} +.datepicker table tr td.range.highlighted.disabled, +.datepicker table tr td.range.highlighted.disabled:active { + background: #e4eef3; + color: #999999; +} +.datepicker table tr td.range.today { + color: #000000; + background-color: #f7ca77; + border-color: #f1a417; +} +.datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today.focus { + color: #000000; + background-color: #f4b747; + border-color: #815608; +} +.datepicker table tr td.range.today:hover { + color: #000000; + background-color: #f4b747; + border-color: #bf800c; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today.active, +.open > .dropdown-toggle.datepicker table tr td.range.today { + color: #000000; + background-color: #f4b747; + border-color: #bf800c; +} +.datepicker table tr td.range.today:active:hover, +.datepicker table tr td.range.today.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:active:focus, +.datepicker table tr td.range.today.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today:active.focus, +.datepicker table tr td.range.today.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.today.focus { + color: #000000; + background-color: #f2aa25; + border-color: #815608; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today.active, +.open > .dropdown-toggle.datepicker table tr td.range.today { + background-image: none; +} +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled:focus, +.datepicker table tr td.range.today[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today.disabled.focus, +.datepicker table tr td.range.today[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.today.focus { + background-color: #f7ca77; + border-color: #f1a417; +} +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:active { + background: #f7ca77; + color: #999999; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected.highlighted { + color: #ffffff; + background-color: #999999; + border-color: #555555; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:focus, +.datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected.focus, +.datepicker table tr td.selected.highlighted.focus { + color: #ffffff; + background-color: #808080; + border-color: #161616; +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.highlighted:hover { + color: #ffffff; + background-color: #808080; + border-color: #373737; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected.highlighted:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.selected, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted { + color: #ffffff; + background-color: #808080; + border-color: #373737; +} +.datepicker table tr td.selected:active:hover, +.datepicker table tr td.selected.highlighted:active:hover, +.datepicker table tr td.selected.active:hover, +.datepicker table tr td.selected.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.selected:hover, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:hover, +.datepicker table tr td.selected:active:focus, +.datepicker table tr td.selected.highlighted:active:focus, +.datepicker table tr td.selected.active:focus, +.datepicker table tr td.selected.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.selected:focus, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected:active.focus, +.datepicker table tr td.selected.highlighted:active.focus, +.datepicker table tr td.selected.active.focus, +.datepicker table tr td.selected.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.selected.focus, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted.focus { + color: #ffffff; + background-color: #6e6e6e; + border-color: #161616; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected.highlighted:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.selected, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted { + background-image: none; +} +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.highlighted.disabled:hover, +.datepicker table tr td.selected[disabled]:hover, +.datepicker table tr td.selected.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.selected:hover, +fieldset[disabled] .datepicker table tr td.selected.highlighted:hover, +.datepicker table tr td.selected.disabled:focus, +.datepicker table tr td.selected.highlighted.disabled:focus, +.datepicker table tr td.selected[disabled]:focus, +.datepicker table tr td.selected.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.selected:focus, +fieldset[disabled] .datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected.disabled.focus, +.datepicker table tr td.selected.highlighted.disabled.focus, +.datepicker table tr td.selected[disabled].focus, +.datepicker table tr td.selected.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.selected.focus, +fieldset[disabled] .datepicker table tr td.selected.highlighted.focus { + background-color: #999999; + border-color: #555555; +} +.datepicker table tr td.active, +.datepicker table tr td.active.highlighted { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:focus, +.datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active.focus, +.datepicker table tr td.active.highlighted.focus { + color: #ffffff; + background-color: #3071a9; + border-color: #193c5a; +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active.highlighted:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active.highlighted:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.active, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td.active:active:hover, +.datepicker table tr td.active.highlighted:active:hover, +.datepicker table tr td.active.active:hover, +.datepicker table tr td.active.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.active:hover, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted:hover, +.datepicker table tr td.active:active:focus, +.datepicker table tr td.active.highlighted:active:focus, +.datepicker table tr td.active.active:focus, +.datepicker table tr td.active.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.active:focus, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active:active.focus, +.datepicker table tr td.active.highlighted:active.focus, +.datepicker table tr td.active.active.focus, +.datepicker table tr td.active.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.active.focus, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted.focus { + color: #ffffff; + background-color: #285e8e; + border-color: #193c5a; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active.highlighted:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.active, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted { + background-image: none; +} +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.highlighted.disabled:hover, +.datepicker table tr td.active[disabled]:hover, +.datepicker table tr td.active.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.active:hover, +fieldset[disabled] .datepicker table tr td.active.highlighted:hover, +.datepicker table tr td.active.disabled:focus, +.datepicker table tr td.active.highlighted.disabled:focus, +.datepicker table tr td.active[disabled]:focus, +.datepicker table tr td.active.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.active:focus, +fieldset[disabled] .datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active.disabled.focus, +.datepicker table tr td.active.highlighted.disabled.focus, +.datepicker table tr td.active[disabled].focus, +.datepicker table tr td.active.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.active.focus, +fieldset[disabled] .datepicker table tr td.active.highlighted.focus { + background-color: #428bca; + border-color: #357ebd; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:focus, +.datepicker table tr td span.active:hover:focus, +.datepicker table tr td span.active.disabled:focus, +.datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active.focus, +.datepicker table tr td span.active:hover.focus, +.datepicker table tr td span.active.disabled.focus, +.datepicker table tr td span.active.disabled:hover.focus { + color: #ffffff; + background-color: #3071a9; + border-color: #193c5a; +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.open > .dropdown-toggle.datepicker table tr td span.active, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td span.active:active:hover, +.datepicker table tr td span.active:hover:active:hover, +.datepicker table tr td span.active.disabled:active:hover, +.datepicker table tr td span.active.disabled:hover:active:hover, +.datepicker table tr td span.active.active:hover, +.datepicker table tr td span.active:hover.active:hover, +.datepicker table tr td span.active.disabled.active:hover, +.datepicker table tr td span.active.disabled:hover.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active:hover:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active:focus, +.datepicker table tr td span.active:hover:active:focus, +.datepicker table tr td span.active.disabled:active:focus, +.datepicker table tr td span.active.disabled:hover:active:focus, +.datepicker table tr td span.active.active:focus, +.datepicker table tr td span.active:hover.active:focus, +.datepicker table tr td span.active.disabled.active:focus, +.datepicker table tr td span.active.disabled:hover.active:focus, +.open > .dropdown-toggle.datepicker table tr td span.active:focus, +.open > .dropdown-toggle.datepicker table tr td span.active:hover:focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active:active.focus, +.datepicker table tr td span.active:hover:active.focus, +.datepicker table tr td span.active.disabled:active.focus, +.datepicker table tr td span.active.disabled:hover:active.focus, +.datepicker table tr td span.active.active.focus, +.datepicker table tr td span.active:hover.active.focus, +.datepicker table tr td span.active.disabled.active.focus, +.datepicker table tr td span.active.disabled:hover.active.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.focus, +.open > .dropdown-toggle.datepicker table tr td span.active:hover.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover.focus { + color: #ffffff; + background-color: #285e8e; + border-color: #193c5a; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.open > .dropdown-toggle.datepicker table tr td span.active, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover { + background-image: none; +} +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active:hover.disabled:hover, +.datepicker table tr td span.active.disabled.disabled:hover, +.datepicker table tr td span.active.disabled:hover.disabled:hover, +.datepicker table tr td span.active[disabled]:hover, +.datepicker table tr td span.active:hover[disabled]:hover, +.datepicker table tr td span.active.disabled[disabled]:hover, +.datepicker table tr td span.active.disabled:hover[disabled]:hover, +fieldset[disabled] .datepicker table tr td span.active:hover, +fieldset[disabled] .datepicker table tr td span.active:hover:hover, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active.disabled:focus, +.datepicker table tr td span.active:hover.disabled:focus, +.datepicker table tr td span.active.disabled.disabled:focus, +.datepicker table tr td span.active.disabled:hover.disabled:focus, +.datepicker table tr td span.active[disabled]:focus, +.datepicker table tr td span.active:hover[disabled]:focus, +.datepicker table tr td span.active.disabled[disabled]:focus, +.datepicker table tr td span.active.disabled:hover[disabled]:focus, +fieldset[disabled] .datepicker table tr td span.active:focus, +fieldset[disabled] .datepicker table tr td span.active:hover:focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active.disabled.focus, +.datepicker table tr td span.active:hover.disabled.focus, +.datepicker table tr td span.active.disabled.disabled.focus, +.datepicker table tr td span.active.disabled:hover.disabled.focus, +.datepicker table tr td span.active[disabled].focus, +.datepicker table tr td span.active:hover[disabled].focus, +.datepicker table tr td span.active.disabled[disabled].focus, +.datepicker table tr td span.active.disabled:hover[disabled].focus, +fieldset[disabled] .datepicker table tr td span.active.focus, +fieldset[disabled] .datepicker table tr td span.active:hover.focus, +fieldset[disabled] .datepicker table tr td span.active.disabled.focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus { + background-color: #428bca; + border-color: #357ebd; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999999; +} +.datepicker .datepicker-switch { + width: 145px; +} +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.input-daterange { + width: 100%; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + border-radius: 0 3px 3px 0; +} +.input-daterange .input-group-addon { + width: auto; + min-width: 16px; + padding: 4px 5px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + text-shadow: 0 1px 0 #fff; + vertical-align: middle; + background-color: #eeeeee; + border: solid #cccccc; + border-width: 1px 0; + margin-left: -5px; + margin-right: -5px; +} diff --git a/static/bootstrap3/css/bootstrap-datepicker3.min.css b/static/bootstrap3/css/bootstrap-datepicker3.min.css new file mode 100755 index 0000000..41cc778 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker3.min.css @@ -0,0 +1,8 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker{border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker.datepicker-rtl{direction:rtl}.datepicker.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0;padding:4px}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(0,0,0,.15);border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid rgba(0,0,0,.15)}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker>div{display:none}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker table tr td,.datepicker table tr th{text-align:center;width:30px;height:30px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.old,.datepicker table tr td.new{color:#999}.datepicker table tr td.day:hover,.datepicker table tr td.focused{background:#eee;cursor:pointer}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td.highlighted{color:#000;background-color:#d9edf7;border-color:#85c5e5;border-radius:0}.datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted.focus{color:#000;background-color:#afd9ee;border-color:#298fc2}.datepicker table tr td.highlighted:hover{color:#000;background-color:#afd9ee;border-color:#52addb}.datepicker table tr td.highlighted:active,.datepicker table tr td.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.highlighted{color:#000;background-color:#afd9ee;border-color:#52addb}.datepicker table tr td.highlighted:active:hover,.datepicker table tr td.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.highlighted:hover,.datepicker table tr td.highlighted:active:focus,.datepicker table tr td.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted:active.focus,.datepicker table tr td.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.highlighted.focus{color:#000;background-color:#91cbe8;border-color:#298fc2}.datepicker table tr td.highlighted:active,.datepicker table tr td.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.highlighted{background-image:none}.datepicker table tr td.highlighted.disabled:hover,.datepicker table tr td.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.highlighted:hover,.datepicker table tr td.highlighted.disabled:focus,.datepicker table tr td.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted.disabled.focus,.datepicker table tr td.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.highlighted.focus{background-color:#d9edf7;border-color:#85c5e5}.datepicker table tr td.highlighted.focused{background:#afd9ee}.datepicker table tr td.highlighted.disabled,.datepicker table tr td.highlighted.disabled:active{background:#d9edf7;color:#999}.datepicker table tr td.today{color:#000;background-color:#ffdb99;border-color:#ffb733}.datepicker table tr td.today:focus,.datepicker table tr td.today.focus{color:#000;background-color:#ffc966;border-color:#b37400}.datepicker table tr td.today:hover{color:#000;background-color:#ffc966;border-color:#f59e00}.datepicker table tr td.today:active,.datepicker table tr td.today.active,.open>.dropdown-toggle.datepicker table tr td.today{color:#000;background-color:#ffc966;border-color:#f59e00}.datepicker table tr td.today:active:hover,.datepicker table tr td.today.active:hover,.open>.dropdown-toggle.datepicker table tr td.today:hover,.datepicker table tr td.today:active:focus,.datepicker table tr td.today.active:focus,.open>.dropdown-toggle.datepicker table tr td.today:focus,.datepicker table tr td.today:active.focus,.datepicker table tr td.today.active.focus,.open>.dropdown-toggle.datepicker table tr td.today.focus{color:#000;background-color:#ffbc42;border-color:#b37400}.datepicker table tr td.today:active,.datepicker table tr td.today.active,.open>.dropdown-toggle.datepicker table tr td.today{background-image:none}.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today[disabled]:hover,fieldset[disabled] .datepicker table tr td.today:hover,.datepicker table tr td.today.disabled:focus,.datepicker table tr td.today[disabled]:focus,fieldset[disabled] .datepicker table tr td.today:focus,.datepicker table tr td.today.disabled.focus,.datepicker table tr td.today[disabled].focus,fieldset[disabled] .datepicker table tr td.today.focus{background-color:#ffdb99;border-color:#ffb733}.datepicker table tr td.today.focused{background:#ffc966}.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:active{background:#ffdb99;color:#999}.datepicker table tr td.range{color:#000;background-color:#eee;border-color:#bbb;border-radius:0}.datepicker table tr td.range:focus,.datepicker table tr td.range.focus{color:#000;background-color:#d5d5d5;border-color:#7c7c7c}.datepicker table tr td.range:hover{color:#000;background-color:#d5d5d5;border-color:#9d9d9d}.datepicker table tr td.range:active,.datepicker table tr td.range.active,.open>.dropdown-toggle.datepicker table tr td.range{color:#000;background-color:#d5d5d5;border-color:#9d9d9d}.datepicker table tr td.range:active:hover,.datepicker table tr td.range.active:hover,.open>.dropdown-toggle.datepicker table tr td.range:hover,.datepicker table tr td.range:active:focus,.datepicker table tr td.range.active:focus,.open>.dropdown-toggle.datepicker table tr td.range:focus,.datepicker table tr td.range:active.focus,.datepicker table tr td.range.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.focus{color:#000;background-color:#c3c3c3;border-color:#7c7c7c}.datepicker table tr td.range:active,.datepicker table tr td.range.active,.open>.dropdown-toggle.datepicker table tr td.range{background-image:none}.datepicker table tr td.range.disabled:hover,.datepicker table tr td.range[disabled]:hover,fieldset[disabled] .datepicker table tr td.range:hover,.datepicker table tr td.range.disabled:focus,.datepicker table tr td.range[disabled]:focus,fieldset[disabled] .datepicker table tr td.range:focus,.datepicker table tr td.range.disabled.focus,.datepicker table tr td.range[disabled].focus,fieldset[disabled] .datepicker table tr td.range.focus{background-color:#eee;border-color:#bbb}.datepicker table tr td.range.focused{background:#d5d5d5}.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:active{background:#eee;color:#999}.datepicker table tr td.range.highlighted{color:#000;background-color:#e4eef3;border-color:#9dc1d3}.datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted.focus{color:#000;background-color:#c1d7e3;border-color:#4b88a6}.datepicker table tr td.range.highlighted:hover{color:#000;background-color:#c1d7e3;border-color:#73a6c0}.datepicker table tr td.range.highlighted:active,.datepicker table tr td.range.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.range.highlighted{color:#000;background-color:#c1d7e3;border-color:#73a6c0}.datepicker table tr td.range.highlighted:active:hover,.datepicker table tr td.range.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.range.highlighted:hover,.datepicker table tr td.range.highlighted:active:focus,.datepicker table tr td.range.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted:active.focus,.datepicker table tr td.range.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.highlighted.focus{color:#000;background-color:#a8c8d8;border-color:#4b88a6}.datepicker table tr td.range.highlighted:active,.datepicker table tr td.range.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.range.highlighted{background-image:none}.datepicker table tr td.range.highlighted.disabled:hover,.datepicker table tr td.range.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.highlighted:hover,.datepicker table tr td.range.highlighted.disabled:focus,.datepicker table tr td.range.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted.disabled.focus,.datepicker table tr td.range.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.range.highlighted.focus{background-color:#e4eef3;border-color:#9dc1d3}.datepicker table tr td.range.highlighted.focused{background:#c1d7e3}.datepicker table tr td.range.highlighted.disabled,.datepicker table tr td.range.highlighted.disabled:active{background:#e4eef3;color:#999}.datepicker table tr td.range.today{color:#000;background-color:#f7ca77;border-color:#f1a417}.datepicker table tr td.range.today:focus,.datepicker table tr td.range.today.focus{color:#000;background-color:#f4b747;border-color:#815608}.datepicker table tr td.range.today:hover{color:#000;background-color:#f4b747;border-color:#bf800c}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today.active,.open>.dropdown-toggle.datepicker table tr td.range.today{color:#000;background-color:#f4b747;border-color:#bf800c}.datepicker table tr td.range.today:active:hover,.datepicker table tr td.range.today.active:hover,.open>.dropdown-toggle.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today:active:focus,.datepicker table tr td.range.today.active:focus,.open>.dropdown-toggle.datepicker table tr td.range.today:focus,.datepicker table tr td.range.today:active.focus,.datepicker table tr td.range.today.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.today.focus{color:#000;background-color:#f2aa25;border-color:#815608}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today.active,.open>.dropdown-toggle.datepicker table tr td.range.today{background-image:none}.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.today:hover,.datepicker table tr td.range.today.disabled:focus,.datepicker table tr td.range.today[disabled]:focus,fieldset[disabled] .datepicker table tr td.range.today:focus,.datepicker table tr td.range.today.disabled.focus,.datepicker table tr td.range.today[disabled].focus,fieldset[disabled] .datepicker table tr td.range.today.focus{background-color:#f7ca77;border-color:#f1a417}.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:active{background:#f7ca77;color:#999}.datepicker table tr td.selected,.datepicker table tr td.selected.highlighted{color:#fff;background-color:#999;border-color:#555;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected:focus,.datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected.focus,.datepicker table tr td.selected.highlighted.focus{color:#fff;background-color:gray;border-color:#161616}.datepicker table tr td.selected:hover,.datepicker table tr td.selected.highlighted:hover{color:#fff;background-color:gray;border-color:#373737}.datepicker table tr td.selected:active,.datepicker table tr td.selected.highlighted:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.selected,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted{color:#fff;background-color:gray;border-color:#373737}.datepicker table tr td.selected:active:hover,.datepicker table tr td.selected.highlighted:active:hover,.datepicker table tr td.selected.active:hover,.datepicker table tr td.selected.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.selected:hover,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted:hover,.datepicker table tr td.selected:active:focus,.datepicker table tr td.selected.highlighted:active:focus,.datepicker table tr td.selected.active:focus,.datepicker table tr td.selected.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.selected:focus,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected:active.focus,.datepicker table tr td.selected.highlighted:active.focus,.datepicker table tr td.selected.active.focus,.datepicker table tr td.selected.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.selected.focus,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted.focus{color:#fff;background-color:#6e6e6e;border-color:#161616}.datepicker table tr td.selected:active,.datepicker table tr td.selected.highlighted:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.selected,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted{background-image:none}.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.highlighted.disabled:hover,.datepicker table tr td.selected[disabled]:hover,.datepicker table tr td.selected.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.selected:hover,fieldset[disabled] .datepicker table tr td.selected.highlighted:hover,.datepicker table tr td.selected.disabled:focus,.datepicker table tr td.selected.highlighted.disabled:focus,.datepicker table tr td.selected[disabled]:focus,.datepicker table tr td.selected.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.selected:focus,fieldset[disabled] .datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected.disabled.focus,.datepicker table tr td.selected.highlighted.disabled.focus,.datepicker table tr td.selected[disabled].focus,.datepicker table tr td.selected.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.selected.focus,fieldset[disabled] .datepicker table tr td.selected.highlighted.focus{background-color:#999;border-color:#555}.datepicker table tr td.active,.datepicker table tr td.active.highlighted{color:#fff;background-color:#428bca;border-color:#357ebd;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active:focus,.datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active.focus,.datepicker table tr td.active.highlighted.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.datepicker table tr td.active:hover,.datepicker table tr td.active.highlighted:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td.active:active,.datepicker table tr td.active.highlighted:active,.datepicker table tr td.active.active,.datepicker table tr td.active.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.active,.open>.dropdown-toggle.datepicker table tr td.active.highlighted{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td.active:active:hover,.datepicker table tr td.active.highlighted:active:hover,.datepicker table tr td.active.active:hover,.datepicker table tr td.active.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.active:hover,.open>.dropdown-toggle.datepicker table tr td.active.highlighted:hover,.datepicker table tr td.active:active:focus,.datepicker table tr td.active.highlighted:active:focus,.datepicker table tr td.active.active:focus,.datepicker table tr td.active.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.active:focus,.open>.dropdown-toggle.datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active:active.focus,.datepicker table tr td.active.highlighted:active.focus,.datepicker table tr td.active.active.focus,.datepicker table tr td.active.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.active.focus,.open>.dropdown-toggle.datepicker table tr td.active.highlighted.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.datepicker table tr td.active:active,.datepicker table tr td.active.highlighted:active,.datepicker table tr td.active.active,.datepicker table tr td.active.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.active,.open>.dropdown-toggle.datepicker table tr td.active.highlighted{background-image:none}.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.highlighted.disabled:hover,.datepicker table tr td.active[disabled]:hover,.datepicker table tr td.active.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.active:hover,fieldset[disabled] .datepicker table tr td.active.highlighted:hover,.datepicker table tr td.active.disabled:focus,.datepicker table tr td.active.highlighted.disabled:focus,.datepicker table tr td.active[disabled]:focus,.datepicker table tr td.active.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.active:focus,fieldset[disabled] .datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active.disabled.focus,.datepicker table tr td.active.highlighted.disabled.focus,.datepicker table tr td.active[disabled].focus,.datepicker table tr td.active.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.active.focus,fieldset[disabled] .datepicker table tr td.active.highlighted.focus{background-color:#428bca;border-color:#357ebd}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;border-radius:4px}.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active:hover,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover{color:#fff;background-color:#428bca;border-color:#357ebd;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active:focus,.datepicker table tr td span.active:hover:focus,.datepicker table tr td span.active.disabled:focus,.datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active.focus,.datepicker table tr td span.active:hover.focus,.datepicker table tr td span.active.disabled.focus,.datepicker table tr td span.active.disabled:hover.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover:hover,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.open>.dropdown-toggle.datepicker table tr td span.active,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td span.active:active:hover,.datepicker table tr td span.active:hover:active:hover,.datepicker table tr td span.active.disabled:active:hover,.datepicker table tr td span.active.disabled:hover:active:hover,.datepicker table tr td span.active.active:hover,.datepicker table tr td span.active:hover.active:hover,.datepicker table tr td span.active.disabled.active:hover,.datepicker table tr td span.active.disabled:hover.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active:hover:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active:active:focus,.datepicker table tr td span.active:hover:active:focus,.datepicker table tr td span.active.disabled:active:focus,.datepicker table tr td span.active.disabled:hover:active:focus,.datepicker table tr td span.active.active:focus,.datepicker table tr td span.active:hover.active:focus,.datepicker table tr td span.active.disabled.active:focus,.datepicker table tr td span.active.disabled:hover.active:focus,.open>.dropdown-toggle.datepicker table tr td span.active:focus,.open>.dropdown-toggle.datepicker table tr td span.active:hover:focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active:active.focus,.datepicker table tr td span.active:hover:active.focus,.datepicker table tr td span.active.disabled:active.focus,.datepicker table tr td span.active.disabled:hover:active.focus,.datepicker table tr td span.active.active.focus,.datepicker table tr td span.active:hover.active.focus,.datepicker table tr td span.active.disabled.active.focus,.datepicker table tr td span.active.disabled:hover.active.focus,.open>.dropdown-toggle.datepicker table tr td span.active.focus,.open>.dropdown-toggle.datepicker table tr td span.active:hover.focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled.focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.open>.dropdown-toggle.datepicker table tr td span.active,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover{background-image:none}.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active:hover.disabled:hover,.datepicker table tr td span.active.disabled.disabled:hover,.datepicker table tr td span.active.disabled:hover.disabled:hover,.datepicker table tr td span.active[disabled]:hover,.datepicker table tr td span.active:hover[disabled]:hover,.datepicker table tr td span.active.disabled[disabled]:hover,.datepicker table tr td span.active.disabled:hover[disabled]:hover,fieldset[disabled] .datepicker table tr td span.active:hover,fieldset[disabled] .datepicker table tr td span.active:hover:hover,fieldset[disabled] .datepicker table tr td span.active.disabled:hover,fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active.disabled:focus,.datepicker table tr td span.active:hover.disabled:focus,.datepicker table tr td span.active.disabled.disabled:focus,.datepicker table tr td span.active.disabled:hover.disabled:focus,.datepicker table tr td span.active[disabled]:focus,.datepicker table tr td span.active:hover[disabled]:focus,.datepicker table tr td span.active.disabled[disabled]:focus,.datepicker table tr td span.active.disabled:hover[disabled]:focus,fieldset[disabled] .datepicker table tr td span.active:focus,fieldset[disabled] .datepicker table tr td span.active:hover:focus,fieldset[disabled] .datepicker table tr td span.active.disabled:focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active.disabled.focus,.datepicker table tr td span.active:hover.disabled.focus,.datepicker table tr td span.active.disabled.disabled.focus,.datepicker table tr td span.active.disabled:hover.disabled.focus,.datepicker table tr td span.active[disabled].focus,.datepicker table tr td span.active:hover[disabled].focus,.datepicker table tr td span.active.disabled[disabled].focus,.datepicker table tr td span.active.disabled:hover[disabled].focus,fieldset[disabled] .datepicker table tr td span.active.focus,fieldset[disabled] .datepicker table tr td span.active:hover.focus,fieldset[disabled] .datepicker table tr td span.active.disabled.focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus{background-color:#428bca;border-color:#357ebd}.datepicker table tr td span.old,.datepicker table tr td span.new{color:#999}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .prev,.datepicker .next,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .prev:hover,.datepicker .next:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-group.date .input-group-addon{cursor:pointer}.input-daterange{width:100%}.input-daterange input{text-align:center}.input-daterange input:first-child{border-radius:3px 0 0 3px}.input-daterange input:last-child{border-radius:0 3px 3px 0}.input-daterange .input-group-addon{width:auto;min-width:16px;padding:4px 5px;font-weight:400;line-height:1.42857143;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:solid #ccc;border-width:1px 0;margin-left:-5px;margin-right:-5px} \ No newline at end of file diff --git a/static/bootstrap3/css/bootstrap-datepicker3.standalone.css b/static/bootstrap3/css/bootstrap-datepicker3.standalone.css new file mode 100755 index 0000000..3d68a7f --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker3.standalone.css @@ -0,0 +1,822 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker { + border-radius: 4px; + direction: ltr; +} +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; + padding: 4px; +} +.datepicker-dropdown:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid rgba(0, 0, 0, 0.15); + border-top: 0; + border-bottom-color: rgba(0, 0, 0, 0.2); + position: absolute; +} +.datepicker-dropdown:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-top: 0; + position: absolute; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { + left: 7px; +} +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-top:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid rgba(0, 0, 0, 0.15); +} +.datepicker-dropdown.datepicker-orient-top:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} +.datepicker > div { + display: none; +} +.datepicker table { + margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.datepicker table tr td, +.datepicker table tr th { + text-align: center; + width: 30px; + height: 30px; + border-radius: 4px; + border: none; +} +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.old, +.datepicker table tr td.new { + color: #999999; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.focused { + background: #eeeeee; + cursor: pointer; +} +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td.highlighted { + color: #000000; + background-color: #d9edf7; + border-color: #85c5e5; + border-radius: 0; +} +.datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted.focus { + color: #000000; + background-color: #afd9ee; + border-color: #298fc2; +} +.datepicker table tr td.highlighted:hover { + color: #000000; + background-color: #afd9ee; + border-color: #52addb; +} +.datepicker table tr td.highlighted:active, +.datepicker table tr td.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.highlighted { + color: #000000; + background-color: #afd9ee; + border-color: #52addb; +} +.datepicker table tr td.highlighted:active:hover, +.datepicker table tr td.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.highlighted:hover, +.datepicker table tr td.highlighted:active:focus, +.datepicker table tr td.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted:active.focus, +.datepicker table tr td.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.highlighted.focus { + color: #000000; + background-color: #91cbe8; + border-color: #298fc2; +} +.datepicker table tr td.highlighted:active, +.datepicker table tr td.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.highlighted { + background-image: none; +} +.datepicker table tr td.highlighted.disabled:hover, +.datepicker table tr td.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.highlighted:hover, +.datepicker table tr td.highlighted.disabled:focus, +.datepicker table tr td.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.highlighted:focus, +.datepicker table tr td.highlighted.disabled.focus, +.datepicker table tr td.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.highlighted.focus { + background-color: #d9edf7; + border-color: #85c5e5; +} +.datepicker table tr td.highlighted.focused { + background: #afd9ee; +} +.datepicker table tr td.highlighted.disabled, +.datepicker table tr td.highlighted.disabled:active { + background: #d9edf7; + color: #999999; +} +.datepicker table tr td.today { + color: #000000; + background-color: #ffdb99; + border-color: #ffb733; +} +.datepicker table tr td.today:focus, +.datepicker table tr td.today.focus { + color: #000000; + background-color: #ffc966; + border-color: #b37400; +} +.datepicker table tr td.today:hover { + color: #000000; + background-color: #ffc966; + border-color: #f59e00; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today.active, +.open > .dropdown-toggle.datepicker table tr td.today { + color: #000000; + background-color: #ffc966; + border-color: #f59e00; +} +.datepicker table tr td.today:active:hover, +.datepicker table tr td.today.active:hover, +.open > .dropdown-toggle.datepicker table tr td.today:hover, +.datepicker table tr td.today:active:focus, +.datepicker table tr td.today.active:focus, +.open > .dropdown-toggle.datepicker table tr td.today:focus, +.datepicker table tr td.today:active.focus, +.datepicker table tr td.today.active.focus, +.open > .dropdown-toggle.datepicker table tr td.today.focus { + color: #000000; + background-color: #ffbc42; + border-color: #b37400; +} +.datepicker table tr td.today:active, +.datepicker table tr td.today.active, +.open > .dropdown-toggle.datepicker table tr td.today { + background-image: none; +} +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today[disabled]:hover, +fieldset[disabled] .datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled:focus, +.datepicker table tr td.today[disabled]:focus, +fieldset[disabled] .datepicker table tr td.today:focus, +.datepicker table tr td.today.disabled.focus, +.datepicker table tr td.today[disabled].focus, +fieldset[disabled] .datepicker table tr td.today.focus { + background-color: #ffdb99; + border-color: #ffb733; +} +.datepicker table tr td.today.focused { + background: #ffc966; +} +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:active { + background: #ffdb99; + color: #999999; +} +.datepicker table tr td.range { + color: #000000; + background-color: #eeeeee; + border-color: #bbbbbb; + border-radius: 0; +} +.datepicker table tr td.range:focus, +.datepicker table tr td.range.focus { + color: #000000; + background-color: #d5d5d5; + border-color: #7c7c7c; +} +.datepicker table tr td.range:hover { + color: #000000; + background-color: #d5d5d5; + border-color: #9d9d9d; +} +.datepicker table tr td.range:active, +.datepicker table tr td.range.active, +.open > .dropdown-toggle.datepicker table tr td.range { + color: #000000; + background-color: #d5d5d5; + border-color: #9d9d9d; +} +.datepicker table tr td.range:active:hover, +.datepicker table tr td.range.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range:hover, +.datepicker table tr td.range:active:focus, +.datepicker table tr td.range.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range:focus, +.datepicker table tr td.range:active.focus, +.datepicker table tr td.range.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.focus { + color: #000000; + background-color: #c3c3c3; + border-color: #7c7c7c; +} +.datepicker table tr td.range:active, +.datepicker table tr td.range.active, +.open > .dropdown-toggle.datepicker table tr td.range { + background-image: none; +} +.datepicker table tr td.range.disabled:hover, +.datepicker table tr td.range[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled:focus, +.datepicker table tr td.range[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range:focus, +.datepicker table tr td.range.disabled.focus, +.datepicker table tr td.range[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.focus { + background-color: #eeeeee; + border-color: #bbbbbb; +} +.datepicker table tr td.range.focused { + background: #d5d5d5; +} +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:active { + background: #eeeeee; + color: #999999; +} +.datepicker table tr td.range.highlighted { + color: #000000; + background-color: #e4eef3; + border-color: #9dc1d3; +} +.datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted.focus { + color: #000000; + background-color: #c1d7e3; + border-color: #4b88a6; +} +.datepicker table tr td.range.highlighted:hover { + color: #000000; + background-color: #c1d7e3; + border-color: #73a6c0; +} +.datepicker table tr td.range.highlighted:active, +.datepicker table tr td.range.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted { + color: #000000; + background-color: #c1d7e3; + border-color: #73a6c0; +} +.datepicker table tr td.range.highlighted:active:hover, +.datepicker table tr td.range.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted:hover, +.datepicker table tr td.range.highlighted:active:focus, +.datepicker table tr td.range.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted:active.focus, +.datepicker table tr td.range.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted.focus { + color: #000000; + background-color: #a8c8d8; + border-color: #4b88a6; +} +.datepicker table tr td.range.highlighted:active, +.datepicker table tr td.range.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.range.highlighted { + background-image: none; +} +.datepicker table tr td.range.highlighted.disabled:hover, +.datepicker table tr td.range.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range.highlighted:hover, +.datepicker table tr td.range.highlighted.disabled:focus, +.datepicker table tr td.range.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range.highlighted:focus, +.datepicker table tr td.range.highlighted.disabled.focus, +.datepicker table tr td.range.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.highlighted.focus { + background-color: #e4eef3; + border-color: #9dc1d3; +} +.datepicker table tr td.range.highlighted.focused { + background: #c1d7e3; +} +.datepicker table tr td.range.highlighted.disabled, +.datepicker table tr td.range.highlighted.disabled:active { + background: #e4eef3; + color: #999999; +} +.datepicker table tr td.range.today { + color: #000000; + background-color: #f7ca77; + border-color: #f1a417; +} +.datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today.focus { + color: #000000; + background-color: #f4b747; + border-color: #815608; +} +.datepicker table tr td.range.today:hover { + color: #000000; + background-color: #f4b747; + border-color: #bf800c; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today.active, +.open > .dropdown-toggle.datepicker table tr td.range.today { + color: #000000; + background-color: #f4b747; + border-color: #bf800c; +} +.datepicker table tr td.range.today:active:hover, +.datepicker table tr td.range.today.active:hover, +.open > .dropdown-toggle.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:active:focus, +.datepicker table tr td.range.today.active:focus, +.open > .dropdown-toggle.datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today:active.focus, +.datepicker table tr td.range.today.active.focus, +.open > .dropdown-toggle.datepicker table tr td.range.today.focus { + color: #000000; + background-color: #f2aa25; + border-color: #815608; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today.active, +.open > .dropdown-toggle.datepicker table tr td.range.today { + background-image: none; +} +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today[disabled]:hover, +fieldset[disabled] .datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled:focus, +.datepicker table tr td.range.today[disabled]:focus, +fieldset[disabled] .datepicker table tr td.range.today:focus, +.datepicker table tr td.range.today.disabled.focus, +.datepicker table tr td.range.today[disabled].focus, +fieldset[disabled] .datepicker table tr td.range.today.focus { + background-color: #f7ca77; + border-color: #f1a417; +} +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:active { + background: #f7ca77; + color: #999999; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected.highlighted { + color: #ffffff; + background-color: #999999; + border-color: #555555; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.selected:focus, +.datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected.focus, +.datepicker table tr td.selected.highlighted.focus { + color: #ffffff; + background-color: #808080; + border-color: #161616; +} +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.highlighted:hover { + color: #ffffff; + background-color: #808080; + border-color: #373737; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected.highlighted:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.selected, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted { + color: #ffffff; + background-color: #808080; + border-color: #373737; +} +.datepicker table tr td.selected:active:hover, +.datepicker table tr td.selected.highlighted:active:hover, +.datepicker table tr td.selected.active:hover, +.datepicker table tr td.selected.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.selected:hover, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:hover, +.datepicker table tr td.selected:active:focus, +.datepicker table tr td.selected.highlighted:active:focus, +.datepicker table tr td.selected.active:focus, +.datepicker table tr td.selected.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.selected:focus, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected:active.focus, +.datepicker table tr td.selected.highlighted:active.focus, +.datepicker table tr td.selected.active.focus, +.datepicker table tr td.selected.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.selected.focus, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted.focus { + color: #ffffff; + background-color: #6e6e6e; + border-color: #161616; +} +.datepicker table tr td.selected:active, +.datepicker table tr td.selected.highlighted:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.selected, +.open > .dropdown-toggle.datepicker table tr td.selected.highlighted { + background-image: none; +} +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.highlighted.disabled:hover, +.datepicker table tr td.selected[disabled]:hover, +.datepicker table tr td.selected.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.selected:hover, +fieldset[disabled] .datepicker table tr td.selected.highlighted:hover, +.datepicker table tr td.selected.disabled:focus, +.datepicker table tr td.selected.highlighted.disabled:focus, +.datepicker table tr td.selected[disabled]:focus, +.datepicker table tr td.selected.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.selected:focus, +fieldset[disabled] .datepicker table tr td.selected.highlighted:focus, +.datepicker table tr td.selected.disabled.focus, +.datepicker table tr td.selected.highlighted.disabled.focus, +.datepicker table tr td.selected[disabled].focus, +.datepicker table tr td.selected.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.selected.focus, +fieldset[disabled] .datepicker table tr td.selected.highlighted.focus { + background-color: #999999; + border-color: #555555; +} +.datepicker table tr td.active, +.datepicker table tr td.active.highlighted { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td.active:focus, +.datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active.focus, +.datepicker table tr td.active.highlighted.focus { + color: #ffffff; + background-color: #3071a9; + border-color: #193c5a; +} +.datepicker table tr td.active:hover, +.datepicker table tr td.active.highlighted:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active.highlighted:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.active, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td.active:active:hover, +.datepicker table tr td.active.highlighted:active:hover, +.datepicker table tr td.active.active:hover, +.datepicker table tr td.active.highlighted.active:hover, +.open > .dropdown-toggle.datepicker table tr td.active:hover, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted:hover, +.datepicker table tr td.active:active:focus, +.datepicker table tr td.active.highlighted:active:focus, +.datepicker table tr td.active.active:focus, +.datepicker table tr td.active.highlighted.active:focus, +.open > .dropdown-toggle.datepicker table tr td.active:focus, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active:active.focus, +.datepicker table tr td.active.highlighted:active.focus, +.datepicker table tr td.active.active.focus, +.datepicker table tr td.active.highlighted.active.focus, +.open > .dropdown-toggle.datepicker table tr td.active.focus, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted.focus { + color: #ffffff; + background-color: #285e8e; + border-color: #193c5a; +} +.datepicker table tr td.active:active, +.datepicker table tr td.active.highlighted:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active.highlighted.active, +.open > .dropdown-toggle.datepicker table tr td.active, +.open > .dropdown-toggle.datepicker table tr td.active.highlighted { + background-image: none; +} +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.highlighted.disabled:hover, +.datepicker table tr td.active[disabled]:hover, +.datepicker table tr td.active.highlighted[disabled]:hover, +fieldset[disabled] .datepicker table tr td.active:hover, +fieldset[disabled] .datepicker table tr td.active.highlighted:hover, +.datepicker table tr td.active.disabled:focus, +.datepicker table tr td.active.highlighted.disabled:focus, +.datepicker table tr td.active[disabled]:focus, +.datepicker table tr td.active.highlighted[disabled]:focus, +fieldset[disabled] .datepicker table tr td.active:focus, +fieldset[disabled] .datepicker table tr td.active.highlighted:focus, +.datepicker table tr td.active.disabled.focus, +.datepicker table tr td.active.highlighted.disabled.focus, +.datepicker table tr td.active[disabled].focus, +.datepicker table tr td.active.highlighted[disabled].focus, +fieldset[disabled] .datepicker table tr td.active.focus, +fieldset[disabled] .datepicker table tr td.active.highlighted.focus { + background-color: #428bca; + border-color: #357ebd; +} +.datepicker table tr td span { + display: block; + width: 23%; + height: 54px; + line-height: 54px; + float: left; + margin: 1%; + cursor: pointer; + border-radius: 4px; +} +.datepicker table tr td span:hover { + background: #eeeeee; +} +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { + background: none; + color: #999999; + cursor: default; +} +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.datepicker table tr td span.active:focus, +.datepicker table tr td span.active:hover:focus, +.datepicker table tr td span.active.disabled:focus, +.datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active.focus, +.datepicker table tr td span.active:hover.focus, +.datepicker table tr td span.active.disabled.focus, +.datepicker table tr td span.active.disabled:hover.focus { + color: #ffffff; + background-color: #3071a9; + border-color: #193c5a; +} +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.open > .dropdown-toggle.datepicker table tr td span.active, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover { + color: #ffffff; + background-color: #3071a9; + border-color: #285e8e; +} +.datepicker table tr td span.active:active:hover, +.datepicker table tr td span.active:hover:active:hover, +.datepicker table tr td span.active.disabled:active:hover, +.datepicker table tr td span.active.disabled:hover:active:hover, +.datepicker table tr td span.active.active:hover, +.datepicker table tr td span.active:hover.active:hover, +.datepicker table tr td span.active.disabled.active:hover, +.datepicker table tr td span.active.disabled:hover.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active:hover:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active:focus, +.datepicker table tr td span.active:hover:active:focus, +.datepicker table tr td span.active.disabled:active:focus, +.datepicker table tr td span.active.disabled:hover:active:focus, +.datepicker table tr td span.active.active:focus, +.datepicker table tr td span.active:hover.active:focus, +.datepicker table tr td span.active.disabled.active:focus, +.datepicker table tr td span.active.disabled:hover.active:focus, +.open > .dropdown-toggle.datepicker table tr td span.active:focus, +.open > .dropdown-toggle.datepicker table tr td span.active:hover:focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active:active.focus, +.datepicker table tr td span.active:hover:active.focus, +.datepicker table tr td span.active.disabled:active.focus, +.datepicker table tr td span.active.disabled:hover:active.focus, +.datepicker table tr td span.active.active.focus, +.datepicker table tr td span.active:hover.active.focus, +.datepicker table tr td span.active.disabled.active.focus, +.datepicker table tr td span.active.disabled:hover.active.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.focus, +.open > .dropdown-toggle.datepicker table tr td span.active:hover.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled.focus, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover.focus { + color: #ffffff; + background-color: #285e8e; + border-color: #193c5a; +} +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.open > .dropdown-toggle.datepicker table tr td span.active, +.open > .dropdown-toggle.datepicker table tr td span.active:hover, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled, +.open > .dropdown-toggle.datepicker table tr td span.active.disabled:hover { + background-image: none; +} +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active:hover.disabled:hover, +.datepicker table tr td span.active.disabled.disabled:hover, +.datepicker table tr td span.active.disabled:hover.disabled:hover, +.datepicker table tr td span.active[disabled]:hover, +.datepicker table tr td span.active:hover[disabled]:hover, +.datepicker table tr td span.active.disabled[disabled]:hover, +.datepicker table tr td span.active.disabled:hover[disabled]:hover, +fieldset[disabled] .datepicker table tr td span.active:hover, +fieldset[disabled] .datepicker table tr td span.active:hover:hover, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active.disabled:focus, +.datepicker table tr td span.active:hover.disabled:focus, +.datepicker table tr td span.active.disabled.disabled:focus, +.datepicker table tr td span.active.disabled:hover.disabled:focus, +.datepicker table tr td span.active[disabled]:focus, +.datepicker table tr td span.active:hover[disabled]:focus, +.datepicker table tr td span.active.disabled[disabled]:focus, +.datepicker table tr td span.active.disabled:hover[disabled]:focus, +fieldset[disabled] .datepicker table tr td span.active:focus, +fieldset[disabled] .datepicker table tr td span.active:hover:focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus, +.datepicker table tr td span.active.disabled.focus, +.datepicker table tr td span.active:hover.disabled.focus, +.datepicker table tr td span.active.disabled.disabled.focus, +.datepicker table tr td span.active.disabled:hover.disabled.focus, +.datepicker table tr td span.active[disabled].focus, +.datepicker table tr td span.active:hover[disabled].focus, +.datepicker table tr td span.active.disabled[disabled].focus, +.datepicker table tr td span.active.disabled:hover[disabled].focus, +fieldset[disabled] .datepicker table tr td span.active.focus, +fieldset[disabled] .datepicker table tr td span.active:hover.focus, +fieldset[disabled] .datepicker table tr td span.active.disabled.focus, +fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus { + background-color: #428bca; + border-color: #357ebd; +} +.datepicker table tr td span.old, +.datepicker table tr td span.new { + color: #999999; +} +.datepicker .datepicker-switch { + width: 145px; +} +.datepicker .datepicker-switch, +.datepicker .prev, +.datepicker .next, +.datepicker tfoot tr th { + cursor: pointer; +} +.datepicker .datepicker-switch:hover, +.datepicker .prev:hover, +.datepicker .next:hover, +.datepicker tfoot tr th:hover { + background: #eeeeee; +} +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.input-daterange { + width: 100%; +} +.input-daterange input { + text-align: center; +} +.input-daterange input:first-child { + border-radius: 3px 0 0 3px; +} +.input-daterange input:last-child { + border-radius: 0 3px 3px 0; +} +.input-daterange .input-group-addon { + width: auto; + min-width: 16px; + padding: 4px 5px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + text-shadow: 0 1px 0 #fff; + vertical-align: middle; + background-color: #eeeeee; + border: solid #cccccc; + border-width: 1px 0; + margin-left: -5px; + margin-right: -5px; +} +.datepicker.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + float: left; + display: none; + min-width: 160px; + list-style: none; + background-color: #ffffff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 5px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + *border-right-width: 2px; + *border-bottom-width: 2px; + color: #333333; + font-size: 13px; + line-height: 1.42857143; +} +.datepicker.dropdown-menu th, +.datepicker.datepicker-inline th, +.datepicker.dropdown-menu td, +.datepicker.datepicker-inline td { + padding: 0px 5px; +} diff --git a/static/bootstrap3/css/bootstrap-datepicker3.standalone.min.css b/static/bootstrap3/css/bootstrap-datepicker3.standalone.min.css new file mode 100755 index 0000000..7d1a122 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datepicker3.standalone.min.css @@ -0,0 +1,8 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ +.datepicker{border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker.datepicker-rtl{direction:rtl}.datepicker.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0;padding:4px}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(0,0,0,.15);border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid rgba(0,0,0,.15)}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker>div{display:none}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker table tr td,.datepicker table tr th{text-align:center;width:30px;height:30px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.old,.datepicker table tr td.new{color:#999}.datepicker table tr td.day:hover,.datepicker table tr td.focused{background:#eee;cursor:pointer}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td.highlighted{color:#000;background-color:#d9edf7;border-color:#85c5e5;border-radius:0}.datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted.focus{color:#000;background-color:#afd9ee;border-color:#298fc2}.datepicker table tr td.highlighted:hover{color:#000;background-color:#afd9ee;border-color:#52addb}.datepicker table tr td.highlighted:active,.datepicker table tr td.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.highlighted{color:#000;background-color:#afd9ee;border-color:#52addb}.datepicker table tr td.highlighted:active:hover,.datepicker table tr td.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.highlighted:hover,.datepicker table tr td.highlighted:active:focus,.datepicker table tr td.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted:active.focus,.datepicker table tr td.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.highlighted.focus{color:#000;background-color:#91cbe8;border-color:#298fc2}.datepicker table tr td.highlighted:active,.datepicker table tr td.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.highlighted{background-image:none}.datepicker table tr td.highlighted.disabled:hover,.datepicker table tr td.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.highlighted:hover,.datepicker table tr td.highlighted.disabled:focus,.datepicker table tr td.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.highlighted:focus,.datepicker table tr td.highlighted.disabled.focus,.datepicker table tr td.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.highlighted.focus{background-color:#d9edf7;border-color:#85c5e5}.datepicker table tr td.highlighted.focused{background:#afd9ee}.datepicker table tr td.highlighted.disabled,.datepicker table tr td.highlighted.disabled:active{background:#d9edf7;color:#999}.datepicker table tr td.today{color:#000;background-color:#ffdb99;border-color:#ffb733}.datepicker table tr td.today:focus,.datepicker table tr td.today.focus{color:#000;background-color:#ffc966;border-color:#b37400}.datepicker table tr td.today:hover{color:#000;background-color:#ffc966;border-color:#f59e00}.datepicker table tr td.today:active,.datepicker table tr td.today.active,.open>.dropdown-toggle.datepicker table tr td.today{color:#000;background-color:#ffc966;border-color:#f59e00}.datepicker table tr td.today:active:hover,.datepicker table tr td.today.active:hover,.open>.dropdown-toggle.datepicker table tr td.today:hover,.datepicker table tr td.today:active:focus,.datepicker table tr td.today.active:focus,.open>.dropdown-toggle.datepicker table tr td.today:focus,.datepicker table tr td.today:active.focus,.datepicker table tr td.today.active.focus,.open>.dropdown-toggle.datepicker table tr td.today.focus{color:#000;background-color:#ffbc42;border-color:#b37400}.datepicker table tr td.today:active,.datepicker table tr td.today.active,.open>.dropdown-toggle.datepicker table tr td.today{background-image:none}.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today[disabled]:hover,fieldset[disabled] .datepicker table tr td.today:hover,.datepicker table tr td.today.disabled:focus,.datepicker table tr td.today[disabled]:focus,fieldset[disabled] .datepicker table tr td.today:focus,.datepicker table tr td.today.disabled.focus,.datepicker table tr td.today[disabled].focus,fieldset[disabled] .datepicker table tr td.today.focus{background-color:#ffdb99;border-color:#ffb733}.datepicker table tr td.today.focused{background:#ffc966}.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:active{background:#ffdb99;color:#999}.datepicker table tr td.range{color:#000;background-color:#eee;border-color:#bbb;border-radius:0}.datepicker table tr td.range:focus,.datepicker table tr td.range.focus{color:#000;background-color:#d5d5d5;border-color:#7c7c7c}.datepicker table tr td.range:hover{color:#000;background-color:#d5d5d5;border-color:#9d9d9d}.datepicker table tr td.range:active,.datepicker table tr td.range.active,.open>.dropdown-toggle.datepicker table tr td.range{color:#000;background-color:#d5d5d5;border-color:#9d9d9d}.datepicker table tr td.range:active:hover,.datepicker table tr td.range.active:hover,.open>.dropdown-toggle.datepicker table tr td.range:hover,.datepicker table tr td.range:active:focus,.datepicker table tr td.range.active:focus,.open>.dropdown-toggle.datepicker table tr td.range:focus,.datepicker table tr td.range:active.focus,.datepicker table tr td.range.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.focus{color:#000;background-color:#c3c3c3;border-color:#7c7c7c}.datepicker table tr td.range:active,.datepicker table tr td.range.active,.open>.dropdown-toggle.datepicker table tr td.range{background-image:none}.datepicker table tr td.range.disabled:hover,.datepicker table tr td.range[disabled]:hover,fieldset[disabled] .datepicker table tr td.range:hover,.datepicker table tr td.range.disabled:focus,.datepicker table tr td.range[disabled]:focus,fieldset[disabled] .datepicker table tr td.range:focus,.datepicker table tr td.range.disabled.focus,.datepicker table tr td.range[disabled].focus,fieldset[disabled] .datepicker table tr td.range.focus{background-color:#eee;border-color:#bbb}.datepicker table tr td.range.focused{background:#d5d5d5}.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:active{background:#eee;color:#999}.datepicker table tr td.range.highlighted{color:#000;background-color:#e4eef3;border-color:#9dc1d3}.datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted.focus{color:#000;background-color:#c1d7e3;border-color:#4b88a6}.datepicker table tr td.range.highlighted:hover{color:#000;background-color:#c1d7e3;border-color:#73a6c0}.datepicker table tr td.range.highlighted:active,.datepicker table tr td.range.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.range.highlighted{color:#000;background-color:#c1d7e3;border-color:#73a6c0}.datepicker table tr td.range.highlighted:active:hover,.datepicker table tr td.range.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.range.highlighted:hover,.datepicker table tr td.range.highlighted:active:focus,.datepicker table tr td.range.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted:active.focus,.datepicker table tr td.range.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.highlighted.focus{color:#000;background-color:#a8c8d8;border-color:#4b88a6}.datepicker table tr td.range.highlighted:active,.datepicker table tr td.range.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.range.highlighted{background-image:none}.datepicker table tr td.range.highlighted.disabled:hover,.datepicker table tr td.range.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.highlighted:hover,.datepicker table tr td.range.highlighted.disabled:focus,.datepicker table tr td.range.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.range.highlighted:focus,.datepicker table tr td.range.highlighted.disabled.focus,.datepicker table tr td.range.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.range.highlighted.focus{background-color:#e4eef3;border-color:#9dc1d3}.datepicker table tr td.range.highlighted.focused{background:#c1d7e3}.datepicker table tr td.range.highlighted.disabled,.datepicker table tr td.range.highlighted.disabled:active{background:#e4eef3;color:#999}.datepicker table tr td.range.today{color:#000;background-color:#f7ca77;border-color:#f1a417}.datepicker table tr td.range.today:focus,.datepicker table tr td.range.today.focus{color:#000;background-color:#f4b747;border-color:#815608}.datepicker table tr td.range.today:hover{color:#000;background-color:#f4b747;border-color:#bf800c}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today.active,.open>.dropdown-toggle.datepicker table tr td.range.today{color:#000;background-color:#f4b747;border-color:#bf800c}.datepicker table tr td.range.today:active:hover,.datepicker table tr td.range.today.active:hover,.open>.dropdown-toggle.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today:active:focus,.datepicker table tr td.range.today.active:focus,.open>.dropdown-toggle.datepicker table tr td.range.today:focus,.datepicker table tr td.range.today:active.focus,.datepicker table tr td.range.today.active.focus,.open>.dropdown-toggle.datepicker table tr td.range.today.focus{color:#000;background-color:#f2aa25;border-color:#815608}.datepicker table tr td.range.today:active,.datepicker table tr td.range.today.active,.open>.dropdown-toggle.datepicker table tr td.range.today{background-image:none}.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today[disabled]:hover,fieldset[disabled] .datepicker table tr td.range.today:hover,.datepicker table tr td.range.today.disabled:focus,.datepicker table tr td.range.today[disabled]:focus,fieldset[disabled] .datepicker table tr td.range.today:focus,.datepicker table tr td.range.today.disabled.focus,.datepicker table tr td.range.today[disabled].focus,fieldset[disabled] .datepicker table tr td.range.today.focus{background-color:#f7ca77;border-color:#f1a417}.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:active{background:#f7ca77;color:#999}.datepicker table tr td.selected,.datepicker table tr td.selected.highlighted{color:#fff;background-color:#999;border-color:#555;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected:focus,.datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected.focus,.datepicker table tr td.selected.highlighted.focus{color:#fff;background-color:gray;border-color:#161616}.datepicker table tr td.selected:hover,.datepicker table tr td.selected.highlighted:hover{color:#fff;background-color:gray;border-color:#373737}.datepicker table tr td.selected:active,.datepicker table tr td.selected.highlighted:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.selected,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted{color:#fff;background-color:gray;border-color:#373737}.datepicker table tr td.selected:active:hover,.datepicker table tr td.selected.highlighted:active:hover,.datepicker table tr td.selected.active:hover,.datepicker table tr td.selected.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.selected:hover,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted:hover,.datepicker table tr td.selected:active:focus,.datepicker table tr td.selected.highlighted:active:focus,.datepicker table tr td.selected.active:focus,.datepicker table tr td.selected.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.selected:focus,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected:active.focus,.datepicker table tr td.selected.highlighted:active.focus,.datepicker table tr td.selected.active.focus,.datepicker table tr td.selected.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.selected.focus,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted.focus{color:#fff;background-color:#6e6e6e;border-color:#161616}.datepicker table tr td.selected:active,.datepicker table tr td.selected.highlighted:active,.datepicker table tr td.selected.active,.datepicker table tr td.selected.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.selected,.open>.dropdown-toggle.datepicker table tr td.selected.highlighted{background-image:none}.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.highlighted.disabled:hover,.datepicker table tr td.selected[disabled]:hover,.datepicker table tr td.selected.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.selected:hover,fieldset[disabled] .datepicker table tr td.selected.highlighted:hover,.datepicker table tr td.selected.disabled:focus,.datepicker table tr td.selected.highlighted.disabled:focus,.datepicker table tr td.selected[disabled]:focus,.datepicker table tr td.selected.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.selected:focus,fieldset[disabled] .datepicker table tr td.selected.highlighted:focus,.datepicker table tr td.selected.disabled.focus,.datepicker table tr td.selected.highlighted.disabled.focus,.datepicker table tr td.selected[disabled].focus,.datepicker table tr td.selected.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.selected.focus,fieldset[disabled] .datepicker table tr td.selected.highlighted.focus{background-color:#999;border-color:#555}.datepicker table tr td.active,.datepicker table tr td.active.highlighted{color:#fff;background-color:#428bca;border-color:#357ebd;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active:focus,.datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active.focus,.datepicker table tr td.active.highlighted.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.datepicker table tr td.active:hover,.datepicker table tr td.active.highlighted:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td.active:active,.datepicker table tr td.active.highlighted:active,.datepicker table tr td.active.active,.datepicker table tr td.active.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.active,.open>.dropdown-toggle.datepicker table tr td.active.highlighted{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td.active:active:hover,.datepicker table tr td.active.highlighted:active:hover,.datepicker table tr td.active.active:hover,.datepicker table tr td.active.highlighted.active:hover,.open>.dropdown-toggle.datepicker table tr td.active:hover,.open>.dropdown-toggle.datepicker table tr td.active.highlighted:hover,.datepicker table tr td.active:active:focus,.datepicker table tr td.active.highlighted:active:focus,.datepicker table tr td.active.active:focus,.datepicker table tr td.active.highlighted.active:focus,.open>.dropdown-toggle.datepicker table tr td.active:focus,.open>.dropdown-toggle.datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active:active.focus,.datepicker table tr td.active.highlighted:active.focus,.datepicker table tr td.active.active.focus,.datepicker table tr td.active.highlighted.active.focus,.open>.dropdown-toggle.datepicker table tr td.active.focus,.open>.dropdown-toggle.datepicker table tr td.active.highlighted.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.datepicker table tr td.active:active,.datepicker table tr td.active.highlighted:active,.datepicker table tr td.active.active,.datepicker table tr td.active.highlighted.active,.open>.dropdown-toggle.datepicker table tr td.active,.open>.dropdown-toggle.datepicker table tr td.active.highlighted{background-image:none}.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.highlighted.disabled:hover,.datepicker table tr td.active[disabled]:hover,.datepicker table tr td.active.highlighted[disabled]:hover,fieldset[disabled] .datepicker table tr td.active:hover,fieldset[disabled] .datepicker table tr td.active.highlighted:hover,.datepicker table tr td.active.disabled:focus,.datepicker table tr td.active.highlighted.disabled:focus,.datepicker table tr td.active[disabled]:focus,.datepicker table tr td.active.highlighted[disabled]:focus,fieldset[disabled] .datepicker table tr td.active:focus,fieldset[disabled] .datepicker table tr td.active.highlighted:focus,.datepicker table tr td.active.disabled.focus,.datepicker table tr td.active.highlighted.disabled.focus,.datepicker table tr td.active[disabled].focus,.datepicker table tr td.active.highlighted[disabled].focus,fieldset[disabled] .datepicker table tr td.active.focus,fieldset[disabled] .datepicker table tr td.active.highlighted.focus{background-color:#428bca;border-color:#357ebd}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;border-radius:4px}.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active:hover,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover{color:#fff;background-color:#428bca;border-color:#357ebd;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active:focus,.datepicker table tr td span.active:hover:focus,.datepicker table tr td span.active.disabled:focus,.datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active.focus,.datepicker table tr td span.active:hover.focus,.datepicker table tr td span.active.disabled.focus,.datepicker table tr td span.active.disabled:hover.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover:hover,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.open>.dropdown-toggle.datepicker table tr td span.active,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.datepicker table tr td span.active:active:hover,.datepicker table tr td span.active:hover:active:hover,.datepicker table tr td span.active.disabled:active:hover,.datepicker table tr td span.active.disabled:hover:active:hover,.datepicker table tr td span.active.active:hover,.datepicker table tr td span.active:hover.active:hover,.datepicker table tr td span.active.disabled.active:hover,.datepicker table tr td span.active.disabled:hover.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active:hover:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active:active:focus,.datepicker table tr td span.active:hover:active:focus,.datepicker table tr td span.active.disabled:active:focus,.datepicker table tr td span.active.disabled:hover:active:focus,.datepicker table tr td span.active.active:focus,.datepicker table tr td span.active:hover.active:focus,.datepicker table tr td span.active.disabled.active:focus,.datepicker table tr td span.active.disabled:hover.active:focus,.open>.dropdown-toggle.datepicker table tr td span.active:focus,.open>.dropdown-toggle.datepicker table tr td span.active:hover:focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active:active.focus,.datepicker table tr td span.active:hover:active.focus,.datepicker table tr td span.active.disabled:active.focus,.datepicker table tr td span.active.disabled:hover:active.focus,.datepicker table tr td span.active.active.focus,.datepicker table tr td span.active:hover.active.focus,.datepicker table tr td span.active.disabled.active.focus,.datepicker table tr td span.active.disabled:hover.active.focus,.open>.dropdown-toggle.datepicker table tr td span.active.focus,.open>.dropdown-toggle.datepicker table tr td span.active:hover.focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled.focus,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:hover.active,.open>.dropdown-toggle.datepicker table tr td span.active,.open>.dropdown-toggle.datepicker table tr td span.active:hover,.open>.dropdown-toggle.datepicker table tr td span.active.disabled,.open>.dropdown-toggle.datepicker table tr td span.active.disabled:hover{background-image:none}.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active:hover.disabled:hover,.datepicker table tr td span.active.disabled.disabled:hover,.datepicker table tr td span.active.disabled:hover.disabled:hover,.datepicker table tr td span.active[disabled]:hover,.datepicker table tr td span.active:hover[disabled]:hover,.datepicker table tr td span.active.disabled[disabled]:hover,.datepicker table tr td span.active.disabled:hover[disabled]:hover,fieldset[disabled] .datepicker table tr td span.active:hover,fieldset[disabled] .datepicker table tr td span.active:hover:hover,fieldset[disabled] .datepicker table tr td span.active.disabled:hover,fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active.disabled:focus,.datepicker table tr td span.active:hover.disabled:focus,.datepicker table tr td span.active.disabled.disabled:focus,.datepicker table tr td span.active.disabled:hover.disabled:focus,.datepicker table tr td span.active[disabled]:focus,.datepicker table tr td span.active:hover[disabled]:focus,.datepicker table tr td span.active.disabled[disabled]:focus,.datepicker table tr td span.active.disabled:hover[disabled]:focus,fieldset[disabled] .datepicker table tr td span.active:focus,fieldset[disabled] .datepicker table tr td span.active:hover:focus,fieldset[disabled] .datepicker table tr td span.active.disabled:focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,.datepicker table tr td span.active.disabled.focus,.datepicker table tr td span.active:hover.disabled.focus,.datepicker table tr td span.active.disabled.disabled.focus,.datepicker table tr td span.active.disabled:hover.disabled.focus,.datepicker table tr td span.active[disabled].focus,.datepicker table tr td span.active:hover[disabled].focus,.datepicker table tr td span.active.disabled[disabled].focus,.datepicker table tr td span.active.disabled:hover[disabled].focus,fieldset[disabled] .datepicker table tr td span.active.focus,fieldset[disabled] .datepicker table tr td span.active:hover.focus,fieldset[disabled] .datepicker table tr td span.active.disabled.focus,fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus{background-color:#428bca;border-color:#357ebd}.datepicker table tr td span.old,.datepicker table tr td span.new{color:#999}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .prev,.datepicker .next,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .prev:hover,.datepicker .next:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-group.date .input-group-addon{cursor:pointer}.input-daterange{width:100%}.input-daterange input{text-align:center}.input-daterange input:first-child{border-radius:3px 0 0 3px}.input-daterange input:last-child{border-radius:0 3px 3px 0}.input-daterange .input-group-addon{width:auto;min-width:16px;padding:4px 5px;font-weight:400;line-height:1.42857143;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:solid #ccc;border-width:1px 0;margin-left:-5px;margin-right:-5px}.datepicker.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;float:left;display:none;min-width:160px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;*border-right-width:2px;*border-bottom-width:2px;color:#333;font-size:13px;line-height:1.42857143}.datepicker.dropdown-menu th,.datepicker.datepicker-inline th,.datepicker.dropdown-menu td,.datepicker.datepicker-inline td{padding:0 5px} \ No newline at end of file diff --git a/static/bootstrap3/css/bootstrap-datetimepicker-standalone.css b/static/bootstrap3/css/bootstrap-datetimepicker-standalone.css new file mode 100755 index 0000000..49e3ea6 --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datetimepicker-standalone.css @@ -0,0 +1,103 @@ +/*! + * Datetimepicker for Bootstrap 3 + * version : 4.17.37 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */ +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} + +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.glyphicon-time:before { + content: "\e023"; +} + +.glyphicon-chevron-left:before { + content: "\e079"; +} + +.glyphicon-chevron-right:before { + content: "\e080"; +} + +.glyphicon-chevron-up:before { + content: "\e113"; +} + +.glyphicon-chevron-down:before { + content: "\e114"; +} + +.glyphicon-calendar:before { + content: "\e109"; +} + +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} + +.collapse { + display: none; +} + + .collapse.in { + display: block; + } + +.dropdown-menu { + position: absolute; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} \ No newline at end of file diff --git a/static/bootstrap3/css/bootstrap-datetimepicker.css b/static/bootstrap3/css/bootstrap-datetimepicker.css new file mode 100755 index 0000000..b8bf43b --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datetimepicker.css @@ -0,0 +1,373 @@ +/*! + * Datetimepicker for Bootstrap 3 + * version : 4.17.37 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */ +.bootstrap-datetimepicker-widget { + list-style: none; +} +.bootstrap-datetimepicker-widget.dropdown-menu { + margin: 2px 0; + padding: 4px; + width: 19em; +} +@media (min-width: 768px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 992px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +@media (min-width: 1200px) { + .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs { + width: 38em; + } +} +.bootstrap-datetimepicker-widget.dropdown-menu:before, +.bootstrap-datetimepicker-widget.dropdown-menu:after { + content: ''; + display: inline-block; + position: absolute; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #cccccc; + border-bottom-color: rgba(0, 0, 0, 0.2); + top: -7px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid white; + top: -6px; + left: 8px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:before { + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 7px solid #cccccc; + border-top-color: rgba(0, 0, 0, 0.2); + bottom: -7px; + left: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.top:after { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid white; + bottom: -6px; + left: 7px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before { + left: auto; + right: 6px; +} +.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after { + left: auto; + right: 7px; +} +.bootstrap-datetimepicker-widget .list-unstyled { + margin: 0; +} +.bootstrap-datetimepicker-widget a[data-action] { + padding: 6px 0; +} +.bootstrap-datetimepicker-widget a[data-action]:active { + box-shadow: none; +} +.bootstrap-datetimepicker-widget .timepicker-hour, +.bootstrap-datetimepicker-widget .timepicker-minute, +.bootstrap-datetimepicker-widget .timepicker-second { + width: 54px; + font-weight: bold; + font-size: 1.2em; + margin: 0; +} +.bootstrap-datetimepicker-widget button[data-action] { + padding: 6px; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Increment Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Decrement Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Hours"; +} +.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Show Minutes"; +} +.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle AM/PM"; +} +.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Clear the picker"; +} +.bootstrap-datetimepicker-widget .btn[data-action="today"]::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Set the date to today"; +} +.bootstrap-datetimepicker-widget .picker-switch { + text-align: center; +} +.bootstrap-datetimepicker-widget .picker-switch::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Toggle Date and Time Screens"; +} +.bootstrap-datetimepicker-widget .picker-switch td { + padding: 0; + margin: 0; + height: auto; + width: auto; + line-height: inherit; +} +.bootstrap-datetimepicker-widget .picker-switch td span { + line-height: 2.5; + height: 2.5em; + width: 100%; +} +.bootstrap-datetimepicker-widget table { + width: 100%; + margin: 0; +} +.bootstrap-datetimepicker-widget table td, +.bootstrap-datetimepicker-widget table th { + text-align: center; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table th { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table th.picker-switch { + width: 145px; +} +.bootstrap-datetimepicker-widget table th.disabled, +.bootstrap-datetimepicker-widget table th.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table th.prev::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Previous Month"; +} +.bootstrap-datetimepicker-widget table th.next::after { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + content: "Next Month"; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th { + cursor: pointer; +} +.bootstrap-datetimepicker-widget table thead tr:first-child th:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td { + height: 54px; + line-height: 54px; + width: 54px; +} +.bootstrap-datetimepicker-widget table td.cw { + font-size: .8em; + height: 20px; + line-height: 20px; + color: #777777; +} +.bootstrap-datetimepicker-widget table td.day { + height: 20px; + line-height: 20px; + width: 20px; +} +.bootstrap-datetimepicker-widget table td.day:hover, +.bootstrap-datetimepicker-widget table td.hour:hover, +.bootstrap-datetimepicker-widget table td.minute:hover, +.bootstrap-datetimepicker-widget table td.second:hover { + background: #eeeeee; + cursor: pointer; +} +.bootstrap-datetimepicker-widget table td.old, +.bootstrap-datetimepicker-widget table td.new { + color: #777777; +} +.bootstrap-datetimepicker-widget table td.today { + position: relative; +} +.bootstrap-datetimepicker-widget table td.today:before { + content: ''; + display: inline-block; + border: solid transparent; + border-width: 0 0 7px 7px; + border-bottom-color: #337ab7; + border-top-color: rgba(0, 0, 0, 0.2); + position: absolute; + bottom: 4px; + right: 4px; +} +.bootstrap-datetimepicker-widget table td.active, +.bootstrap-datetimepicker-widget table td.active:hover { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td.active.today:before { + border-bottom-color: #fff; +} +.bootstrap-datetimepicker-widget table td.disabled, +.bootstrap-datetimepicker-widget table td.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget table td span { + display: inline-block; + width: 54px; + height: 54px; + line-height: 54px; + margin: 2px 1.5px; + cursor: pointer; + border-radius: 4px; +} +.bootstrap-datetimepicker-widget table td span:hover { + background: #eeeeee; +} +.bootstrap-datetimepicker-widget table td span.active { + background-color: #337ab7; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.bootstrap-datetimepicker-widget table td span.old { + color: #777777; +} +.bootstrap-datetimepicker-widget table td span.disabled, +.bootstrap-datetimepicker-widget table td span.disabled:hover { + background: none; + color: #777777; + cursor: not-allowed; +} +.bootstrap-datetimepicker-widget.usetwentyfour td.hour { + height: 27px; + line-height: 27px; +} +.bootstrap-datetimepicker-widget.wider { + width: 21em; +} +.bootstrap-datetimepicker-widget .datepicker-decades .decade { + line-height: 1.8em !important; +} +.input-group.date .input-group-addon { + cursor: pointer; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} diff --git a/static/bootstrap3/css/bootstrap-datetimepicker.min.css b/static/bootstrap3/css/bootstrap-datetimepicker.min.css new file mode 100755 index 0000000..63c2a3a --- /dev/null +++ b/static/bootstrap3/css/bootstrap-datetimepicker.min.css @@ -0,0 +1,5 @@ +/*! + * Datetimepicker for Bootstrap 3 + * version : 4.17.37 + * https://github.com/Eonasdan/bootstrap-datetimepicker/ + */.bootstrap-datetimepicker-widget{list-style:none}.bootstrap-datetimepicker-widget.dropdown-menu{margin:2px 0;padding:4px;width:19em}@media (min-width:768px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:992px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:1200px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}.bootstrap-datetimepicker-widget.dropdown-menu:before,.bootstrap-datetimepicker-widget.dropdown-menu:after{content:'';display:inline-block;position:absolute}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before{border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);top:-7px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid white;top:-6px;left:8px}.bootstrap-datetimepicker-widget.dropdown-menu.top:before{border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.dropdown-menu.top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid white;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget .list-unstyled{margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget a[data-action]:active{box-shadow:none}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:bold;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Increment Hours"}.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Increment Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Decrement Hours"}.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Decrement Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Show Hours"}.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Show Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Toggle AM/PM"}.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Clear the picker"}.bootstrap-datetimepicker-widget .btn[data-action="today"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Set the date to today"}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget .picker-switch::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Toggle Date and Time Screens"}.bootstrap-datetimepicker-widget .picker-switch td{padding:0;margin:0;height:auto;width:auto;line-height:inherit}.bootstrap-datetimepicker-widget .picker-switch td span{line-height:2.5;height:2.5em;width:100%}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget table td,.bootstrap-datetimepicker-widget table th{text-align:center;border-radius:4px}.bootstrap-datetimepicker-widget table th{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table th.picker-switch{width:145px}.bootstrap-datetimepicker-widget table th.disabled,.bootstrap-datetimepicker-widget table th.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table th.prev::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Previous Month"}.bootstrap-datetimepicker-widget table th.next::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Next Month"}.bootstrap-datetimepicker-widget table thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget table thead tr:first-child th:hover{background:#eee}.bootstrap-datetimepicker-widget table td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget table td.cw{font-size:.8em;height:20px;line-height:20px;color:#777}.bootstrap-datetimepicker-widget table td.day{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table td.day:hover,.bootstrap-datetimepicker-widget table td.hour:hover,.bootstrap-datetimepicker-widget table td.minute:hover,.bootstrap-datetimepicker-widget table td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget table td.old,.bootstrap-datetimepicker-widget table td.new{color:#777}.bootstrap-datetimepicker-widget table td.today{position:relative}.bootstrap-datetimepicker-widget table td.today:before{content:'';display:inline-block;border:solid transparent;border-width:0 0 7px 7px;border-bottom-color:#337ab7;border-top-color:rgba(0,0,0,0.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget table td.disabled,.bootstrap-datetimepicker-widget table td.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table td span{display:inline-block;width:54px;height:54px;line-height:54px;margin:2px 1.5px;cursor:pointer;border-radius:4px}.bootstrap-datetimepicker-widget table td span:hover{background:#eee}.bootstrap-datetimepicker-widget table td span.active{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td span.old{color:#777}.bootstrap-datetimepicker-widget table td span.disabled,.bootstrap-datetimepicker-widget table td span.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget.usetwentyfour td.hour{height:27px;line-height:27px}.bootstrap-datetimepicker-widget.wider{width:21em}.bootstrap-datetimepicker-widget .datepicker-decades .decade{line-height:1.8em !important}.input-group.date .input-group-addon{cursor:pointer}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0} \ No newline at end of file diff --git a/static/bootstrap3/js/bootstrap-datepicker.js b/static/bootstrap3/js/bootstrap-datepicker.js new file mode 100755 index 0000000..80fafe4 --- /dev/null +++ b/static/bootstrap3/js/bootstrap-datepicker.js @@ -0,0 +1,1908 @@ +/*! + * Datepicker for Bootstrap v1.5.0-dev (https://github.com/eternicode/bootstrap-datepicker) + * + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */(function(factory){ + if (typeof define === "function" && define.amd) { + define(["jquery"], factory); + } else if (typeof exports === 'object') { + factory(require('jquery')); + } else { + factory(jQuery); + } +}(function($, undefined){ + + function UTCDate(){ + return new Date(Date.UTC.apply(Date, arguments)); + } + function UTCToday(){ + var today = new Date(); + return UTCDate(today.getFullYear(), today.getMonth(), today.getDate()); + } + function isUTCEquals(date1, date2) { + return ( + date1.getUTCFullYear() === date2.getUTCFullYear() && + date1.getUTCMonth() === date2.getUTCMonth() && + date1.getUTCDate() === date2.getUTCDate() + ); + } + function alias(method){ + return function(){ + return this[method].apply(this, arguments); + }; + } + function isValidDate(d) { + return d && !isNaN(d.getTime()); + } + + var DateArray = (function(){ + var extras = { + get: function(i){ + return this.slice(i)[0]; + }, + contains: function(d){ + // Array.indexOf is not cross-browser; + // $.inArray doesn't work with Dates + var val = d && d.valueOf(); + for (var i=0, l=this.length; i < l; i++) + if (this[i].valueOf() === val) + return i; + return -1; + }, + remove: function(i){ + this.splice(i,1); + }, + replace: function(new_array){ + if (!new_array) + return; + if (!$.isArray(new_array)) + new_array = [new_array]; + this.clear(); + this.push.apply(this, new_array); + }, + clear: function(){ + this.length = 0; + }, + copy: function(){ + var a = new DateArray(); + a.replace(this); + return a; + } + }; + + return function(){ + var a = []; + a.push.apply(a, arguments); + $.extend(a, extras); + return a; + }; + })(); + + + // Picker object + + var Datepicker = function(element, options){ + this._process_options(options); + + this.dates = new DateArray(); + this.viewDate = this.o.defaultViewDate; + this.focusDate = null; + + this.element = $(element); + this.isInline = false; + this.isInput = this.element.is('input'); + this.component = this.element.hasClass('date') ? this.element.find('.add-on, .input-group-addon, .btn') : false; + this.hasInput = this.component && this.element.find('input').length; + if (this.component && this.component.length === 0) + this.component = false; + + this.picker = $(DPGlobal.template); + this._buildEvents(); + this._attachEvents(); + + if (this.isInline){ + this.picker.addClass('datepicker-inline').appendTo(this.element); + } + else { + this.picker.addClass('datepicker-dropdown dropdown-menu'); + } + + if (this.o.rtl){ + this.picker.addClass('datepicker-rtl'); + } + + this.viewMode = this.o.startView; + + if (this.o.calendarWeeks) + this.picker.find('tfoot .today, tfoot .clear') + .attr('colspan', function(i, val){ + return parseInt(val) + 1; + }); + + this._allow_update = false; + + this.setStartDate(this._o.startDate); + this.setEndDate(this._o.endDate); + this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); + this.setDaysOfWeekHighlighted(this.o.daysOfWeekHighlighted); + this.setDatesDisabled(this.o.datesDisabled); + + this.fillDow(); + this.fillMonths(); + + this._allow_update = true; + + this.update(); + this.showMode(); + + if (this.isInline){ + this.show(); + } + }; + + Datepicker.prototype = { + constructor: Datepicker, + + _process_options: function(opts){ + // Store raw options for reference + this._o = $.extend({}, this._o, opts); + // Processed options + var o = this.o = $.extend({}, this._o); + + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + var lang = o.language; + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + lang = defaults.language; + } + o.language = lang; + + switch (o.startView){ + case 2: + case 'decade': + o.startView = 2; + break; + case 1: + case 'year': + o.startView = 1; + break; + default: + o.startView = 0; + } + + switch (o.minViewMode){ + case 1: + case 'months': + o.minViewMode = 1; + break; + case 2: + case 'years': + o.minViewMode = 2; + break; + default: + o.minViewMode = 0; + } + + switch (o.maxViewMode) { + case 0: + case 'days': + o.maxViewMode = 0; + break; + case 1: + case 'months': + o.maxViewMode = 1; + break; + default: + o.maxViewMode = 2; + } + + o.startView = Math.min(o.startView, o.maxViewMode); + o.startView = Math.max(o.startView, o.minViewMode); + + // true, false, or Number > 0 + if (o.multidate !== true){ + o.multidate = Number(o.multidate) || false; + if (o.multidate !== false) + o.multidate = Math.max(0, o.multidate); + } + o.multidateSeparator = String(o.multidateSeparator); + + o.weekStart %= 7; + o.weekEnd = ((o.weekStart + 6) % 7); + + var format = DPGlobal.parseFormat(o.format); + if (o.startDate !== -Infinity){ + if (!!o.startDate){ + if (o.startDate instanceof Date) + o.startDate = this._local_to_utc(this._zero_time(o.startDate)); + else + o.startDate = DPGlobal.parseDate(o.startDate, format, o.language); + } + else { + o.startDate = -Infinity; + } + } + if (o.endDate !== Infinity){ + if (!!o.endDate){ + if (o.endDate instanceof Date) + o.endDate = this._local_to_utc(this._zero_time(o.endDate)); + else + o.endDate = DPGlobal.parseDate(o.endDate, format, o.language); + } + else { + o.endDate = Infinity; + } + } + + o.daysOfWeekDisabled = o.daysOfWeekDisabled||[]; + if (!$.isArray(o.daysOfWeekDisabled)) + o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/); + o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function(d){ + return parseInt(d, 10); + }); + + o.daysOfWeekHighlighted = o.daysOfWeekHighlighted||[]; + if (!$.isArray(o.daysOfWeekHighlighted)) + o.daysOfWeekHighlighted = o.daysOfWeekHighlighted.split(/[,\s]*/); + o.daysOfWeekHighlighted = $.map(o.daysOfWeekHighlighted, function(d){ + return parseInt(d, 10); + }); + + o.datesDisabled = o.datesDisabled||[]; + if (!$.isArray(o.datesDisabled)) { + var datesDisabled = []; + datesDisabled.push(DPGlobal.parseDate(o.datesDisabled, format, o.language)); + o.datesDisabled = datesDisabled; + } + o.datesDisabled = $.map(o.datesDisabled,function(d){ + return DPGlobal.parseDate(d, format, o.language); + }); + + var plc = String(o.orientation).toLowerCase().split(/\s+/g), + _plc = o.orientation.toLowerCase(); + plc = $.grep(plc, function(word){ + return /^auto|left|right|top|bottom$/.test(word); + }); + o.orientation = {x: 'auto', y: 'auto'}; + if (!_plc || _plc === 'auto') + ; // no action + else if (plc.length === 1){ + switch (plc[0]){ + case 'top': + case 'bottom': + o.orientation.y = plc[0]; + break; + case 'left': + case 'right': + o.orientation.x = plc[0]; + break; + } + } + else { + _plc = $.grep(plc, function(word){ + return /^left|right$/.test(word); + }); + o.orientation.x = _plc[0] || 'auto'; + + _plc = $.grep(plc, function(word){ + return /^top|bottom$/.test(word); + }); + o.orientation.y = _plc[0] || 'auto'; + } + if (o.defaultViewDate) { + var year = o.defaultViewDate.year || new Date().getFullYear(); + var month = o.defaultViewDate.month || 0; + var day = o.defaultViewDate.day || 1; + o.defaultViewDate = UTCDate(year, month, day); + } else { + o.defaultViewDate = UTCToday(); + } + o.showOnFocus = o.showOnFocus !== undefined ? o.showOnFocus : true; + o.zIndexOffset = o.zIndexOffset !== undefined ? o.zIndexOffset : 10; + }, + _events: [], + _secondaryEvents: [], + _applyEvents: function(evs){ + for (var i=0, el, ch, ev; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.on(ev, ch); + } + }, + _unapplyEvents: function(evs){ + for (var i=0, el, ev, ch; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.off(ev, ch); + } + }, + _buildEvents: function(){ + var events = { + keyup: $.proxy(function(e){ + if ($.inArray(e.keyCode, [27, 37, 39, 38, 40, 32, 13, 9]) === -1) + this.update(); + }, this), + keydown: $.proxy(this.keydown, this), + paste: $.proxy(this.paste, this) + }; + + if (this.o.showOnFocus === true) { + events.focus = $.proxy(this.show, this); + } + + if (this.isInput) { // single input + this._events = [ + [this.element, events] + ]; + } + else if (this.component && this.hasInput) { // component: input + button + this._events = [ + // For components that are not readonly, allow keyboard nav + [this.element.find('input'), events], + [this.component, { + click: $.proxy(this.show, this) + }] + ]; + } + else if (this.element.is('div')){ // inline datepicker + this.isInline = true; + } + else { + this._events = [ + [this.element, { + click: $.proxy(this.show, this) + }] + ]; + } + this._events.push( + // Component: listen for blur on element descendants + [this.element, '*', { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }], + // Input: listen for blur on element + [this.element, { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }] + ); + + if (this.o.immediateUpdates) { + // Trigger input updates immediately on changed year/month + this._events.push([this.element, { + 'changeYear changeMonth': $.proxy(function(e){ + this.update(e.date); + }, this) + }]); + } + + this._secondaryEvents = [ + [this.picker, { + click: $.proxy(this.click, this) + }], + [$(window), { + resize: $.proxy(this.place, this) + }], + [$(document), { + mousedown: $.proxy(function(e){ + // Clicked outside the datepicker, hide it + if (!( + this.element.is(e.target) || + this.element.find(e.target).length || + this.picker.is(e.target) || + this.picker.find(e.target).length || + this.picker.hasClass('datepicker-inline') + )){ + this.hide(); + } + }, this) + }] + ]; + }, + _attachEvents: function(){ + this._detachEvents(); + this._applyEvents(this._events); + }, + _detachEvents: function(){ + this._unapplyEvents(this._events); + }, + _attachSecondaryEvents: function(){ + this._detachSecondaryEvents(); + this._applyEvents(this._secondaryEvents); + }, + _detachSecondaryEvents: function(){ + this._unapplyEvents(this._secondaryEvents); + }, + _trigger: function(event, altdate){ + var date = altdate || this.dates.get(-1), + local_date = this._utc_to_local(date); + + this.element.trigger({ + type: event, + date: local_date, + dates: $.map(this.dates, this._utc_to_local), + format: $.proxy(function(ix, format){ + if (arguments.length === 0){ + ix = this.dates.length - 1; + format = this.o.format; + } + else if (typeof ix === 'string'){ + format = ix; + ix = this.dates.length - 1; + } + format = format || this.o.format; + var date = this.dates.get(ix); + return DPGlobal.formatDate(date, format, this.o.language); + }, this) + }); + }, + + show: function(){ + if (this.element.attr('readonly') && this.o.enableOnReadonly === false) + return; + if (!this.isInline) + this.picker.appendTo(this.o.container); + this.place(); + this.picker.show(); + this._attachSecondaryEvents(); + this._trigger('show'); + if ((window.navigator.msMaxTouchPoints || 'ontouchstart' in document) && this.o.disableTouchKeyboard) { + $(this.element).blur(); + } + return this; + }, + + hide: function(){ + if (this.isInline) + return this; + if (!this.picker.is(':visible')) + return this; + this.focusDate = null; + this.picker.hide().detach(); + this._detachSecondaryEvents(); + this.viewMode = this.o.startView; + this.showMode(); + + if ( + this.o.forceParse && + ( + this.isInput && this.element.val() || + this.hasInput && this.element.find('input').val() + ) + ) + this.setValue(); + this._trigger('hide'); + return this; + }, + + remove: function(){ + this.hide(); + this._detachEvents(); + this._detachSecondaryEvents(); + this.picker.remove(); + delete this.element.data().datepicker; + if (!this.isInput){ + delete this.element.data().date; + } + return this; + }, + + paste: function(evt){ + var dateString; + if (evt.originalEvent.clipboardData && evt.originalEvent.clipboardData.types + && $.inArray('text/plain', evt.originalEvent.clipboardData.types) !== -1) { + dateString = evt.originalEvent.clipboardData.getData('text/plain'); + } + else if (window.clipboardData) { + dateString = window.clipboardData.getData('Text'); + } + else { + return; + } + this.setDate(dateString); + this.update(); + evt.preventDefault(); + }, + + _utc_to_local: function(utc){ + return utc && new Date(utc.getTime() + (utc.getTimezoneOffset()*60000)); + }, + _local_to_utc: function(local){ + return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000)); + }, + _zero_time: function(local){ + return local && new Date(local.getFullYear(), local.getMonth(), local.getDate()); + }, + _zero_utc_time: function(utc){ + return utc && new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate())); + }, + + getDates: function(){ + return $.map(this.dates, this._utc_to_local); + }, + + getUTCDates: function(){ + return $.map(this.dates, function(d){ + return new Date(d); + }); + }, + + getDate: function(){ + return this._utc_to_local(this.getUTCDate()); + }, + + getUTCDate: function(){ + var selected_date = this.dates.get(-1); + if (typeof selected_date !== 'undefined') { + return new Date(selected_date); + } else { + return null; + } + }, + + clearDates: function(){ + var element; + if (this.isInput) { + element = this.element; + } else if (this.component) { + element = this.element.find('input'); + } + + if (element) { + element.val(''); + } + + this.update(); + this._trigger('changeDate'); + + if (this.o.autoclose) { + this.hide(); + } + }, + setDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, args); + this._trigger('changeDate'); + this.setValue(); + return this; + }, + + setUTCDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, $.map(args, this._utc_to_local)); + this._trigger('changeDate'); + this.setValue(); + return this; + }, + + setDate: alias('setDates'), + setUTCDate: alias('setUTCDates'), + + setValue: function(){ + var formatted = this.getFormattedDate(); + if (!this.isInput){ + if (this.component){ + this.element.find('input').val(formatted); + } + } + else { + this.element.val(formatted); + } + return this; + }, + + getFormattedDate: function(format){ + if (format === undefined) + format = this.o.format; + + var lang = this.o.language; + return $.map(this.dates, function(d){ + return DPGlobal.formatDate(d, format, lang); + }).join(this.o.multidateSeparator); + }, + + setStartDate: function(startDate){ + this._process_options({startDate: startDate}); + this.update(); + this.updateNavArrows(); + return this; + }, + + setEndDate: function(endDate){ + this._process_options({endDate: endDate}); + this.update(); + this.updateNavArrows(); + return this; + }, + + setDaysOfWeekDisabled: function(daysOfWeekDisabled){ + this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); + this.update(); + this.updateNavArrows(); + return this; + }, + + setDaysOfWeekHighlighted: function(daysOfWeekHighlighted){ + this._process_options({daysOfWeekHighlighted: daysOfWeekHighlighted}); + this.update(); + return this; + }, + + setDatesDisabled: function(datesDisabled){ + this._process_options({datesDisabled: datesDisabled}); + this.update(); + this.updateNavArrows(); + }, + + place: function(){ + if (this.isInline) + return this; + var calendarWidth = this.picker.outerWidth(), + calendarHeight = this.picker.outerHeight(), + visualPadding = 10, + container = $(this.o.container), + windowWidth = container.width(), + scrollTop = container.scrollTop(), + appendOffset = container.offset(); + + var parentsZindex = []; + this.element.parents().each(function(){ + var itemZIndex = $(this).css('z-index'); + if (itemZIndex !== 'auto' && itemZIndex !== 0) parentsZindex.push(parseInt(itemZIndex)); + }); + var zIndex = Math.max.apply(Math, parentsZindex) + this.o.zIndexOffset; + var offset = this.component ? this.component.parent().offset() : this.element.offset(); + var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false); + var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false); + var left = offset.left - appendOffset.left, + top = offset.top - appendOffset.top; + + this.picker.removeClass( + 'datepicker-orient-top datepicker-orient-bottom '+ + 'datepicker-orient-right datepicker-orient-left' + ); + + if (this.o.orientation.x !== 'auto'){ + this.picker.addClass('datepicker-orient-' + this.o.orientation.x); + if (this.o.orientation.x === 'right') + left -= calendarWidth - width; + } + // auto x orientation is best-placement: if it crosses a window + // edge, fudge it sideways + else { + if (offset.left < 0) { + // component is outside the window on the left side. Move it into visible range + this.picker.addClass('datepicker-orient-left'); + left -= offset.left - visualPadding; + } else if (left + calendarWidth > windowWidth) { + // the calendar passes the widow right edge. Align it to component right side + this.picker.addClass('datepicker-orient-right'); + left = offset.left + width - calendarWidth; + } else { + // Default to left + this.picker.addClass('datepicker-orient-left'); + } + } + + // auto y orientation is best-situation: top or bottom, no fudging, + // decision based on which shows more of the calendar + var yorient = this.o.orientation.y, + top_overflow; + if (yorient === 'auto'){ + top_overflow = -scrollTop + top - calendarHeight; + yorient = top_overflow < 0 ? 'bottom' : 'top'; + } + + this.picker.addClass('datepicker-orient-' + yorient); + if (yorient === 'top') + top -= calendarHeight + parseInt(this.picker.css('padding-top')); + else + top += height; + + if (this.o.rtl) { + var right = windowWidth - (left + width); + this.picker.css({ + top: top, + right: right, + zIndex: zIndex + }); + } else { + this.picker.css({ + top: top, + left: left, + zIndex: zIndex + }); + } + return this; + }, + + _allow_update: true, + update: function(){ + if (!this._allow_update) + return this; + + var oldDates = this.dates.copy(), + dates = [], + fromArgs = false; + if (arguments.length){ + $.each(arguments, $.proxy(function(i, date){ + if (date instanceof Date) + date = this._local_to_utc(date); + dates.push(date); + }, this)); + fromArgs = true; + } + else { + dates = this.isInput + ? this.element.val() + : this.element.data('date') || this.element.find('input').val(); + if (dates && this.o.multidate) + dates = dates.split(this.o.multidateSeparator); + else + dates = [dates]; + delete this.element.data().date; + } + + dates = $.map(dates, $.proxy(function(date){ + return DPGlobal.parseDate(date, this.o.format, this.o.language); + }, this)); + dates = $.grep(dates, $.proxy(function(date){ + return ( + date < this.o.startDate || + date > this.o.endDate || + !date + ); + }, this), true); + this.dates.replace(dates); + + if (this.dates.length) + this.viewDate = new Date(this.dates.get(-1)); + else if (this.viewDate < this.o.startDate) + this.viewDate = new Date(this.o.startDate); + else if (this.viewDate > this.o.endDate) + this.viewDate = new Date(this.o.endDate); + else + this.viewDate = this.o.defaultViewDate; + + if (fromArgs){ + // setting date by clicking + this.setValue(); + } + else if (dates.length){ + // setting date by typing + if (String(oldDates) !== String(this.dates)) + this._trigger('changeDate'); + } + if (!this.dates.length && oldDates.length) + this._trigger('clearDate'); + + this.fill(); + this.element.change(); + return this; + }, + + fillDow: function(){ + var dowCnt = this.o.weekStart, + html = '