transport-accessibility/transport_accessibility/api/views.py
Johannes Randerath 3853d25c1e Added LICENSE
- Code uses AGPL
- Docs use GNU FDL
2024-07-08 22:10:53 +02:00

155 lines
7.2 KiB
Python

"""
Views
=====
Views serving (mostly JSON) data via HTTP, no actual web pages.
Functions
---------
timetable
Fetches timetables for given routes on api/timetable/
data
Serves api/models/
GET:
Fetches models given their primary keys
PUT:
Creates new model objects or updates them with complete representations. If object with the given primary keys exist, they will be deleted and replaced.
PATCH:
Updates models, identified by their primary keys without deleting them. Can be incomplete representations.
DELETE:
Deletes models, identified by their primary keys.
"""
# This file is part of transport-accessibility.
# Copyright (C) 2024 Janek Kiljanski, Johannes Randerath
#
# transport-accessibility is free software: you can redistribute it and/or modify it under the terms of the
# GNU General Public License as published by the Free Software Foundation, either version 3
# of the License, or (at your option) any later version.
#
# transport-accessibility is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with transport-accessibility.
# If not, see <https://www.gnu.org/licenses/>.
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, HttpRequest
from django.core.exceptions import ObjectDoesNotExist
from django.core import serializers
import django.db.models
from MySQLdb import IntegrityError
from pt_map.models import *
import json
from django.views.decorators.csrf import csrf_exempt
from pt_map.query import *
def timetable(request):
"""
Lookup timetable data for given routes.
Request
-------
GET:
Find timetables for all routes passed via GET.
Successful response is a Json representation of a dict of timetables in the following form:
.. highlight:: python
.. code-block:: python
{
route_id (from GET): {
'stop_sequence': [stop_ids for all stops the route server, in order],
'stop_times': {
stop_id (from stop_sequence): [str in the format HH:MM representing stop times]
}
}
"""
if request.method == "GET":
try:
routes = obj_from_get(request.GET)[Route]
trips = get_trips(routes)
stop_sequences = get_stop_sequences(routes, trips)
timetables = {r.route_id: get_timetable(r, trips[r["route_id"]], stop_sequences[r["route_id"]]) for r in routes}
return HttpResponse(json.dumps(timetables), content_type="application/json")
except Route.DoesNotExist:
return HttpResponseBadRequest("Route not found.")
return HttpResponseNotAllowed(["GET"])
@csrf_exempt
def data(request):
"""
Handle database requests from the frontend. Using Http semantics to specify what to do with the data.
Request
-------
PUT
Create a new object if no object with the given primary key exists in the database or delete and replace an existing object.
Body must be a json dict of lists of fully specified, valid models. Primary keys can be omitted and will be ignored if the element does not exist in the database.
If primary keys are given, the elements are deleted and replaced. Note that if there is an error in creating the new object, the object to replace will still probably already have been deleted.
Successful response is 200 with a list of primary keys of the created and replaced objects.
PATCH
Modify an existing objects given the instructions in the body.
Body must be a json dict of lists of fields to change and their valid values existing objects in the database, identified by their valid primary keys.
Responds 400 if any of the primary keys given does not exist.
Successful response is 200 with a list of the primary keys of the modified objects.
GET
Return json of models identified by primary keys.
Responds 400 if any of the requested pks does not exist.
DELETE
Delete models with given primary keys if they exist.
Responds 400 if any of the primary keys given does not exist in the database.
Successful response is 200 and the number of deleted models.
"""
if request.method in ["PUT", "PATCH", "DELETE"]:
new = 0
modified = 0
if not request.META["CONTENT_TYPE"] == 'application/json':
HttpResponseBadRequest('Request must be JSON.')
try:
for o in serializers.deserialize('json', request.body):
try:
obj = o.object.__class__.objects.get(pk=o.object.pk)
modified += 1
if request.method == "PATCH":
for f,v in o.object.__dict__.items():
if v:
setattr(obj, f, v)
obj.save()
else:
obj.delete()
if request.method == "PUT":
o.save()
except ObjectDoesNotExist:
if not request.method == "PUT":
return HttpResponseBadRequest("Object(s) not found. {modified} objects touched.")
new += 1
o.save()
except django.db.IntegrityError:
return HttpResponseBadRequest("Could not write to database. Probably the objects you were trying to create or update where not compliant with GTFS's or the API's specification.")
except IntegrityError:
return HttpResponseBadRequest("There was an error while trying to write to the database. Probably a foreign key could not be resolved. Did you specify the objects in the correct order, if they depend on each other?")
except serializers.base.DeserializationError:
return HttpResponseBadRequest("Could not process the JSON you sent me correctly. Did you comply with the format required by the API and used valid JSON?")
except json.JSONDecodeError:
return HttpResponseBadRequest("Invalid JSON.")
except django.db.utils.DataError:
return HttpResponseBadRequest("One of your objects has fields that do not fit in their corresponding fields in the database. Did you comply to all data type requirements?")
return HttpResponse(f"OK. {new} new objects, {modified} replaced.") if request.method == "PUT" else HttpResponse(f"OK. {'Patched' if request.method == 'PATCH' else 'Deleted'} {modified} objects.")
elif request.method == "GET":
try:
pks = get_pks_from_get(request.GET)
except ValueError:
return HttpResponseBadRequest("No valid pks given.")
try:
return HttpResponse(json.dumps({mdl._meta.object_name: serializers.serialize('json', v) for mdl,v in obj_from_get(request.GET).items()}), content_type='application/json')
except ObjectDoesNotExist:
return HttpResponseBadRequest("Object(s) not found.")
return HttpResponseNotAllowed(['PUT', 'PATCH', 'GET'])