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>