Get slug name list by url path name - django

I am reverse-engineering django urlpatterns. For my purpose, I need to dynamically know the list of slug names developers chose to use within url. (for weird decorator purposes outside of scope of this question)
Example:
path("<slug:membership_slug>/<slug:parent_slug>/data/", rzd.ReportingUnitDataView.as_view(), name="hierarchy.item.data"),
So, in the world, where everyone writes perfect code, my function whould take "hierarcy.item.data" and spit out ["membership_slug", "parent_slug"]
I looked at the reverse function, hoping it would return something useful without kwargs, but it does not. Have been googling like Neo, but the hope does not leave my heart... somewhere django should hold all that urlpatterns stuff! I still believe.
Perhaps you can at suggest how to get a hold of list of urls, even if in the raw format, that would already be a help. Thank you.

Search for book called Django 2x scopes this book hold every thing related to django best practices and of course the urls part.
This book will help you to understand more how django developers think.
Happy hacking 🙃

I think you're best guess is to write a Regex..
From what I've found in the source code they use _lazy_re_compile(r'<(?:(?P<converter>[^>:]+):)?(?P<parameter>[^>]+)>') which can be found in urls\resolvers.py in _route_to_regex
this is what I've come up with after butchering the source code
def geturlnamedarguments(route):
from django.utils.regex_helper import _lazy_re_compile
named_arguments = []
while True:
match = _lazy_re_compile(r'<(?:(?P<converter>[^>:]+):)?(?P<parameter>[^>]+)>').search(route)
if not match:
break
route = route[match.end():]
parameter = match['parameter']
named_arguments.append(parameter)
return named_arguments
You can also get the raw URL route as a string by doing
urlpatterns[index].pattern._route
You can find the URLPattern Object also in urls\resolvers.py if you want to play around with it.. I never found a place where they have the names in a raw format

Solved, It occurred to me, that I can just include urls.py and parse it out myself :)
from myapp import urls
class UrlPatterns:
#staticmethod
def _get_params(s):
items = []
is_exp = False
is_prefix = False
type = ''
name = ''
for n in range(len(s)):
if not is_exp:
if s[n] == '<':
is_exp = True
is_prefix = True
type = ''
elif is_prefix:
if s[n] == ':':
is_prefix = False
else:
type += s[n]
else:
if s[n] == '>':
is_exp = False
items.append({
'type': type,
'name': name
})
name = ''
else:
name += s[n]
return items
#staticmethod
def _find_pattern(url_name):
for url in urls.urlpatterns:
if url.name == url_name:
return str(url.pattern)
return None
#staticmethod
def get_required_params(url_name):
url_pattern = UrlPatterns._find_pattern(url_name)
if url_pattern:
return UrlPatterns._get_params(url_pattern)
return None

Related

error when rendering through get_object_or_404 django

