I am fairly new to CBV and am trying to make sense of it. I copied the following example from the django doc page:
https://docs.djangoproject.com/en/dev/topics/class-based-views/generic-editing/
models.py
from django.core.urlresolvers import reverse
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse('author-detail', kwargs={'pk': self.pk})
views.py
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.core.urlresolvers import reverse_lazy
from myapp.models import Author
class AuthorCreate(CreateView):
model = Author
fields = ['name']
class AuthorUpdate(UpdateView):
model = Author
fields = ['name']
class AuthorDelete(DeleteView):
model = Author
success_url = reverse_lazy('author-list')
urls.py
from django.conf.urls import patterns, url
from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
urlpatterns = patterns('',
# ...
url(r'author/add/$', AuthorCreate.as_view(), name='author_add'),
url(r'author/(?P<pk>\d+)/$', AuthorUpdate.as_view(), name='author_update'),
url(r'author/(?P<pk>\d+)/delete/$', AuthorDelete.as_view(), name='author_delete'),
)
At author/add/ I indeed get the form, but when I enter the string I get the following error:
Reverse for 'author-detail' with arguments '()' and keyword arguments '{'pk': 3}' not found.
It seems like the new entry has been added to the database, but it could not resolve the URL for the next view?
So I am puzzled, what is this get_absolute_url() object's method supposed to do, how does it work (I could not grasp it from the django doc) and how do I fix the issue?
Thanks.
EDIT 1: added the template:
author_form.html:
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Create" />
</form>
By default, when a new model is created django will redirect you to a models absolute url as returned by the get_absolute_url method. In your example you would need to add a url with the name author-detail that accepts a pk keyword argument.
urlpatterns = patterns('',
# ...
url(r'author/(?P<pk>\d+)/$', AuthorDetail.as_view(), name='author-detail'),
)
Note the name of the url matches the name of the view in the get_absolute_url method.
Use it in your templates:
{% for author in authors %}
{{author.name}}
{% endfor %}
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.
When I try to use DetailView to view my posts I keep getting an exception error.
ImproperlyConfigured at /post/1/
BlogDetailView is missing a QuerySet. Define BlogDetailView.model, BlogDetailView.queryset, or override BlogDetailView.get_queryset().
Request Method: GET
Request URL: http://127.0.0.1:8000/post/1/
Django Version: 2.2
Exception Type: ImproperlyConfigured
Exception Value:
BlogDetailView is missing a QuerySet. Define BlogDetailView.model, BlogDetailView.queryset, or override BlogDetailView.get_queryset().
Exception Location: C:\Users\julia.virtualenvs\Documents-SYi_ANcG\lib\site-packages\django\views\generic\detail.py in get_queryset, line 73
Python Executable: C:\Users\julia.virtualenvs\Documents-SYi_ANcG\Scripts\python.exe
Python Version: 3.7.3
I have reviewed my code against the book Django For Beginners by Will Vicent Still I can't find any problems
models.py
from django.db import models
# Create your models here.
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
)
body = models.TextField()
def __str__(self):
return self.title
views.py
from django.views.generic import ListView, DetailView # new
from .models import Post
# Create your views here.
class BlogListView(ListView):
model = Post
template_name = 'home.html'
class BlogDetailView(DetailView): # new
Model = Post
template_name = 'post_detail.html'
urls.py
# blog/urls.py
from django.urls import path
from .views import BlogListView, BlogDetailView # new
urlpatterns = [
path('post/<int:pk>/', BlogDetailView.as_view(), name='post_detail'), # new
path('', BlogListView.as_view(), name='home'),
]
post_detail.html
<!-- templates/post_detail.html-->
{% extends 'base.html' %}
{% block content %}
<div class="post-entry">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
{% endblock content %}
This code is supposed to allow me to see my posts when I browse to http://127.0.0.1/posts/1 or post/2
You have a simple typo in your DetailView: Model instead of model.
So I'm trying to wrap my head around the following issue. The Class-based view for customer_list works like a charm, it show a list of all customers residing in the customer tabel. However, the function based view, which should do exactly the same returns empty.
Maybe there's something I'm missing but I just want to know how both views differ and when to use what.
If there's someone who could explain what I'm missing?
urls.py
from django.conf.urls import url, include
from . import views
# Function based view
url(r'^all_customers/$', views.customer_list, name='all_customers'),
# Class based view
url(r'^all_customers/$', views.customer_list.as_view(),name='all_customers'),
views.py
from django.views import generic
from django.shortcuts import render
from .models import Customer
# Function based view
def customer_list(request):
customers = Customer.objects.all()
return render(request, 'all_customers.html', {'customers': customers})
# Class based view
class CustomerList(generic.ListView):
model = Customer
template_name = 'all_customers.html'
models.py
from django.db import models
class Customer(models.Model):
first_name = models.CharField(max_length=200)
prefix = models.CharField(max_length=8, null=True, blank=True)
last_name = models.CharField(max_length=200)
And this is a section of the template
{% for customer in customer_list %}
<tr>
<td>{{customer.pk}}</td>
<td>
{% if customer.prefix %}
{{customer.first_name}} {{customer.prefix}} {{customer.last_name}}
{% else %}
{{customer.first_name}} {{customer.last_name}}
{% endif %}
</td>
</tr>
{% endfor %}
your import is wrong in urls.py
you should use from .views import customer_list
I am trying to create a table view with pagination, sorting, and filtering, using the most common/standard/recommended approach for Django 1.6. This seems to be Generic Class-Based Views and django-tables2. I've found at least two different examples of how to add filtering, one with django-filters and crispy-forms and the other with django_filters, but neither includes a complete working example. When I follow either approach, I get stuck filling in the missing material. Using the crispy approach from Nicolas Kuttler, I have:
models.py
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
tables.py
import django_tables2 as dt2
from .models import Author
class AuthorTable(dt2.Table):
class Meta:
model = Author
If I understand correctly, PagedFilteredTableView is a generic class from which I then subclass AuthorView, as opposed to the other example, in which FilteredSingleTableView is, I think, supposed to be understood as something like, if Author was the table, AuthorFilteredSingleTableView.
views.py
from .tables import AuthorTable
from .models import Author
from django_tables2 import SingleTableView
class PagedFilteredTableView(SingleTableView):
"""
Generic class from http://kuttler.eu/post/using-django-tables2-filters-crispy-forms-together/
which should probably be in a utility file
"""
filter_class = None
formhelper_class = None
context_filter_name = 'filter'
def get_queryset(self, **kwargs):
qs = super(PagedFilteredTableView, self).get_queryset()
self.filter = self.filter_class(self.request.GET, queryset=qs)
self.filter.form.helper = self.formhelper_class()
return self.filter.qs
def get_table(self, **kwargs):
table = super(PagedFilteredTableView, self).get_table()
RequestConfig(self.request, paginate={'page': self.kwargs['page'],
"per_page": self.paginate_by}).configure(table)
return table
def get_context_data(self, **kwargs):
context = super(PagedFilteredTableView, self).get_context_data()
context[self.context_filter_name] = self.filter
return context
class AuthorTableView(PagedFilteredTableView):
model = Author
table_class = AuthorTable
paginate_by = 30
filter_class = AuthorFilter
formhelper_class = AuthorFilterFormHelper
This is, aside from the template, all of the example code from the source, and manage.py is complaining that AuthorFilter isn't defined, so I guess that goes into ... maybe filters.py?
filters.py
import django_filters as df
from .models import Author
class AuthorFilter(df.FilterSet):
class Meta:
model = Author
And, back in views.py, from .filters import AuthorFilter.
And now AuthorFilterFormHelper isn't defined, and it's not clear if that's something I should explicitly define (how?), as implied by the line formhelper_class = FooFilterFormHelper, or if that should actually be done automatically somehow, as implied by self.filter.form.helper = self.formhelper_class(). And we still haven't gotten to urls.py or the template. Please help fill in the blanks, or indicate a better path to go down to add filtering to a generic class-based view.
With trial and error and some suggestions from Nicolas Kuttler, I was able to get his example working. I butchered a little bit of his example but this does seem to be close to the least amount of code necessary in Django to have a generic class-based list view page with sorting, filtering (and thus search), and pagination, and I don't think it violates (too m)any Django coding practices. Here is all of the code required:
models.py (no changes from the question)
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
And a slight change to the tables.py in the question:
import django_tables2 as dt2
from .models import Author
class AuthorTable(dt2.Table):
class Meta:
model = Author
attrs = {"class": "paleblue"}
per_page = 30
filters.py (no changes from the question)
import django_filters as df
from .models import Author
class AuthorFilter(df.FilterSet):
class Meta:
model = Author
forms.py. I couldn't figure out how/where to get the form submit button out of django_filters in combination with everything else, so this code suppresses the form wrapper tags from crispy, and then we provide that HTML in the template, which is probably the kludgiest part of this.
from django import forms
from .models import Author
from crispy_forms.helper import FormHelper
class AuthorListFormHelper(FormHelper):
model = Author
form_tag = False
I moved the helper function out of views.py to a separate file and had to remove the pagination code to prevent an error (though pagination still works). So,
utils.py
from django_tables2 import SingleTableView
from django_tables2.config import RequestConfig
class PagedFilteredTableView(SingleTableView):
filter_class = None
formhelper_class = None
context_filter_name = 'filter'
def get_queryset(self, **kwargs):
qs = super(PagedFilteredTableView, self).get_queryset()
self.filter = self.filter_class(self.request.GET, queryset=qs)
self.filter.form.helper = self.formhelper_class()
return self.filter.qs
def get_context_data(self, **kwargs):
context = super(PagedFilteredTableView, self).get_context_data()
context[self.context_filter_name] = self.filter
return context
Instead of the views.py in the question, this views.py:
from .models import Author
from .tables import AuthorTable
from .filters import AuthorListFilter
from .forms import AuthorListFormHelper
from utils import PagedFilteredTableView
class AuthorList(PagedFilteredTableView):
model = Author
table_class = AuthorTable
filter_class = AuthorListFilter
formhelper_class = AuthorListFormHelper
And this template:
author_list.html (the name is not explicitly specified anywhere because it's implied by the model in the view, I think)
{% extends "base.html" %}
{% load render_table from django_tables2 %}
{% load crispy_forms_tags %}
{% block content %}
{% render_table table %}
<hr/>
<form action="" method="get">
{% crispy filter.form filter.form.helper %}
<input type="submit" value="Filter"/>
</form>
{% endblock content %}
And, for completeness, a line in urls.py:
...
url(r'^$', views.AuthorTableList.as_view(), name='author_table'),
...
And you must have the packages django-tables2, django-filters, and crispy-forms installed and configured as per their instructions. The one thing that tripped me up was that I somehow, on the first attempt, missed that I needed this in settings.py:
CRISPY_TEMPLATE_PACK = 'bootstrap'
All of this is for Django 1.6.
Your answer help me, so i edited your code to include the submit button from crispy-forms
forms.py. I couldn't figure out how/where to get the form submit button out of django_filters in combination with everything else, so this code suppresses the form wrapper tags from crispy, and then we provide that HTML in the template, which is probably the kludgiest part of this.
forms.py (UPDATED)
from django import forms
from .models import Author
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, ButtonHolder, Submit
class AuthorListFormHelper(FormHelper):
model = Author
form_tag = False
# Adding a Filter Button
layout = Layout('name', ButtonHolder(
Submit('submit', 'Filter', css_class='button white right')
))
author-list.html (UPDATED)
{% extends "base.html" %}
{% load render_table from django_tables2 %}
{% load crispy_forms_tags %}
{% block content %}
{% render_table table %}
<hr/>
<form action="" method="get">
{% crispy filter.form filter.form.helper %}
</form>
{% endblock content %}
I'm trying to create a marketplace website similar to craigslist.
I created a form according to the Django tutorial "Working with forms", but I don't know how to render information I got from the POST forms.
I want to make information(subject,price...etc) that I got from POST show up on another page like this. http://bakersfield.craigslist.org/atq/3375938126.html and, I want the "Subject"(please look at form.py) of this product(eg.1960 French Chair) to show up on another page like this. http://bakersfield.craigslist.org/ata/ }
Can I get some advice to handle submitted information?
Here's present codes. I'll appreciate all your answers and helps.
<-! Here's my codes -->
◆forms.py
from django import forms
class SellForm(forms.Form):
subject = forms.CharField(max_length=100)
price = forms.CharField(max_length=100)
condition = forms.CharField(max_length=100)
email = forms.EmailField()
body = forms.TextField()
◆views.py
from django.shortcuts import render, render_to_response
from django.http import HttpResponseRedirect
from site1.forms import SellForm
def sell(request):
if request.method =="POST":
form =SellForm(request.POST)
if form.is_valid():
subject = form.cleaned_data['subject']
price = form.cleaned_data['price']
condition = form.cleaned_data['condition']
email = form.cleaned_data['email']
body = form.cleaned_data['body']
return HttpResponseRedirect('/books/')
else:
form=SellForm()
render(request, 'sell.html',{'form':form,})
◆urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^sechand/$','site1.views.sell'),
url(r'^admin/', include(admin.site.urls)),
)
◆sell.html
<form action = "/sell/" method = "post">{% csrf_token%}
{{ form.as_p }}
<input type = "submit" value="Submit" />
</form>
I assume you have a Sell model/table in your db(where you store the users' "sells"), otherwise it wouldn't make any sense. This means you can save yourself some time and use a ModelForm,
instead of a simple Form. A model form takes a database table and produces an html form for it.
forms.py
from django.forms import ModelForm
from yourapp.models import Sell
class SellForm(ModelForm):
class Meta:
model = Sell
In your views.py you need one more view that displays the Sells that your users have
posted for others to see. You also need an html template that this view will render with context about each Sell.
sell_display.html
{% extends 'some_base_template_of_your_site.html' %}
{% block content %}
<div id="sell">
<h3> {{ sell.subject }}</h3>
<p> {{ sell.condition }}</p>
<p> {{ sell.body }}</p>
<!-- the rest of the fields.. -->
</div>
{% endblock %}
We also need a new url entry for the displaying of a specific Sell
urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Changed `sell` view to `sell_create`
url(r'^sechand/$','site1.views.sell_create'),
# We also add the detail displaying view of a Sell here
url(r'^sechand/(\d+)/$','site1.views.sell_detail'),
url(r'^admin/', include(admin.site.urls)),
)
views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response, get_object_or_404
from yourapp.models import Sell
from yourapp.forms import SellForm
def sell_detail(request, pk):
sell = get_object_or_404(Sell, pk=int(pk))
return render_to_response('sell_display.html', {'sell':sell})
def sell_create(request):
context = {}
if request.method == 'POST':
form = SellForm(request.POST)
if form.is_valid():
# The benefit of the ModelForm is that it knows how to create an instance of its underlying Model on your database.
new_sell = form.save() # ModelForm.save() return the newly created Sell.
# We immediately redirect the user to the new Sell's display page
return HttpResponseRedict('/sechand/%d/' % new_sell.pk)
else:
form = SellForm() # On GET request, instantiate an empty form to fill in.
context['form'] = form
return render_to_response('sell.html', context)
This is enough to get you going I think. There are patterns to make these things more modular and better, but I don't want to flood you with too much information, since you are a django beginner.