I have a form implemented in a ListView. The form is used for editing existing items. However I get a key error when i try to submit the form:
File "C:\Users\John\Desktop\website\app\views.py", line 124, in form_valid
id = self.kwargs['id']
KeyError: 'id'
[14/Dec/2016 21:42:09] "POST /car/ HTTP/1.1" 500 99346
This is the main code:
class CarListFormView(FormView):
form_class = CarListForm
def form_valid(self, form):
id = self.kwargs['id']
obj = Car.objects.get(pk=id)
form = CarListForm(instance=obj)
car = form.save(commit=False)
if self.request.user.is_staff:
car.agency = Agency.objects.get(agency_id=9000)
else:
car.agency = self.request.user.agency
car.save()
return redirect('car_list')
What causes this key error?
Thanks!
id is not in your kwargs, that is causing the KeyError. In fact, you don't need to load the car like that. Your method can be simplified to this:
from django.views.generic import UpdateView
class CarListFormView(UpdateView):
model = Car
form_class = CarListForm
template_name = 'something/car_form.html'
def form_valid(self, form):
car = form.save(commit=False)
if self.request.user.is_staff:
car.agency = Agency.objects.get(agency_id=9000)
else:
car.agency = self.request.user.agency
car.save()
return redirect('car_list')
Related
I wanto to display the current user in the form before submitting.
views.py
class PostEncabezadoReporte(LoginRequiredMixin, CreateView):
login_url = '/login/'
redirect_field_name = 'redirect_to'
form_class = PostEncabezadoReporteForm
template_name = "crear_reporte.html"
def form_valid(self, form):
object = form.save(commit=False)
object.user = self.request.user
object.startweek, object.endweek = self.weekdatetimeconverter(
object.semana)
object.folio = self.getfolio(
object.user, object.semana, object.tipo_reporte)
self.validar_unico = self.reporte_unico(
object.user, object.semana, object.cliente)
if self.validar_unico == 0:
object.save()
else:
return self.form_invalid(form)
return super(PostEncabezadoReporte, self).form_valid(form)
forms.py
class PostEncabezadoReporteForm(forms.ModelForm):
class Meta:
model = EncabezadoReporte
fields = ('user', 'tipo_reporte', 'tipo_gasto', 'cliente',
'semana', 'folio')
widgets = {'semana': forms.DateInput(attrs={'type': 'week'}),
}
I alreayd tried to override the init in the form and is not working, I can select the user in the field but I want it to be displayed at init.
I have tried to override the init in the form but I had a problem, I was missing a line after the super, this is an example of another form init that I did:
def __init__(self, *args, **kwargs):
self.carro = kwargs.pop('encabezado')
super(AgregarGastoReporte, self).__init__(*args, **kwargs)
self.fields['encabezado'].initial = self.carro
I'm new to Django and having trouble redirecting after the AddContactEvent form has been filled out. After submitting the form, here is the redirect error:
No URL to redirect to. Either provide a url or define a
get_absolute_url method on the Model.
I am having trouble figuring out how to redirect it since the AddContactEvent url path('contacts/<int:pk1>/addcontactevent)
only has one pk. In the EventDetail url there are clearly two pk which would have the contact pk and the event pk. The EventDetail page seems to be creating, but I can't get it to redirect to that page due to multiple PK. how would you handle the redirect?
urls.py
path('contacts/<int:pk>', contact_detail.as_view(), name="contact_detail"),
path('contacts/<int:pk1>/addcontactevent', AddContactEvent.as_view(), name="addcontactevent"),
path('contacts/<int:pk1>/event/<int:pk2>/update', UpdateContactEvent.as_view(), name="updatecontactevent"),
path('contacts/<int:pk1>/event/<int:pk2>', EventDetail.as_view(), name="eventdetail"),
views.py
class AddContactEvent(CreateView):
form_class = ContactEventForm
template_name = 'crm/contactevent.html'
def dispatch(self, request, *args, **kwargs):
"""
Overridden so we can make sure the `Ipsum` instance exists
before going any further.
"""
self.contact = get_object_or_404(Contact, pk=kwargs['pk1'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
""" Save the form instance. """
contact = get_object_or_404(Contact, pk=self.kwargs['pk1'])
form.instance.contact = contact
form.instance.created_by = self.request.user
return super().form_valid(form)
class UpdateContactEvent(UpdateView):
model = Event
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
contact = get_object_or_404(Contact, pk=pk1)
event = get_object_or_404(Event, pk=pk2)
return event
class DeleteContactEvent(DeleteView):
model = Event
class EventDetail(DetailView):
template_name = 'crm/eventdetail.html'
model = Event
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
contact = get_object_or_404(Contact, pk=pk1)
event = get_object_or_404(Event, pk=pk2)
return event
one way to get rid of the error is to define a get absolute url in contact model
def get_absolute_url(self):
return reverse("contact_detail", kwargs={"pk": self.pk})
You have a saved Event object (which has a pk) and you have the contact pk
def get_success_url(self):
return reverse('eventdetail', kwargs={'pk1': self.kwargs['pk1'], 'pk2': self.object.pk})
I have used SuccessMessageMixin class but then also in the createview I did'nt get the success message but in the updateview it is working when I return the super().form_valid(form)
class DepartmentCreateView(LoginRequiredMixin, PermissionRequiredMixin,SuccessMessageMixin ,CreateView):
template_name = 'departments/create_department.html'
form_class = DepartmentForm
success_url = reverse_lazy('departments:departments')
permission_required = ('departments.add_department',)
def form_valid(self, form):
department = form.save(commit=False)
department.created_by = self.request.user
department.updated_by = self.request.user
department.slug = slugify(uuid.uuid4())
department.save()
message = '[{"created": {}}]'
# retriving ContentType object
ct_obj = ContentType.objects.get(model='department')
# creating history object
history = History.objects.create(
action_time=timezone.now(),
action='created',
user=department.created_by,
content_type=ct_obj,
object_id=department.id,
change_message=message,
)
history.save()
return super().form_valid(form)
You haven't actually set a success message.
class DepartmentCreateView(LoginRequiredMixin, PermissionRequiredMixin,SuccessMessageMixin ,CreateView):
success_message = "Department was created successfully"
...
Note, your form_valid is saving things twice. You should do:
def form_valid(self, form):
form.instance.slug = slugify(uuid.uuid4())
return super().form_valid(form)
I tried to use SuccessMessageMixin with CreateView but got an error.
I used it with UpdateView and it worked.
It'd be nice to get a hint about what to do next. Thanks.
Repo: https://github.com/jeremy886/DjangoBasics/blob/DjangoForms/courses/views.py
Error:
AttributeError at /courses/2/create_quiz/
'Quiz' object has no attribute 'cleaned_data'
Request Method: POST
Request URL: http://localhost:8000/courses/2/create_quiz/
Django Version: 2.0.5
Exception Type: AttributeError
Exception Value:
'Quiz' object has no attribute 'cleaned_data'
Exception Location: C:\Users\jeremy\.virtualenvs\django\lib\site-packages\django\contrib\messages\views.py in form_valid, line 12
Code:
class QuizCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = "course"
pk_url_kwarg = "course_pk"
context_object_name = 'course'
form_class = forms.QuizForm
template_name = "courses/quiz_create.html"
success_message = "%(title)s was created successfully"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
course_pk = self.kwargs.get(self.pk_url_kwarg)
course = get_object_or_404(models.Course, pk=course_pk)
context["course"] = course
return context
def form_valid(self, form):
course_pk = self.kwargs.get(self.pk_url_kwarg)
quiz_form = form.save(commit=False)
quiz_form.course = get_object_or_404(models.Course, pk=course_pk)
# find a way to add "Successfully added!" message
return super().form_valid(quiz_form)
def get_success_url(self):
course_pk = self.kwargs["course_pk"]
return reverse_lazy('courses:detail', kwargs={'pk': course_pk})
# More: how to get the quiz id from the above quiz form
The problem is in form_valid function: return super().form_valid(quiz_form). It should be return super().form_valid(form) instead.
def form_valid(self, form):
course_pk = self.kwargs.get(self.pk_url_kwarg)
form.instance.course = get_object_or_404(models.Course, pk=course_pk)
return super().form_valid(form)
I am looking for a simple answer by example to this common problem. The answers I found so far leave out critical points for us beginners.
I have an app where almost every model has a ForeignKey to User, and there is a unique_together constraint, where one of the fields is always 'user'.
For example:
class SubscriberList(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=70)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = (
('user', 'name',),
)
def __unicode__(self):
return self.name
A SubscriberList is always created by a logged in User, and thus in the form to create a Subscriber List, I exclude the user field and give it a value of self.request.user when saving the form, like so:
class SubscriberListCreateView(AuthCreateView):
model = SubscriberList
template_name = "forms/app.html"
form_class = SubscriberListForm
success_url = "/app/lists/"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
return super(SubscriberListCreateView, self).form_valid(form)
And here is the accompanying form:
class SubscriberListForm(ModelForm):
class Meta:
model = SubscriberList
exclude = ('user')
With this code, valid data is fine. When I submit data that is not unique_together, I get an Integrity Error from the database. The reason is clear to me - Django doesn't validate the unique_together because the 'user' field is excluded.
How do I change my existing code, still using CreateView, so that submitted data that is not unique_together throws a form validation error, and not an Integrity Error from the db.
Yehonatan's example got me there, but I had to call the messages from within the ValidationError of form_valid, rather than a separate form_invalid function.
This works:
class SubscriberCreateView(AuthCreateView):
model = Subscriber
template_name = "forms/app.html"
form_class = SubscriberForm
success_url = "/app/subscribers/"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
try:
self.object.full_clean()
except ValidationError:
#raise ValidationError("No can do, you have used this name before!")
#return self.form_invalid(form)
from django.forms.util import ErrorList
form._errors["email"] = ErrorList([u"You already have an email with that name man."])
return super(SubscriberCreateView, self).form_invalid(form)
return super(SubscriberCreateView, self).form_valid(form)
Taking from the docs at:
https://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#validating-objects
You should only need to call a model’s full_clean() method if you plan to handle validation errors yourself, or if you have excluded fields from the ModelForm that require validation.
Taking from the docs at:
https://docs.djangoproject.com/en/dev/ref/class-based-views/#formmixin
Views mixing FormMixin must provide an implementation of form_valid() and form_invalid().
This means that in order to view the error (which isn't form related) you'll need to implement your own form_invalid, add the special error message there, and return it.
So, running a full_clean() on your object should raise the unique_together error, so your code could look like this:
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
# validate unique_together constraint
try:
self.object.full_clean()
except ValidationError:
# here you can return the same view with error messages
# e.g.
return self.form_invalid(form)
return super(SubscriberListCreateView, self).form_valid(form)
def form_invalid(self, form):
# using messages
# from django.contrib import messages
# messages.error('You already have a list with that name')
# or adding a custom error
from django.forms.util import ErrorList
form._errors["name"] = ErrorList([u"You already have a list with that name"])
return super(SubscriberListCreateView, self).form_invalid(form)
HTH
adding another example that might be a bit easier for noobs.
forms.py
class GroupItemForm(ModelForm):
def form_valid(self):
self.object = self.save(commit=False)
try:
self.object.full_clean()
except ValidationError:
# here you can return the same view with error messages
# e.g. field level error or...
self._errors["sku"] = self.error_class([u"You already have an email with that name."])
# ... form level error
self.errors['__all__'] = self.error_class(["error msg"]
return False
return True
views.py
def add_stock_item_detail(request, item_id, form_class=GroupItemForm, template_name="myapp/mytemplate.html"):
item = get_object_or_404(Item, pk=item_id)
product = Product(item=item)
if request.method == 'POST':
form = form_class(request.POST, instance=product)
if form.is_valid() and form.form_valid():
form.save()
return HttpResponseRedirect('someurl')
else:
form = form_class(instance=product)
ctx.update({
"form" : form,
})
return render_to_response(template_name, RequestContext(request, ctx))