Skip to content

Commit

Permalink
feat(http_method_override.py): Create the HTTPMethodOverrideMiddlewar…
Browse files Browse the repository at this point in the history
…e, Responsible for creating support to accept PUT, DELETE and PATCH HTTP verbs.
  • Loading branch information
marcuxyz committed Nov 9, 2023
1 parent f01682a commit b607e17
Show file tree
Hide file tree
Showing 16 changed files with 347 additions and 97 deletions.
25 changes: 11 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

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

This real world implementation `FLASK MVC` example: https://github.com/negrosdev/negros.dev

## Installation

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

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

You structure should be look like this:
You structure should be look like this:

```text
app
Expand Down Expand Up @@ -150,7 +148,7 @@ user.update PATCH, PUT /api/v1/user/<id>

## Controller

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

```python
from flask import render_template
Expand All @@ -177,10 +175,10 @@ class HomeController:

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

## PUT / DELETE
## PUT / PATCH / DELETE ...

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


```python
Expand All @@ -198,22 +196,21 @@ class MessagesController:
```


```html
```jinja
<!-- app/views/messages/edit.html -->
{% block content %}
<form action="{{ url_for('messages.update', id=1) }}" method="POST">
<input type="hidden" name="_method" value="PUT">
<input type="text" name="title" id="title" value="Yeahh!">
<form action='{{ url_for("messages.update", id=1) }}' method="put">
<textarea name="message"></textarea>
<input type="submit" value="update" />
<input type="submit" value="send">
</form>

{{ mvc_form }}

{% endblock %}

```

The `<input type="hidden" name="_method" value="PUT">` is necessary to work sucessfully!

## Views

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.
Expand Down
10 changes: 7 additions & 3 deletions mvc_flask/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from importlib import import_module

from flask import Flask, render_template, request
from flask import Flask
from flask.blueprints import Blueprint
from mvc_flask import plugins

from .router import Router
from .middleware.http_method_override import (
HTTPMethodOverrideMiddleware,
CustomRequest,
)


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

app.template_folder = "views"
app.request_class = CustomRequest
app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app)

self.register_blueprint(app)
plugins.register(app)

def register_blueprint(self, app: Flask):
# load routes defined from users
Expand Down
48 changes: 48 additions & 0 deletions mvc_flask/middleware/http_method_override.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from flask import Request
from werkzeug.formparser import parse_form_data


class HTTPMethodOverrideMiddleware:
allowed_methods = frozenset(
[
"GET",
"POST",
"DELETE",
"PUT",
"PATCH",
]
)
bodyless_methods = frozenset(["GET", "HEAD", "OPTIONS", "DELETE"])

def __init__(self, app, input_name="_method"):
self.app = app
self.input_name = input_name

def __call__(self, environ, start_response):
if environ["REQUEST_METHOD"].upper() == "POST":
stream, form, files = parse_form_data(environ)
method = (form.get(self.input_name) or "").upper()

if method in self.allowed_methods:
environ["wsgi._post_form"] = form
environ["wsgi._post_files"] = files
environ["REQUEST_METHOD"] = method

if method in self.bodyless_methods:
environ["CONTENT_LENGTH"] = "0"

return self.app(environ, start_response)


class CustomRequest(Request):
@property
def form(self):
if "wsgi._post_form" in self.environ:
return self.environ["wsgi._post_form"]
return super().form

@property
def files(self):
if "wsgi._post_files" in self.environ:
return self.environ["wsgi._post_files"]
return super().files
9 changes: 0 additions & 9 deletions mvc_flask/plugins/__init__.py

This file was deleted.

40 changes: 0 additions & 40 deletions mvc_flask/plugins/form.py

This file was deleted.

Loading

0 comments on commit b607e17

Please sign in to comment.