Starting using werkzeug, i try to map urls (from a file urls.py) to views (from a folder views and then in different files to manage differents kinds of view), my folder organisation looks like that :
myapp/
application.py
urls.py
views/
__init__.py
common.py
places.py
...
my urls.py files looks like that:
from werkzeug.routing import Map, Rule
url_map = Map([
Rule('/places', endpoint='places.overview')
])
and obviously i got that piece in the views/places.py file :
def overview(request):
mycode...
render_template('places.html', extra...)
Most of werkzeug examples show the utilisation of the decorator expose to attach urls to views. It's practical for an app with 5 or 6 urls but can become a hell when you got more...
Is there a simple way to map the urls directly to the views???
Thanks.
Here is a simplified example:
import views
def app(environ, start_response):
urls = url_map.bind_to_environ(environ)
request = Request(environ)
endpoint, params = urls.match()
names = endpoint.split('.')
view = views
for name in names:
if not hasattr(view, name):
__import__(view.__name__, None, None, [name])
view = getattr(view, name)
try:
response = view(request)
except werkzeug.exceptions.HTTPException, exc:
response = exc
return response(environ, start_response)
import letters # our views module
url_map = Map([
Rule('/letters', endpoint=letters.index),
Rule('/letters/<int:item_id>', endpoint=letters.item),
Rule('/letters/<string:section_slug>', endpoint=letters.index),
Rule('/letters/<string:section_slug>/<int:item_id>',
endpoint=letters.item),
])
endpoint can be anything, including function, so you can just skip import magic from Denis's example
I'm not sure if it is preferred way to tackle this problem (I haven't found any similar example in werkzeug repo and I'm still only playing with this lib) but it is also possible to simply subclass Rule:
class CoolRule(Rule):
def __init__(self, view, *args, **kwargs):
self.view = view
super(CoolRule, self).__init__(*args, **kwargs)
def empty(self):
"""We need this method if we want to use
Submounts or Subdomain factories
"""
defaults = dict(self.defaults) if self.defaults else None
return CoolRule(self.view, self.rule, defaults, self.subdomain,
self.methods, self.build_only, self.endpoint,
self.strict_slashes, self.redirect_to,
self.alias, self.host)
_url_map = Map([
CoolRule(user.views.login, '/login', endpoint='user-login'),
CoolRule(user.views.logout, '/logout', endpoint='user-logout'),
])
def dispatch(request):
urls = _url_map.bind_to_environ(request.environ)
rule, arguments = urls.match(return_rule=True)
return rule.view(request, **arguments)
In that way you can preserve layer of view naming abstraction and avoid strange magic with 'string importing'.
Related
I'm still new to Flask, so there may be an obvious way to accomplish this, but I haven't been able to figure it out so far from the documentation. My app is divided into several mostly disparate parts that share things like users/sessions/security and base template and everything but mostly do not interact much, and should be routed under different paths like /part1/.... I think this is pretty much exactly what blueprints are for. But what if I need to group routes and logic further under a blueprint?
For example, I have blueprint1 with url_prefix='/blueprint1' and maybe under that I want to have a collection of views revolving around a user sharing photos and other users commenting on them. I can't think of a better way of doing it than:
# app/blueprints/blueprint1/__init__.py
blueprint1 = Blueprint('blueprint1', __name__, template_folder='blueprint1')
#blueprint1.route('/photos')
def photos_index():
return render_template('photos/index.html')
#blueprint.route('/photos/<int:photo_id>')
def photos_show(photo_id):
photo = get_a_photo_object(photo_id)
return render_template('photos/show.html', photo=photo)
#blueprint.route('/photos', methods=['POST'])
def photos_post():
...
The problem here is that all the views related to the photos section of blueprint1 are located at the "top level," right with maybe blueprints for videos or audio or whatever (named videos_index()...). Is there any way to group them in a more hierarchical manner, like how the templates go under the 'blueprint1/photos' sub-directory? Of course I can put all the photo views in their own module to keep them organized separately, but what if I want to change the parent 'blueprint1/photos' path to something else? I'm sure I can invent a function or decorator that groups related routes under the same root path, but then I still have to name all the functions with the photos_ prefix and reference them like url_for('blueprint1.photos_show') It seems like blueprints are the answer when a Flask app gets large and you need to group and compartmentalize similar parts together, but you cannot do the same thing when the blueprints themselves get large.
For reference, in Laravel you can group related "views" under a Controller class where the views are methods. Controllers can reside in hierarchical namespaces like app\Http\Controllers\Blueprint1\Photocontroller, routes can be grouped together like
Route::group(['prefix' => 'blueprint1'], function() {
Route::group(['prefix' => 'photos'], function() {
Route::get('/', ['as' => 'blueprint.photos.index', 'uses' => 'ModelApiController#index']);
Route::post('/', ['as' => 'blueprint.photos.store', 'uses' => 'ModelApiController#store']);
Route::get('/{id}', ['as' => 'blueprint.photos.get', 'uses' => 'ModelApiController#get'])
->where('id', '[0-9]+');
});
});
and routes can be gotten like action('Blueprint1\PhotoController#index').
If only I could make a photos blueprint, then just do blueprint1.register_blueprint(photos_blueprint, url_prefix='/photos') or the like, these problems would pretty much be solved. Unfortunately Flask does not seem to support nesting blueprints like this. Is there an alternative way to handle this problem?
UPDATE
Flask 2 was released with support for nested blueprints.
[ START: Part from the docs ]
Nesting Blueprints
It is possible to register a blueprint on another blueprint.
parent = Blueprint('parent', __name__, url_prefix='/parent')
child = Blueprint('child', __name__, url_prefix='/child')
parent.register_blueprint(child)
app.register_blueprint(parent)
The child blueprint will gain the parent’s name as a prefix to its name, and child URLs will be prefixed with the parent’s URL prefix.
url_for('parent.child.create')
/parent/child/create
Blueprint-specific before request functions, etc. registered with the parent will trigger for the child. If a child does not have an error handler that can handle a given exception, the parent’s will be tried.
[ END: Part from the docs ]
Source: https://flask.palletsprojects.com/en/2.0.x/blueprints/#nesting-blueprints
OLD ANSWER
My hacky work around is that I made a class called ParentBP that has the following code
from typing import List
from flask import Blueprint
class ParentBP(object):
name: str
url_prefix: str
subdomain: str
blueprints: List[Blueprint]
def __init__(self, name="", url_prefix="", subdomain="") -> None:
self.name = name
self.url_prefix = url_prefix
self.subdomain = subdomain
self.blueprints = []
def register_blueprint(self, bp: Blueprint) -> None:
bp.name = self.name + "-" + bp.name
bp.url_prefix = self.url_prefix + (bp.url_prefix or "")
if self.subdomain:
bp.subdomain = self.subdomain
self.blueprints.append(bp)
so you can call it similar to a blueprint like below
blueprint1 = Blueprint("blueprint1", __name__)
blueprint2 = Blueprint("blueprint2", __name__, url_prefix="/bp2")
api_v1 = ParentBP("api-v1", url_prefix="/api/v1")
api_v1.register_blueprint(blueprint1)
api_v1.register_blueprint(blueprint)
to make the interface similar to normal registering of blueprints to the flask app, I extended the Flask class as follows
class ExtendedFlask(Flask):
def register_blueprint(self, blueprint: Union[Blueprint, ParentBP], **options: Any) -> None:
if isinstance(blueprint, ParentBP):
for bp in blueprint.blueprints:
super().register_blueprint(bp, **options)
else:
return super().register_blueprint(blueprint, **options)
now you can normally do the following
app = ExtendedFlask(__name__)
app.register_blueprint(api_v1)
Unfortunately, nested blueprints are not a current feature in Flask. You'll have to do it manually. You could probably code something that works for your specific case, but a general solution has not been added to Flask. There has been some discussion on the issue tracker:
https://github.com/mitsuhiko/flask/issues/593
https://github.com/mitsuhiko/flask/issues/1548
https://github.com/pallets/flask/issues/3215
Adding nestable blueprints into Flask is not as trivial as automatically appending a prefix to routes. There are many other features of blueprints that need to be considered when nesting that make a general implementation significantly more complicated. The reason this has not been implemented yet is that no one in the community has had a great enough need for it that wasn't solved by a quick workaround vs contributing a general implementation.
I made a class called NestedBlueprint to hack it.
class NestedBlueprint(object):
def __init__(self, blueprint, prefix):
super(NestedBlueprint, self).__init__()
self.blueprint = blueprint
self.prefix = '/' + prefix
def route(self, rule, **options):
rule = self.prefix + rule
return self.blueprint.route(rule, **options)
Here is my base file which contains the blueprint: panel/__init__.py
from flask import Blueprint
panel_blueprint = Blueprint(PREFIX, __name__, url_prefix='/panel')
from . import customize
Here is the specific/nested file which contains nested blueprint: panel/customize.py
from rest.api.panel import panel_blueprint
from rest.api.util.nested_blueprint import NestedBlueprint
nested_blueprint = NestedBlueprint(panel_blueprint, 'customize')
#nested_blueprint.route('/test', methods=['GET'])
def test():
return ':)'
You can then call like this:
$ curl http://localhost:5000/panel/customize/test
:)
Here is my workaround:
When importing a blueprint, I define my nested routes:
app.register_blueprint(product_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>/categories/<int:category_id>/products/<int:product_id>')
app.register_blueprint(category_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>/categories/<int:category_id>')
app.register_blueprint(menu_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>')
app.register_blueprint(site_endpoints, url_prefix='/sites/<int:site_id>')
And inside the blueprints, I'm reusing route parse functions. For example, in the product_endpoints file:
from category_endpoints import get_category_data
product_endpoints = Blueprint('product_endpoints', __name__)
#product_endpoints.url_value_preprocessor
def get_product_data(endpoint, values):
if 'category_id' in values:
get_category_data(endpoint, values)
product = Product.get_by_id(int(values.pop('product_id')))
if not product:
abort(404)
g.product = product
and in category_endpoints file:
from menu_endpoints import get_menu_data
category_endpoints = Blueprint('category_endpoints', __name__)
#category_endpoints.url_value_preprocessor
def get_category_data(endpoint, values):
if 'menu_id' in values:
get_menu_data(endpoint, values)
category = ProductCategory.get_by_id(int(values.pop('category_id')))
if not category:
abort(404)
g.category = category
etc... With that approach, my blueprint is also usable with direct routes like /products/<int:product_id>.
This approach worked for me very well. I hope it can also help you.
I'm still new to Flask, so there may be an obvious way to accomplish this, but I haven't been able to figure it out so far from the documentation. My app is divided into several mostly disparate parts that share things like users/sessions/security and base template and everything but mostly do not interact much, and should be routed under different paths like /part1/.... I think this is pretty much exactly what blueprints are for. But what if I need to group routes and logic further under a blueprint?
For example, I have blueprint1 with url_prefix='/blueprint1' and maybe under that I want to have a collection of views revolving around a user sharing photos and other users commenting on them. I can't think of a better way of doing it than:
# app/blueprints/blueprint1/__init__.py
blueprint1 = Blueprint('blueprint1', __name__, template_folder='blueprint1')
#blueprint1.route('/photos')
def photos_index():
return render_template('photos/index.html')
#blueprint.route('/photos/<int:photo_id>')
def photos_show(photo_id):
photo = get_a_photo_object(photo_id)
return render_template('photos/show.html', photo=photo)
#blueprint.route('/photos', methods=['POST'])
def photos_post():
...
The problem here is that all the views related to the photos section of blueprint1 are located at the "top level," right with maybe blueprints for videos or audio or whatever (named videos_index()...). Is there any way to group them in a more hierarchical manner, like how the templates go under the 'blueprint1/photos' sub-directory? Of course I can put all the photo views in their own module to keep them organized separately, but what if I want to change the parent 'blueprint1/photos' path to something else? I'm sure I can invent a function or decorator that groups related routes under the same root path, but then I still have to name all the functions with the photos_ prefix and reference them like url_for('blueprint1.photos_show') It seems like blueprints are the answer when a Flask app gets large and you need to group and compartmentalize similar parts together, but you cannot do the same thing when the blueprints themselves get large.
For reference, in Laravel you can group related "views" under a Controller class where the views are methods. Controllers can reside in hierarchical namespaces like app\Http\Controllers\Blueprint1\Photocontroller, routes can be grouped together like
Route::group(['prefix' => 'blueprint1'], function() {
Route::group(['prefix' => 'photos'], function() {
Route::get('/', ['as' => 'blueprint.photos.index', 'uses' => 'ModelApiController#index']);
Route::post('/', ['as' => 'blueprint.photos.store', 'uses' => 'ModelApiController#store']);
Route::get('/{id}', ['as' => 'blueprint.photos.get', 'uses' => 'ModelApiController#get'])
->where('id', '[0-9]+');
});
});
and routes can be gotten like action('Blueprint1\PhotoController#index').
If only I could make a photos blueprint, then just do blueprint1.register_blueprint(photos_blueprint, url_prefix='/photos') or the like, these problems would pretty much be solved. Unfortunately Flask does not seem to support nesting blueprints like this. Is there an alternative way to handle this problem?
UPDATE
Flask 2 was released with support for nested blueprints.
[ START: Part from the docs ]
Nesting Blueprints
It is possible to register a blueprint on another blueprint.
parent = Blueprint('parent', __name__, url_prefix='/parent')
child = Blueprint('child', __name__, url_prefix='/child')
parent.register_blueprint(child)
app.register_blueprint(parent)
The child blueprint will gain the parent’s name as a prefix to its name, and child URLs will be prefixed with the parent’s URL prefix.
url_for('parent.child.create')
/parent/child/create
Blueprint-specific before request functions, etc. registered with the parent will trigger for the child. If a child does not have an error handler that can handle a given exception, the parent’s will be tried.
[ END: Part from the docs ]
Source: https://flask.palletsprojects.com/en/2.0.x/blueprints/#nesting-blueprints
OLD ANSWER
My hacky work around is that I made a class called ParentBP that has the following code
from typing import List
from flask import Blueprint
class ParentBP(object):
name: str
url_prefix: str
subdomain: str
blueprints: List[Blueprint]
def __init__(self, name="", url_prefix="", subdomain="") -> None:
self.name = name
self.url_prefix = url_prefix
self.subdomain = subdomain
self.blueprints = []
def register_blueprint(self, bp: Blueprint) -> None:
bp.name = self.name + "-" + bp.name
bp.url_prefix = self.url_prefix + (bp.url_prefix or "")
if self.subdomain:
bp.subdomain = self.subdomain
self.blueprints.append(bp)
so you can call it similar to a blueprint like below
blueprint1 = Blueprint("blueprint1", __name__)
blueprint2 = Blueprint("blueprint2", __name__, url_prefix="/bp2")
api_v1 = ParentBP("api-v1", url_prefix="/api/v1")
api_v1.register_blueprint(blueprint1)
api_v1.register_blueprint(blueprint)
to make the interface similar to normal registering of blueprints to the flask app, I extended the Flask class as follows
class ExtendedFlask(Flask):
def register_blueprint(self, blueprint: Union[Blueprint, ParentBP], **options: Any) -> None:
if isinstance(blueprint, ParentBP):
for bp in blueprint.blueprints:
super().register_blueprint(bp, **options)
else:
return super().register_blueprint(blueprint, **options)
now you can normally do the following
app = ExtendedFlask(__name__)
app.register_blueprint(api_v1)
Unfortunately, nested blueprints are not a current feature in Flask. You'll have to do it manually. You could probably code something that works for your specific case, but a general solution has not been added to Flask. There has been some discussion on the issue tracker:
https://github.com/mitsuhiko/flask/issues/593
https://github.com/mitsuhiko/flask/issues/1548
https://github.com/pallets/flask/issues/3215
Adding nestable blueprints into Flask is not as trivial as automatically appending a prefix to routes. There are many other features of blueprints that need to be considered when nesting that make a general implementation significantly more complicated. The reason this has not been implemented yet is that no one in the community has had a great enough need for it that wasn't solved by a quick workaround vs contributing a general implementation.
I made a class called NestedBlueprint to hack it.
class NestedBlueprint(object):
def __init__(self, blueprint, prefix):
super(NestedBlueprint, self).__init__()
self.blueprint = blueprint
self.prefix = '/' + prefix
def route(self, rule, **options):
rule = self.prefix + rule
return self.blueprint.route(rule, **options)
Here is my base file which contains the blueprint: panel/__init__.py
from flask import Blueprint
panel_blueprint = Blueprint(PREFIX, __name__, url_prefix='/panel')
from . import customize
Here is the specific/nested file which contains nested blueprint: panel/customize.py
from rest.api.panel import panel_blueprint
from rest.api.util.nested_blueprint import NestedBlueprint
nested_blueprint = NestedBlueprint(panel_blueprint, 'customize')
#nested_blueprint.route('/test', methods=['GET'])
def test():
return ':)'
You can then call like this:
$ curl http://localhost:5000/panel/customize/test
:)
Here is my workaround:
When importing a blueprint, I define my nested routes:
app.register_blueprint(product_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>/categories/<int:category_id>/products/<int:product_id>')
app.register_blueprint(category_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>/categories/<int:category_id>')
app.register_blueprint(menu_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>')
app.register_blueprint(site_endpoints, url_prefix='/sites/<int:site_id>')
And inside the blueprints, I'm reusing route parse functions. For example, in the product_endpoints file:
from category_endpoints import get_category_data
product_endpoints = Blueprint('product_endpoints', __name__)
#product_endpoints.url_value_preprocessor
def get_product_data(endpoint, values):
if 'category_id' in values:
get_category_data(endpoint, values)
product = Product.get_by_id(int(values.pop('product_id')))
if not product:
abort(404)
g.product = product
and in category_endpoints file:
from menu_endpoints import get_menu_data
category_endpoints = Blueprint('category_endpoints', __name__)
#category_endpoints.url_value_preprocessor
def get_category_data(endpoint, values):
if 'menu_id' in values:
get_menu_data(endpoint, values)
category = ProductCategory.get_by_id(int(values.pop('category_id')))
if not category:
abort(404)
g.category = category
etc... With that approach, my blueprint is also usable with direct routes like /products/<int:product_id>.
This approach worked for me very well. I hope it can also help you.
TL;DR
Is there a way to have (namespaced,) well-named views defined when using ModelAdmin.get_urls and ModelAdmins extended by inheritance?
Preferably without resorting to ModelAdmin.model._meta or some other solution of slightly questionable nature.
Pretext
View names added through get_urls get overridden when using and inheriting from custom ModelAdmins.
That is, the view name admin:tighten gets overriden in the following example:
class Screw(models.Model):
"A screw"
class HexCapScrew(Screw):
"A hex cap screw"
class ScrewAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(ScrewAdmin, self).get_urls()
extra_urls = patterns('',
url(r'^tighten/$', self.tighten, name='tighten'),
)
return extra_urls + urls
def tighten(self, request):
pass
class HexCapScrewAdmin(ScrewAdmin):
pass
admin.site.register(Screw, ScrewAdmin)
admin.site.register(HexCapScrew, HexCapScrewAdmin)
On shell the following happens:
In [1]: reverse('admin:tighten')
Out[1]: u'/admin/parts/hexscrew/tighten/'
This is of course understandable since the registration of HexCapScrewAdmin overides the tighten in ScrewAdmin however now it's impossible to reverse ScrewAdmin.tighten.
A preferred solution
However I would like to be able to
reference both views separatedly and
preferably have views in their own instance namespaces.
Progress so far
The best I've come up with is the following setup (can be copy&pasted directly to some app for testing):
from django.contrib import admin
from django.db import models
class Screw(models.Model):
"A screw"
class Meta:
app_label = 'parts'
class HexCapScrew(Screw):
"A hex cap screw"
class Meta:
app_label = 'parts'
proxy = True
class ScrewAdmin(admin.ModelAdmin):
def tighten(self, request):
pass
def get_urls(self):
urls = super(ScrewAdmin, self).get_urls()
extra_urls = patterns('',
url(r'^tighten/$', self.tighten, name='tighten'),
)
# Find out the slugified name of the model this admin is bound to
# TODO: Feels dirty
model_name = self.model._meta.model_name
# Add the to `extra_urls` to their own namespace
namespaced_extra_urls = patterns('',
url(r'^', include(extra_urls, namespace=model_name, app_name='screw')),
)
return namespaced_extra_urls + urls
class HexCapScrewAdmin(ScrewAdmin):
pass
admin.site.register(Screw, ScrewAdmin)
admin.site.register(HexCapScrew, HexCapScrewAdmin)
Now I have the following:
In [1]: reverse('admin:screw:tighten')
Out[1]: u'/admin/parts/screw/tighten/'
In [2]: reverse('admin:hexscrew:tighten')
Out[2]: u'/admin/parts/hexscrew/tighten/'
In [3]: reverse('admin:screw:tighten', current_app='hexscrew')
Out[3]: u'/admin/parts/hexscrew/tighten/'
which is nice and works but includes a bit of hackery.
Is this the best that's available or am I just missing something? Any suggestions?
(At least one other way would be to do as Django's ModelAdmin.get_urls use ModelAdmin.model._meta to parametrize the view names but then I would use the namespaces.)
If you look at the way the admin does it here, you will see that in addition to defining the url, the model admin also prefixes the app_label and model_name to the url name, thus avoiding the subclassing issue to begin with. It also has the advantage of securing the view against unauthorised users (using the self.admin_site.admin_view decorator). Your get_urls() method would then become:
def get_urls(self):
from django.conf.urls import url
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
urlpatterns = super(ScrewAdmin, self).get_urls()
urlpatterns.append(
url(r'^tighten/$', wrap(self.tighten), name='%s_%s_tighten' % info))
return urlpatterns
Then, you'd look up your url like: reverse('admin:app_screw_tighten') or reverse('admin:app_hex_screw_tighten').
In a Django application, I have more than a handful of views which return JSON, with an invocation similar to:
return HttpResponse(json.dumps(content), mimetype="application/json")
I want to start creating views that return either HTML or JSON depending on the Accept headers from the request. Possibly other types, too, but those are the main ones. I also want to get multiple URLs routed to this view; the file extensions ".html" and ".json" help tell clients which types they should Accept when making their request, and I want to avoid the "?format=json" antipattern.
What's the correct, blessed way to do this in Django with a minimum of boilerplate or repeated code?
(Edit: Rephrase in order to better follow SO's community guidelines.)
I think a class-based view mixin (django 1.3+) is the easiest way to do this. All your views would inherit from a base class that contains logic to respond with the appropriate content.
I think I may not be seeing your big picture here but this is what I would do:
Have a html template that you render when html is requested and keep your json.dumps(content) for when json is requested. Seems to be obvious but I thought i should mention it anyway.
Set your URLs to send you "json" or 'html'. :
(r'^some/path/(?P<url_path>.*)\.(?P<extension>html|json)$', 'some.redirect.view'),
(r'^/(?P<extension>html|json)/AppName', include(MyApp)),
# etc etc
and your view:
def myRedirectView(request, url_path, extension):
view, args, kwargs = resolve("/" + extension + "/" + urlPath)
kwargs['request'] = request
return view(*args, **kwargs)
I know this is a bit vague because I haven't fully thought it through but its where I would start.
I have addressed this by creating a generic view class, based on Django's own generic.View class, that defines a decorator 'accept_types'. This modifies the view to which it is applied so that it returns None if the indicated content-type is not in the Accept header. Then, the get() method (which is called by the generic.View dispatcher) looks like this:
def get(self, request):
self.request = request # For clarity: generic.View does this anyway
resultdata = { 'result': data, etc. }
return (
self.render_uri_list(resultdata) or
self.render_html(resultdata) or
self.error(self.error406values())
)
The actual view renderers are decorated thus:
#ContentNegotiationView.accept_types(["text/uri-list"])
def render_uri_list(self, resultdata):
resp = HttpResponse(status=200, content_type="text/uri-list")
# use resp.write(...) to assemble rendered response body
return resp
#ContentNegotiationView.accept_types(["text/html", "application/html", "default_type"])
def render_html(self, resultdata):
template = loader.get_template('rovserver_home.html')
context = RequestContext(self.request, resultdata)
return HttpResponse(template.render(context))
The (one-off) generic view class that declares the decorator looks like this:
class ContentNegotiationView(generic.View):
"""
Generic view class with content negotiation decorators and generic error value methods
Note: generic.View dispatcher assigns HTTPRequest object to self.request.
"""
#staticmethod
def accept_types(types):
"""
Decorator to use associated function to render the indicated content types
"""
def decorator(func):
def guard(self, values):
accept_header = self.request.META.get('HTTP_ACCEPT',"default_type")
accept_types = [ a.split(';')[0].strip().lower()
for a in accept_header.split(',') ]
for t in types:
if t in accept_types:
return func(self, values)
return None
return guard
return decorator
(The parameter handling in the decorator should be generalized - this code works, but is still in development as I write this. The actual code is in GitHub at https://github.com/wf4ever/ro-manager/tree/develop/src/roverlay/rovweb/rovserver, but in due course should be separated to a separate package. HTH.)
I want to use some middleware I wrote across the whole of my site (large # of pages, so I chose not to use decorators as I wanted to use the code for all pages). Only issue is that I don't want to use the middleware for the admin code, and it seems to be active on them.
Is there any way I can configure the settings.py or urls.py perhaps, or maybe something in the code to prevent it from executing on pages in the admin system?
Any help much appreciated,
Cheers
Paul
The main reason I wanted to do this was down to using an XML parser in the middleware which was messing up non-XML downloads. I have put some additional code for detecting if the code is XML and not trying to parse anything that it shouldn't.
For other middleware where this wouldn't be convenient, I'll probably use the method piquadrat outlines above, or maybe just use a view decorator - Cheers piquadrat!
A general way would be (based on piquadrat's answer)
def process_request(self, request):
if request.path.startswith(reverse('admin:index')):
return None
# rest of method
This way if someone changes /admin/ to /django_admin/ you are still covered.
You could check the path in process_request (and any other process_*-methods in your middleware)
def process_request(self, request):
if request.path.startswith('/admin/'):
return None
# rest of method
def process_response(self, request, response):
if request.path.startswith('/admin/'):
return response
# rest of method
You don't need to muck around with paths.
If you want to exclude a single middleware from a view, you must first import that middleware and do:
from django.utils.decorators import decorator_from_middleware
from your.path.middlewares import MiddleWareYouWantToExclude
#decorator_from_middleware(MiddleWareYouWantToExclude)
def your_view(request):
....
If you want to exclude ALL middleware regardless of what they are/do, do this:
from django.conf import settings
from django.utils.module_loading import import_string
from django.utils.decorators import decorator_from_middleware
def your_view(request):
...
# loop over ALL the active middleware used by the app, import them
# and add them to the `decorator_from_middleware` decorator recursively
for m in [import_string(s) for s in settings.MIDDLEWARE]:
your_view = decorator_from_middleware(m)(your_view)
middleware functions are basically called for every request, including image src, api, form, ajax calls etc. somehow people reckon it is only called before and after view. This not only can cause performance concern, but also is hard to change.
I do not recommend path comparison unless using reverse, on the one hand, reference modification may create more trouble; on the other hand, if conditionals seems messy.
here I provide a solution for converting middleware to decorator, this method requires you to add decorator manually to all views. Fully optimised, you can make decisions which right middleware is for the right view
before I had:
class AjaxMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
def is_ajax(self):
return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
request.is_ajax = is_ajax.__get__(request)
response = self.get_response(request)
return response
class DeletionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
from staff.models import Deletion
response = self.get_response(request)
Deletion.auto_clean_up()
return response
MIDDLEWARE = [
'common.middleware.DeletionMiddleware',
'common.middleware.AjaxMiddleware',
]
now I created a decorators.py:
from django.contrib.auth.models import User
class Shape:
#staticmethod
def before(request):
pass
#staticmethod
def after(request):
pass
# All default to call, pass in as excluder param to escape
# Part default not to call, pass in as includer param to execute
class All(Shape): pass
class Part(Shape): pass
class Mixins:
class Online(Part):
#staticmethod
def after(request):
if request.user.is_authenticated:
request.user.profile.visit()
class Deletion(All):
#staticmethod
def after(request):
from staff.models import Deletion
Deletion.auto_clean_up()
class Ajax(All):
#staticmethod
def before(request):
def is_ajax(self):
return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
request.is_ajax = is_ajax.__get__(request)
def common_dec(excluders=[], includers=[]):
def common(function):
# def wrapper_func(*args, **kwargs):
# return function(*args, **kwargs)
# return wrapper_func
"""
common view that returns a template
does not incldue images and cdn access
"""
def wrapper_func(request, *args, **kwargs):
# iterate all classes in Mixins
alls = All.__subclasses__()
parts = Part.__subclasses__()
list(map(lambda cls:cls.before(request) if cls.__name__ not in excluders else 0, alls))
list(map(lambda cls:cls.before(request) if cls.__name__ in includers else 0, parts))
response = function(request, *args, **kwargs)
list(map(lambda cls:cls.after(request) if cls.__name__ not in excluders else 0, alls))
list(map(lambda cls:cls.after(request) if cls.__name__ in includers else 0, parts))
return response
return wrapper_func
return common
def common_mix(excluders=[], includers=[]):
class CommonMixin:
def dispatch(self, request, *args, **kwargs):
alls = All.__subclasses__()
parts = Part.__subclasses__()
list(map(lambda cls:cls.before(request) if cls.__name__ not in excluders else 0, alls))
list(map(lambda cls:cls.before(request) if cls.__name__ in includers else 0, parts))
response = super().dispatch(request, *args, **kwargs)
list(map(lambda cls:cls.after(request) if cls.__name__ not in excluders else 0, alls))
list(map(lambda cls:cls.after(request) if cls.__name__ in includers else 0, parts))
return response
return CommonMixin
which I can do
# common_dec implement all Shape.All middleware
#common_dec()
def index(request):
return render(request, 'index.html')
# this will except Deletion middleware, and include Online middleware, and execute all Shape.All
#common_dec(['Deletion'], ['Online'])
def index(request):
return render(request, 'index.html')
for class based view:
# this will execute all Shape.All, and include Online which is a Shape.Part
class ArticleDetailView(common_mix(excluders=['Online']), LoginRequiredMixin, DetailView):
pass
I have give up middleware since it lacks flexibility, this method largely saves performance and is implementable for large scopes view definition, will not causing any bug.
if you want to add a middleware, simply add a class to Mixin inheriting Shape.All, then it will be called for all decorated view function and classes. if you have a new type of view function that needs a new middleware, define it as Part and include it in decorator .it is also extremely convenient to exclude some middleware in particular view.