I have a project that requires an update form, I am using the Django generic views, specifically the UpdateView.
I Think this is an error with the URL, but I dont find where it is.
Error also refers to the url The current path, actualizar_empleado/, didn’t match any of these.
BTW if you see something else in my code that should be corrected, or that I can add a better practice, feel free to let me now.
My code is the following:
Views.py
from django.shortcuts import render, get_object_or_404
from django.views.generic import (
CreateView,
DetailView,
ListView,
UpdateView,
ListView,
DeleteView
)
from . models import EmployeesInfo
from . forms import EmployeeForm
class EmployeeCreate(CreateView):
form_class = EmployeeForm
template_name = 'employeeCreate.html'
success_url = '/lista_empleados/'
def form_valid(self, form):
print(form.cleaned_data)
return super().form_valid(form)
class EmployeeList(ListView):
model = EmployeesInfo
template_name = 'employeeList.html'
success_url = 'lista-empleados/exitoso'
class EmployeeDetail(DetailView):
model = EmployeesInfo
template_name = 'employeeDetail.html'
success_url = 'detalle-empleado/exitoso'
def get_object(self):
id_ = self.kwargs.get("pk")
return get_object_or_404(EmployeesInfo, pk=id_)
class EmployeeUpdate(UpdateView):
form_class = EmployeeForm
queryset = EmployeesInfo.objects.all()
template_name = 'employeeUpdate.html'
success_url = '/listaempleados/'
def form_valid(self, form):
print(form.cleaned_data)
return super().form_valid(form)
def get_object(self):
id_ = self.kwargs.get("pk")
return get_object_or_404(EmployeesInfo, pk=id_)
urls.py
from django.contrib import admin
from django.urls import path, re_path
from . views import EmployeeCreate, EmployeeList, EmployeeDetail, EmployeeUpdate
urlpatterns = [
path('crear_empleado/', EmployeeCreate.as_view(), name = 'createmp'),
path('lista_empleados', EmployeeList.as_view(), name = 'listemp'),
path('detalle_empleado/<int:pk>', EmployeeDetail.as_view(), name = 'showemp'),
path('actualizar_empleado/<int:pk>', EmployeeUpdate.as_view(), name = 'updatemp'),
]
employeeUpdate.html
<body>
{%extends 'base.html'%}
{%block content%}
<div class="row">
<div class="col-md-9">
<div class="card card-body">
<form method="PUT" action="." enctype="multipart/form-data">
{%csrf_token%}
<table>
{{form.as_table}}
</table>
<input type="submit" value="Actualizar">
</form>
</div>
</div>
</div>
{%endblock%}
</body>
Use django.urls.reverse for resolving urls, don't put raw paths, it's harder to maintain. Also, your success_url in is wrong in the post (I'm guessing it's a wrong copy/paste). Anyway, you should use reverse for getting that URL. You also need to add a PK because your URL contains a PK.
As Arif Rasim mentioned in comments, instead of queryset, define model in your EmployeeUpdate view.
Your use of get_object is correct but not necessary as this is done by the views (SingleObjectMixin). See more here
Use absolute imports (recommended by PEP8). It's not wrong or incorrect to use relative imports but absolute imports give more precise error messages.
The action attribute in your form in the HTML template should be empty (right now it's ".") if you want to submit the form to the current page (recommended for flexibility).
Specifying enctype in your form is not necessary if the form does not handle file fields. Let the browser and framework take care of it.
The method attribute in your form can only take values of "GET" and "POST".
# ...
# Using absolute imports, yourapp is the package name of your app
from yourapp.models import EmployeesInfo
from yourapp.forms import EmployeeForm
class EmployeeCreate(CreateView):
form_class = EmployeeForm
template_name = 'employeeCreate.html'
# Here you can use reverse_lazy (reverse doesn't work) to keep things
# shorter (as opposed to using get_success_url method)
success_url = reverse_lazy('createmp') # shouldn't it be "createemp"?
def form_valid(self, form):
print(form.cleaned_data)
return super().form_valid(form)
class EmployeeList(ListView):
model = EmployeesInfo
template_name = 'employeeList.html'
success_url = reverse_lazy('listemp')
class EmployeeDetail(DetailView):
model = EmployeesInfo
template_name = 'employeeDetail.html'
# Unnecessary - done by DetailView
# def get_object(self):
# id_ = self.kwargs.get("pk")
# return get_object_or_404(EmployeesInfo, pk=id_)
def get_success_url(self):
# NOTE: don't forget the comma after the PK as "args"
# param should be a tuple
return reverse('showemp', args=(self.kwargs['pk'],))
class EmployeeUpdate(UpdateView):
form_class = EmployeeForm
model = EmployeeInfo
def form_valid(self, form):
print(form.cleaned_data)
return super().form_valid(form)
# Unnecessary - done by UpdateView
# def get_object(self):
# id_ = self.kwargs.get("pk")
# return get_object_or_404(EmployeesInfo, pk=id_)
def get_success_url(self):
# NOTE: don't forget the comma after the PK as "args"
# param should be a tuple
# BTW, Shouldn't this be "updateemp"?
return reverse('updatemp', args=(self.kwargs['pk'],))
<!-- employeeUpdate.html -->
<form method="POST" action="">
Related
I'm trying to get all the post by a single user and display it using DetailView and I also want to pass the username of the user on the URL.
this is my urls.py:
from django.urls import path
from .views import ProfileDetail
from . import views
urlpatterns = [
path('<str:username>/', ProfileDetail.as_view(), name = 'profile'),
]
this is my views.py:
from django.views.generic import (DetailView)
from django.shortcuts import render , redirect, get_object_or_404
from django.contrib.auth.models import User
from blog.models import Post
class ProfileDetail(DetailView):
model = Post
template_name = 'users/myprofile.html'
context_object_name = 'posts'
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author = user).order_by('-date_posted')
I have a class-based view almost exactly just like this one and it is working.
This one always gives me this AttributeError: Generic detail view ProfileDetail must be called with either an object pk or a slug in the URLconf.
If you want to display multiple posts, then a ListView with model = Post would be more suitable.
from django.views.generic import ListView
class ProfileDetail(List):
model = Post
template_name = 'users/myprofile.html'
context_object_name = 'posts'
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author = user).order_by('-date_posted')
Alternatively, if you want to use DetailView, then you should have model = User because you are showing the posts for a single user. You can avoid the "must be called with either an object pk or a slug" error by overriding get_object.
from django.views.generic import DetailView
class ProfileDetail(ListView):
model = User
template_name = 'users/myprofile.html'
def get_object(self):
return User.objects.get(username=self.kwargs['username'])
Then, in the template, you can loop over the user's posts with something like:
{% for post in user.post_set.all %}
{{ post }}
{% endfor %}
Note that by switching to DetailView, you lose the pagination features of ListView.
on my landing page / home page I want to add a contact form and a subscribe to newsletter form. Since I want to keep the logic for both forms in separate apps I am wondering how I can use Django's internal form error handling in case the user does an input error. Similar to the standard form error handling I want that the landing page is reloaded with the errors next to the form fields. This is fairly easy if the logic stays within the same app but I can't wrap my head around it how to do this when the logic is in another app. Here is the basic layout:
project/views.py:
from django.views.generic.base import TemplateView
from contact.forms import ContactForm
from newsletter.forms import NewsletterForm
class HomeView(TemplateView):
template_name = "home.html"
contact_form = ContactForm()
newsletter_form = NewsletterForm()
title = "Home"
contact/forms.py
from django import forms
from .models import Contact
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
fields = ['contact_name', 'contact_email', 'content']
widgets = {
'contact_name': forms.TextInput(attrs={'placeholder': 'Your name'}),
'contact_email': forms.TextInput(attrs={'placeholder': 'Your Email address'}),
'content': forms.Textarea(attrs={'placeholder': 'Your message'}),
}
contact/models.py
from django.db import models
class Contact(models.Model):
contact_name = models.CharField(max_length=120)
contact_email = models.EmailField()
content = models.TextField()
timestamp = models.TimeField(auto_now_add=True, auto_now=False)
def __str__(self):
return self.contact_name + self.timestamp
contact/views.py
from django.shortcuts import HttpResponseRedirect
from django.urls import reverse_lazy
from django.views import View
from .forms import ContactForm
class SendContactForm(View):
form_class = ContactForm
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
print("all ok")
return HttpResponseRedirect(reverse_lazy('home'))
else:
print(form.contact_name.errors)
return HttpResponseRedirect(reverse_lazy('home'))
So the question is now how must the contact (or newsletter) view look like and the project view look like to handle errors with Django's normal internal form handling (adding error messages next to the incorrect input fields).
Thanks for the help!
UPDATE:
I wanted to share the solution I ended up using. Maybe there is a best practice how to show several forms on the front page but handle the logic in respective apps. However, this ended up working for me.
project/views.py:
from django.views.generic.base import TemplateView
from contact.forms import ContactForm
from newsletter.forms import NewsletterForm
class HomeView(TemplateView):
template_name = "home2.html"
title = "Home"
def get_context_data(self, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
try:
old_contact_form = self.request.session["contact_form"]
except:
old_contact_form = None
try:
old_newsletter_form = self.request.session["newsletter_form"]
except:
old_newsletter_form = None
context["contact_form"] = ContactForm(old_contact_form)
context["newsletter_form"] = ContactForm(newsletter_form)
return context
contact/views.py:
from django.shortcuts import HttpResponseRedirect
from django.urls import reverse_lazy
from django.views import View
from .forms import ContactForm
class SendContactForm(View):
form_class = ContactForm
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
print("all ok")
else:
request.session["contact_form"] = request.POST
return HttpResponseRedirect(reverse_lazy("home"))
And the same in the respective newsletter. Using sessions did the trick for me.
This could me one of the solution. If form validation fails send the form back as context
if form.is_valid():
print("all ok")
return HttpResponseRedirect(reverse_lazy('home'))
else:
print(form.contact_name.errors)
return Render(request, '/path/to/your/tempalate', context={'form': form})
You can do something like this to capture error on your template
home.html
<div class="fieldWrapper">
{{ form.contact_name.errors }}
<label for="{{ form.contact_name.id_for_label }}">Email subject:</label>
{{ form.contact_name }}
</div>
<div class="fieldWrapper">
{{ form.contact_email.errors }}
<label for="{{ form.contact_email.id_for_label }}">Your message:</label>
{{ form.contact_email }}
</div>
I have a ListView class in view.py
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic
from entertainment.models import Entertainmentblog
class ListView(generic.ListView):
template_name = 'entertainment/index.html'
context_object_name = 'latest_article_list'
slug = None
id = None
def get_queryset(self):
return Entertainmentblog.objects.order_by('-posted')[:25]
class DetailView(generic.DetailView):
model = Entertainmentblog
template_name = 'entertainment/article.html'
and I'm using this view to display a list of articles in index.html.But,I would like to show the same list of articles in article.html after the article.I have used the blocks correctly but,it won't show any articles because in ListViewthe template name is index.html.How do I solve this?
Use a Mixin:
class LatestArticleMixin(object):
def get_context_data(self, **kwargs):
context = super(LatestArticleMixin, self).get_context_data(**kwargs)
try:
context['latest_article_list'] = Entertainmentblog.objects.order_by('-posted')[:25]
except:
pass
return context
Then refactor your DetailView:
class DetailView(LatestArticleMixin, generic.DetailView):
model = Entertainmentblog
template_name = 'entertainment/article.html'
In your template if there are articles:
{% if latest_article_list %}
....
{% endif %}
In urls.py you can set template_name as attribute to ListView url entry router.
urls.py
urlpatterns = patterns('',
(r'^a/$', ListView.as_view(model=Poll, template_name="a.html")),
(r'^b/$', ListView.as_view(model=Poll, template_name="b.html")),
)
In views.py even you don't need to set template.
views.py
class ListView(generic.ListView):
model = Poll
I am new in Django and I am creating a form,
#forms.py
class SetupForm(forms.Form):
pass
and corresponding view is
#views.py
class SetupView(FormView):
template_name = 'template/setup_form.html'
form_class = SetupForm
success_url = 'template/base_collect'
def form_valid(self,form):
return super(SetupView, self).form_valid(form)
def base(request):
return render_to_response('template/base_collect.html')
and url patterns are,
#urls.py
urlpatterns = patterns(url(r'^setup-form/$', views.SetupView.as_view(), name='setup-form'),
url(r'^base_collect/$', views.base),)
After submitting the form, the redirected URL is shown as
http://127.0.0.1:8000/template/setup-form/template/base_collect.html instead of http://127.0.0.1:8000/template/base_collect.
What am I missing here? How to achieve later?
Modify as below :
#urls.py
urlpatterns = patterns(
url(r'^setup-form/$', views.SetupView.as_view(), name='setup-form'),
url(r'^base_collect/$', views.base, name = 'base-form'),
)
And
Replace
success_url = 'template/base_collect.html'
with
success_url = reverse('base-form')
Import
from django.core.urlresolvers import reverse
In your views.py
You mistakenly put the template name in the success_url property for the class. You also don't need the other two methods, as they aren't adding any functionality to what the default FormView class provides.
class SetupView(FormView):
template_name = 'template/setup_form.html'
form_class = SetupForm
success_url = 'collect/base_collect' # the URL goes here
#def form_valid(self,form):
# return super(SetupView, self).form_valid(form)
#def base(request):
# return render_to_response('template/base_collect.html')
This is my form class:
from django import forms
from .models import Category
class AddResource(forms.Form):
CATEGORY_CHOICES = [cat.title for cat in Category.objects.all()]
ResourceTitle = forms.CharField()
Description = forms.Textarea()
Link = forms.URLField()
Categories = forms.MultipleChoiceField(choices=CATEGORY_CHOICES)
This is my view for the form URL:
from django.shortcuts import render
from django.views.generic import View, TemplateView, FormView
from .models import *
from .forms import AddResource
class AddResourceView(FormView):
template_name = 'addRes.html'
form_class = AddResource
success_url = '/'
def get_form(self, form_class):
return form_class()
def form_valid(self, form):
print form
return super(AddResourceView, self).form_valid(form)
The trouble that I'm having is calling this inside templates. How do I call the form that I assigned inside of django-templates?
The FormView supplies a context variable called form that holds your actual form object, so you can use:
{{ form.as_table }}
Or any of the other rendering options that form supplies. See: https://docs.djangoproject.com/en/dev/topics/forms/#displaying-a-form-using-a-template
I found the answer. Its how you include choices in django forms. So CATEGORY_CHOICES has to be a list of tuples, which it was not in this case.
Thus, this simple change made it work:
CATEGORY_CHOICES = [('', cat.title)
for cat in Category.objects.all()]