I'm trying to write a slug field so users can view my activity_detail page. I think I wrote the code right, but I'm getting 404 error with No Activity matches the given query.
. Here is my code:
my urls.py
from django.urls import re_path
from . views import activity_list, activity_detail, activity_index
app_name = 'activity'
urlpatterns = [
re_path(r'^$', activity_index, name='index'),
re_path(r'^(?P<year>[0-9]{4})/$', activity_list, name='list'),
re_path(r'^(?P<year>[0-9]{4})/(?P<slug>[\w-]+)/$', activity_detail, name='detail'),
]
my views.py:
def activity_detail(request, year, slug=None):
activity = get_object_or_404(Activity, year=year, slug=slug)
context = {
'activity': activity,
}
return render(request, "activity/detail.html", context)
I'm planning to call my url addresses from the browser as follows:
http://localhost/activity/
http://localhost/activity/2018/
http://localhost/activity/2018/myactivity
The only problem with this approach is that if you do not specify the slug, then the view is called with slug=None, and thus then you filter with slug=None, which will fail.
You can solve this with a None check:
def activity_detail(request, year, slug=None):
filter = {'year': year}
if slug is not None:
filter['slug'] = slug
activity = get_object_or_404(Activity, **filter)
context = {
'activity': activity,
}
return render(request, "activity/detail.html", context)
So here we first make an initial filter dictionary that contains only the year, and if slug is not None, then we add an extra filter.
I find however the year filter rather strange: typically there will be multiple Activitys for a given year, so then this will error.
In case you obtain an error like:
No Activity matches the given query.
This thus means that there is no record in your databases that has the given year, and slug. The 404 error is not a problem: it simply says that for that given URL, there is no corresponding Activity object available. So it makes sense to return such error.
In case you want to display all the Activitys that match the filter, you can use the get_list_or_404 [Django-doc].
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 have 2 Models: Projects and Members, each one with a form. I was able to add to the URL the number of the project (id) this:
class PageCreate(CreateView):
model = Page
form_class = PageForm
success_url = reverse_lazy('members:create')
def get_success_url(self):
return reverse_lazy('members:create', args=[self.object.id])
When I finish of filling the Project form, it redirects the page to the Member form.
What I want to do is to extract the ID of the Project from the URL and use it in the Member form. I cannot think any other solution.
Currently I have a Selection list to select the Project in the Member form but I want the Project loaded as soon as is created.
I am using the CreateView in the models for both Projects and Members. This is the view for MemberCreate
#method_decorator(login_required, name='dispatch')
class MemberCreate(CreateView):
model = Member
form_class = MemberForm
success_url = reverse_lazy('pages:pages')
Only attempt I had to visualize the ID in the HTML was using
{{ request.get }}
To somehow get the value from the GET but I could not do it.
Url Parameters
import imp
from django.urls import path
from .views import PageListView, PageDetailView, PageCreate, PageUpdate, PageDelete, MemberCreate, MemberDelete, MemberUpdate
pages_patterns = ([
path('', PageListView.as_view(), name='pages'),
path('<int:pk>/<slug:slug>/', PageDetailView.as_view(), name='page'),
path('create/', PageCreate.as_view(), name='create'),
path('update/<int:pk>', PageUpdate.as_view(), name='update'),
path('delete/<int:pk>', PageDelete.as_view(), name='delete'),
], 'pages')
members_patterns = ([
path('create/<int:pk>', MemberCreate.as_view(), name='create'),
path('update/<int:pk>', MemberUpdate.as_view(), name='update'),
path('delete/<int:pk>', MemberDelete.as_view(), name='delete'),
], 'members')
Stuff parsed from the URL ends up in the view's self.kwargs. From
return reverse_lazy('members:create', args=[self.object.id])
you pass through an URL like
path( 'create/<int:project>', MemberCreateView.as_view(), name='create' )
and in the view, the id is now self.kwargs['project']. (note, an URL can specify multiple named variables separated by slashes, it's not limited to just one). You typically then use
project = Project.objects.get( pk = self.kwargs['project'] )
Request.GET is something different: it's where the dict encoded as a querystring goes. If your client supplies
http://server/app/foo?bar=27&baz=hello
then when you arrive in the view which handles app/foo, request.GET contains
{ 'bar':'27', 'baz':'hello' }
(actually it's a QueryDict, not a Python dict, which has some subtle diffreernces. Consult the Django documentation. The main difference is that the values attached to keys can be multi-valued, for a querystring like ?bar=27&bar=54&bar=silly
url.py
urlpatterns = [
path('api_doc/', schema_view),
path('admin/', admin.site.urls),
# regex for swagger creation
path(r'^request.GET.get(‘tag’)&request.GET.get(‘order_by’)', views.QuestionList.as_view()),
# path(r'^?tag={tag}&order_by={name}', views.QuestionList.as_view()),
]
This is mu url.py file and i a trying to input "tag" and "order_by" in url in swagger but it's not working? i have tried above url options
In the last one "?" is not reconized by url.
The query string [wiki] is not part of the path. So you can not capture or check this in the urlpatterns. Your path looks like:
path('', views.QuestionList.as_view())
In your view, you can then filter the queryset accordingly:
from django.views.generic.list import ListView
from app.models import Question
class QuestionList(ListView):
model = Question
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
if 'tag' in self.request.GET:
qs = qs.filter(tag__id=self.request.GET['tag'])
if 'order_by' in self.request.GET:
qs = qs.order_by(self.request.GET['order_by'])
return qs
That being said, you are introducing a security vulnerability by allowing arbitrary ordening.
It furthermore might be worth to take a look at django-filter [GitHub] to do filtering based on a QueryDict in a more declarative way.
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)
I am new to Django, and I am trying to have a separate page where I can view individual articles. Currently I have:
#views.py
class ArticleView(DateDetailView):
template_name = 'blog/article.html'
model = Article
date_field = "pub_date"
#I am not sure which one to use
slug_field = "unique_url_suffix"
slug_url_kwarg = 'unique_url_suffix'
and
#urls.py
urlpatterns = [
url(r'^(index\.html)?$',views.IndexView.as_view(),name='index'),
url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/(?P<day>[0-9]+)/(?P<slug>[-\w]+)/$',
views.ArticleView.as_view(),
name="article_detail"),
]
and in index.html inside a loop of objects from the Article class:
<h2>{{article.title}}</h2>
I have also tried manually inputting the arguments, like this:
<h2>{{article.title}}</h2>
I keep on getting a "NoReverseMatch at /blog/" error. What am I doing incorrectly?
Edit: On top of the changes recommended for the answer, there was a typo causing problems. It does not affect the answer below, though.
First off, you should not be generating this URL in your template. You should define a get_absolute_url method in your Article model that looks like this:
from django.core.urlresolvers import reverse
def get_absolute_url(self):
# Note - you have to supply each of the date components separately
# because you need to match the URL regex.
return reverse (
'blog:article_detail',
kwargs={'year': self.pub_date.strftime("%Y"), 'month': self.pub_date.strftime("%b"),
'day': self.pub_date.strftime("%d"), 'slug': self.unique_url_suffix}
)
And then in your template:
<h2>{{article.title}}</h2>