django catching all URLS under a specific name - django

I'd like to have one entry under urls.py that'll catch all sub-folders in a URL starting with a particular folder, example in this instance:
example:
/business
/business/one
/business/one/two
/business/one/two/three
/business/one/two/three/four
I want all those URLS to go a single view, where I can later determine how many levels of folders there are after /business/ and their names.

You can make use of the <path:…> path converter [Django-doc], but this requires the element to be non-empty.
using re_path
If you thus want to match paths including empty ones, you can make use of the ([^/]+/)* regex pattern:
from django.urls import re_path
urlpatterns = [
re_path(r'^business/(?P<path>([^/]+/)*)$', views.myview, name='myview'),
]
and then in the view split the path:
def myview(request, path):
path_items = path.split('/')
path_items is than a list of path elements.
Custom path converter
You can also register a custom path converter:
# app/converters.py
class EmptyPathConverter:
regex = '([^/]+/)*'
def to_python(self, value):
return value.split('/')
def to_url(self, value):
return '/'.join(value)
and then register the path converter and use it when defining a path:
from app.converters import EmptyPathConverter
from django.urls import path, register_converter
register_converter(EmptyPathConverter, 'emptypath')
urlpatterns = [
path('business/<emptypath:paths>', views.myview, name='myview'),
]
then we can use this in a view, which will already do the splitting for us:
def myview(request, paths):
# paths is a list of strings
# …

Related

Match Question Mark character in Legacy Path

The following allows me to match surname/Smith and surname.php/surname=Smith but I wish to match surname.php?surname=Smith instead of the latter so as to ensure that people using the old links can still find the info they are after. How should I do this?
from django.urls import path, re_path
from . import views
urlpatterns = [
path('surname/<str:surname>/', views.surname, name="surname"),
path('surname.php/surname=<str:surname>',views.surname),
]
I don't think you can do it using urlpatterns, but in views.surname, check for surname as a GET arg:
views.py
def surname(request, surname=None):
try:
_surname = surname or request.GET['surname']
except KeyError:
raise PermissionDenied('Surname must be included')
...

Django url path converter not working in production

I'm using path converter in my django app like so:
# urls.py
from . import views
from django.urls import path
urlpatterns = [
path('articles/<str:collection>', views.ArticleView),
]
# views.py
#login_required
def ArticleView(request, collection):
print(collection)
if collection == "None":
articles_query = ArticleModel.objects.all()
...
This works fine in development for a url suck as : http://localhost:8000/articles/My Collection which gets encoded to http://localhost:8000/articles/My%20Collection, and is decoded properly in the ArticleView. However, in development, I have to edit the view like so to get it to work:
# views.py
import urllib.parse
#login_required
def ArticleView(request, collection):
collection = urllib.parse.unquote(collection)
print(collection)
if collection == "None":
articles_query = ArticleModel.objects.all()
...
Otherwise, the print(collection) shows My%20Collection and the whole logic in the rest of the view fails.
requirements.txt
asgiref==3.2.10
Django==3.1.1
django-crispy-forms==1.9.2
django-floppyforms==1.9.0
django-widget-tweaks==1.4.8
lxml==4.5.2
Pillow==7.2.0
python-pptx==0.6.18
pytz==2020.1
sqlparse==0.3.1
XlsxWriter==1.3.3
pymysql
What am I doing wrong here?
Thanks in advance!
The URL is being urlencoded which encodes spaces as %20. There are a number of other encodings. As you've discovered you need to decode that parameter in order to compare it to what you'd expect. As you've likely realized, if you have a value that actually wants The%20News and not The News, you have no recourse. To handle this people will create a slug field. Django has a model field for this in the framework.
This is typically a URL-friendly, unique value for the record.
Assuming you add a slug = models.SlugField() to ArticleModel, your urls and view can change into:
urlpatterns = [
# Define a path without a slug to identify the show all code path and avoid the None sentinel value.
path('articles', views.ArticleView, name='article-list'),
path('articles/<slug:slug>' views.ArticleView, name='article-slug-list'),
]
#login_required
def ArticleView(request, slug=None):
articles_query = ArticleModel.objects.all()
if slug:
articles_query = articles_query.filter(slug=slug)

how to have options in urls in django 2.0

In Django 1 I used to have url choices like this:
url('meeting/(?P<action>edit|delete)/', views.meeting_view, name='meeting'),
How I do this in Django 2.0 with the <> syntax:
Maybe something like this?
path('meeting/(<action:edit|delete>)/', views.meeting_view, name='meeting'),
If I understand the documentation, your first syntax should work right out-of-the-box.
Anyway here's how you could do with the new syntax:
First file = make a Python package converters and add edit_or_delete.py with that:
import re
class EditOrDeleteConverter:
regex = '(edit|delete)'
def to_python(self, value):
result = re.match(regex, value)
return result.group() if result is not None else ''
def to_url(self, value):
result = re.match(regex, value)
return result.group() if result is not None else ''
And for your urls.py file, this:
from django.urls import register_converter, path
from . import converters, views
register_converter(converters.EditOrDeleteConverter, 'edit_or_delete')
urlpatterns = [
path('meeting/<edit_or_delete:action>/', views.meeting_view, name='meeting'),
]
I would not use verbs in urls to indicate the purpose. Rather, rely on HTTP verbs such as GET, PUT, POST, DELETE and handle them in your view. That way, you can have just one view class handling all those different methods with just one URL.

