Django: include other urlpatterns in a single urls.py - django

I am doing something like this in myproject.myapp.urls:
from django.conf.urls.defaults import *
urlpatterns = patterns('myproject.myapp.views',
(ur'^$', 'index'),
(ur'^browse/$', 'browse'),
(ur'^request/new/$', 'new_request'),
(ur'^(?P<url_key>[-a-zA-Z0-9]+)/$', 'view1'),
(ur'^(?P<url_key>[-a-zA-Z0-9]+)/asdf$', 'view2'),
(ur'^(?P<url_key>[-a-zA-Z0-9]+)/qwer$', 'view3'),
(ur'^(?P<url_key>[-a-zA-Z0-9]+)/zxcv$', 'view4'),
(ur'^(?P<url_key>[-a-zA-Z0-9]+)/tyui$', 'view5'),
(ur'^(?P<url_key>[-a-zA-Z0-9]+)/ghjk$', 'view6'),
(ur'^(?P<url_key>[-a-zA-Z0-9]+)/bnm/more-looong-url/$', 'view7'),
...
)
I've tried to refactor above rules and define them in another file urls2.py like this:
(ur'^(?P<url_key>[-a-zA-Z0-9]+)/', include('myproject.myapp.urls2')),
but it seems to cause problems with unit tests including urlresolvers.
Is there better way to "refactor" the common part of regular expression (<url_key>) here?

I'm no django expert, but wouldn't the 'view1' item match all of the other entries below it since it doesn't have a '$' at the end? So the other views wouldn't have a chance to get matched.

I don't think you can do what you are trying to do with this line:
(ur'^(?P<url_key>[-a-zA-Z0-9]+)/', include('myproject.myapp.urls2'))
What view is the url_key parameter going to be passed to?
I'm not sure why you want to refactor the urlpatterns to begin with, but maybe this would be better?:
from django.conf.urls.defaults import *
URL_KEY = ur'^(?P<url_key>[-a-zA-Z0-9]+)'
urlpatterns = patterns('myproject.myapp.views',
(ur'^$', 'index'),
(ur'^browse/$', 'browse'),
(ur'^request/new/$', 'new_request'),
(URL_KEY+ur'/$', 'view1'),
(URL_KEY+ur'/asdf$', 'view2'),
(URL_KEY+ur'/qwer$', 'view3'),
...etc
)

Perhaps you could simplify the expressions in myproject.myapp.urls, and instead pass the information along as a parameter to a function in myproject.myapp.views?
I'm not sure what was going wrong in your tests, but generally speaking you'll be able to do more in myproject.myapp.views since you don't have to base it all on regex logic.
That function in myproject.myapp.views would be a switchboard that calls view1, view2, etc

Related

How can I use a catch all route using `path` or `re_path` so that Django passes all unmatched requests to my index view?

My url patterns look like this:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
re_path('.*', IndexView.as_view()),
]
This works but it matches all URLs, including those prefixed with admin and api. I want those URLs to still match, and for any unmatched URLs to render IndexView.
Before 2.0 I used this regex for this purpose. I tried using it in re_path but that didn't work, which is what led me to trying the above.
url(r'^(?P<path>.*)/$', HtmlView.as_view())
Use case is a SPA where I handle 404s client side.
Many thanks in advance.
You can use two entries (one for '/', another one for anything else), but using path for both of them, which should be (slightly) more efficient:
urlpatterns = [
path('', IndexView.as_view(), {'resource': ''}),
path('<path:resource>', IndexView.as_view())
]
In this case, I'm using <path:resource> because path catches all resource names, inluding that with / in them. But it does not capture the main index resource, /. That's why the first entry. The dictionary as last argument for it is because we need to provide a resource parameter if we want to use the same view than in the second entry.
That view, of course, should have 'resource' as a paremeter:
def as_view(request, resource):
...
So as I said in the question description, trying the regex I was using before Django 2.0 in re_path did not work. It would basically match for all requests except / (i.e., index path). I fixed this by using both that regex and a second path matching / specifically. Here's the code:
urlpatterns = [
re_path(r'^(?P<path>.*)/$', IndexView.as_view()),
path('', IndexView.as_view()),
]
With these changes my other routes would match and these two routes would account for all other urls.
One Idea to go about this is let the django catch 404.
url.py
from django.conf.urls import handler404
handler404 = 'app_name.views.bad_request'
and in your views.py
views.py
def bad_request(request):
return redirect(reverse('home'))
You can always do some regex thingy to catch unmatched urls. but hey this gets the job done. :)

