Compare commits
3 Commits
d4f8109663
...
aabe20cff7
Author | SHA1 | Date | |
---|---|---|---|
aabe20cff7 | |||
be1232ebd5 | |||
d6beb92544 |
@ -53,6 +53,10 @@ This function is called after the view has run and returned a response. You will
|
||||
|
||||
Unlike `process_request`, returning a value here doesn't change anything. We're already processing a request, and there are opportunities to turn away requests / change the response at both the `process_request` layer and the view layer, so Spiderweb assumes that whatever it is working on here is what you mean to return to the user. The response object that you receive in the middleware is still prerendered, so any changes you make to it will take effect after it finishes the middleware and renders the response.
|
||||
|
||||
## on_error(self, request, triggered_exception):
|
||||
|
||||
This is a helper function that is available for you to override; it's not often used by middleware, but there are some ([like the pydantic middleware](pydantic.md)) that call `on_error` when there is a validation failure.
|
||||
|
||||
## UnusedMiddleware
|
||||
|
||||
```python
|
||||
|
@ -0,0 +1,57 @@
|
||||
# pydantic form validation
|
||||
|
||||
```python
|
||||
from spiderweb import SpiderwebRouter
|
||||
|
||||
app = SpiderwebRouter(
|
||||
middleware=["spiderweb.middleware.pydantic.PydanticMiddleware"],
|
||||
)
|
||||
```
|
||||
When working with form data, you may not want to always have to perform your own validation on the incoming data. Spiderweb gives you a way out of the box to perform this validation using Pydantic.
|
||||
|
||||
Let's assume that we have a form view that looks like this:
|
||||
|
||||
```python
|
||||
@app.route("/myform", allowed_methods=["GET", "POST"])
|
||||
def form(request):
|
||||
if request.method == "POST":
|
||||
if "username" in request.POST and "comment" in request.POST:
|
||||
# there's presumably other data in there, but we care about these two
|
||||
return JsonResponse(
|
||||
data={
|
||||
"username": request.POST["username"],
|
||||
"comment": request.POST["comment"]
|
||||
}
|
||||
)
|
||||
else:
|
||||
return TemplateResponse(request, "myform.html")
|
||||
```
|
||||
|
||||
Our form takes in an indeterminate amount of data, but if we really care about some of the fields (or all of them) then we can utilize Pydantic to handle this validation for us. Once the middleware is enabled, we can update our view:
|
||||
|
||||
```python
|
||||
from pydantic import EmailStr
|
||||
|
||||
from spiderweb.middleware.pydantic import RequestModel
|
||||
|
||||
|
||||
class CommentForm(RequestModel):
|
||||
email: EmailStr
|
||||
comment: str
|
||||
|
||||
@app.route("/myform", allowed_methods=["GET", "POST"])
|
||||
def form(request: CommentForm):
|
||||
if request.method == "POST":
|
||||
return JsonResponse(request.validated_data.dict())
|
||||
else:
|
||||
return TemplateResponse(request, "myform.html")
|
||||
```
|
||||
|
||||
The Pydantic middleware will automatically detect that the model that you want to use for the request has been added as a type hint, and it will run the validation during the middleware phase so that it can return an error immediately if it fails validation.
|
||||
|
||||
> [!NOTE]
|
||||
> Your validator **must** inherit from RequestModel to function correctly! If it doesn't, it will not trigger.
|
||||
|
||||
If the validation fails, the middleware will call `on_error`, which by default will return a 400 with a list of the broken fields. You may not want this behavior, so the easiest way to address it is to subclass PydanticMiddleware with your own version and override `on_error` to do whatever you'd like.
|
||||
|
||||
If validation succeeds, the data from the validator will appear on the request object under `request.validated_data` — to access it, just call `.dict()` on the validated data.
|
@ -4,9 +4,7 @@
|
||||
from spiderweb import SpiderwebRouter
|
||||
|
||||
app = SpiderwebRouter(
|
||||
middleware=[
|
||||
"spiderweb.middleware.sessions.SessionMiddleware",
|
||||
],
|
||||
middleware=["spiderweb.middleware.sessions.SessionMiddleware"],
|
||||
)
|
||||
```
|
||||
|
||||
@ -16,7 +14,7 @@ Visitors are assigned a random value when they visit for the first time, and tha
|
||||
|
||||
## request.SESSION
|
||||
|
||||
When the sessions middleware is enabled, the request object will have a new attribute labeled `SESSION`. This is a dictionary, and you can put pretty much anything you want in it as long as its serializable to JSON! When the user visits again with an active session, the data will automatically be available on the `SESSION` object again. Here's an example of a complete server using sessions:
|
||||
When the sessions middleware is enabled, the request object will have a new attribute labeled `SESSION`. This is a dictionary, and you can put pretty much anything you want in it as long as it's serializable to JSON! When the user visits again with an active session, the data will automatically be available on the `SESSION` object again. Here's an example of a complete server using sessions:
|
||||
|
||||
```python
|
||||
from spiderweb import SpiderwebRouter, HttpResponse
|
||||
@ -70,7 +68,7 @@ This marks that the cookie will only be sent back to the server with a valid HTT
|
||||
|
||||
### session_cookie_http_only
|
||||
|
||||
This marks whether the session cookie will have the `HttpOnly` attribute. This makes it invisible to client-side javascript. The default is `False`.
|
||||
This marks whether the session cookie will have the `HttpOnly` attribute. This makes it unreadable to client-side javascript. The default is `False`.
|
||||
|
||||
### session_cookie_same_site
|
||||
|
||||
|
@ -28,3 +28,6 @@ class SpiderwebMiddleware:
|
||||
self, request: Request, response: HttpResponse
|
||||
) -> HttpResponse | None:
|
||||
pass
|
||||
|
||||
def on_error(self, request: Request, e: Exception) -> HttpResponse | None:
|
||||
pass
|
@ -1,3 +1,4 @@
|
||||
import inspect
|
||||
from typing import get_type_hints
|
||||
|
||||
from pydantic import BaseModel
|
||||
@ -19,10 +20,12 @@ class PydanticMiddleware(SpiderwebMiddleware):
|
||||
if not request.method == "POST":
|
||||
return
|
||||
types = get_type_hints(request.handler)
|
||||
if "request" not in types:
|
||||
return
|
||||
# we don't know what the user named the request object, but
|
||||
# we know that it's first in the list, and it's always an arg.
|
||||
request_arg_name = inspect.getfullargspec(request.handler).args[0]
|
||||
if types.get(request_arg_name) in RequestModel.__subclasses__():
|
||||
try:
|
||||
data = types["request"].parse_obj(request.POST)
|
||||
data = types[request_arg_name].parse_obj(request.POST)
|
||||
request.validated_data = data
|
||||
except ValidationError as e:
|
||||
return self.on_error(request, e)
|
||||
|
Loading…
Reference in New Issue
Block a user