how to "break" from a regex in django urlconf - regex

I have the following views:
def tag_page(request, tag):
products = Product.objects.filter(tag=tag)
return render(request, 'shop/tag_page.html', {'products': products, 'tag': tag})
def product_page(request, slug):
product = Product.objects.get(slug=slug)
return render(request, 'shop/product_page.html', {'product': product})
along with the following url configurations:
url(r'^(?P<tag>.+)/$', 'tag_page'),
url(r'^(?P<tag>.+)/(?P<slug>[-\w]+)/$', 'product_page'),
The regex that has "tag" in it allows a url path to grow arbitrarily while sort of circularly redirecting to the tag_page view.
This lets me have the url: /mens/shirts/buttonups/, where all sections (/mens, /mens/shirts, /mens/shirts/buttonups/) of the path direct to the tag_page view, which is desired.
I want to end this behavior at some point however, and direct to a product_page view, which I attempt to accomplish with:
url(r'^(?P<tag>.+)/(?P<slug>[-\w]+)/$', 'product_page'),
When I follow a product_page link:
{{ product }}
I am directed to the tag_pag view. Presumably because that slug url matches the tag regex.
So the question: Is there a way I can keep the flexible tag regex redirect behavior but then "break" from it once I reach a product page? One important thing to note is that I want to keep the product page within the built up url scheme like: mens/shirts/buttonups/shirt-product/
Any insight is appreciated, Thanks!

Do you really need the forward slash on the end of the product page URL? A URL which ends with a forward slash is distinct from one which does not.
Just like delimiters are left at the end of a path to suggest a directory (with files underneath it) and left off at the end of file paths, so too could you leave the slash on for the tag sections but lop it off for individual products.
That gets around the problem entirely :-)

I think that you can't do it with just urlconf- .* always matches everything.
I would do that in this way:
url(r'^(?P<path>.+)/$', 'path_page'),
def path_page(request,path):
tags,unknown = path.rsplit('/',1)
try:
product = Product.objects.get(slug=unknown)
return some_view_function(request,path,product)
except Product.DoesNotExist:
return some_another_view_function(request,path)
But- I see here a few problems:
What if tag has the same name as product's slug?
Your solution is SEO unfriendly unless you want to bother with duplicate content meta tags

Related

Django url patterns - how to make slug url work for multiple views

I see a url patterns in some websites which shows the same pattern for different objects like categories and products.
For example, when you go to tablet cases and sony camera you see the same pattern like www.website/tablet-cases and www.website/sony-camera.
How is it possible to do that in Django without getting an error as below.
Page not found (404)
Request Method: GET
Request URL: http://127.0.0.1:8000/tablet-cases/
Raised by: Product.views.category_detail_view
I mean, how can I use the same pattern for both categories and products as below:
url(r'^(?P<slug>[\w-]+)/$', ProductDetailView.as_view(), name='product_detail'),
url(r'^(?P<slug>[\w-]+)/$', CategoryDetailView.as_view(), name='category_detail'),
Django matches the first regex, in this case the product_detail view. By default, Django doesn't have a way to try each url in turn, so the category view will never be called with your current urls.
The easiest fix is to namespace your urls. This might be a good idea - at the moment, you'll have problems if you ever have a category and a product with the same slug.
url(r'^products/(?P<slug>[\w-]+)/$', ProductDetailView.as_view(), name='product_detail'),
url(r'^categories/(?P<slug>[\w-]+)/$', CategoryDetailView.as_view(), name='category_detail'),
Another option is to create a single view, say product_or_category. In that, you could test to see whether the slug matches a category or slug, and continue. This will work, but you might have a bit of duplicated code, and it might not seem as as elegant as the two class based views you currently have.
url(r'^(?P<slug>[\w-]+)/$', product_or_category, name='product_or_category'),
Finally, there's a project django-multiurl that appears to do what you want. I have never used it, so I can't make any claims for it.

How to set up my urls with posts/1, posts/2, etc?

I'm following a few of the basic django blog tutorials. The part I am stuck on is how to set variables in my urls.
I want my urls to look like:
posts/1
posts/2
posts/3
Currently when i visit my index.html i see the list of blog posts (just the titles), and when I hover the cursor over each link it does show posts/1, posts/2, etc.
The problem is that when I click on these links, it basically just refreshes the page and does not show the detailed view.
my urls.py currently looks like this:
url(r'^posts/', index),
url(r'^posts/(?P<post_id>[0-9]+)/$', detailedview),
I'm not sure exactly what (?P[0-9]+)/$', does and I'm assuming this is the problem because detailedview is never being called.
This method is inside my views.py but again, it is never being called.
def detailedview(request, post_id):
targetpost = Post.objects.get(id="post_id")
context = {'targetpost': targetpost}
return render(request, 'posts/detailedview.html', context)
Your question about the second URL:
(?P<post_id>[0-9]+) is a regex that means "set the post_id argument to the value of any number with one or more digits (more info at https://docs.python.org/2/library/re.html.)
The way to fix your problem is to add a $ to the end of the first pattern, so it looks like this:
url(r'^posts/$', index),
This will make it only match the URL /posts/.
There is also a problem with your view: the line
targetpost = Post.objects.get(id="post_id")
should be:
targetpost = Post.objects.get(id=post_id)
This will make Django look for the Post with the id specified in the variable post_id, rather than the Post with the id that equals the string "post_id"

Use anchored urls in django (anchored by the id of http objects)