How to get a list of name arguments in Django's urlpatterns

Suppose I have two apps, App1 and App2. I want to get the list of names of each url in urlpatterns.
App1 urls.py:
urlpatterns = (
url(regex=r'^$', view=views.index_view, name='index_url'),
url(regex=r'^about/$', view=views.about_view, name='about_url'),
url(regex=r'^contact/$', view=views.contact_view, name='contact_url'),
)
In App2, I want to get the following list:
['index_url', 'about_url', 'contact_url']
I can get individual names:
>>> import App1.urls
>>> App1.urls.urlpatterns[1].name
'index_url'
Technically, I can go over a loop to collect each name in a list. But is there a direct way to get them, like: App1.urls.urlpatterns.names?
Posting the comment as an answer:
Use
names=[u.name for u in urls.urlpatterns if hasattr(u,'name')]
to populate the array of names.
It seems like there is no direct accessor.

Django pattern prefix in URL isn't spreading to included views: Bug or misunderstanding?

If I do that:
urlpatterns += patterns('datasets.views',
url(r'^$', 'home', name="home"),
url(r'^(?P<slug>\w+)/', include(patterns('',#HERE I OMIT THE PREFIX
url(r'^edit/', 'edit_api', name="edit_api"),
))),
)
I will get a ``TypeError at /my-slug-name/ 'str' object is not callable
But If I include the prefix a second time, It's working.
urlpatterns += patterns('datasets.views',
url(r'^$', 'home', name="home"),
url(r'^(?P<slug>\w+)/', include(patterns('datasets.views', #HERE THE PREFIX IS REPEATED
url(r'^edit/', 'edit_api', name="edit_api"),
))),
)
Do I misunderstand how include works ? Should I report this as a bug ?
It's not how include() works, but how patterns works. Without the prefix, edit_api is just a string to pattern, it cannot resolve it to a view. Providing prefix to the first pattern doesn't make it implicitly include in the nested one. The way you are using patterns is a bit ugly. You need to consider each patterns() individually. The prefix is there to make your url configs clean, Consider this -
api_patterns = patterns('datasets.views',
url(r'^edit/', 'edit_api', name="edit_api"),
# --------------^ Here edit_api is actually datasets.views.edit_api
# if you don't want to provide the prefix, you write the full path to the view
# url(r'^edit/', 'datasets.views.edit_api', name="edit_api"),
)
urlpatterns += patterns('datasets.views',
url(r'^$', 'home', name="home"),
url(r'^(?P<slug>\w+)/', include(api_patterns)),
# ------------------------------^
# Here include doesn't use the pattern prefix you used 3 lines above
)
The reason is, include is designed to include patterns from various places like apps etc. And each of them might have individual pattern prefix. So to keep things simple, you either provide a pattern prefix and write relative view names or you omit pattern prefix and write complete view path.

How do I unit test django urls?

