Update django to 1.11.14
Update django to 1.11.14 LTS and related packages to latest
versions.
For the new version, the following changes had to be done:
- timezone.UTC() was replaced by timezone.utc
- rest_framework.filters.FilterSet was replaced by
django_filters.rest_framework.FilterSet
(http://www.django-rest-framework.org/topics/3.6-announcement/#djangofilterbackend)
- rest_framework.filters.DjangoFilterBackend was replaced by
django_filters.rest_framework.DjangoFilterBackend
(http://www.django-rest-framework.org/topics/3.6-announcement/#djangofilterbackend)
- django_filters.rest_framework.DateFilter which is now used
has a 'field_name' parameter instead of a 'name' parameter
which was used in django_filters.DateFilter
- ModelSerializers need an explicit call for fields = '__all__'
(http://www.django-rest-framework.org/api-guide/serializers/#specifying-which-fields-to-include)
Further, the affected code has been cleaned up and documented to
match the expectations of the linters.
Issue: HIC-164
Change-Id: Icf10d211860f7f055a63b9b8e680832753a0e149
diff --git a/crashreport_stats/rest_endpoints.py b/crashreport_stats/rest_endpoints.py
index 6378d01..7da1a5f 100644
--- a/crashreport_stats/rest_endpoints.py
+++ b/crashreport_stats/rest_endpoints.py
@@ -1,29 +1,58 @@
+"""REST API for accessing the crashreports statistics."""
+import zipfile
+
from rest_framework import generics
from rest_framework import serializers
+from rest_framework.exceptions import NotFound
from rest_framework.response import Response
from rest_framework.views import APIView
-from crashreports.permissions import (
- HasRightsOrIsDeviceOwnerDeviceCreation, HasStatsAccess)
+
+from django.core.exceptions import ObjectDoesNotExist
from django.db import connection
-from . import raw_querys
-from crashreport_stats.models import *
-import zipfile
-from crashreports.models import *
from django.db.models.expressions import F
-import django_filters
-from rest_framework import filters
+
+from django_filters.rest_framework import (
+ DjangoFilterBackend, DateFilter,
+ FilterSet, CharFilter, BooleanFilter
+)
+
+from crashreport_stats.models import (
+ Version, VersionDaily, RadioVersion, RadioVersionDaily
+)
+from crashreports.models import (
+ Device, Crashreport, HeartBeat, LogFile
+)
+from crashreports.permissions import (
+ HasRightsOrIsDeviceOwnerDeviceCreation, HasStatsAccess
+)
+from . import raw_querys
+
def dictfetchall(cursor):
- "Returns all rows from a cursor as a dict"
+ """Return all rows from a cursor as a dict."""
desc = cursor.description
return [
dict(zip([col[0] for col in desc], row))
for row in cursor.fetchall()
]
+
class DeviceUpdateHistory(APIView):
+ """View the update history of a specific device."""
+
permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
+
def get(self, request, uuid, format=None):
+ """Get the update history of a device.
+
+ Args:
+ request: Http request
+ uuid: The UUID of the device
+ format: Optional response format parameter
+
+ Returns: The update history of the requested device.
+
+ """
cursor = connection.cursor()
raw_querys.execute_device_update_history_query(
cursor,
@@ -33,9 +62,23 @@
res = dictfetchall(cursor)
return Response(res)
+
class DeviceReportHistory(APIView):
+ """View the report history of a specific device."""
+
permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
+
def get(self, request, uuid, format=None):
+ """Get the report history of a device.
+
+ Args:
+ request: Http request
+ uuid: The UUID of the device
+ format: Optional response format parameter
+
+ Returns: The report history of the requested device.
+
+ """
cursor = connection.cursor()
raw_querys.execute_device_report_history(
cursor,
@@ -45,117 +88,186 @@
res = dictfetchall(cursor)
return Response(res)
+
class Status(APIView):
+ """View the number of devices, crashreports and heartbeats."""
+
permission_classes = (HasStatsAccess,)
+
def get(self, request, format=None, ):
+ """Get the number of devices, crashreports and heartbeats.
+
+ Args:
+ request: Http request
+ format: Optional response format parameter
+
+ Returns: The number of devices, crashreports and heartbeats.
+
+ """
num_devices = Device.objects.count()
num_crashreports = Crashreport.objects.count()
num_heartbeats = HeartBeat.objects.count()
- return Response( {
- 'devices': num_devices,
- 'crashreports': num_crashreports,
- 'heartbeats': num_heartbeats
+ return Response({
+ 'devices': num_devices,
+ 'crashreports': num_crashreports,
+ 'heartbeats': num_heartbeats
})
+
class DeviceStat(APIView):
+ """View an overview of the statistics of a device."""
+
permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
+
def get(self, request, uuid, format=None, ):
- device = Device.objects.filter(uuid=uuid)
- last_active = HeartBeat.objects.filter(device=device).order_by('-date')[0].date
- heartbeats = HeartBeat.objects.filter(device=device).count()
- crashreports = Crashreport.objects.filter(device=device).filter(boot_reason__in=Crashreport.CRASH_BOOT_REASONS).count()
+ """Get some general statistics for a device.
+
+ Args:
+ request: Http request
+ uuid: The UUID of the device
+ format: Optional response format parameter
+
+ Returns: Some general information of the device in a dictionary.
+
+ """
+ device = Device.objects.filter(uuid=uuid)
+ last_active = HeartBeat.objects.filter(device=device).order_by(
+ '-date')[0].date
+ heartbeats = HeartBeat.objects.filter(device=device).count()
+ crashreports = Crashreport.objects.filter(device=device).filter(
+ boot_reason__in=Crashreport.CRASH_BOOT_REASONS).count()
crashes_per_day = crashreports*1.0/heartbeats if heartbeats > 0 else 0
- smpls = Crashreport.objects.filter(device=device).filter(boot_reason__in=Crashreport.SMPL_BOOT_REASONS).count()
- smpl_per_day = smpls*1.0/heartbeats if heartbeats > 0 else 0
+ smpls = Crashreport.objects.filter(device=device).filter(
+ boot_reason__in=Crashreport.SMPL_BOOT_REASONS).count()
+ smpl_per_day = smpls*1.0/heartbeats if heartbeats > 0 else 0
return Response(
{
- 'uuid' : uuid,
- 'last_active' : last_active,
- 'heartbeats' : heartbeats,
- 'crashreports' : crashreports,
- 'crashes_per_day' : crashes_per_day,
- 'smpls' : smpls,
- 'smpl_per_day' : smpl_per_day,
- 'board_date' : device[0].board_date,
+ 'uuid': uuid,
+ 'last_active': last_active,
+ 'heartbeats': heartbeats,
+ 'crashreports': crashreports,
+ 'crashes_per_day': crashes_per_day,
+ 'smpls': smpls,
+ 'smpl_per_day': smpl_per_day,
+ 'board_date': device[0].board_date,
})
+
class LogFileDownload(APIView):
+ """View for downloading log files."""
+
permission_classes = (HasRightsOrIsDeviceOwnerDeviceCreation, )
- def get(self, request, id, format=None):
+
+ def get(self, request, id_logfile, format=None):
+ """Get a logfile.
+
+ Args:
+ request: Http request
+ id_logfile: The id of the log file
+ format: Optional response format parameter
+
+ Returns: The log file with the corresponding id.
+
+ """
try:
- logfile = LogFile.objects.get(id=id)
- except:
+ logfile = LogFile.objects.get(id=id_logfile)
+ except ObjectDoesNotExist:
raise NotFound(detail="Logfile does not exist.")
- zf = zipfile.ZipFile(logfile.logfile.path)
+ zip_file = zipfile.ZipFile(logfile.logfile.path)
ret = {}
- for f in zf.filelist:
- fo = zf.open(f)
- ret[f.filename] = fo.read()
+ for file in zip_file.filelist:
+ file_open = zip_file.open(file)
+ ret[file.filename] = file_open.read()
return Response(ret)
-class _VersionStatsFilter(filters.FilterSet):
- first_seen_before = django_filters.DateFilter(name="first_seen_on", lookup_expr='lte')
- first_seen_after = django_filters.DateFilter(name="first_seen_on", lookup_expr='gte')
- released_before = django_filters.DateFilter(name="released_on", lookup_expr='lte')
- released_after = django_filters.DateFilter(name="released_on", lookup_expr='gte')
+class _VersionStatsFilter(FilterSet):
+ first_seen_before = DateFilter(field_name="first_seen_on",
+ lookup_expr='lte')
+ first_seen_after = DateFilter(field_name="first_seen_on",
+ lookup_expr='gte')
+ released_before = DateFilter(field_name="released_on", lookup_expr='lte')
+ released_after = DateFilter(field_name="released_on", lookup_expr='gte')
+
class _VersionStatsSerializer(serializers.ModelSerializer):
permission_classes = (HasStatsAccess,)
+
class _VersionStatsListView(generics.ListAPIView):
permission_classes = (HasStatsAccess,)
- filter_backends = (filters.DjangoFilterBackend,)
+ filter_backends = (DjangoFilterBackend,)
-class _DailyVersionStatsFilter(filters.FilterSet):
- date_start = django_filters.DateFilter(name="date", lookup_expr='gte')
- date_end = django_filters.DateFilter(name="date", lookup_expr='lte')
+
+class _DailyVersionStatsFilter(FilterSet):
+ date_start = DateFilter(field_name="date", lookup_expr='gte')
+ date_end = DateFilter(field_name="date", lookup_expr='lte')
+
class _DailyVersionStatsSerializer(serializers.ModelSerializer):
permission_classes = (HasStatsAccess,)
+
class _DailyVersionStatsListView(generics.ListAPIView):
permission_classes = (HasStatsAccess,)
- filter_backends = (filters.DjangoFilterBackend,)
+ filter_backends = (DjangoFilterBackend,)
class VersionSerializer(_VersionStatsSerializer):
- class Meta:
+ """Serializer for the Version class."""
+
+ class Meta: # noqa: D106
model = Version
fields = '__all__'
+
class VersionFilter(_VersionStatsFilter):
- class Meta:
+ """Filter for Version instances."""
+
+ class Meta: # noqa: D106
model = Version
fields = '__all__'
+
class VersionListView(_VersionStatsListView):
+ """View for listing versions."""
+
queryset = Version.objects.all().order_by('-heartbeats')
filter_class = VersionFilter
serializer_class = VersionSerializer
-class VersionDailyFilter(_DailyVersionStatsFilter):
- version__build_fingerprint = django_filters.CharFilter()
- version__is_official_release = django_filters.BooleanFilter()
- version__is_beta_release = django_filters.BooleanFilter()
- class Meta:
+class VersionDailyFilter(_DailyVersionStatsFilter):
+ """Filter for VersionDaily instances."""
+
+ version__build_fingerprint = CharFilter()
+ version__is_official_release = BooleanFilter()
+ version__is_beta_release = BooleanFilter()
+
+ class Meta: # noqa: D106
model = VersionDaily
fields = '__all__'
+
class VersionDailySerializer(_DailyVersionStatsSerializer):
+ """Serializer for VersionDaily instances."""
+
build_fingerprint = serializers.CharField()
- class Meta:
+ class Meta: # noqa: D106
model = VersionDaily
fields = '__all__'
+
class VersionDailyListView(_DailyVersionStatsListView):
+ """View for listing VersionDaily instances."""
+
queryset = (
- VersionDaily.objects
- .annotate(build_fingerprint=F('version__build_fingerprint'))
- .all()
- .order_by('date')
+ VersionDaily
+ .objects
+ .annotate(build_fingerprint=F('version__build_fingerprint'))
+ .all()
+ .order_by('date')
)
filter_class = VersionDailyFilter
filter_fields = (
@@ -167,42 +279,59 @@
class RadioVersionSerializer(_VersionStatsSerializer):
- class Meta:
+ """Serializer for RadioVersion instances."""
+
+ class Meta: # noqa: D106
model = RadioVersion
fields = '__all__'
+
class RadioVersionFilter(_VersionStatsFilter):
- class Meta:
+ """Filter for RadioVersion instances."""
+
+ class Meta: # noqa: D106
model = RadioVersion
fields = '__all__'
+
class RadioVersionListView(_VersionStatsListView):
+ """View for listing RadioVersion instances."""
+
queryset = RadioVersion.objects.all().order_by('-heartbeats')
serializer_class = RadioVersionSerializer
filter_class = RadioVersionFilter
-class RadioVersionDailyFilter(_DailyVersionStatsFilter):
- version__radio_version = django_filters.CharFilter()
- version__is_official_release = django_filters.BooleanFilter()
- version__is_beta_release = django_filters.BooleanFilter()
- class Meta:
+class RadioVersionDailyFilter(_DailyVersionStatsFilter):
+ """Filter for RadioVersionDaily instances."""
+
+ version__radio_version = CharFilter()
+ version__is_official_release = BooleanFilter()
+ version__is_beta_release = BooleanFilter()
+
+ class Meta: # noqa: D106
model = RadioVersionDaily
fields = '__all__'
+
class RadioVersionDailySerializer(_DailyVersionStatsSerializer):
+ """Serializer for RadioVersionDaily instances."""
+
radio_version = serializers.CharField()
- class Meta:
+ class Meta: # noqa: D106
model = RadioVersionDaily
fields = '__all__'
+
class RadioVersionDailyListView(_DailyVersionStatsListView):
+ """View for listing RadioVersionDaily instances."""
+
queryset = (
- RadioVersionDaily.objects
- .annotate(radio_version=F('version__radio_version'))
- .all()
- .order_by('date')
+ RadioVersionDaily.objects.annotate(
+ radio_version=F('version__radio_version'))
+ .all()
+ .order_by('date')
)
filter_class = RadioVersionDailyFilter
filter_fields = (
diff --git a/crashreports/serializers.py b/crashreports/serializers.py
index 4d88163..732e595 100644
--- a/crashreports/serializers.py
+++ b/crashreports/serializers.py
@@ -1,19 +1,25 @@
+"""Serializers for Crashreport-related models."""
from django.utils import timezone
+from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers
from rest_framework.exceptions import NotFound
-from crashreports.models import Crashreport
-from crashreports.models import Device
-from crashreports.models import HeartBeat
-from crashreports.models import LogFile
-
from rest_framework import permissions
+
+from crashreports.models import (
+ Crashreport, Device, HeartBeat, LogFile
+)
from crashreports.permissions import user_is_hiccup_staff
class PrivateField(serializers.ReadOnlyField):
+ """Class for making a field private.
+
+ The private attribute can then only be read by Hiccup staff members
+ """
def get_attribute(self, instance):
- """
+ """Get the private attribute.
+
Given the *outgoing* object instance, return the primitive value
that should be used for this field.
"""
@@ -23,6 +29,8 @@
class CrashReportSerializer(serializers.ModelSerializer):
+ """Serializer for CrashReport instances."""
+
permission_classes = (permissions.AllowAny,)
logfiles = serializers.HyperlinkedRelatedField(
read_only=True,
@@ -32,16 +40,24 @@
uuid = serializers.CharField(max_length=64)
id = PrivateField()
device_local_id = serializers.IntegerField(required=False)
- date = serializers.DateTimeField(default_timezone=timezone.UTC())
+ date = serializers.DateTimeField(default_timezone=timezone.utc)
- class Meta:
+ class Meta: # noqa: D106
model = Crashreport
exclude = ('device',)
def create(self, validated_data):
+ """Create a crashreport.
+
+ Args:
+ validated_data: Data of the crashreport, excluding the device
+
+ Returns: The created report
+
+ """
try:
device = Device.objects.get(uuid=validated_data['uuid'])
- except:
+ except ObjectDoesNotExist:
raise NotFound(detail="uuid does not exist")
validated_data.pop('uuid', None)
report = Crashreport(**validated_data)
@@ -51,20 +67,30 @@
class HeartBeatSerializer(serializers.ModelSerializer):
+ """Serializer for HeartBeat instances."""
+
permission_classes = (permissions.IsAuthenticated,)
uuid = serializers.CharField(max_length=64)
id = PrivateField()
device_local_id = serializers.IntegerField(required=False)
- date = serializers.DateTimeField(default_timezone=timezone.UTC())
+ date = serializers.DateTimeField(default_timezone=timezone.utc)
- class Meta:
+ class Meta: # noqa: D106
model = HeartBeat
exclude = ('device',)
def create(self, validated_data):
+ """Create a heartbeat report.
+
+ Args:
+ validated_data: Data of the heartbeat, excluding the device
+
+ Returns: The created heartbeat
+
+ """
try:
device = Device.objects.get(uuid=validated_data['uuid'])
- except:
+ except ObjectDoesNotExist:
raise NotFound(detail="uuid does not exist")
validated_data.pop('uuid', None)
heartbeat = HeartBeat(**validated_data)
@@ -74,24 +100,31 @@
class LogFileSerializer(serializers.ModelSerializer):
+ """Serializer for LogFile instances."""
+
permission_classes = (permissions.IsAdminUser,)
- class Meta:
+ class Meta: # noqa: D106
model = LogFile
+ fields = '__all__'
class DeviceSerializer(serializers.ModelSerializer):
- permission_classes = (permissions.IsAdminUser,)
- board_date = serializers.DateTimeField(default_timezone=timezone.UTC())
- last_heartbeat = serializers.DateTimeField(default_timezone=timezone.UTC())
+ """Serializer for Device instances."""
- class Meta:
+ permission_classes = (permissions.IsAdminUser,)
+ board_date = serializers.DateTimeField(default_timezone=timezone.utc)
+ last_heartbeat = serializers.DateTimeField(default_timezone=timezone.utc)
+
+ class Meta: # noqa: D106
model = Device
+ fields = '__all__'
class DeviceCreateSerializer(DeviceSerializer):
+ """Serializer for creating Device instances."""
- class Meta:
+ class Meta: # noqa: D106
model = Device
fields = ('board_date', 'chipset')
extra_kwargs = {
diff --git a/hiccup/settings.py b/hiccup/settings.py
index 171a3e1..42b15bb 100644
--- a/hiccup/settings.py
+++ b/hiccup/settings.py
@@ -47,6 +47,7 @@
'bootstrap3',
'bootstrapform',
'django_extensions',
+ 'django_filters',
'djfrontend',
'djfrontend.skeleton',
'allauth',
@@ -71,13 +72,12 @@
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
-# 'django.template.loaders.eggs.Loader',
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(BASE_DIR,'hiccup', 'templates')],
+ 'DIRS': [os.path.join(BASE_DIR, 'hiccup', 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -100,7 +100,7 @@
'allauth.account.auth_backends.AuthenticationBackend',
)
-#WSGI_APPLICATION = 'hiccup_server.wsgi.application'
+# WSGI_APPLICATION = 'hiccup_server.wsgi.application'
# Database
@@ -119,16 +119,20 @@
AUTH_PASSWORD_VALIDATORS = [
{
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ 'NAME': 'django.contrib.auth.password_validation'
+ '.UserAttributeSimilarityValidator',
},
{
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ 'NAME': 'django.contrib.auth.password_validation'
+ '.MinimumLengthValidator',
},
{
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ 'NAME': 'django.contrib.auth.password_validation'
+ '.CommonPasswordValidator',
},
{
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ 'NAME': 'django.contrib.auth.password_validation'
+ '.NumericPasswordValidator',
},
]
@@ -150,19 +154,21 @@
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
- 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
- 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
+ 'DEFAULT_FILTER_BACKENDS':
+ ('django_filters.rest_framework.DjangoFilterBackend',),
+ 'DEFAULT_PAGINATION_CLASS':
+ 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 100
}
SITE_ID = 1
-SOCIALACCOUNT_ADAPTER ="hiccup.allauth_adapters.FairphoneAccountAdapter"
-LOGIN_REDIRECT_URL="/hiccup_stats/"
+SOCIALACCOUNT_ADAPTER = "hiccup.allauth_adapters.FairphoneAccountAdapter"
+LOGIN_REDIRECT_URL = "/hiccup_stats/"
# disable form signups
-ACCOUNT_ADAPTER ="hiccup.allauth_adapters.FormAccountAdapter"
-ACCOUNT_EMAIL_REQUIRED=True
-ACCOUNT_LOGOUT_REDIRECT_URL="/accounts/login/"
+ACCOUNT_ADAPTER = "hiccup.allauth_adapters.FormAccountAdapter"
+ACCOUNT_EMAIL_REQUIRED = True
+ACCOUNT_LOGOUT_REDIRECT_URL = "/accounts/login/"
SOCIALACCOUNT_PROVIDERS = {
'google': {
@@ -207,6 +213,6 @@
}
try:
- from local_settings import *
+ from local_settings import * # noqa: F403, F401
except ImportError as e:
pass
diff --git a/requirements.txt b/requirements.txt
index ec856ae..fb9d75c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,40 +1,40 @@
cffi==1.8.3
-coreapi==2.0.8
+coreapi==2.3.3
decorator==4.0.10
defusedxml==0.5.0
-Django==1.10.2
-django-allauth==0.32.0
+Django==1.11.14
+django-allauth==0.36.0
django-appconf==1.0.2
-django-bootstrap-form==3.2.1
+django-bootstrap-form==3.4
django-bootstrap-forms==0.1
-django-bootstrap3==7.1.0
-django-coreapi==0.5.0
-django-crispy-forms==1.6.0
-django-extensions==1.7.4
-django-filter==0.14.0
+django-bootstrap3==10.0.1
+django-coreapi==2.3.0
+django-crispy-forms==1.7.2
+django-extensions==2.1.0
+django-filter==2.0.0
django-frontend==1.8.0
django-frontend-skeleton==3.0.0
django-jwt-auth==0.0.2
-django-ratelimit==1.0.0
-django-taggit==0.21.3
-django-user-accounts==1.3.1
-djangorestframework==3.4.6
+django-ratelimit==1.1.0
+django-taggit==0.22.2
+django-user-accounts==2.0.3
+djangorestframework==3.8.2
entrypoints==0.2.2
enum34==1.1.6
-idna==2.1
+idna==2.6
ipykernel==4.5.0
ipython==5.1.0
ipython-genutils==0.1.0
ipywidgets==5.2.2
itypes==1.1.0
-Jinja2==2.8
+Jinja2==2.10
jsonschema==2.5.1
jupyter==1.0.0
jupyter-client==4.4.0
jupyter-console==5.1.0
jupyter-core==4.2.0
Markdown==2.6.8
-MarkupSafe==0.23
+MarkupSafe==1.0
mistune==0.7.3
nbconvert==4.2.0
nbformat==4.1.0
@@ -50,13 +50,13 @@
PyJWT==1.4.2
python-dateutil==2.6.0
python3-openid==3.1.0
-pytz==2016.6.1
+pytz==2018.5
pyzmq==16.0.0
qtconsole==4.3.0
-requests==2.11.1
+requests==2.18.4
requests-oauthlib==0.8.0
simplegeneric==0.8.1
-simplejson==3.10.0
+simplejson==3.13.2
six==1.10.0
terminado==0.6
tornado==4.4.2