Fix infoscreen pep8 and add docstrings

This commit is contained in:
henu
2017-09-20 20:08:26 +03:00
parent f738e76726
commit ecc0ac965e
7 changed files with 177 additions and 52 deletions
+2
View File
@@ -1,3 +1,5 @@
"""Admin site registers."""
from django.contrib import admin
from infoscreen.models import Rotation, InfoItem, InfoInstance
from infoscreen.models import ImageInfoItem, ExternalImageInfoItem, ABBInfoItem
+4
View File
@@ -1,5 +1,9 @@
"""Django apps configuration file."""
from django.apps import AppConfig
class InfoscreenConfig(AppConfig):
"""Infoscreen app configuration."""
name = 'infoscreen'
+26 -10
View File
@@ -1,3 +1,5 @@
"""File containing Infoscreen HSL data fetcher classes."""
import urllib.request
import json
import logging
@@ -9,36 +11,49 @@ from infoscreen.models import HSLDataModel
class HSLFetcher:
"""Main class of Infoscreen HSL fetcher."""
last_fetched = datetime.fromtimestamp(0) # epoch
INTERVAL = 1 # minutes
logging.info("Set up scheduled HSL API fetch every {} minutes".format(INTERVAL))
logging.info(
"Set up scheduled HSL API fetch every {} minutes".format(INTERVAL))
def fetch_if_needed(self):
if datetime.now() - HSLFetcher.last_fetched > timedelta(minutes=HSLFetcher.INTERVAL):
"""Check if new fetch from HSL API is needed."""
if (datetime.now() - HSLFetcher.last_fetched >
timedelta(minutes=HSLFetcher.INTERVAL)):
self.fetch()
def fetch(self):
"""Fetch data from HSL API."""
location_coords = (2545565, 6675319)
src = urllib.request.urlopen(
"https://api.reittiopas.fi/hsl/prod/?userhash={}&request=stops_area&center_coordinate={},{}"
.format(settings.HSL_USERHASH, location_coords[0], location_coords[1]))\
("https://api.reittiopas.fi/hsl/prod/?userhash={}"
"&request=stops_area&center_coordinate={},{}")
.format(settings.HSL_USERHASH, location_coords[0],
location_coords[1]))\
.read().decode("utf-8")
data = json.loads(src)
arr = []
time = datetime.now() + timedelta(minutes=settings.HSL_DEPARTURE_THRESHOLD)
time = (datetime.now() +
timedelta(minutes=settings.HSL_DEPARTURE_THRESHOLD))
time = "{0:02d}{0:02d}".format(time.hour, time.minute)
for element in data:
src = urllib.request.urlopen(
"https://api.reittiopas.fi/hsl/prod/?userhash={}&request=stop&code={}&dep_limit=20&time={}"
.format(settings.HSL_USERHASH, element['code'], time)).read().decode("utf-8")
("https://api.reittiopas.fi/hsl/prod/?userhash={}"
"&request=stop&code={}&dep_limit=20&time={}")
.format(settings.HSL_USERHASH, element['code'], time)
).read().decode("utf-8")
parsed = json.loads(src)[0]
arr.append({"name": parsed['name_fi'], "lines": parsed['lines'],
"dist": element['dist'], "departures": parsed['departures']})
arr.append({
"name": parsed['name_fi'],
"lines": parsed['lines'],
"dist": element['dist'],
"departures": parsed['departures']})
model_arr = HSLDataModel.objects.all()
count = len(model_arr)
@@ -53,4 +68,5 @@ class HSLFetcher:
now = datetime.now()
HSLFetcher.last_fetched = now
logging.info("Fetched HSL timetable data with size {} bytes.".format(len(src)))
logging.info(
"Fetched HSL timetable data with size {} bytes.".format(len(src)))
+96 -16
View File
@@ -1,3 +1,5 @@
"""File containing Infoscreen models."""
from datetime import datetime
from django.db import models
@@ -9,31 +11,40 @@ from django.utils.translation import ugettext as _
class InfoItem(models.Model):
"""Abstract model representing single Infoscreen item."""
class __meta__:
abstract = True
name = models.CharField(max_length=255)
expire_date = models.DateTimeField(blank=True, null=True) # None means never expiring item
# expire_date = None means never expiring item
expire_date = models.DateTimeField(blank=True, null=True)
display_name = "Default item"
def get_template_url(self):
raise NotImplementedError("inheriting classes must implement get_template_url")
"""Get infoscreen template url."""
raise NotImplementedError(
"inheriting classes must implement get_template_url")
@staticmethod
def get_create_template_url():
raise NotImplementedError("inheriting classes must implement get_create_template_url")
"""Get create infoscreen template url command."""
raise NotImplementedError(
"inheriting classes must implement get_create_template_url")
@classmethod
def create_from_dict(cls, d):
"""Convert given dict to model."""
item = cls()
item.update_from_dict(d)
return item
def update_from_dict(self, d):
"""Update model based on given dict."""
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
self.expire_date = datetime.strptime(
expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
@@ -48,6 +59,7 @@ class InfoItem(models.Model):
self.save()
def get_dict(self):
"""Convert django model to dict and return it."""
return {
'id': self.id,
'name': self.name,
@@ -59,65 +71,86 @@ class InfoItem(models.Model):
}
def delete(self):
# since generic foreign keys suck, delete info items pointing here manually
InfoInstance.objects.filter(item_id=self.id, item_type=ContentType.objects.get_for_model(self)).delete()
"""Delete infoinstance object."""
# since generic foreign keys suck, delete info
# items pointing here manually
InfoInstance.objects.filter(
item_id=self.id,
item_type=ContentType.objects.get_for_model(self)).delete()
super().delete()
@classmethod
def get_subclasses(cls):
"""Get item subclasses."""
for subclass in cls.__subclasses__():
yield from subclass.get_subclasses()
yield subclass
def __str__(self):
"""Return class name."""
return self.name
class ABBInfoItem(InfoItem):
"""Class for ABB Infoscreen item."""
display_name = _("ABB jobs")
def get_template_url(self):
"""Return ABB infoitem template url."""
return "/static/html/abb.html"
@staticmethod
def get_create_template_url():
"""Call create ABB infoitem template url command."""
return "/static/html/abb_create.html"
class ApyInfoItem(InfoItem):
"""Class for APY Infoscreen item."""
display_name = _("APY Item")
def get_template_url(self):
"""Return APY infoitem template url."""
return "/static/html/apy.html"
@staticmethod
def get_create_template_url():
"""Call create APY infoitem template url command."""
return "/static/html/apy_create.html"
class ExternalWebsiteInfoItem(InfoItem):
"""Class for external website info item."""
display_name = _("External website")
url = models.URLField()
def get_template_url(self):
"""Return external website infoitem template url."""
return "/static/html/external_website.html?url={}".format(self.name)
@staticmethod
def get_create_template_url():
"""Call create external website infoitem template url command."""
return "/static/html/external_website_create.html"
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'url': self.url}
return d
@classmethod
def create_from_dict(cls, d):
"""Convert given dict to model."""
item = cls()
item.update_from_dict(d)
return item
def get_list(self):
"""Return list containing infoitem data."""
return {
'id': self.id,
'name': self.name,
@@ -125,9 +158,11 @@ class ExternalWebsiteInfoItem(InfoItem):
}
def update_from_dict(self, d):
"""Update model based on given dict."""
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
self.expire_date = datetime.strptime(
expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
@@ -144,99 +179,130 @@ class ExternalWebsiteInfoItem(InfoItem):
class SossoInfoItem(InfoItem):
"""Class for Sosso Infoscreen item."""
display_name = _("Sössö articles")
def get_template_url(self):
"""Return Sosso infoitem template url."""
return "/static/html/sosso.html"
@staticmethod
def get_create_template_url():
"""Call create Sosso infoitem template url command."""
return "/static/html/sosso_create.html"
class EventInfoItem(InfoItem):
"""Class for Event Infoscreen item."""
display_name = _("Events")
def get_template_url(self):
"""Return Event infoitem template url."""
return "/static/html/events.html"
@staticmethod
def get_create_template_url():
"""Call create Event infoitem template url command."""
return "/static/html/events_create.html"
class ImageInfoItem(InfoItem):
"""Class for Image Infoscreen item."""
display_name = _("Image")
img = models.ImageField(upload_to="infoimages/")
def get_template_url(self):
# get param to avoid angular from optimizing same template with different options
"""Return Image infoitem template url."""
# get param to avoid angular from optimizing same template
# with different options
return "/static/html/generic_image.html?img={}".format(self.name)
@staticmethod
def get_create_template_url():
"""Call create Image infoitem template url command."""
return "/static/html/generic_image_create.html"
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'img': self.img.url}
return d
class VideoInfoItem(InfoItem):
"""Class for Video Infoscreen item."""
display_name = ("Video")
video = models.FileField(upload_to="infovideos/")
def get_template_url(self):
"""Return Video infoitem template url."""
return "/static/html/generic_video.html?video={}".format(self.name)
@staticmethod
def get_create_template_url():
"""Call create Video infoitem template url command."""
return "/static/html/generic_video_create.html"
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'video': self.video.url}
return d
class HslInfoItem(InfoItem):
"""Class for HSL Infoscreen item."""
display_name = _("HSL timetables")
def get_template_url(self):
"""Return HSL infoitem template url."""
return "/static/html/hsl.html"
@staticmethod
def get_create_template_url():
"""Call create HSL infoitem template url command."""
return "/static/html/hsl_create.html"
class ExternalImageInfoItem(InfoItem):
"""Class for External Image Infoscreen item."""
display_name = _("External image")
url = models.URLField()
def get_template_url(self):
"""Return External Image infoitem template url."""
return "/static/html/generic_image.html?img={}".format(self.name)
@staticmethod
def get_create_template_url():
"""Call create External Image infoitem template url command."""
return "/static/html/generic_external_image_create.html"
def get_dict(self):
"""Convert django model to dict and return it."""
d = super().get_dict()
d["options"] = {'img': self.url}
return d
@classmethod
def create_from_dict(cls, d):
"""Convert given dict to model."""
item = cls()
item.update_from_dict(d)
return item
def update_from_dict(self, d):
"""Update model based on given dict."""
try:
expire_date = d.pop('expire_date', None)
self.expire_date = datetime.strptime(expire_date, "%Y-%m-%d %H:%M:%S")
self.expire_date = datetime.strptime(
expire_date, "%Y-%m-%d %H:%M:%S")
except:
pass
@@ -253,6 +319,8 @@ class ExternalImageInfoItem(InfoItem):
class InfoInstance(models.Model):
"""Class for Info instance in Infoscreen."""
rotation = models.ForeignKey('Rotation', related_name='instances')
duration = models.FloatField(default=15.0) # seconds
# generic relation to some kind of InfoItem
@@ -262,6 +330,7 @@ class InfoInstance(models.Model):
@classmethod
def create_from_dict(cls, d):
"""Convert given dict to model."""
try:
rotation = Rotation.objects.get(pk=int(d["rotation_id"]))
ct = ContentType.objects.get_for_id(int(d["item_type"]))
@@ -279,6 +348,7 @@ class InfoInstance(models.Model):
raise RuntimeError("error while adding instance to db")
def get_dict(self):
"""Convert django model to dict and return it."""
return {
'id': self.id,
'item': self.item.get_dict(),
@@ -286,17 +356,24 @@ class InfoInstance(models.Model):
}
def __str__(self):
return "{}: {} ({}s)".format(self.rotation.name, self.item.name, self.duration)
"""Return model name."""
return "{}: {} ({}s)".format(
self.rotation.name, self.item.name, self.duration)
class Rotation(models.Model):
"""Class for rotation model."""
name = models.CharField(max_length=255)
def get_dict(self):
# exclude expired items from rotation (note: using tricky syntax to avoid excluding items with no expire_date)
"""Convert django model to dict and return it."""
# exclude expired items from rotation (note: using tricky syntax
# to avoid excluding items with no expire_date)
now = timezone.now()
instances = self.instances.all()
filtered = filter(lambda i: (i.item.expire_date or now) >= now, list(instances))
filtered = filter(lambda i: (i.item.expire_date or now)
>= now, list(instances))
instance_list = list(map(lambda i: i.get_dict(), filtered))
return {
@@ -306,29 +383,32 @@ class Rotation(models.Model):
}
def get_list(self):
"""Return list containing infoitem data."""
return {
'id': self.id,
'name': self.name,
}
def __str__(self):
"""Return model name."""
return self.name
class ImageUploadForm(forms.Form):
'''
Form used to handle imageuploads to
infoscreen app
'''
"""Form used to handle imageuploads to infoscreen app."""
name = forms.CharField()
image = forms.ImageField()
class UploadFileForm(forms.Form):
"""Form used for uploading file."""
name = forms.CharField()
video = forms.FileField()
class HSLDataModel(models.Model):
"""Model representing HSL data."""
data = models.TextField(default="", editable=False)
+13 -17
View File
@@ -1,41 +1,37 @@
"""File containing Infoscreen tests."""
from django.test import TestCase
from infoscreen.models import Rotation
from infoscreen.models import SossoInfoItem
from django.http import HttpRequest, HttpResponse
from django.http import HttpRequest
import infoscreen.views
class InfoscreenTestCase(TestCase):
'''
Test cases for testing infoscreen methods
'''
"""Test cases for testing infoscreen methods."""
def setUp(self):
'''
Create some dummy models
'''
"""Create some dummy models."""
Rotation.objects.create(name="test_rot")
SossoInfoItem.objects.create()
def test_rotation_created(self):
'''
Check if the dummy model actually exists
'''
"""Check if the dummy model actually exists."""
rot = Rotation.objects.get(name="test_rot")
self.assertIsNotNone(rot)
def test_sosso_infoitem_created(self):
'''
Check if the dummy model actually exists
'''
"""Check if the dummy model actually exists."""
item = SossoInfoItem.objects.get()
self.assertIsNotNone(item)
def test_get_infoitems(self):
'''
Check if infoItems returns a response with non-zero content length
That would mean that something meaningful has been included in the response
'''
"""
Check if infoItems returns a response with non-zero content length.
That would mean that something meaningful has been included
in the response.
"""
req = HttpRequest()
resp = infoscreen.views.info_items(req)
content = resp.content.decode('utf-8')
+2
View File
@@ -1,3 +1,5 @@
"""File containing infoscreen urls."""
from django.conf.urls import url
from infoscreen.views import index
+34 -9
View File
@@ -1,3 +1,5 @@
"""File containing infoscreen views."""
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import ensure_csrf_cookie
@@ -13,7 +15,8 @@ import threading
import requests
from infoscreen.models import Rotation, InfoItem, InfoInstance
from infoscreen.models import ABBInfoItem, ExternalImageInfoItem, ImageInfoItem, SossoInfoItem, HslInfoItem
from infoscreen.models import (ABBInfoItem, ExternalImageInfoItem,
ImageInfoItem, SossoInfoItem, HslInfoItem)
from infoscreen.models import EventInfoItem
from infoscreen.models import ExternalWebsiteInfoItem
from infoscreen.models import ImageUploadForm
@@ -24,15 +27,18 @@ from infoscreen.hsl_fetcher import HSLFetcher
def index(request, idx, *args, **kwargs):
"""Render infoscreen index page."""
return render(request, 'infoscreen_index.html', {'rotation': idx})
@permission_required('infoscreen.change_infoinstance', login_url='/login')
def admin(request, *args, **kwargs):
"""Render infoscreen admin page."""
return render(request, 'infoscreen_admin.html', {})
def default(request, *args, **kwargs):
"""Try getting first rotation item."""
try:
first = Rotation.objects.all()[0].id
except:
@@ -41,11 +47,14 @@ def default(request, *args, **kwargs):
def get_apy_json(request):
return HttpResponse(requests.get("https://api-diilikone.apy.fi/deals/top-groups").text)
"""Render APY diilikone page."""
return HttpResponse(
requests.get("https://api-diilikone.apy.fi/deals/top-groups").text)
@require_http_methods(["GET"])
def rotation(request, idx, *args, **kwargs):
"""Get rotation."""
try:
rotation = Rotation.objects.get(pk=idx)
except Rotation.DoesNotExist:
@@ -57,6 +66,7 @@ def rotation(request, idx, *args, **kwargs):
def create_item_generator(model):
"""Create Infoscreen item generator."""
@ensure_csrf_cookie
@require_http_methods(["POST"])
@permission_required('infoscreen.change_infoinstance', login_url='/login')
@@ -64,16 +74,19 @@ def create_item_generator(model):
try:
data = json.loads(request.body.decode("utf-8"))
except ValueError:
return HttpResponseBadRequest('{"status":"failure","error":"invalid json supplied"}')
return HttpResponseBadRequest(
'{"status":"failure","error":"invalid json supplied"}')
try:
model.create_from_dict(data)
return HttpResponse('{"status":"success"}')
except RuntimeError as e:
return HttpResponseBadRequest(json.dumps({"status": "failure", "error": str(e)}))
return HttpResponseBadRequest(
json.dumps({"status": "failure", "error": str(e)}))
return create_item
def delete_item_generator(model):
"""Delete Infoscreen item generator."""
@ensure_csrf_cookie
@require_http_methods(["DELETE"])
@permission_required('infoscreen.change_infoinstance', login_url='/login')
@@ -100,6 +113,7 @@ def delete_item_generator(model):
@permission_required('infoscreen.change_infoinstance', login_url='/login')
@require_http_methods(["DELETE"])
def delete_info_item(request, *args, **kwargs):
"""Delete info item."""
type_id = kwargs.pop("type_id", 0)
idx = kwargs.pop("idx", 0)
if True:
@@ -120,12 +134,14 @@ def delete_info_item(request, *args, **kwargs):
@require_http_methods(["GET"])
def rotations(request, *args, **kwargs):
"""Return rotation lists."""
rotations = list(map(lambda r: r.get_list(), Rotation.objects.all()))
return HttpResponse(json.dumps(rotations))
@require_http_methods(["GET"])
def info_types(request, *args, **kwargs):
"""Return info item types."""
types = []
classes = InfoItem.get_subclasses()
for c in classes:
@@ -137,6 +153,7 @@ def info_types(request, *args, **kwargs):
def info_items(request, *args, **kwargs):
"""Return Infoscreen items."""
items = []
classes = InfoItem.get_subclasses()
for c in classes:
@@ -149,6 +166,7 @@ def info_items(request, *args, **kwargs):
@ensure_csrf_cookie
@permission_required('infoscreen.change_infoinstance', login_url='/login')
def create_image_item(request, *args, **kwargs):
"""Create image Infoscreen item."""
form = ImageUploadForm(request.POST, request.FILES)
if not form.is_valid():
return HttpResponseBadRequest('{"status": "failure",'
@@ -164,6 +182,7 @@ def create_image_item(request, *args, **kwargs):
@ensure_csrf_cookie
@permission_required('infoscreen.change_infoinstance', login_url='/login')
def create_video_item(request, *args, **kwargs):
"""Create video Infoscreen item."""
form = UploadFileForm(request.POST, request.FILES)
print(form.errors)
print("hurdurr")
@@ -181,6 +200,7 @@ def create_video_item(request, *args, **kwargs):
@ensure_csrf_cookie
@permission_required('infoscreen.add_rotation', login_url='/login')
def create_rotation(request, *args, **kwargs):
"""Create rotation."""
try:
data = json.loads(request.body.decode("utf-8"))
except:
@@ -191,7 +211,8 @@ def create_rotation(request, *args, **kwargs):
Rotation.objects.create(name=name)
resp = HttpResponse(status=200)
except:
resp = HttpResponse('{"error" : "could not create rotation!"}', status=400)
resp = HttpResponse(
'{"error" : "could not create rotation!"}', status=400)
return resp
@@ -200,7 +221,7 @@ def create_rotation(request, *args, **kwargs):
@ensure_csrf_cookie
@permission_required('infoscreen.delete_rotation', login_url='/login')
def delete_rotation(request, *args, **kwargs):
"""Delete rotation."""
id = kwargs.pop("id", 0)
logging.warning("Deleting rotation with id={}".format(id))
@@ -208,13 +229,15 @@ def delete_rotation(request, *args, **kwargs):
Rotation.objects.filter(id=id).delete()
resp = HttpResponse(status=200)
except:
resp = HttpResponse('{"error" : "could not delete rotation!"}', status=400)
resp = HttpResponse(
'{"error" : "could not delete rotation!"}', status=400)
return resp
@require_http_methods(["GET"])
def hsl_timetable_settings(request, *args, **kwargs):
"""Set HSL timetable settings."""
d = {"departure_threshold": settings.HSL_DEPARTURE_THRESHOLD,
"hurry_threshold": settings.HSL_HURRY_THRESHOLD}
resp = json.dumps(d)
@@ -223,7 +246,7 @@ def hsl_timetable_settings(request, *args, **kwargs):
@require_http_methods(["GET"])
def CurrentHSLView(request, *args, **kwargs):
"""Get HSL data and return it."""
fetcher = HSLFetcher()
fetcherThread = threading.Thread(target=fetcher.fetch_if_needed, args=[])
fetcherThread.setDaemon(False)
@@ -231,7 +254,9 @@ def CurrentHSLView(request, *args, **kwargs):
data = HSLDataModel.objects.all()
if len(data) < 1:
return HttpResponse('{"error" : "Could not find timetables from database."}', status=500)
return HttpResponse(
'{"error" : "Could not find timetables from database."}',
status=500)
return HttpResponse(data[len(data) - 1].data, status=200)