I have achieved 100% test coverage in my application everywhere except my urls.py. Do you have any recommendations for how I could write meaningful unit tests for my URLs?
FWIW This question has arisen as I am experimenting with Test-Driven Development and want failing tests before I write code to fix them.
One way would be to reverse URL names and validate
Example
urlpatterns = [
url(r'^archive/(\d{4})/$', archive, name="archive"),
url(r'^archive-summary/(\d{4})/$', archive, name="archive-summary"),
]
Now, in the test
from django.urls import reverse
url = reverse('archive', args=[1988])
assertEqual(url, '/archive/1988/')
url = reverse('archive-summary', args=[1988])
assertEqual(url, '/archive-summary/1988/')
You are probably testing the views anyways.
Now, to test that the URL connect to the right view, you could use resolve
from django.urls import resolve
resolver = resolve('/summary/')
assertEqual(resolver.view_name, 'summary')
Now in the variable resolver (ResolverMatch class instance), you have the following options
'app_name',
'app_names',
'args',
'func',
'kwargs',
'namespace',
'namespaces',
'url_name',
'view_name'
Just complementing the answer from #karthikr (on the basis of his)
-> you may assert that the view in charge of resolve is the one you'd expect using resolver.func.cls
example
from unittest import TestCase
from django.urls import resolve
from foo.api.v1.views import FooView
class TestUrls(TestCase):
def test_resolution_for_foo(self):
resolver = resolve('/api/v1/foo')
self.assertEqual(resolver.func.cls, FooView)
Sorry for beeing a miner but I found this place as a first under key words "django url testing".
I fairly agree with my predecessors but I am also sure there is a better way to test your URLs. The main reason why we should not use "resolver = resolve('url/path')" is that the paths are of some kind fluent when the view's names are more fixed.
Well in simple words, this is better:
class TestUrls(TestCase):
def test_foo_url_resolves(self):
url = reverse('bar:foo')
self.assertEqual(resolve(url).func.view_class, FooBarView)
'bar:foo' - bar is a namespace and foo is a view name
than this
class TestUrls(TestCase):
def test_resolution_for_foo(self):
resolver = resolve('/api/v1/foo')
self.assertEqual(resolver.func.cls, FooView)

Django error with (CRUD) urls.py

as part of my continuing want to do well on my Uni course, I'm doing a bit of web-dev in Python(2.7) using Django. I had followed Django's tutorial and now I am following this tutorial. However, I get a somewhat inexplicable error when I add in the urls.py part to give me some viewing models. The project is called 'practice' and the app is called 'orders'. Within 'orders' are the models (which all validate)
The (relevant part of) urls.py is:
'django.views.generic.list_details',
url(r'^orders/$', 'object_list', {'queryset': 'orders.Product.objects.all()'}),
url(r'^orders(?P<slug>[-\W]+)/$', 'object_detail', {'queryset': 'orders.Product.objects.all()'})
I've double checked ROOT_URLCONF is correctly set so the error appears to be somewhere within 'django.views.generic.list_details' as a use.
The error message is:
AttributeError: 'str' object has no attribute resolve
A good Google didn't seem to produce anything reasonable so any chance of a hand please guys?
Thanks!
Did you forget "patterns"?
urlpatterns = patterns('',
(r'^$', ...),
# ...
Also I've noticed a slash missing:
url(r'^orders(?P<slug>[-\W]+)/$', 'object_detail', {'queryset': 'orders.Product.objects.all()'})
url(r'^orders/(?P<slug>[-\W]+)/$', 'object_detail', {'queryset': 'orders.Product.objects.all()'})
The AttributeError suggests to me that the string 'django.views.generic.list_details' is being treated as a url to be resolved. However, you've omitted too much of your urls.py to say for sure.
Make sure the prefix string is the first argument to django.conf.urls.patterns. If you want to break up your urls and use different prefix strings, invoke patterns multiple times as described in the documentation:
urlpatterns = patterns('myapp.views',
url(r'^$', 'app_index'),
url(r'^(?P<year>\d{4})/(?P<month>[a-z]{3})/$','month_display'),
)
urlpatterns += patterns('weblog.views',
url(r'^tag/(?P<tag>\w+)/$', 'tag'),
)
You've quoted the value in the arguments dictionary in each pattern, so it's being treated as a string. It should be:
url(r'^orders/$', 'object_list', {'queryset': orders.Product.objects.all()})
Not that you'll need to import orders - except I doubt that will work, because Product will be defined in the models file inside orders. It would be easier to just import Product and refer to it directly.