Skip to content

Commit 2fc33e9

Browse files
authored
Implement HTTP Method Override Middleware (#34)
* feat(http_method_override.py): Create the HTTPMethodOverrideMiddleware, Responsible for creating support to accept PUT, DELETE and PATCH HTTP verbs. * chore(pyproject.toml): update version from 2.5.0 to 2.7.0 to reflect changes in the project chore(pyproject.toml): add missing newline at the end of the file for consistency * feat(README.md): add support for retrieving and updating message title in edit route of MessagesController
1 parent f01682a commit 2fc33e9

File tree

16 files changed

+359
-102
lines changed

16 files changed

+359
-102
lines changed

README.md

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
You can use the mvc pattern in your flask application using this extension.
44

5-
This real world implementation `FLASK MVC` example: https://github.com/negrosdev/negros.dev
6-
75
## Installation
86

97
Run the follow command to install `mvc_flask`:
@@ -48,7 +46,7 @@ def create_app():
4846

4947
Now, you can use `src` as default directory for prepare your application.
5048

51-
You structure should be look like this:
49+
You structure should be look like this:
5250

5351
```text
5452
app
@@ -150,7 +148,7 @@ user.update PATCH, PUT /api/v1/user/<id>
150148

151149
## Controller
152150

153-
Now that configure routes, the `home_controller.py` file must contain the `HomeController` class, registering the `action`, e.g:
151+
Now that configure routes, the `home_controller.py` file must contain the `HomeController` class, registering the `action`, e.g:
154152

155153
```python
156154
from flask import render_template
@@ -177,43 +175,49 @@ class HomeController:
177175

178176
The previous example describes the `hi(self)` will be called every times that the visitors access the controller.
179177

180-
## PUT / DELETE
178+
## PUT / PATCH / DELETE ...
181179

182180
we know that the HTML form doesn't send payload to `action` with `put` or `delete` method as attribute of `form tag`. But,
183-
the `FLASK MVC` does the work for you, everything you need is add the `{{ mvc_form }} tag in HTML template. Look:
184-
181+
the `FLASK MVC` does the work for you, everything you need is add the tag in HTML template. Look:
185182

186183
```python
187184
# app/controllers/messages_controller.py
188185

189-
from flask import render_template, redirect, url_for, flash
186+
from flask import render_template, redirect, url_for, flash, request
190187

191188
class MessagesController:
192189
def edit(self, id):
193-
return render_template("messages/edit.html")
190+
message = Message.query.get(id)
191+
192+
return render_template("messages/edit.html", message=message)
194193

195194
def update(self, id):
195+
message = Message.query.get(id)
196+
message.title = request.form.get('title')
197+
198+
db.session.add(message)
199+
db.session.commit()
196200
flash('Message sent successfully!')
201+
197202
return redirect(url_for(".edit"))
198203
```
199204

200205

201-
```html
206+
```jinja
202207
<!-- app/views/messages/edit.html -->
203208
204209
{% block content %}
210+
<form action="{{ url_for('messages.update', id=message.id) }}" method="POST">
211+
<input type="hidden" name="_method" value="put">
212+
<input type="text" name="title" id="title" value="Yeahh!">
205213
206-
<form action='{{ url_for("messages.update", id=1) }}' method="put">
207-
<textarea name="message"></textarea>
208-
<input type="submit" value="update" />
214+
<input type="submit" value="send">
209215
</form>
210-
211-
{{ mvc_form }}
212-
213216
{% endblock %}
214-
215217
```
216218

219+
The `<input type="hidden" name="_method" value="put">` is necessary to work sucessfully!
220+
217221
## Views
218222

219223
Flask use the `templates` directory by default to store `HTMLs` files. However, using the `mvc-flask` the default becomes `views`. You can use the `app/views` directory to stores templates.

mvc_flask/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from importlib import import_module
22

3-
from flask import Flask, render_template, request
3+
from flask import Flask
44
from flask.blueprints import Blueprint
5-
from mvc_flask import plugins
65

76
from .router import Router
7+
from .middleware.http_method_override import (
8+
HTTPMethodOverrideMiddleware,
9+
CustomRequest,
10+
)
811

912

1013
class FlaskMVC:
@@ -17,9 +20,10 @@ def init_app(self, app: Flask = None, path="app"):
1720
self.path = path
1821

1922
app.template_folder = "views"
23+
app.request_class = CustomRequest
24+
app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app)
2025

2126
self.register_blueprint(app)
22-
plugins.register(app)
2327

2428
def register_blueprint(self, app: Flask):
2529
# load routes defined from users
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from flask import Request
2+
from werkzeug.formparser import parse_form_data
3+
4+
5+
class HTTPMethodOverrideMiddleware:
6+
allowed_methods = frozenset(
7+
[
8+
"GET",
9+
"POST",
10+
"DELETE",
11+
"PUT",
12+
"PATCH",
13+
]
14+
)
15+
bodyless_methods = frozenset(["GET", "HEAD", "OPTIONS", "DELETE"])
16+
17+
def __init__(self, app, input_name="_method"):
18+
self.app = app
19+
self.input_name = input_name
20+
21+
def __call__(self, environ, start_response):
22+
if environ["REQUEST_METHOD"].upper() == "POST":
23+
stream, form, files = parse_form_data(environ)
24+
method = (form.get(self.input_name) or "").upper()
25+
26+
if method in self.allowed_methods:
27+
environ["wsgi._post_form"] = form
28+
environ["wsgi._post_files"] = files
29+
environ["REQUEST_METHOD"] = method
30+
31+
if method in self.bodyless_methods:
32+
environ["CONTENT_LENGTH"] = "0"
33+
34+
return self.app(environ, start_response)
35+
36+
37+
class CustomRequest(Request):
38+
@property
39+
def form(self):
40+
if "wsgi._post_form" in self.environ:
41+
return self.environ["wsgi._post_form"]
42+
return super().form
43+
44+
@property
45+
def files(self):
46+
if "wsgi._post_files" in self.environ:
47+
return self.environ["wsgi._post_files"]
48+
return super().files

mvc_flask/plugins/__init__.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

mvc_flask/plugins/form.py

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)