I have a old endpoint with a URL like this:
url(r'^vehicles/([^/]{1,50})/trips/data/?$', 'vehicle_trip_data'),
which was mapped to my function based view.
I am now refactoring the works to work with drf-nested-routers.
What I know is, that I can route to custom methods. Like when I have the former Endpoint url(r'^vehicles/([^/]{1,50})/trips/?$', 'vehicle_trips_view') I am just adding a method trips like this:
#detail_route(methods=['GET'], permission_classes=[IsAuthenticated, VehiclePermissions])
def trips(self, request, pk=None):
I obviously can't just name the method trips/data.
So is there a way how I can make a detailed route for the first example!?
Try specifying trips/data in url_path parameter.
#detail_route(methods=['GET'], permission_classes=[IsAuthenticated, VehiclePermissions], url_path='trips/data')
def trips(self, request, pk=None):
Related
In Flask, you create routes like this:
from flask import Flask
app = Flask(__name__)
#app.route("/foobar")
def foo_bar_endpoint():
...
But the route decorator also has an endpoint parameter. The documentation just states:
the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint
Why should / would one not simply name the function as the endpoint should be called? Is this relevant for anything else than url_for?
The "endpoint" is an arbitrary name you provide, which is basically the canonical thing that a route refers to. You're not actually mapping a URL to a function there, you're mapping a URL to an endpoint and are attaching a view function to that endpoint.
URL → endpoint → function
You're just doing all that in one action, and the name of the function automatically becomes the name of the endpoint in the process. But you could do this all separately and register an endpoint for a URL, and later attach a function to that endpoint:
Basically this example:
#app.route('/')
def index():
pass
Is equivalent to the following:
def index():
pass
app.add_url_rule('/', 'index', index)
If the view_func is not provided you will need to connect the endpoint to a view function like so:
app.view_functions['index'] = index
https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.add_url_rule
You may want to name your endpoint independently of your function, if you're under some constraint for the function name but are already referring to the endpoint from elsewhere, especially during refactoring, as one possible example where that might become relevant.
The URL is for external clients, for HTTP-requests.
The endpoint is for internal usage if you need to know which URL to redirect for example without any hardcoding.
In case you not defining endpoint your code looks like:
blueprint = Blueprint("bp_1", __name__)
#blueprint.route("/foobar")
def foo_bar_endpoint():
...
url = flask.url_for("bp_1.foo_bar_endpoint")
In case you define endpoint, you use function url_for with its name:
#blueprint.route("/foobar", endpoint="custom_name")
def foo_bar_endpoint():
...
url = flask.url_for("bp_1. custom_name")
What is the best way to get DRF API view data into another Django view?
Currently I am just calling the view through requests module:
response = requests.get('my_api_view')
but is there a cleaner way?
I can also just instantiate the API view class in another view, but this will give me unserialized objects.
Not sure what you mean by getting unserialized objects. You can do the following if you're using function-based views:
def view(request):
# some stuff done
return Response(<result>)
def another_view(request)
return view(request)
If you're views are class based then you can do the following:
class AClassBasedView(SomeMixin, SomeOtherMixin):
def get(self, request):
# do something with the request
return Response(<some result>)
class AnotherClassBasedView(SomeMixin, SomeOtherMixin):
def compute_context(self, request, username):
#some stuff here here
return AnotherClassBasedView.as_view()(request)
Both of these will return a <class 'rest_framework.response.Response'> object which can be passed further.
I'm not sure what exactly you want to achieve but wanting to call a view from another view maybe a sign of bad architecture. It could mean that you have a business logic implemented in the second view which you want to access in the first. Usually the rule of thumb is to move such a common logic somewhere else so it could be used by different views.
Again I don't know what you want to achieve but this is a possibilty.
I have a ViewSet like
class CustomerViewSet(ModelViewSet):
queryset = models.Customer.objects.all()
serializer_class = serializers.CustomerSerializer
filter_class = filters.CustomerFilterSet
#detail_route
def licenses(self, request, pk=None):
customer = self.get_object()
licenses = Item.objects.active().sold().for_customer(customer)
serializer = serializers.ItemSerializer(licenses, many=True)
return Response(serializer.data)
Strapped into urls.py with router.register(r'customers', views.CustomerViewSet)
i can GET /api/customers and GET /api/customers/1000, but GET /api/customers/1000/licenses is not found. It gives me 404. The flow never enters the licenses method.
I looked at similar questions here, but they used an incorrect signature, which I do not: def licenses(self, request, pk=None)
python 3.4.0
djangorestframework==3.2.3
EDIT: Once again, I find my answer less than a minute after asking... Apparantly, the decorater needs parenthesees like #detail_route(). I thought these were always optional by convention..?
For a post request you'll need to specify the method because the detail_route decorator will route get requests by default.
Like so: #detail_route(methods=['POST'])
By default, router adds trailing slash to the url. So, GET /api/customers/ would be working instead of GET /api/customers. If you don't want to use trailing slash you can pass trailing_slash = False to router initializer.
E.g.-
router = DefaultRouter(trailing_slash = False)
router.register(r'customers', views.CustomerViewSet)
If that does not works, then there is a problem with the way you are importing router urls into main urls.
I need to have a dynamic URL prefix for all URLs in my app.
I'm familiar with doing a static prefix, such as url(r'^myprefix/app', include('app.urls')).
Instead, I need the myprefixto be dynamic, such as url(r'^(?P<prefix>\w+)/app', include('app.urls')).
That works, but here's the kicker. I don't want that prefix to be sent as a keyword argument to all of the views. I want to be able to capture it and use it in Middleware or something similar.
To give my specific use case, we have software (this Django project) used to manage various testing labs. The app needs knowledge of which lab it is operating on.
Currently I'm doing this with the following:
class LabMiddleware(object):
def process_request(self, request):
request.current_lab = 'lab1' # Note that this is currently hard coded
The requirement states that the users be able to go to a URL such as http://host/<lab_name>/app where the lab_name would then get used in my LabMiddleware. Because of this, I obviously don't want to have to accept the lab_name in every single one of my views as it's cumbersome and overkill.
UPDATE:
Building on what Sohan gave in his answer, I ended up using a custom middleware class:
urls.py
url(r'^(?P<lab_name>\w+)/', include('apps.urls')),
apps/urls.py
url(r'^app1/', include('apps.app1.urls', namespace='app1')),
middleware.py
class LabMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
lab_name = view_kwargs.get('lab_name')
if lab_name:
request.current_lab = Lab.objects.get(name=lab_name)
# Remove the lab name from the kwargs before calling the view
view_kwargs.pop('lab_name')
return view_func(request, *view_args, **view_kwargs)
settings.py
MIDDLEWARE_CLASSES = (
# Default Django middleware classes here...
'raamp.middleware.LabMiddleware',
)
This allowed me to have the lab name in the URL and to add it to the request. Then by removing it from view_kwargs, it doesn't get passed on to the view function and everything works as I intended it.
Also note that the code I have above isn't the most optimized (e.g. I'm querying the database for every request). I stripped out the code I have for caching this as it's not important to showing how this problem was solved, but is worth mentioning that some improvements should be made to this code if you are using it in a production system.
You can create a decorator that wraps each view function. The decorator can take care of any processing work you have on the lab name parameter, and every view won't need to see the lab_name param.
def process_lab_name(request, lab_name):
request.current_lab = lab_name
def lab(view_func):
def _decorator(request, lab_name, *args, **kwargs):
# process the lab_name argument
process_lab_name(request, lab_name)
# when calling the view function, exclude the lab_name argument
response = view_func(request, *args, **kwargs)
return response
return wraps(view_func)(_decorator)
#lab
def some_view(request):
return render(...)
And your route will look like url(r'^(?P<lab_name>\w+)/app'
The Flask documentation shows:
add_url_rule(*args, **kwargs)
Connects a URL rule. Works exactly like the route() decorator.
If a view_func is provided it will be registered with the endpoint.
endpoint – the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint
What exactly is meant by an "endpoint"?
How Flask Routing Works
The entire idea of Flask (and the underlying Werkzeug library) is to map URL paths to some logic that you will run (typically, the "view function"). Your basic view is defined like this:
#app.route('/greeting/<name>')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
Note that the function you referred to (add_url_rule) achieves the same goal, just without using the decorator notation. Therefore, the following is the same:
# No "route" decorator here. We will add routing using a different method below.
def give_greeting(name):
return 'Hello, {0}!'.format(name)
app.add_url_rule('/greeting/<name>', 'give_greeting', give_greeting)
Let's say your website is located at 'www.example.org' and uses the above view. The user enters the following URL into their browser:
http://www.example.org/greeting/Mark
The job of Flask is to take this URL, figure out what the user wants to do, and pass it on to one of your many python functions for handling. It takes the path:
/greeting/Mark
...and matches it to the list of routes. In our case, we defined this path to go to the give_greeting function.
However, while this is the typical way that you might go about creating a view, it actually abstracts some extra info from you. Behind the scenes, Flask did not make the leap directly from URL to the view function that should handle this request. It does not simply say...
URL (http://www.example.org/greeting/Mark) should be handled by View Function (the function "give_greeting")
Actually, it there is another step, where it maps the URL to an endpoint:
URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "give_greeting".
Requests to Endpoint "give_greeting" should be handled by View Function "give_greeting"
Basically, the "endpoint" is an identifier that is used in determining what logical unit of your code should handle the request. Normally, an endpoint is just the name of a view function. However, you can actually change the endpoint, as is done in the following example.
#app.route('/greeting/<name>', endpoint='say_hello')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
Now, when Flask routes the request, the logic looks like this:
URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "say_hello".
Endpoint "say_hello" should be handled by View Function "give_greeting"
How You Use the Endpoint
The endpoint is commonly used for the "reverse lookup". For example, in one view of your Flask application, you want to reference another view (perhaps when you are linking from one area of the site to another). Rather than hard-code the URL, you can use url_for(). Assume the following
#app.route('/')
def index():
print url_for('give_greeting', name='Mark') # This will print '/greeting/Mark'
#app.route('/greeting/<name>')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
This is advantageous, as now we can change the URLs of our application without needing to change the line where we reference that resource.
Why not just always use the name of the view function?
One question that might come up is the following: "Why do we need this extra layer?" Why map a path to an endpoint, then an endpoint to a view function? Why not just skip that middle step?
The reason is because it is more powerful this way. For example, Flask Blueprints allow you to split your application into various parts. I might have all of my admin-side resources in a blueprint called "admin", and all of my user-level resources in an endpoint called "user".
Blueprints allow you to separate these into namespaces. For example...
main.py:
from flask import Flask, Blueprint
from admin import admin
from user import user
app = Flask(__name__)
app.register_blueprint(admin, url_prefix='admin')
app.register_blueprint(user, url_prefix='user')
admin.py:
admin = Blueprint('admin', __name__)
#admin.route('/greeting')
def greeting():
return 'Hello, administrative user!'
user.py:
user = Blueprint('user', __name__)
#user.route('/greeting')
def greeting():
return 'Hello, lowly normal user!'
Note that in both blueprints, the '/greeting' route is a function called "greeting". If I wanted to refer to the admin "greeting" function, I couldn't just say "greeting" because there is also a user "greeting" function. Endpoints allow for a sort of namespacing by having you specify the name of the blueprint as part of the endpoint. So, I could do the following...
print url_for('admin.greeting') # Prints '/admin/greeting'
print url_for('user.greeting') # Prints '/user/greeting'
Endpoint is the name used to reverse-lookup the url rules with url_for and it defaults to the name of the view function.
Small example:
from flask import Flask, url_for
app = Flask(__name__)
# We can use url_for('foo_view') for reverse-lookups in templates or view functions
#app.route('/foo')
def foo_view():
pass
# We now specify the custom endpoint named 'bufar'. url_for('bar_view') will fail!
#app.route('/bar', endpoint='bufar')
def bar_view():
pass
with app.test_request_context('/'):
print url_for('foo_view')
print url_for('bufar')
# url_for('bar_view') will raise werkzeug.routing.BuildError
print url_for('bar_view')
If you have same class name and want to map with multiple routes, then specify the endpoint, so that framework will differentiate between two:
class ClassName(Resource):
def get(self):
if request.endpoint!='hello':
return {"data": "Hello"}
elif:
return {"data" : "World"}
api.add_resource(ClassName, '/rout1', endpoint = "world")
api.add_resource(ClassName, '/rout2', endpoint="hello")
#app.route('/') #Endpoint
def a_function(): #View function
return 'view'
Inside Flask, every endpoint with its request methods mapped to a view function. When you use app.route decorator you are actually adding a URL rule.