How to set static_url_path in Flask application - flask

I want to do something like this:
app = Flask(__name__)
app.config.from_object(mypackage.config)
app.static_url_path = app.config['PREFIX']+"/static"
when I try:
print app.static_url_path
I get the correct static_url_path
But in my templates when I use url_for('static'), The html file generated using jinja2 still has the default static URL path /static with the missing PREFIX that I added.
If I hardcode the path like this:
app = Flask(__name__, static_url_path='PREFIX/static')
It works fine. What am I doing wrong?

Flask creates the URL route when you create the Flask() object. You'll need to re-add that route:
# remove old static map
url_map = app.url_map
try:
for rule in url_map.iter_rules('static'):
url_map._rules.remove(rule)
except ValueError;
# no static view was created yet
pass
# register new; the same view function is used
app.add_url_rule(
app.static_url_path + '/<path:filename>',
endpoint='static', view_func=app.send_static_file)
It'll be easier just to configure your Flask() object with the correct static URL path.
Demo:
>>> from flask import Flask
>>> app = Flask(__name__)
>>> app.url_map
Map([<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])
>>> app.static_url_path = '/PREFIX/static'
>>> url_map = app.url_map
>>> for rule in url_map.iter_rules('static'):
... url_map._rules.remove(rule)
...
>>> app.add_url_rule(
... app.static_url_path + '/<path:filename>',
... endpoint='static', view_func=app.send_static_file)
>>> app.url_map
Map([<Rule '/PREFIX/static/<filename>' (HEAD, OPTIONS, GET) -> static>])

The accepted answer is correct, but slightly incomplete. It is true that in order to change the static_url_path one must also update the app's url_map by removing the existing Rule for the static endpoint and adding a new Rule with the modified url path. However, one must also update the _rules_by_endpoint property on the url_map.
It is instructive to examine the add() method on the underlying Map in Werkzeug. In addition to adding a new Rule to its ._rules property, the Map also indexes the Rule by adding it to ._rules_by_endpoint. This latter mapping is what is used when you call app.url_map.iter_rules('static'). It is also what is used by Flask's url_for().
Here is a working example of how to completely rewrite the static_url_path, even if it was set in the Flask app constructor.
app = Flask(__name__, static_url_path='/some/static/path')
a_new_static_path = '/some/other/path'
# Set the static_url_path property.
app.static_url_path = a_new_static_path
# Remove the old rule from Map._rules.
for rule in app.url_map.iter_rules('static'):
app.url_map._rules.remove(rule) # There is probably only one.
# Remove the old rule from Map._rules_by_endpoint. In this case we can just
# start fresh.
app.url_map._rules_by_endpoint['static'] = []
# Add the updated rule.
app.add_url_rule(f'{a_new_static_path}/<path:filename>',
endpoint='static',
view_func=app.send_static_file)

Just to complete the answer above, we also need to clear the view_functions that maps the endpoint with the delegate:
app.view_functions["static"] = None

I think you missed static_folder
app = Flask(__name__)
app.config.from_object(mypackage.config)
app.static_url_path = app.config['PREFIX']+"/static"
app.static_folder = app.config['PREFIX']+"/static"

Related

Persian text in url Django

I have some links that include Persian texts, such as:
http://sample.com/fields/طب%20نظامی
And in the view function I want to access to Persian part, so:
url = request.path_info
key = re.findall('/fields/(.+)', url)[0]
But I get the following error:
IndexError at /fields/
list index out of range
Actually, the problem is with the index zero because it can not see anything there! It should be noted that it is a Django project on IIS Server and I have successfully tested it with other servers and the local server. I think it has some thing related to IIS. Moreover I have tried to slugify the url without success. I can encode urls successfully, but I think it is not the actual answer to this question.
Based on the comments:
I checked the request.path too and the same problem. It contains:
/fields/
I implemented a sample django project in local server and here is my views:
def test(request):
t = request.path
return HttpResponse(t)
The results:
http://127.0.0.1:8000/تست/
/تست/
Without any problem.
Based on the #sytech comment, I have created a middlware.py in my app directory:
from django.core.handlers.wsgi import WSGIHandler
class SimpleMiddleware(WSGIHandler):
def __call__(self, environ, start_response):
print(environ['UNENCODED_URL'])
return super().__call__(environ, start_response)
and in settings.py:
MIDDLEWARE = [
...
'apps.middleware.SimpleMiddleware',
]
But I am getting the following error:
__call__() missing 1 required positional argument: 'start_response'
Assuming you don't have another problem in your rewrite configuration, on IIS, depending on your rewrite configuration, you may need to access this through the UNENCODED_URL variable which will contain the unencoded value.
This can be demonstrated in a simple WSGI middleware:
from django.core.handlers.wsgi import WSGIHandler
class MyHandler(WSGIHandler):
def __call__(self, environ, start_response):
print(environ['UNENCODED_URL'])
return super().__call__(environ, start_response)
You would see the unencoded URL and the path part that's in Persian would be passed %D8%B7%D8%A8%2520%D9%86%D8%B8%D8%A7%D9%85%DB%8C. Which you can then decode with urllib.parse.unquote
urllib.parse.unquote('%D8%B7%D8%A8%2520%D9%86%D8%B8%D8%A7%D9%85%DB%8C')
# طب%20نظامی
If you wanted, you could use a middleware to set this as an attribute on the request object or even override the request.path_info.
You must be using URL rewrite v7.1.1980 or higher for this to work.
You could also use the UNENCODED_URL directly in the rewrite rule, but that may result in headaches with routing.
I can encode urls successfully, but I think it is not the actual answer to this question.
Yeah, that is another option, but may result in other issues like this: IIS10 URL Rewrite 2.1 double encoding issue
You can do this by using python split() method
url = "http://sample.com/fields/طب%20نظامی"
url_key = url.split(sep="/", maxsplit=4)
url_key[-1]
output : 'طب%20نظامی'
in this url is splited by / which occurs 4 time in string so it will return a list like this
['http:', '', 'sample.com', 'fields', 'طب%20نظامی']
then extract result like this url_key[-1] from url_key
you can Split the URL by :
string = http://sample.com/fields/طب%20نظامی
last_part = string. Split("/")[-1]
print(last_part)
output :< طب%20نظامی >
slugify(last_part)
or
slugify(last_part, allow_unicode=True)
I guess This Will Help You :)

