EDIT: I'm using rest_framework.reverse and providing the app name not the namespace for the reverse lookup. An example reverse call is:
reverse('api_app:system-detail', kwargs={...}, request=self.context.get('request'))
So I have a django project with a single app, and I have two different url patterns mapped to this one app. I currently have this mostly working, however for a few cases using reverse() results in the wrong namespace. My base urls.py looks like this:
from django.conf.urls import url, include
from django.views.generic.base import RedirectView
urlpatterns = [
url(r'^api/', include('api_project.api_app.urls',
namespace='api')),
url(r'^beta/api/', include('api_project.api_app.urls',
namespace='beta')),
url(r'^$', RedirectView.as_view(url='api', permanent=False),
name='index')
]
EDIT: api_app.urls looks like:
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from . import views
from rest_framework.schemas import get_schema_view
schema_view = get_schema_view(title="Schema")
app_name = 'api_app'
urlpatterns = [
url(r'^$', views.APIRoot.as_view(), name='api-root'),
url(r'^schema/$', schema_view, name'api-schema'),
....
In most cases reverse works as expected, e.g. using /beta/endpoint has links on the page that look like:
/beta/endpoint/example
However, in a few cases using /beta/endpoint has links like:
/api/endpoint/different-example
My question is less about how reverse determines which namespace to use, but instead is this the appropriate way to use two namespaces for the same app? Is this something I should approach completely differently?
Most of my research so far has not resulted in much success, so I'm thinking this might be fundamentally the wrong approach.
If this approach is okay, then I'm curious why reverse is inconsistent in which namespace it returns.
The documentation on reversing namespaced URLs shows you what happens. In your case, you are instantiating the same app twice, once with the instance namespace api and one with the instance namespace beta.
Your application namespace is api_app, as defined by the app_name variable in your included urls.py file. So it doesn't match any of the two instance namespaces, meaning there is no default application instance.
So in your case, the URL resolver will mostly resolve to case 4: If you don't specify your current_app in the view, it will resolve to the last deployed instance (meaning the last registered), which is "beta".
You should specify your current_app in your view, both when using reverse and on the request object (so that your templates can also resolve correctly).
Related
I’m still learning django and assume this may be easy for some. I’m trying to figure out the best way of simply setting up the API URLs (and so that they all display in the api root and can actually be used in the project - in my case at /api/). I’m using django rest framework, and can’t seem to set up more than one API - it works for just one, but the problem occurs when trying to set up another.
So I have an app called pages and accounts (and core - the default where the main urls.py is). I’ve created another urls.py inside the pages app and another inside the accounts app.
accounts/urls.py:
from . import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r"accounts", views.AccountsViewSet, basename="accounts")
urlpatterns = router.urls
pages/urls.py:
from . import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r"pages", views.PagesViewSet, basename="pages")
urlpatterns = router.urls
And the core urls.py:
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from rest_framework.routers import DefaultRouter
router = routers.DefaultRouter()
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("pages.urls")), # works but root only shows pages API
# path("api/", include("pages.urls", "accounts.urls")), # fails with error: “django.core.exceptions.ImproperlyConfigured: Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead.”
# path("api/", include(router.urls)), # no error but root api is empty
]
I would assume, possibly incorrectly, that just router.urls should include all the apis when visiting the root. The root apis currently looks like this when using just include("pages.urls”):
{
"pages": "http://localhost:8000/api/pages/"
}
How can I get it to correctly show all apis? The only way I could do it was by putting router.register(r"accounts", views.AccountsViewSet, basename="accounts”) in the pages urls.py, which is very undesirable, especially as the project grows even further.
Thank you
Have you tried to use:
path("api/pages/", include("pages.urls")),
path("api/accounts/", include("accounts.urls")),
In your urls.py?
Possibly that would mean your routes would be:
{
"pages": "http://localhost:8000/api/pages/pages/"
"accounts": "http://localhost:8000/api/accounts/accounts/"
}
In that case you could try to use
router.register("", views.AccountsViewSet, basename="accounts")
in accounts/urls.py.
And similarly,
router.register("", views.AccountsViewSet, basename="pages")
in pages/urls.py.
That way, you might achieve to have routes like:
{
"pages": "http://localhost:8000/api/pages/"
"accounts": "http://localhost:8000/api/accounts/"
}
if that is what you want.
When include()ing urlconf from the Django app to the project's urls.py, some kind of app's name (or namespace) should be specified as:
app_namespace in include((pattern_list, app_namespace), namespace=None) in main urls.py
or
app_name variable in app's urls.py.
Since, I guess, Django 2, the second method is the preferred one Although I copy-pasted first function signature from Django 3 documentation. But that's not the main point.
My current understanding of namespace parameter of include() is that it's what I use when using reverse().
What is the purpose of app_name in app's urls.py or app_namespace in main urls.py?
Are these exactly the same thing?
How is it used by Django?
Existing questions (and answers) I've found here explain HOW I should specify it rather than WHY.
In this answer, I am taking the DRF package and its URL patterns. If you want to try any of the snippets mentioned in this answer, you must install (pip install djangorestframework) and add rest_framework to INSTALLED_APPS list.
The application namespace can be set in two ways, [Ref: Django doc]
in urls.py using app_name varibale.
You can see that DRF has set the app_name in urls.py. Django will use this app_name as the application namespace only if we are included the patterns with module reference.
That is, include(module, namespace=None)
Example:
urlpatterns = [
path('drf-auth/bare/', include('rest_framework.urls')),
]
in include((pattern_list, app_namespace), namespace=None) function using app_namespace parameter.
In this method, you can set an additional app_namespace for the application, if you want.
Most importantly, we are passing a pattern_list instead of module
Example:
from rest_framework.urls import urlpatterns as drf_urlpatterns
urlpatterns = [
path('drf-auth/foo/', include((drf_urlpatterns, 'foo-app-namespace'))),
]
Complete Example
from django.urls import path, include, reverse
from rest_framework.urls import urlpatterns as drf_urlpatterns
urlpatterns = [
path('drf-auth/bare/', include('rest_framework.urls')),
path('drf-auth/foo/', include((drf_urlpatterns, 'foo-app-namespace'))),
path('drf-auth/bar/', include((drf_urlpatterns, 'bar-app-namespace'))),
]
print(reverse('rest_framework:login'))
print(reverse('foo-app-namespace:login'))
print(reverse('bar-app-namespace:login'))
#results
/drf-auth/bare/login/
/drf-auth/foo/login/
/drf-auth/bar/login/
What is the purpose of app_name in app's urls.py or app_namespace in main urls.py?
Both are used to set the application namespace. The app_name can be used as a default application namespace, if defined in the urls.py.
Are these exactly the same thing?
No.
How is it used by Django?
The application namespace and instance namespace are used to retrieve the URL path. In Django, whenever the reverse(...) function get executed, Django looking for an application namespace first, than any other. You can read more about how Django resolve the URL here, Reversing namespaced URLs
app_name in app/urls.py and app_namespace in include((pattern_list, app_namespace), namespace=None) in main urls.py are the same referenced as application namespace which describes the name of the application that is being deployed.
One can either pass the whole app/urls.py or a string reference of app/urls.py with app_name to include()
# blog.urls
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.index(), name='index'),
path('<int:pk>/', views.post(), name='detail'),
]
# project urls
from django.urls import include, path
urlpatterns = [
path('', include('blog.urls')),
]
OR
a tuple of url patterns and app_namespace to include()
# project urls
from django.urls import include, path
from blog.urls import urlpatterns as blogpatterns
urlpatterns = [
path('', include((blogpatterns, 'blog'))),
]
app_namespace will be the default application namespace when provided in include(). If app_namespace is not provided, then it will look for app_name in blog/urls.py and that will be the default namespace.
Without the app namespace, urls are added to global namespace which may lead to url conficts.
URL namespaces and included URLconfs | Term application namespace | include()
For years, we've (at Django) been skirting around the (confusing) distinction between an application name(space) and an instance namespace. We've always just recommended using instance namespace, as per the examples in the docs.
What's happened with Django 2.0 (and onwards) is they've made it so you can't use an instance namespace without also (first) using an application name. Instead of fixing code, we should update our examples to the correct usage.
The include needs to go like this:
urlpatterns += [ url('API/', include((router.urls, 'pikachu')) ]
The tendency is to include the second namespace='pikachu' instance namespace parameter as well, but that's not needed — it defaults to None and is set to 'pikachu' in this case automatically.
Generally, users want to be including an app-level URLs module explicitly setting the app_name attribute there, rather than including the router by hand.
From https://docs.djangoproject.com/en/3.0/topics/http/urls/#introduction :
URL namespaces allow you to uniquely reverse named URL patterns even
if different applications use the same URL names. It’s a good practice
for third-party apps to always use namespaced URLs (as we did in the
tutorial). Similarly, it also allows you to reverse URLs if multiple
instances of an application are deployed. In other words, since
multiple instances of a single application will share named URLs,
namespaces provide a way to tell these named URLs apart.
I'm setting the docs for my api and when I ask for (http://localhost:8000/api/v0/docs/) The app show me this error:
NoReverseMatch at /api/v0/docs/
'api-docs' is not a registered namespace
when try to do this
<script src="{% url **'api-docs:schema-js'** %}"></script>
this is my urls.py file:
from django.contrib import admin
from django.conf.urls import url, include
from django.contrib.auth.models import User
from . import views
from rest_framework import routers, serializers, viewsets
from rest_framework.documentation import include_docs_urls
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'goals', views.GoalViewSet)
urlpatterns = [
url(r'^goals/$', include(router.urls)),
url(r'^docs/', include_docs_urls(title='My API title')),
]
I added the namespace to include_docs_urls but it doesn't work. (I have installed coreapi, pygments and markdown).
Beforehand thanks for helping me.
You should move your docs url pattern into your main urls.py file. This is still a problem in DRF, there is also an issue opened on the official GitHub source: https://github.com/encode/django-rest-framework/issues/4984
The official developer of Django REST Framework, Tom Christie, says:
It's important enough that I'd like to see it highly prioritized, but there's a lot of other things going on at the moment too. Even after we release 3.6.3, there's still also all the work towards 3.7.0's realtime integration.
So your docs pattern should not have any prefixes (like api/v0/).
In conclusion, change your main urls.py:
url(r'^docs/', include_docs_urls(title='API Title'))
In Django, is it possible to have two different files with url patterns, neither of which is called urls.py ? Or does Django rely on there being only one set of url patterns per Django app, and that it must be called urls.py ?
I'm using Django CMS and I want to split an app across two apphooks and two menus. So I've tried splitting urls.py into pub_urls.py and train_urls.py but I appear to have broken things by doing that, despite the cms_app.py naming the correct urls - eg:
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from django.utils.translation import ugettext_lazy as _
from resources.menu import TrainingMenu, PublicationMenu
class PublicationApp(CMSApp):
name = _("Publication App") # give your app a name, this is required
urls = ["resources.pub_urls"] # link your app to url configuration(s)
menus = [PublicationMenu]
class TrainingApp(CMSApp):
name = _("Training App") # give your app a name, this is required
urls = ["resources.train_urls"] # link your app to url configuration(s)
menus = [TrainingMenu]
apphook_pool.register(PublicationApp) # register your app
apphook_pool.register(TrainingApp) # register your app
Is something like this possible? Or do I have to split this into two different apps?
There is nothing to stop your urls.py simply acting as a way of including multiple other urls files:
urls.py:
from django.conf.urls.defaults import patterns, include
urlpatterns = urlpatterns + patterns('',
(r'^', include('pub_urls')),
(r'^', include('train_urls')))
pub_urls.py:
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('',
(r'^$', 'homeview'),
(r'^stuff/$', 'stuffview')
)
etc.
ROOT_URLCONF in your settings file points to the root url file.
Django doesn't care what your urlpatterns file is called. The default base urlconf is by convention called urls.py, but in fact that's just a setting and can be overridden. After that, you need to explicitly include urlconfs by module name, so again it makes no difference what they're called.
I'm not familiar with Django-CMS and I don't know what it's doing in its CMSApp class, but I suspect you're going to have to dig into that to see what's going on.
It is configurable using ROOT_URLCONF setting.
From django docs
ROOT_URLCONF
A string representing the full Python import path to your root URLconf.
For example: "mydjangoapps.urls". Can be overridden on a per-request basis
by setting the attribute urlconf on the incoming HttpRequest object. See How
Django processes a request for details.
You can also write/get a middleware which can set it appropriately depending upon the host or other parameters in the request.
Is there a benefit to passing a string in your url patters vs a function instance? It seems like it could be optimized to not actually load the function until it is needed, but is this in fact true?
from django.conf.urls.defaults import *
from myapp.views import myView
urlpatterns = patterns('',
# as a string
url(r'^as-string/$', "myapp.views.myView"),
# Uploading photos
url(r'^as-instance/$', myView),
)
edit: If it's true that it doesn't import until they're needed, then it would be an optimization for memory, but non-existant functions and other errors would not be raised until you actually try to visit the url.
Of course that isn't an issue if you write tests though ;)
The main benefit is that when you're working with the actual callable object you can do things like apply decorators to it in the URLConf. So you can do things like:
from django.conf.urls.defaults import *
from django.contrib.auth.decorators import login_required
from some_module import some_view
urlpatterns = patterns('',
(r'^some_url/$', some_view),
(r'^some_other_url/$', login_required(some_view)))
etc.
This lets you have decorators which are only applied to a view when you specifically want them to, rather than decorating in the views file and then having to live with it always having that decorator applied.
Perusing the source to RegexURLPattern (which is what defaults.url is using under the covers) confirms that the import only happens when and if needed, and so your ideas are correct: it's a slight optimization but does require thorough tests!