how to get a list of all views in a django application?

Is there any way to get a list of all views in an django app? I have googled for answer. All answers shows a way to get list of urls.
Getting list of all the views of a Django project:
To get all the views present in a Django project, we create a function get_all_view_names() which takes urlpatterns as input and returns the complete list of views being used in the project as the output.
First, we import the root_urlconf module using settings.ROOT_URLCONF. Then root_urlconf.urls.urlpatterns will give us the list of project's urlpatterns.
The above urlpatterns list contains RegexURLPattern and RegexURLResolver objects. Accessing .urlpatterns on a RegexURLResolver will further give us a list of RegexURLPattern and RegexURLResolver objects.
A RegexURLPattern object will give us the view name which we are interested in. The callback attribute on it contains the callable view. When we pass either a string in our urls like 'foo_app.views.view_name' representing the path to a module and a view function name, or a callable view, then callback attribute is set to this. Further accessing .func_name will give us the view name.
We call the function get_all_view_names() recursively and add the view names obtained from a RegexURLPattern object to a global list VIEW_NAMES.
from django.conf import settings
from django.core.urlresolvers import RegexURLResolver, RegexURLPattern
root_urlconf = __import__(settings.ROOT_URLCONF) # import root_urlconf module
all_urlpatterns = root_urlconf.urls.urlpatterns # project's urlpatterns
VIEW_NAMES = [] # maintain a global list
def get_all_view_names(urlpatterns):
global VIEW_NAMES
for pattern in urlpatterns:
if isinstance(pattern, RegexURLResolver):
get_all_view_names(pattern.url_patterns) # call this function recursively
elif isinstance(pattern, RegexURLPattern):
view_name = pattern.callback.func_name # get the view name
VIEW_NAMES.append(view_name) # add the view to the global list
return VIEW_NAMES
get_all_view_names(all_urlpatterns)
Getting list of all the views in a Django application:
To get the list of all the views present in a Django application, we will use the get_all_view_names() function defined above.
We will first import all the urlpatterns of the application and pass this list to the get_all_view_names() function.
from my_app.urls import urlpatterns as my_app_urlpatterns # import urlpatterns of the app
my_app_views = get_all_view_names(my_app_urlpatterns) # call the function with app's urlpatterns as the argument
my_app_views gives us the list of all the views present in my_app Django app.
Adding on to above fix by Rahul, if anyone is using Python3, you will need to use __name__ instead of func_name:
...
view_name = pattern.callback.__name__
...
otherwise you will get the following:
AttributeError: 'function' object has no attribute 'get_all_view_names'
(Thanks to scipy-gitbot at https://github.com/scipy/scipy/issues/2101#issuecomment-17027406
As an alternative, if you are disinclined to using global variables, here is what I ended up using :
all_urlpatterns = __import__(settings.ROOT_URLCONF).urls.urlpatterns
detail_views_list = []
def get_all_view_names(urlpatterns):
for pattern in urlpatterns:
if isinstance(pattern, RegexURLResolver):
get_all_view_names(pattern.url_patterns)
elif isinstance(pattern, RegexURLPattern):
detail_views_list.append(pattern.callback.__name__)
get_all_view_names(all_urlpatterns)
all_views_list = []
# remove redundant entries and specific ones we don't care about
for each in detail_views_list:
if each not in "serve add_view change_view changelist_view history_view delete_view RedirectView":
if each not in all_views_list:
all_views_list.append(each)
Then you can just iterate through all_views_list to get the list of filtered views.
update: Mar 1 2018
In Django 2.0, django.core.urlresolvers is moved to django.urls. RegexURLPattern and RegexURLResolver are renamed to URLPattern and URLResolver. So you should use
from django.urls import URLResolver, URLPattern
instead of
from django.core.urlresolvers import RegexURLResolver, RegexURLPattern
if you are using Django 2.
Get all Django and DRF views w/o using global vars
def get_all_views(urlpatterns, views=None):
views = views or {}
for pattern in urlpatterns:
if hasattr(pattern, 'url_patterns'):
get_all_views(pattern.url_patterns, views=views)
else:
if hasattr(pattern.callback, 'cls'):
view = pattern.callback.cls
elif hasattr(pattern.callback, 'view_class'):
view = pattern.callback.view_class
else:
view = pattern.callback
views[pattern.name] = view
return views

Django: Get relative uri from views.py

Here is what I have currently:
urls.py:
...
url(r'this/is/relative', 'myapp.views.callview', name='myapp_callview'),
...
views.py:
def callview(request, **kwargs):
# I can get the complete url by doing this
print request.build.absolute_uri() # Prints: https://domain:8080/myapp/this/is/relative
# How do I just get: /myapp/this/is/relative or even /this/is/relative
I would like to extract the relative uri from the view. I could just use regex, but I think there is already something out there that would let me do this.
This will give you "/myapp/this/is/relative":
from django.core import urlresolvers
relative_uri = urlresolvers.reverse("myapp_callview")
Link to Django docs page: https://docs.djangoproject.com/en/dev/ref/urlresolvers/