flask route with default argument at beginning of path

In flask, I can specify a default argument to a route thus:
#app.route("/user", defaults={'uid': 3})
#user.route('/user/<uid>')
def user(uid):
# If /user or /user/ is requested, I'll see uid=3.
# If /user/4 is requested, I'll see uid=4.
But I have a reverse path:
#app.route("/user", defaults={'thing': None})
#user.route('/<thing>/user/')
def user(thing):
# If /user or /user/ is requested, I want to see thing == None.
# If /dog/user/ is requested, I'll see thing == 'dog'.
Basically, if thing isn't provided, I'll do something a bit complex to compute it. But it's fixed for a session, so I pass it around.
I know that this is asking quite a lot from the route parser. (In particular, no thing had better conflict with another path, and I could easily cause quite a lot of inefficient backtracking if I'm not careful. But things are designed never to conflict.)
The question is whether there's a way to do this?
What I've done is to create two functions. But I have a handful of functions like this, so this is feeling heavy.
#app.route("/user")
def user():
return redirect(url_for('main.user', thing=ComputeThing()))
#user.route('/<thing>/user/')
def user(thing):
# If /user or /user/ is requested, I want to see thing == None.
# If /dog/user/ is requested, I'll see thing == 'dog'.
I must be missing something, can you expand on what you mean by a reverse path, because just using defaults seems to achieve what you want in your last example.
from flask import Flask
from random import choice
app = Flask(__name__)
choices = ['fox', 'cat', 'eel', 'cow']
def compute_thing():
return choice(choices)
#app.route("/user", defaults={'thing': None})
#app.route("/<thing>/user/")
def user(thing):
if thing is None:
thing = compute_thing()
return f"`thing` set as {thing}"
if __name__ == '__main__':
tc = app.test_client()
ru = tc.get('/user')
print(ru.data) # `thing` set as cow'
rd = tc.get('/dog/user/')
print(rd.data) # `thing` set as dog'

Airflow plugins, RBAC enabled Blueprint not working

We had our Airflow custom UI based on this link and it was working fine with Airflow 1.9.0.
Following this we upgraded to 1.10.1 and also enabled RBAC. Our custom UI stopped coming after this.
We followed this explanation note-on-role-based-views and tried to use our old UI templates with appbuilder_views. On the using the TestAppBuilderBaseView from /tests/plugins/test_plugin.py,
class TestAppBuilderBaseView(AppBuilderBaseView):
#expose("/")
def test(self):
return self.render("test_plugin/test.html", content="Hello galaxy!")
we get the menu and the link, but on clicking we get the error
object has no attribute 'render'
On changing this to
return self.render_template("test_plugin/test.html",content="Hello galaxy!")
we get the error
jinja2.exceptions.TemplateNotFound: test_plugin/test.html
I have tried all possible combination placing the templates folder and the html file, but still its the same error.
I do find some forums telling to enable debug on Blueprint. but I am not aware on how you can do that with Airflow
Any guidance on this please?.
Thanks in Advance
Jeenson
The version 1.10.0 when released had a bug that was not installing the plugins correctly in the new UI. This was fixed in the version 1.10.1, but the code example for plugins in Airflow documentation is broken.
I wrote a sample project to make the integration work, you can check it here: https://github.com/felipegasparini/airflow_plugin_rbac_test
But in short, you need to:
Import the BaseView from appbuilder correctly using:
from flask_appbuilder import BaseView as AppBuilderBaseView
Change the name of the method 'test' to 'list'
Set the template_folder property to point to where your templates are.
Something like this:
from airflow.plugins_manager import AirflowPlugin
from flask_appbuilder import BaseView as AppBuilderBaseView
class TestAppBuilderBaseView(AppBuilderBaseView):
template_folder = '/root/airflow/plugins/test_plugin/templates'
#expose("/")
def list(self):
return self.render_template("test.html", content="Hello galaxy!")
v_appbuilder_view = TestAppBuilderBaseView()
v_appbuilder_package = {"name": "Test View",
"category": "Test Plugin",
"view": v_appbuilder_view}
# Defining the plugin class
class AirflowTestPlugin(AirflowPlugin):
name = "test_plugin"
# operators = [PluginOperator]
# sensors = [PluginSensorOperator]
# hooks = [PluginHook]
# executors = [PluginExecutor]
# macros = [plugin_macro]
# admin_views = [v]
# flask_blueprints = [bp]
# menu_links = [ml]
appbuilder_views = [v_appbuilder_package]
# appbuilder_menu_items = [appbuilder_mitem]
I am also faced the same issue.
After including template folder in blueprint its picking up the correct folder and here is my working example.
Please keep the folder structure like below
Plugin
|_test_plugin
|_templates
|_test.html
test_plugin.py
test_plugin.py
from airflow.plugins_manager import AirflowPlugin
from flask import Blueprint
from flask_admin import BaseView, expose
from flask_admin.base import MenuLink
class TestView(BaseView):
#expose('/')
def test(self):
return self.render("test.html", content="Hello galaxy!")
v = TestView(category="Test Plugin", name="Test View")
blue_print_ = Blueprint("test_plugin",
__name__,
template_folder='templates')
class AirflowTestPlugin(AirflowPlugin):
name = "MenuLinks"
# operators = []
flask_blueprints = [blue_print_]
# hooks = []
# executors = []
admin_views = [v]
#appbuilder_views = [v_appbuilder_package]
fgasparini's answer is correct, but I also need to enable the RBAC setting
rbac = True
in airflow.cfg in order for flask_appbuilder to work with airflow, otherwise the menu won't show up.

What would be considered the root directory to a third party javascript in Django

When using Send Pulse they instruct you place a javascript in the head of your template. This javascript then refers to 2 other scripts that they instruct you to place in the root of you web site. Where can these be placed so that their javascript will automatically find them in the "web root"?
The point is that the service is expecting to find the scripts directly under /, eg mywebsite.com/script1.js. But in Django static files are usually under /static or the equivalent. There is nowhere you can just "place" them to get them to appear at the right URL.
But that doesn't mean you can't do it. The best thing to do would be to add an explicit mapping in your web server (nginx or Apache) for those scripts. For example, in nginx:
location /script1.js { alias /path/to/staticdir/script1.js; }
You have a number of possible options. In no particular order:
#daniel-roseman's suggestion of using a rewrite/alias configured in your webserver
Modify the javascript that you're going to place in the head of your page, so that it loads the scripts from your /static folder (followed by placing those scripts there, of course)
Use an HttpResponse to return these script(s) from a view function, and then map the target URLs (like mywebsite.com/script1.js) to the view.
This might look like:
# urls.py - assuming >= Django 2.0
from django.urls import path
urlpatterns = [
# place script1.js into your static folder, then reference its location within the static folder
path('script1.js', views.javascript_loader, {'script_path': 'script1.js'}),
]
# views.py
from django.http import HttpResponse
from django.conf import settings
import os
def javascript_loader(request, script_path):
# note: you should cache this content for the use case you've described, just
# consider this an illustration of how to load/return content from a static
# file using a view.
full_script_path = os.path.join(settings.STATIC_ROOT, script_path)
with open(full_script_path, 'r') as f:
javascript_contents = f.read()
return HttpResponse(javascript_contents, mimetype="text/javascript")
Again, not sure which solution would be best for your needs, but one of those should get you the result you need.

How to construct a full URL in django

I need to get django to send an email which contains a URL like this
http://www.mysite.org/history/
Where 'history' is obtained like so:
history_url = urlresolvers.reverse('satchmo_order_history')
history_url is a parameter that I pass on to the function that sends the email, and it correctly produces '/history/'. But how do I get the first part? (http://www.mysite.org)
Edit 1
Is there anything wrong or unportable about doing it like this? :
history = urlresolvers.reverse('satchmo_order_history')
domain = Site.objects.get_current().domain
history_url = 'http://' + domain + history
If you have access to an HttpRequest instance, you can use HttpRequest.build_absolute_uri(location):
absolute_uri = request.build_absolute_uri(relative_uri)
In alternative, you can get it using the sites framework:
import urlparse
from django.contrib.sites.models import Site
domain = Site.objects.get_current().domain
absolute_uri = urlparse.urljoin('http://{}'.format(domain), relative_uri)
Re: Edit1
I tend to use urlparse.join, because it's in the standard library and it's technically the most Pythonic way to combine URIs, but I think that your approach is fine too.