Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More
Answering to invalid requests¶
Side note: Code snippets (lines starting with >>>
) in this document get
tested as part of our development workflow. The following
initialization snippet tells you which demo project is being used in
this document.
>>> from lino import startup
>>> startup('lino_book.projects.cosi1.settings')
>>> from lino.api.doctest import *
>>> settings.SITE.kernel.web_front_ends
[<lino_react.react.Plugin lino_react.react(needs ['lino.modlib.jinja'])>]
403 PermissionDenied¶
The following request caused a traceback
type object 'AnonymousUser' has no attribute 'get_chooser_for_field'
rather than responding PermissionDenied:
>>> url = '/api/users/Me/2?dm=detail&fmt=json&ul=en&wt=d'
>>> res = test_client.get(url)
Forbidden (Permission denied): /api/users/Me/2
Traceback (most recent call last):
...
django.core.exceptions.PermissionDenied: No permission to run ActionRequest My settings on users.Me
>>> res.status_code
403
Lino currently returns HTML content even though fmt=json
has been requested:
>>> print(res.content.decode())
<!doctype html>
<html lang="en">
<head>
<title>403 Forbidden</title>
</head>
<body>
<h1>403 Forbidden</h1><p></p>
</body>
</html>
TODO: return JSON content rather than HTML
400 Bad Request¶
A status code of 400 (Bad Request) means “The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications.” (w3.org)
We are going to send some requests to contacts.RolesByPerson
, a slave table on person. So we need a
person as master instance:
>>> contacts.Person.objects.get(pk=114)
Person #114 ('Mrs Annette Arens')
>>> contacts.RolesByPerson.master
<class 'lino_xl.lib.contacts.models.Person'>
Value of mt
(“master type”) in the following snippets must be 8:
>>> contenttypes.ContentType.objects.get_for_model(contacts.Person).id
8
We are authenticated as robin
:
>>> test_client.force_login(rt.login('robin').user)
Here is a valid request to contacts.RolesByPerson
:
>>> url = "/api/contacts/RolesByPerson?fmt=json&start=0&limit=15&mt=8&mk=114"
>>> res = test_client.get(url)
>>> print(res.status_code)
200
>>> d = AttrDict(json.loads(res.content.decode()))
>>> d.count
2
>>> print(d.title)
Mrs Annette Arens is contact person for
The examples below explore what happens when a request specifies an invalid primary key for the master (114114 instead of 114).
>>> url = "/api/contacts/RolesByPerson?fmt=json&start=0&limit=15&mt=8&mk=114114"
Lino’s default behaviour is to return an empty list of rows and a title indicating the problem:
>>> res = test_client.get(url)
20240731 <class 'lino.utils.MissingRow'> is not a <class 'lino_xl.lib.contacts.models.Person'> (contacts.RolesByPerson.master_key = 'person')
>>> res.status_code
200
>>> print(res.content.decode())
{ "count": 0, "html_text": null, "no_data_text": "No data to display", "rows": [
], "success": true, "title": "MissingRow(Person matching query does not exist.
(pk=114114)) is contact person for" }
Another behaviour is when lino.core.site.Site.strict_master_check
is set
to True. Django will internally raise an ObjectDoesNotExist
exception,
but Lino catches this and raises a BadRequest
instead, for which has
some out of the box handling:
>>> settings.SITE.strict_master_check = True
>>> res = test_client.get(url)
MissingRow(Person matching query does not exist. (pk=114114)): /api/contacts/RolesByPerson
Traceback (most recent call last):
...
django.core.exceptions.BadRequest: MissingRow(Person matching query does not exist. (pk=114114))
Django handles BadRequest
exceptions automatically.
They usually just get logged, and the response gets a 400 status code.
Or when you set the logging level to CRITICAL, they won’t even get logged.
>>> import logging
>>> logger = logging.getLogger("django.request")
>>> logger.setLevel(logging.CRITICAL)
>>> settings.DEBUG = False
>>> url = "/api/contacts/RolesByPerson?fmt=json&start=0&limit=15&mt=8&mk=foo"
>>> res = test_client.get(url)
>>> res.status_code
400
>>> print(res.content.decode())
<!doctype html>
<html lang="en">
<head>
<title>Bad Request (400)</title>
</head>
<body>
<h1>Bad Request (400)</h1><p></p>
</body>
</html>
>>> logger.setLevel(logging.INFO)
Most slave tables have a known Actor.master
, which is given by the
target of the master_key
field. For example, RolesByPerson has
contacts.Person as master.
>>> contacts.RolesByPerson.master
<class 'lino_xl.lib.contacts.models.Person'>
So the mt
url parameter is notrequired. But if it is specified, it
overrides the default value, and an invalid value for mt
raises an
exception:
>>> url = "/api/contacts/RolesByPerson?fmt=json&start=0&limit=15&mt=8888&mk=114"
>>> res = test_client.get(url)
Invalid master_type 8888: /api/contacts/RolesByPerson
Traceback (most recent call last):
...
django.core.exceptions.BadRequest: Invalid master_type 8888
>>> res.status_code
400
Request data not supplied¶
After 20170410 the following AJAX request no longer raises a real exception but
continues to log it. Raising an exception had the disadvantage of having an
email sent to the ADMINS
, which was just disturbing and not helpful
because it had no “request data supplied”. Now the end user gets an
appropriate message because it receives a status code 400.
>>> url = '/api/cal/EventsByProject?_dc=1491615952104&fmt=json&rp=ext-comp-1306&start=0&limit=15&mt=13&mk=188'
>>> res = test_client.get(url)
Not Found: /api/cal/EventsByProject
>>> res.status_code
404
>>> #print(json.loads(res.content)['message'])
>>> print(res.content.decode())
<!doctype html>
<html lang="en">
<head>
<title>Not Found</title>
</head>
<body>
<h1>Not Found</h1><p>The requested resource was not found on this server.</p>
</body>
</html>