I want to accept both urls with and without parameters, such as /top/ /top/1
I tried a few patterns:
path('top/<int:pk>/', views.top, name='top_edit'),
path('top/(<int:pk>)/', views.top, name='top_edit'),
path('top/(<int:pk>)/$', views.top, name='top_edit'),
def top(request: HttpRequest,pk = None) -> HttpResponse:
return render(request, 'index.html', context)
It accept /top/1 however /top/ is not accepted.
How can I make it work?
Instead of pk = None in view Try this,
def top(request: HttpRequest,pk = '') -> HttpResponse:
return render(request, 'index.html', context)
You need to define the URL without the parameter too:
path('top/<int:pk>/', views.top, name='top_edit'),
path('top/(<int:pk>)/', views.top, name='top_edit'),
path('top/(<int:pk>)/$', views.top, name='top_edit'),
path('top/', views.top, name='top'),
def top(request: HttpRequest,pk = None) -> HttpResponse:
return render(request, 'index.html', context)
I think you want to make it optional i.e. both top/ and top/1 should be accepted with one url path and one view.
You can make the URL pattern optional by adding a question mark ? after the parameter in the URL pattern so:
path('top/(?P<pk>\d+)?/', views.top, name='top_edit'),
In this pattern, (?P<pk>\d+)? is the optional parameter. The ? at the end of the pattern makes the entire group optional, so that it matches either zero or one times. The (?P<pk>\d+) part of the pattern is the regular expression for matching an integer value.
Then with this URL pattern, your view function should be able to handle both /top/ and /top/1 URLs so:
def top(request: HttpRequest, pk=None) -> HttpResponse
# when pk is None (i.e. /top/ URL)
if pk is None:
# Do something for the /top/ URL
else:
# Do something for the /top/1 URL
return render(request, 'index.html', context)
You can further see the question: Django optional URL parameters
Related
Alright, let me give you guys an example;
We have the following url configuration in Django.
Django will try to match the url with the rules down below. Once it finds a match, it will use the appropriate view and lookup the object in the model.
The thing is, once it finds a match in the URL pattern, it will match the view. But once the object
in the view can't be found, it will return a page not found (404) error.
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('articles/<slug:category>/<slug:editor>/', views.ArticleByThemeView.as_view(), name='articles_by_editor'),
path('articles/<slug:category>/<slug:theme>/', views.ArticleDetailView.as_view(), name='articles_by_theme')
]
views.py
class ArticleByThemeView(ListView):
"""
List all articles by a certain theme; "World War 2".
"""
model = Article
def dispatch(self, request, *args, **kwargs):
try:
# Check if the theme_slug matches a theme
theme = ArticleTheme.objects.get(slug=self.kwargs['theme_slug'])
except ArticleTheme.DoesNotExist:
# Theme does not exist, slug must be an article_slug
return redirect(
'article_detail',
category_slug=category_slug
article_slug=theme_slug
)
return super().dispatch(request, *args, **kwargs)
class ArticleDetailView(DetailView):
"""
Detailview for a certain article
"""
model = Article
def get_object(self):
return get_object_or_404(
Article,
category__slug=self.kwargs['category_slug'],
slug=self.kwargs['article_slug']
)
We have the following url patterns, we can sort articles either by the editor or by theme. We do this to create a logical url structure for SEO purposes.
Is their any way we can redirect to another view once the object isn't found?
Can we modify the dispatch method to return to the url patterns and find the following matching rule?
What about redirection like this:
def articles_by_editor(request, category, editor):
try:
article = Article.objects.get(category=category, editor=editor)
# return article
except Article.DoesNotExist:
# redirect to another view
return redirect('articles_by_theme', category=category)
Alright,
Based on the suggestion from Sunderam Dubey, I'wrote a function view, which uses two differn routes to the same view.
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('articles/<slug:category>/<slug:slug>/', views.article_theme_or_detail_view, name='article_by_theme'),
path('articles/<slug:category>/<slug:slug>/', views.article_theme_or_detail_view, name='article_detail')
]
views.py
def article_theme_or_detail_view(
request,
category_slug,
slug=None
):
"""
This view could either be for a theme view or detailview,
depending on the slug.
"""
try:
# Check if the slug represents a theme
theme = ArticleTheme.objects.get(slug=slug)
article_list = Article.object.filter(theme=theme)
# Add context
context = {
'theme': theme,
'article_list': article_list
}
# Render the template with context
return render(
request,
'article_by_theme.html',
context
)
except ArticleTheme.DoesNotExists:
# The theme does not exist so the slug must be for a detail view
context = {
article = Article.objects.get(slug=slug)
}
return render(
request,
'article_detail.html',
context
)
Todo:
Remove one of the url routes
I'm following a Django tutorial and have reached the point of using return renderand currently my views.py looks like this:
from django.http import HttpResponse
from django.shortcuts import render
# Create your views here.
def construction_view(*args, **kwargs):
return HttpResponse("<h1> This site is currently being constructed. Please check back later </h1>")
def home_view(*args, **kwargs):
return render(request, "home.html", {})
I am getting an error whien trying to go to my home page:
views.py", line 9, in home_view
return render(request, "home.html", {})
NameError: name 'request' is not defined
Not sure what is causing this as according to Django docs request is part of render which is imported above.
request is always the first parameter of any view. In your view, your function only has *args and **kwargs, so request will be the first item in the args. It is better to make the request parameter explicit and work with:
# request ↓
def home_view(request, *args, **kwargs):
return render(request, "home.html", {})
Since you likely only use this view for URL patterns without URL patterns, you can probably omit the *args and **kwargs:
# ↓ omit *args and **kwargs
def home_view(request):
return render(request, "home.html", {})
view.py
when i test my api with post man ,postman indicate tha this urls do not exist(404 not found) any solution please
url postman:localhost:8000/photoByCategory/nature/
#api_view(['GET'])
#permission_classes((permissions.IsAuthenticated,))
def photo_by_categorie(request, category=False):
if request.method == 'GET':
if category:
photo_by_cat = Photo.objects.filter(category=category)
serializer = PhotoSerializer(photo_by_cat, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
urls.py
re_path(r'photoByCategory/(?:(?P<category>\d+)/)?$', views.photo_by_categorie, name='photo_by_categorie'),
Well, I guess the problem is with your regex
Try this
from django.urls import path, re_path
# Match string after drinks/ prefix
path('photoByCategory/<str:category>/',views.photo_by_categorie),
# Match one or more characters (non-digit regular expression) after drinks/ prefix
re_path(r'^photoByCategory/(?P<category>\D+)/',views.photo_by_categorie),
Here you can find the relevant information.
Note: I have ran first one myself and it works as intended
I have some question about redirect. When i am using mapviews.index there are no errors but when i am using mpviews.index
Reverse for 'mythicPlus.views.index' not found. 'mythicPlus.views.index' is not a valid view function or pattern name
What should i do to fix this problem?
shopping_cart/views.py
from mythicPlus import views as mpviews
from mainPage import views as mapviews
return redirect(reverse(mpviews.index))
mythicPlus/urls.py
path('', views.index, name = 'mythicPlus_list'),
mythicPlus/views.py
def index(request):
boost_keys_list = mythicPlusOrders.objects.all()
context = {'object_list': boost_keys_list}
return render(request, "mythicPlus/posts.html", context)
mainPage/views.py
def index(request):
return render(request, 'mainPage/homePage.html')
reverse uses the viewname from the url so in path('my-url/', views.MyView.as_view(), name="my-view") the viewname is my-view
You need to provide that in your reverse. Or you could just do
redirect(mpviews.index)
I have two views with the same regular expression, as you can see below. It's a Category and an Article view, their entry slugs will never be the same so it should be no problem. But at the moment it doesn't work well, as you prolly know the category-view will get triggered.
Please do not suggest to make the url structure unique, the slugs of categories and articles will never be the same. It should be as short as possible.
urls.py
urlpatterns = [
url(r'^index', index.Index.as_view(), name='index'),
url(r'^search', search.Index.as_view(), name='search'),
url(r'^(?P<slug>.+)$', category.Index.as_view(), name='category'),
url(r'^(?P<slug>.+)$', article.Index.as_view(), name='article'),
]
I tried to reverse from views.category back to urls.py if there is no category to find like this:
views.category.py
class Index(View):
def get(self, request, slug):
category = CategoryModel.objects.get(slug=slug)
if category is None:
return HttpResponseRedirect(reverse('article', args=[slug]))
context = {
'category': category
}
return render(request, 'category/index.html', context)
The error (but there is a article with slug 'test123'):
NoReverseMatch at /wiki/test123
Reverse for 'article' with arguments '('test123',)' and keyword arguments '{}' not found. 0 pattern(s) tried: []
Using Python 3.6
Why dont you try differentiating the URLs like so
urlpatterns = [
url(r'^index', index.Index.as_view(), name='index'),
url(r'^search', search.Index.as_view(), name='search'),
url(r'^category/(?P<slug>.+)$', category.Index.as_view(), name='category'),
url(r'^article/(?P<slug>.+)$', article.Index.as_view(), name='article'),
]
You get to use the same regular expressions without the URL being ambiguous.
You can remove the article.Index view and rather than trying to redirect when there's no object for Category, you can call the method you defined in article.Index with the same parameters as the get method takes in article.Index view.
Example:
urls.py
urlpatterns = [
url(r'^index', index.Index.as_view(), name='index'),
url(r'^search', search.Index.as_view(), name='search'),
url(r'^(?P<slug>.+)$', category.Index.as_view(), name='category'),
# article url removed
]
views.category.py
from path.to.article import Index as ArticleIndexView
class Index(View):
def get(self, request, slug):
category = CategoryModel.objects.get(slug=slug)
if category is None:
# calling article app's Index get method
article_index_view_obj = ArticleIndexView()
return article_index_view_obj.get(request, slug)
context = {
'category': category
}
return render(request, 'category/index.html', context)
If you make the article.Index class view as function-based view.
You can import from path.to.article import index as article_index
and then, instead of instantiating the object, you can directly call article_index(request, slug).
There are several issues here.
(1) You call reverse with args but you have specified a kwarg.
if category is None:
return HttpResponseRedirect(reverse('article', args=[slug]))
Reverse for 'article' with arguments '('test123',)' and keyword arguments '{}' not found.
Says exactly that - as you did not provide the keyword argument, it could not find the matching URL pattern. This would be correct:
if category is None:
return HttpResponseRedirect(reverse('article', kwargs={'slug':slug}))
(2) You will end up in that same code again and again - is what I expect to happen once you fix (1). Because if reverse really does reverse the URL - the result of reverse will of course also match the category URL which means it will simply call the category.Index view - again.
I think your URL setup could actually work because the resolver does try all URLs sequentially until one comes along that matches. I'm just not sure if you can make the view return something that will lead to the URL resolver to kick in and decide to take the next URL (article) instead of category which just resolved. Probably not.
In this case, if you are fine with redirects, you could just define 3 URL patterns. 1 for the view that will operate as a switch and redirect to the CategoryView or ArticleView respectively.
Otherwise go with Sachin Kukreja's solution of handling both in one view.
Finally I made it like this. Actually I wanna use 2 different views but I guess it's fine too. Does someone see a mistake?
class Index(View):
def get(self, request, slug):
self.request = request
self.slug = slug
self.item = None
is_article = ArticleModel.objects.filter(slug=self.slug).exists()
if is_article:
self.item = 'article'
return self.article()
is_category = CategoryModel.objects.filter(slug=self.slug).exists()
if is_category:
self.item = 'category'
return self.category()
self.item = None
# 404 here
return HttpResponse(self.item)
def article(self):
article = ArticleModel.objects.get(slug=self.slug)
context = {
'article': article
}
return render(self.request, 'article/index.html', context)
def category(self):
category = CategoryModel.objects.get(slug=self.slug)
context = {
'category': category
}
return render(self.request, 'article/index.html', context)