I am new to Django and have been making a sample project. I have been trying to use Generic Detailview. It seems that url redirection works fine but DetailView can't get primarykey from the url.
Main url.py::
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^',include('personal.urls')),
]
Here is my app's urls.py code:
urlpatterns = [
url(r'(?P<pk>\d+)/$',views.detailView.as_view(),name="detail"),]
View file for the DetailView:
from django.shortcuts import render
from django.views import generic
from .models import Story
class detailView(generic.DetailView):
model = Story
template_name = 'personal/storydetail.html'
def get_context_data(self, **kwargs):
pk = kwargs.get('pk') # this is the primary key from your URL
print("PK:",pk)
Template Code:
{% block content %}
{{ Story.writer }}
<h6> on {{ Story.story_title }}</h6>
<div class = "container">
{{ Story.collection }}
</div>
{% endblock %}
Story Class code:
class Story(models.Model):
story_title = models.CharField(max_length=200) #Story title
writer = models.CharField(max_length=200) #WriterName
collection=models.CharField(max_length=200) #Collection/Book name
When I check primary key value on view it shows it 'NONE'. I can't find issue with the code. My pased url looks like : http://127.0.0.1:8000/personal/2/ where personal is the name of app and 2 should be taken as id.
The problem is that you are using kwargs instead of self.kwargs inside the get_context_data method. It should be something like:
def get_context_data(self, **kwargs):
# You need to call super() here, so that the context from the DetailView is included
kwargs = super(detailView, self).get_context_data(**kwargs)
pk = self.kwargs['pk'] # No need for get() here -- if you get a KeyError then you have a problem in your URL config that should be fixe # this is the primary key from your URL
# edit kwargs as necessary
...
return kwargs
In the get_context_data method, kwargs are those passed to the method to make up the context. They are different from self.kwargs, which are from the url pattern.
Related
I created a small Django application to manage data that fits a simple a model. For now I only need two views: one to list all records and another to edit a record with a generic form. Everything functions as expected, except the redirection from the edit view upon a successful update. In urls.py are the following contents:
from django.urls import path
from . import views
app_name = 'reqs'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.ReqUpdateView.as_view(), name='update'),
]
In forms.py:
from django.forms import ModelForm
from .models import Requirement
class RequirementForm(ModelForm):
class Meta:
model = Requirement
fields = ['name', 'priority', 'source' , 'rationale']
And the templeate requirement_form.html:
<h1>{{ requirement.id }} - {{ requirement.name }}</h1>
<form method="post" novalidate>
{% csrf_token %}
<table>
{{ form.as_table }}
<tr><td></td><td><button type="submit">Save</button></td></tr>
</table>
</form>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<br><br>
Back to list
Finally views.py, on a first attempt to redirect the update to the list:
from django.views.generic import ListView, UpdateView
from django.urls import reverse_lazy
from .models import Requirement
from .forms import RequirementForm
class IndexView(ListView):
template_name = 'reqs/index.html'
context_object_name = 'requirements_list'
def get_queryset(self):
return Requirement.objects.order_by('subject')
class ReqUpdateView(UpdateView):
model = Requirement
form_class = RequirementForm
success_url = reverse_lazy('/')
With this formulation the Save button produces this error:
Reverse for '/' not found. '/' is not a valid view function or pattern name.
I also tried an empty string as argument to reverse_lazy, as well as the path name index, but a similar error message is produced.
On a second attempt I tried to redirect to the same page, redefining the get_success_url method to do nothing:
class ReqUpdateView(UpdateView):
model = Requirement
form_class = RequirementForm
context_object_name = 'requirement_update'
def get_success_url(self):
pass #return the appropriate success url
This returns a 404 error trying to redirect the browser to /reqs/1/None.
A third attempt to redirect to the form with the same record:
class ReqUpdateView(UpdateView):
model = Requirement
form_class = RequirementForm
context_object_name = 'requirement_update'
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse("update", kwargs={"pk": pk})
Which complains about not finding the view:
Reverse for 'update' not found. 'update' is not a valid view function or pattern name.
How can I redirect success to a valid URL? It can either be the items list or the item update view, as long as it works.
There are few misconception that you did
reverse parameter should be as documented
URL pattern name or the callable view object
You have set namespace but you are not reversing with namespace as documented
So in your case
def get_success_url(self):
pk = self.kwargs["pk"]
return reverse("reqs:update", kwargs={"pk": pk})
reverse / reverse_lazy are used to get the url using view name or pattern name. If you want to use a url directly just write:
success_url = '/'
For the case of return reverse("update", kwargs={"pk": pk}) not working since you set app_name = 'reqs' you should be using return reverse("reqs:update", kwargs={"pk": pk}) instead.
How to load multiple models in one template in Django 2?
i've read Django Pass Multiple Models to one Template but, i'm lost in the answer and there is no specific answer there to get that view into the template,
Im trying to pass 3 models in views to template, which is Info, Slogan & Berita.
My views.py
from django.shortcuts import render
from django.http import HttpResponse
from .models import Artikel, Berita, Data, Galeri, Info, Slogan, Profil
from django.views.generic import ListView, DetailView
def home(request):
extra_context : {"info" : Info.objects.all(),
"slogan": Slogan.objects.all(),
"berita": Berita.objects.all(),
#and so on for all the desired models...
}
return render (request, 'blog/home.html')
class HomeListView(ListView):
context_object_name = 'slogan'
template_name = 'blog/home.html'
queryset = Info.objects.all()
def get_context_data(self, **kwargs):
context = super(HomeListView, self).get_context_data(**kwargs)
context['slogan'] = Slogan.objects.all()
context['berita'] = Berita.objects.all()
# And so on for more models
return context
my template home.html looks like:
{% for slogans in slogan %}
<p>{{ slogans.konten }}</p>
<p>{{ slogans.author }}</p>
{% endfor %}
my url urls.py looks like:
from blog.views import HomeListView
urlpatterns = [
url(r'^$', HomeListView.as_view(), name="home"),
]
and page source on browser shows nothing.
Im stuck on this, i found at least 3 similar result on this site but not works for me.
I want to show the details of a product added.
I followed this link and i could create an add product page
https://simpleisbetterthancomplex.com/tutorial/2018/01/29/how-to-implement-dependent-or-chained-dropdown-list-with-django.html
once the product is added, i want to show the details of the product added in a new page.
So i tried the following:
class ProductDetailView(DetailView):
model = Product
context_object_name = 'product'
queryset = Product.objects.filter(pk)
I really don't know what to put in the filter, or using filter is correct. i had tried using latest and order by instead of filter but they did not work.
my urls.py is as follows:
urlpatterns = [
path('', views.ProductDetailView.as_view(), name='product_changelist'),
#path('', views.ProductListView.as_view(), name='product_changelist'),
path('add/', views.ProductCreateView.as_view(), name='product_add'),
path('<int:pk>/', views.ProductUpdateView.as_view(), name='product_change'),
path('ajax/load-subcategory/', views.load_subcategory, name='ajax_load_subcategory'),
#path('<int:product_id>', views.detail, name='detail'),
]
Currently i am getting error as
AttributeError at /product/
Generic detail view ProductDetailView must be called with either an object pk or a slug.
I read that we have to provide the pk in urls.py so i tried providing pk as follows:
path('<int:pk>', views.ProductDetailView.as_view(), name='product_changelist'),
but then i get an error as follows:
NoReverseMatch at /product/add/
Reverse for 'product_changelist' with no arguments not found. 1 pattern(s) tried: ['product\\/(?P<pk>[0-9]+)$']
Any help in solving this problem is highly appreciated. I am new to django so may have done many mistakes above.
Edit 1:
I tried the suggestion given by #Radico, but still the same error: What i did is as follows:
Changed my ProductDetailView as follows:
class ProductDetailView(DetailView):
model = Product
context_object_name = 'product'
the product_list.html now has the following content
{% extends 'base.html' %}
{% block content %}
<br />
<br />
<br />
<br />
<br />
This is the page getting displayed121
{% url 'product_changelist' pk=object.pk %}
{% endblock %}
Still i get the same error as
AttributeError at /product/
Generic detail view ProductDetailView must be called with either an object pk or a slug
I did not change anything in urls.py.... do i have to change anything in it as well?
Edit 2:
Here is my urls.py
from django.urls import include, path
from . import views
urlpatterns = [
path('<int:pk>', views.ProductDetailView.as_view(), name='product_changelist'),
#path('', views.ProductDetailView.as_view(), name='product_changelist'),
#path('', views.ProductListView.as_view(), name='product_changelist'),
path('add/', views.ProductCreateView.as_view(), name='product_add'),
path('', views.ProductUpdateView.as_view(), name='product_change'),
path('ajax/load-subcategory/', views.load_subcategory, name='ajax_load_subcategory'),
#path('<int:product_id>', views.detail, name='detail'),
]
My views.py is as follows:
from django.shortcuts import render
from django.urls import reverse_lazy
from django.views.generic import CreateView, UpdateView, ListView, DetailView
from category.models import Subcategory
from product.forms import ProductForm
from product.models import Product
class ProductListView(ListView):
model = Product
context_object_name = 'product'
queryset = Product.objects.filter()
class ProductDetailView(DetailView):
model = Product
context_object_name = 'product'
# queryset = Product.objects.filter()
# class ProductDetailView(DetailView):
# template_name = 'product/product_list.html'
# #model = User
# #context_object_name = 'foo'
#
# def get_object(self):
# #return get_object_or_404(Product, pk=request.session['product_id'])
# return get_object_or_404(Product, pk=self.request.
class ProductCreateView(CreateView):
model = Product
form_class = ProductForm
success_url = reverse_lazy('product_changelist')
def productlist(request, product_id):
product = Product.objects.get(productid=product_id)
return render(request, 'product/product_list.html', {'product': product})
# def productlist(request):
# prodlist = Product.objects.order_by('-pk')[0]
# return render(request, 'product/product_list.html', {'prodlist': prodlist})
class ProductUpdateView(UpdateView):
model = Product
form_class = ProductForm
success_url = reverse_lazy('product_changelist')
def load_subcategory(request):
category_id = request.GET.get('category')
subcategory = Subcategory.objects.filter(category_id=category_id).order_by('name')
return render(request, 'product/subcategory_dropdown_list_options.html', {'subcategory': subcategory})
My product_list.html is as i had pasted earlier
{% extends 'base.html' %}
{% block content %}
<br />
<br />
<br />
<br /><br />
This is the page getting displayed121
{% url 'product_changelist' pk=object.pk %}
{% endblock %}
First: You don't need to get query set within the detailview the object is already available, you can call it in the template object or class name lowercase in your case product.
Second: make sure that you pass pk in urls refer to the detailview within templates i.e {% url 'product_change' pk=object.pk %}
I believe you got the error for second part check the url in product_changelist template and you may forgot to pass pk=object.pk
If you want to use Regular expressions when you're using path:
from django.urls import path , re_path
re_path(r'^product/(?P<pk>\d+)$', views.ProductUpdateView.as_view(), name='product_change'),
I am trying to show a newsletter form, and it is not shown in the page
This is my models.py
from django.db import models
# Create your models here.
class newsletter_user(models.Model):
email = models.EmailField()
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.email
This is my forms.py
from django import forms
from .models import newsletter_user
class newsletterForm(forms.ModelForm):
class Meta:
model = newsletter_user
fields = ['email']
def clean_email(self):
email = self.cleaned_data.get('email')
return email
This is my admin.py
from django.contrib import admin
from .models import newsletter_user
# Register your models here.
class newsletterAdmin(admin.ModelAdmin):
list_display = ('email','date_added',)
admin.site.register(newsletter_user,newsletterAdmin)
This is the views.py
from django.shortcuts import render
from .models import newsletter_user
from .forms import newsletterForm
# Create your views here.
def newsletter_subscribe(request):
form = newsletterForm(request.POST or none)
if form.is_valid():
instance = form.save(commit=false)
if newsletter_user.objects.filter(email=instance.email).exists():
print("already exists")
else:
instance.save()
context = {'form':form,}
template = "/blog/templates/footer.html"
return render(request, template, context)
This is the html
<form method="post" action=''>
<div class = "input-group">
{{form}} {% csrf_token %}
<span class = "input-group-btn">
<button class="btn btn-default" type="submit">Subscribe</button>
</span>
</div>
</form>
This is my urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', views.BlogIndex.as_view(), name='home'),
url(r'^(?P<slug>[-\w]+)/$', views.BlogDetail.as_view(), name='entry_detail'),
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
url(r'^footer/$', subscribe_views.newsletter_subscribe, name='subscribe'),
]
My Project directory
The button is shown
But the form is not shown..
This is my source in web browser RIGHT-CLICK->VIEW SOURCE
The url router will send the request to the first matching view. That is the only one that is called, and that view has to provide the context data that the template consumes. (You can also write your own context processor to insert context that you need everywhere.)
Since another pattern also matches /footer/, your request is clearly handled by some other view.
url(r'^(?P<slug>[-\w]+)/$', views.BlogDetail.as_view(), name='entry_detail'),
If the other view doesn't provide form into the context, there's nothing for Django to render.
Your view function newsletter_detail() is not called from other views, so that context is not used. (Using the undefined none there would have caused a run time error, which shows that the code was never evaluated.)
Catch-all routes such as entry_detail should either be used as the last url route, or be made more specific. Something like r'^/blog/(?P<slug>[-\w]+)/$', for instance, which will not match /footer/.
For a simple "subscribe" form in the footer, I recommend writing it as just html, and set up a route /subscribe/ to handle POST requests. There's not anything to gain by using Django's form framework for such a simple case (Just one field).
The django docs has an example of how you can implement something like this.
You footer.html template fragment should not require any context that is not automatically inserted by a context processor. Django's CsrfViewMiddleware provides the {% csrf_token %}, so that's an example of something you can use in template fragments such as a footer.
If you need some complicated form in your footer, you can write custom middleware to insert a Django Form instance in every context, (but you should probably give it a less generic name than form).
You may need to make some changes in your view somewhat like this,
def newsletter_subscribe(request):
if request.method == 'POST':
form = newsletterForm(request.POST)
if form.is_valid():
instance = form.save(commit=false)
if newsletter_user.objects.filter(email=instance.email).exists():
print("already exists")
else:
instance.save()
else:
form = newsletterForm()
context = {'form':form,}
template = "/blog/templates/footer.html"
return render(request, template, context)
You only need to initialise the form with request.POST , if request method is actually "POST". Else, just initialise a blank form.
I'm trying to build my own Blog app with Django 1.6. I've generated a category list by generic views like this:
urls.py
url(r'^categories/?$', views.ListView.as_view(model=Category), name='categories'),
category_list.html
<h3>Categories</h3>
{% for category in object_list %}
<ul>
<li>{{ category.title }}</li>
</ul>
{% endfor %}
all categories are now listed at /categories.
My problem is when I add it to base.html or index.html file, output changes to article.title not category.title How can I add this Category list to other pages such as Index or Article?
Here's my complete views.py file:
views.py
from django.shortcuts import get_object_or_404, render
from django.views.generic import ListView, DetailView
from blog.models import Article, Category
class IndexView(ListView):
template_name = 'blog/index.html'
context_object_name = 'latest_article_list'
def get_queryset(self):
return Article.objects.order_by('-pub_date')[:10]
class ArticleView(DetailView):
model = Article
template_name = 'blog/article.html'
It renders article.title because object_list points into article view context, you cannot include isolated view into another view.
I think the cleanest way would be to create a mixin class for categories context and add it to each view, that needs to render it.
Something like this:
class CategoryMixin(object):
def get_categories(self):
return Category.objects.all()
def get_context_data(self, **kwargs):
context = super(CategoryMixin, self).get_context_data(**kwargs)
context['categories'] = self.get_categories()
return context
then add it to the view class:
class IndexView(CategoryMixin, ListView):
...
and also include category_list.html inside each template, passing the context variable (this way you have isolated variable names):
{% include "category_list.html" with object_list=categories only %}