My level in front-end development is pretty low.
Nevertheless, I want to implement the very wide-spread behaviour of having several parts anchored in one single page instead of several separate pages, and refer to these parts in the url.
So instead of having mysite.com/how_to_walk and mysite.com/how_to_run as two different pages and templates, I would like to have one page mysite.com/how_to_do_stuff and then depending on if you want to #walk or #run, refer to the html headers with the id field as suffixes of the url.
I don't really know how to do it with django. I'd like to create only one url dispatcher that - I guess - will look like that:
url(r'^how_to_stuff/#(?P<partId>[-\w]*)', views.how_to, name='how_to')
...and then I have to create a simple view, but how to refer to the id in the render() call, I have no idea.
I found the answer to my own question. The crucial element is that when the client (browser) goes for such an anchored url mysite.com/how_to_do_stuff#run, it sends to the server only the root url mysite.com/how_to_do_stuff and then applies the anchor to it locally. So you need:
A classic, simple url/view/template combination that loads the page mysite.com/how_to_do_stuff when it is asked by the client.
A way to send the client to these anchored pages and reference them for development. I do this through an other url/view couple that redirects the client to the right anchored url.
Below is the result:
In urls.py:
...
url(r'^how_to_do_stuff/(?P<part_id>[-\w]+)', views.how_to_redirect, name='how_to'),
url(r'^how_to_do_stuff', views.how_to)
In views.py:
def how_to_redirect(request, part_id):
return HttpResponseRedirect("/how_to_do_stuff/#"+part_id)
def how_to(request):
return render(request, "GWSite/how_to_do_stuff.html")
And then I refer to these in my templates through:
{% url "how_to" "run"}
From django project website
Take a look at how you they send the num var to views.py
# URLconf
from django.conf.urls import url
urlpatterns = [
url(r'^blog/$', 'blog.views.page'),
url(r'^blog/page(?P<num>[0-9]+)/$', 'blog.views.page'),
]
# View (in blog/views.py)
def page(request, num="1"):
# Output the appropriate page of blog entries, according to num.
...

Is there a way to alter the request.path before matching the urls?

When I get a request for a path that includes the word 'self' I want to replace it with the user id before matching it to a URL. I tried using a middleware like this:
def process_request(self, request):
if '/self/' in request.path:
request.path = request.path.replace('/self/','/' + str(request.user.id) + '/')
The replacement works but apparently is done after the URL matching. Is there any way to alter the path before this point?
Apparently, the URL marching is not done using request.path but request.path_info. The same middleware altering this variable works.
Why do you want to change the url, then match that new url? Why not have the url direct to the view and method you want, then work on the request.user.id like you would had you changed the url?
Perhaps another example would illustrate what you are trying to do.
(Making this an answer since I can't comment)

Django Generic object_list pagination. Adding instead of replacing arguments

I'm having some trouble with the django generic object_list function's pagination not really being "smart" enough to compensate my daftness.
I'm trying to do a url for listing with optional arguments for page number and category.
The url in urls.py looks like this:
url(r'^all/(?:(?P<category>[-\w]+)/page-(?P<urlpage>\d+))?/$',
views.listing,
),
The category and urlpage arguments are optional beacuse of the extra "(?: )?" around them and that works nicely.
views.listing is a wrapper function looking like this( i don't think this is where my problem occurs):
def listing(request,category="a-z",urlpage="1"):
extra_context_dict={}
if category=="a-z":
catqueryset=models.UserProfile.objects.all().order_by('user__username')
elif category=="z-a":
catqueryset=models.UserProfile.objects.all().order_by(-'user__username')
else:
extra_context_dict['error_message']='Unfortunately a sorting error occurred, content is listed in alphabetical order'
catqueryset=models.UserProfile.objects.all().order_by('user__username')
return object_list(
request,
queryset=catqueryset,
template_name='userlist.html',
page=urlpage,
paginate_by=10,
extra_context=extra_context_dict,
)
In my template userlist.html I have links looking like this (This is where I think the real problem lies):
{%if has_next%}
<a href=page-{{next}}>Next Page> ({{next}})</a>
{%else%}
Instead of replacing the page argument in my url the link adds another page argument to the url. The urls ends up looking like this "/all/a-z/page-1/page-2/
It's not really surprising that this is what happens, but not having page as an optional argument actually works and Django replaces the old page-part of the url.
I would prefer this DRYer (atleast I think so) solution, but can't seem to get it working.
Any tips how this could be solved with better urls.py or template tags would be very appreciated.
(also please excuse non-native english and on the fly translated code. Any feedback as to if this is a good or unwarranted Stack-overflow question is also gladly taken)
You're using relative URLs here - so it's not really anything to do with Django. You could replace your link with:
Next Page> ({{ next }})
and all would be well, except for the fact that you'd have a brittle link in your template, which would break as soon as you changed your urls.py, and it wouldn't work unless category happened to be a-z.
Instead, use Django's built-in url tag.
Next Page> ({{ next }})
To make that work, you'll have to pass your category into the extra_context_dict, which you create on the first line of your view code:
extra_context_dict = { 'category': category }
Is /all/a-z/page-1/page-2/ what appears in the source or where the link takes you to? My guess is that the string "page-2" is appended by the browser to the current URL. You should start with a URL with / in order to state a full path.
You should probably add the category into the extra_context and do:
next page ({{next}})
"Instead of replacing the page argument in my url the link adds another page argument to the url. The urls ends up looking like this "/all/a-z/page-1/page-2/"
that is because
'<a href=page-{{next}}>Next Page> ({{next}})</a>'
links to the page relative to the current url and the current url is already having /page-1/ in it.
i'm not sure how, not having page as an optional argument actually works and Django replaces the old page-part of the url
one thing i suggest is instead of defining relative url define absolute url
'Next Page> ({{ next }})'