The following code in my views.py throws an error if I don't use
else conditional statement specifying detail = None
Is there any better way to simplify the code without using else statement?
(just in case you don't understand the structure of my app, it only has detail, and list templates. list.html only shows the list of name record in model, and detail only shows the each name in detail page.)
def SimpleView(request, sluggy=None):
s = Simple.objects.all()
if sluggy:
detail = get_object_or_404(Simple, slug=sluggy)
else:
detail=None
return render(request,'simple_app/list.html',{'s':s,'d':detail})
def detaily(request,sluggy):
sluggys = get_object_or_404(Simple,slug=sluggy)
return render(request, 'simple_app/detail.html',{'sluggy':sluggys})
You can build up the context directly:
def SimpleView(request, sluggy=None):
context = {'s': Simple.objects.all()}
if sluggy:
context['d'] = get_object_or_404(Simple, slug=sluggy)
return render(request,'simple_app/list.html', context)
(Though please use more descriptive names for the template vars; there's no reason to use single characters.)
If you are really fixated on no using the else part, you should define the detail variable before the if statement. So if "if qualification" is not satisfied, "detail" variable in return statement will at-least have a defined value as None.
def SimpleView(request, sluggy=None):
s = Simple.objects.all()
detail=None
if sluggy:
detail = get_object_or_404(Simple, slug=sluggy)
return render(request,'simple_app/list.html',{'s':s,'d':detail})
def detaily(request,sluggy):
sluggys = get_object_or_404(Simple,slug=sluggy)
return render(request, 'simple_app/detail.html',{'sluggy':sluggys})

Most appropriate way to redirect page after successful POST request in Django

I have build a view and a form in Django1.5. If the POST request is successful (based on some values I set) then I need the page to redirect to another URL which is created simultaneously.
Otherwise, if the POST was not successful I need to stay on the same page. Right now I have solved the problem as following but I am quite sure this is not the best way to do it:
This is a part of my view:
def layer_create(request, template='layers/layer_create.html'):
if request.method == 'GET':
....
elif request.method == 'POST':
out = {}
...
new_table = 'something that comes from the form'
if form.is_valid():
...
try:
...
out['success'] = True
except:
...
out['success'] = False
finally:
if out['success']:
status_code = 200
# THIS IS THE PART WHICH I THINK I CAN IMPROVE
template = '/something/workspace:' + new_table + '/metadata'
else: # if form not valid
out['success'] = False
return render_to_response(template, RequestContext(request, {'form': form}))
This part of the code:
template = '/something/workspace:' + new_table + '/metadata'
seems very ugly to me. But as I am quite new in Django I am not sure how to approach this matter.
A side note first about Django 1.5 - you're highly advised to upgrade to a supported version like 1.8.
Redirecting
For redirecting you can use the redirect shortcut. (Or HttpResponseRedirect)
from django.shortcuts import redirect
# out of a view context
return redirect('/url/to/redirect/to/')
Building URLs
Indeed - as you did mention, your attempt with template = '/something/workspace:' + new_table + '/metadata' is not the cleanest way :)
Django provides a really nice way with the URL dispatcher.
A complete solution here would go too far (or definitely would require more detailed information about your project structure) - I would recommend you to dive into the Django URL dispatcher.
In short you would do something like:
# app/urls.py
urlpatterns = [
#...
url(r'^workspace/(?P<id>[0-9]+)/metadata/$', views.workspace_detail, name='workspace-detail-metadata'),
#...
]
Then you are able to reverse your URL patterns:
from django.core.urlresolvers import reverse
url = reverse('workspace-detail-metadata', kwargs={'id': 123})
# would result in:
# .../workspace/123/metadata/
After all, I have used the "reverse" method as follows:
layer = 'geonode:' + new_table
return HttpResponseRedirect(
reverse(
'layer_metadata',
args=(
layer,
)))
Where my urls.py file includes:
url(r'^(?P<layername>[^/]*)/metadata$', 'layer_metadata', name="layer_metadata"),
As described here this is the most appropriate way to do it.

Flask: Dumping "request.data" in middleware? [duplicate]

I can't seem to figure out how to access POST data using WSGI. I tried the example on the wsgi.org website and it didn't work. I'm using Python 3.0 right now. Please don't recommend a WSGI framework as that is not what I'm looking for.
I would like to figure out how to get it into a fieldstorage object.
Assuming you are trying to get just the POST data into a FieldStorage object:
# env is the environment handed to you by the WSGI server.
# I am removing the query string from the env before passing it to the
# FieldStorage so we only have POST data in there.
post_env = env.copy()
post_env['QUERY_STRING'] = ''
post = cgi.FieldStorage(
fp=env['wsgi.input'],
environ=post_env,
keep_blank_values=True
)
body= '' # b'' for consistency on Python 3.0
try:
length= int(environ.get('CONTENT_LENGTH', '0'))
except ValueError:
length= 0
if length!=0:
body= environ['wsgi.input'].read(length)
Note that WSGI is not yet fully-specified for Python 3.0, and much of the popular WSGI infrastructure has not been converted (or has been 2to3d, but not properly tested). (Even wsgiref.simple_server won't run.) You're in for a rough time doing WSGI on 3.0 today.
This worked for me (in Python 3.0):
import urllib.parse
post_input = urllib.parse.parse_qs(environ['wsgi.input'].readline().decode(),True)
I had the same issue and I invested some time researching a solution.
the complete answer with details and ressources (since the one accepted here didnt work for me on python3, many errors to correct in env library etc):
# the code below is taken from and explained officially here:
# https://wsgi.readthedocs.io/en/latest/specifications/handling_post_forms.html
import cgi
def is_post_request(environ):
if environ['REQUEST_METHOD'].upper() != 'POST':
return False
content_type = environ.get('CONTENT_TYPE', 'application/x-www-form-urlencoded')
return (content_type.startswith('application/x-www-form-urlencoded' or content_type.startswith('multipart/form-data')))
def get_post_form(environ):
assert is_post_request(environ)
input = environ['wsgi.input']
post_form = environ.get('wsgi.post_form')
if (post_form is not None
and post_form[0] is input):
return post_form[2]
# This must be done to avoid a bug in cgi.FieldStorage
environ.setdefault('QUERY_STRING', '')
fs = cgi.FieldStorage(fp=input,
environ=environ,
keep_blank_values=1)
new_input = InputProcessed()
post_form = (new_input, input, fs)
environ['wsgi.post_form'] = post_form
environ['wsgi.input'] = new_input
return fs
class InputProcessed(object):
def read(self, *args):
raise EOFError('The wsgi.input stream has already been consumed')
readline = readlines = __iter__ = read
# the basic and expected application function for wsgi
# get_post_form(environ) returns a FieldStorage object
# to access the values use the method .getvalue('the_key_name')
# this is explained officially here:
# https://docs.python.org/3/library/cgi.html
# if you don't know what are the keys, use .keys() method and loop through them
def application(environ, start_response):
start_response('200 OK', [('Content-type', 'text/plain')])
user = get_post_form(environ).getvalue('user')
password = get_post_form(environ).getvalue('password')
output = 'user is: '+user+' and password is: '+password
return [output.encode()]
I would suggest you look at how some frameworks do it for an example. (I am not recommending any single one, just using them as an example.)
Here is the code from Werkzeug:
http://dev.pocoo.org/projects/werkzeug/browser/werkzeug/wrappers.py#L150
which calls
http://dev.pocoo.org/projects/werkzeug/browser/werkzeug/utils.py#L1420
It's a bit complicated to summarize here, so I won't.

using two templates from one view

I am trying to present content from a view in two ways: html and csv download. The only way I was able to do it was to use 2 different views, one for html presentation and one for csv. This duplicates my code and I am looking for a more elegant solution.
Any suggestions?
Here is the sample code:
# views.py
[...]
def member_list(request):
member_list = Member.objects.all()
return render_to_response("member_list.html",
{'member_list':member_list)
def member_csv_list(request):
member_list = Member.objects.all()
csv_list = HttpResponse(content_type='text/csv')
csv_list['Content-Disposition'] = 'attachment; filename="member_list.csv"'
writer = csv.writer(csv_list)
writer.writerow(['Name', 'Member Type', 'Rooms'])
for member in member_list:
fields = [member.name, member.member_type, member.room]
writer.writerow(fields)
return member_list
You can use a parameter in your url and implement a view like
def myview(request) :
type = request.GET.get('type', 'html')
# do processing
if type == 'html':
# return html
else if type == 'csv':
# return csv
If you access a url like http://yourserver/myview?type=csv it will render the csv part of the view. When the url http://yourserver/myview is accessed it will return the html part of the view.
Rohan's answer is absolutely the right paradigm. For an excellent tutorial-style introduction to this topic, cf. Multiple Templates in Django.
Here are a few quotes (all credit goes to Scott Newman).
To serve a printable version of an article, for example, we can add ?printable to the end of the URL.
To make it work, we'll add an extra step in our view to check the URL for this variable. If it exists, we'll load up a printer-friendly template file. If it doesn't exist, we'll load the normal template file.
def detail(request, pid):
'''
Accepts a press release ID and returns the detail page
'''
p = get_object_or_404(PressRelease, id=pid)
if request.GET.has_key('printable'):
template_file = 'press/detail_printable.html'
else:
template_file = 'press/detail.html'
t = loader.get_template(template_file)
c = Context({'press': p})
return HttpResponse(t.render(c))
He continues with template overrides and different templates by domain names. All this is excellent.

Django: How can I localize my urls?

How can I localize my urls in Django?
url(u'^{0}/$'.format(_('register')), RegisterView.as_view(), name='register'),
I tried the above but it only seems to work when I stop and start the server. I guess that the urls are translated when the application start.
So if there a way to solve this?
It's a bit more complicated than just throwing _() in urls.py. You have spotted the reason yourself: The URLs are evaluated once, when Django starts, and not for every request. Therefore you will have to
a) put every possible translation in urls.py, or
b) implement routing yourself
A. All in urls.py
url('hello', hello, name="hello_en"),
url('hallo', hello, name="hello_de"),
url('buenos_dias', hello, name="hello_es"),
Obviously not a nice solution, but it works for small projects.
B. Implementing routing
That has it's own drawback, especially when it comes to use reverse(). However, it works in principle:
urls.py:
#...
url('(?<path>.+)', dispatcher),
#...
views.py:
def dispatcher(request, path):
if path == "hallo":
lang = "de"
elif path == "buenos_dias":
lang = "de"
else:
lang = "en"
Of course, you can make the lookup more intelligent, but then you have to make preassumptions:
# if request.session['language'] can be trusted:
def dispatcher(request, path):
list_of_views = ['contact', 'about', 'foo']
v = None
for view in list_of_views:
if _(view) == path:
v = view
break
if v is None:
# return 404 or the home page
Internationalization of URLs has been introduced in django 1.4
see https://docs.djangoproject.com/en/dev/topics/i18n/translation/#url-internationalization
this is exactly what you are looking for