I have a ModelForm and I'm trying to display it with some of the fields disabled. In this case, process_id.
The important part of my Model looks like this:
models.py
class Process(models.Model):
related_processes = models.ManyToManyField('self', blank=True, symmetrical=False)
process_id = models.CharField(max_length=20, primary_key=True)
normal_field = models.CharField(max_length=20)
# a lot of fields here...
So basically I have a Process that can have zero or more related processes.
This is what I have on forms.py:
forms.py
class ProcessForm(ModelForm):
class Meta:
model = Process
fields = '__all__'
class EditProcessForm(ProcessForm):
readonly_fields = ('process_id', )
def __init__(self, *args, **kwargs):
super(EditProcessForm, self).__init__(*args, **kwargs)
for field in (field for name, field in self.fields.items() if name in self.readonly_fields):
field.widget.attrs['disabled'] = 'true'
field.required = False
def clean(self):
for f in self.readonly_fields:
self.cleaned_data.pop(f, None)
return super(EditProcessForm, self).clean()
class NewVersionProcessForm(EditProcessForm):
readonly_fields = ('process_id', )
def __init__(self, *args, **kwargs):
super(NewVersionProcessForm, self).__init__(*args, **kwargs)
for field in (field for name, field in self.fields.items() if name in self.readonly_fields):
# field.widget.attrs['disabled'] = 'true'
# Remember this line ^
field.required = False
The first time the client is filling the form, I want all fields to be editable, so I use ProcessForm. But when the client is editing the Process, I want some fields to be read-only. I found this nice solution here on stackoverflow (unfortunately I couldn't find it again) and it works perfectly when I'm editing a process.
The difference between EditProcessForm and NewVersionProcessForm is their Views and the commented line on __init__.
views.py
class ProcessFormView(FormView):
template_name = 'my_app/fill_form.html'
form_class = ProcessForm
def form_valid(self, form):
form.save()
return redirect('my_app:show_process_page', form.cleaned_data.get('process_id'))
class EditProcessView(UpdateView):
model = Process
form_class = EditProcessForm
template_name = 'my_app/edit_form.html'
pk_url_kwarg = 'process_id'
def post(self, request, process_id):
# This is a little hack I found here: https://stackoverflow.com/a/21262262/3773461
# to edit an immutable QueryDict.
mutable = request.POST._mutable
request.POST._mutable = True
request.POST['process_id'] = process_id
request.POST._mutable = mutable
return super().post(request)
def form_valid(self, form):
form.save()
return redirect('my_app:show_process_page', self.kwargs['process_id'])
class NewVersionProcessView(EditProcessView):
template_name = 'my_app/new_version_form.html'
form_class = NewVersionProcessForm
def get(self, request, process_id):
try:
Process.objects.get(process_id=process_id)
return redirect('my_app:process_already_exists_page')
except Process.DoesNotExist:
self.object = Process(process_id=process_id)
last_id = self.object.get_last_version_id()
last_process = Process.objects.get(process_id=last_id)
last_process_dict = last_process.__dict__
related_processes = last_process.related_processes.all()[:]
exclude = ['_state', 'process_id']
for key, value in last_process_dict.items():
if key not in exclude:
self.object.__dict__[key] = value
# self.object.related_processes.add(*related_processes)
# Remember this line too ^
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.form_invalid(form)
def post(self, request, process_id):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.save()
return redirect('my_app:show_process_page', form.cleaned_data['process_id'])
The first view is a simple FormView with ProcessForm. The second view is an UpdateView. On post I just add the field that is disabled on the form (and therefore is not passed ahead normally). On form_valid I just save the form on its model (with the just added field).
The third view is my current problem. Keep in mind that I can do 3 things with a Process: create a new one, edit it, or create a new version of an existing process. This third view intends to do the third one.
On get, I check if I'm trying to create a process that already exists. If it doesn't, I create the Process and copy all useful data on the base process to it, except the ManyToMany field (the second "remember this line"), because I couldn't figure out a way to do that without saving the Model first.
On post, I'm checking if the form is valid, and on form_valid, I'm saving the process.
What works
I can create and edit processes just fine. The read-only fields when editing works perfectly. The model is saved as intended.
Also, I can create a new process using another existing process as a base (NewVersionProcessView) if the first "remember this line" is commented, that is, if all the fields are editable.
What doesn't work
If the first "remember this line" is uncommented, that is, if some of the fields are read-only, my view crashes on post. Most specifically, on form_invalid. The only thing I can think of is that somehow there's not a Process object linked to my NewVersionProcessView. I can't understand why though, because I always assign something to self.object on get. I cannot get the object from the database on post because it is not saved yet. Also, I cannot understand why a field being enabled or disabled affects self.object. Remember, the only thing that changes is that field.widget.attrs['disabled'] = 'true' line.
Here is the log:
Request Method: POST
Request URL: http://127.0.0.1:8000/my_app/new_version/999-11/
Django Version: 2.0.7
Python Version: 3.4.4
Installed Applications:
['my_app.apps.MyAppConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Traceback:
File "C:\Python34\lib\site-packages\django\core\handlers\exception.py" in inner
35. response = get_response(request)
File "C:\Python34\lib\site-packages\django\core\handlers\base.py" in _get_response
128. response = self.process_exception_by_middleware(e, request)
File "C:\Python34\lib\site-packages\django\core\handlers\base.py" in _get_response
126. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Python34\lib\site-packages\django\views\generic\base.py" in view
69. return self.dispatch(request, *args, **kwargs)
File "C:\Python34\lib\site-packages\django\views\generic\base.py" in dispatch
89. return handler(request, *args, **kwargs)
File "C:\Users\***\Desktop\my_app\views.py" in post
124. return self.form_invalid(form)
File "C:\Python34\lib\site-packages\django\views\generic\edit.py" in form_invalid
61. return self.render_to_response(self.get_context_data(form=form))
File "C:\Python34\lib\site-packages\django\views\generic\edit.py" in get_context_data
67. return super().get_context_data(**kwargs)
File "C:\Python34\lib\site-packages\django\views\generic\detail.py" in get_context_data
93. if self.object:
Exception Type: AttributeError at /my_app/new_version/999-11/
Exception Value: 'NewVersionProcessView' object has no attribute 'object'
I can provide the urls.py and the htmls if needed.
Any help is appreciated.
Related
I am trying to store form-data to a model, when the user submits the form all of the data is saved correctly but a many to many field (catagory) which is throwing errors. Can someone please help me edit my view so that I can save this information? Thank you in advance.
I tried to save the field 'Catagory' but at first couldn't. Then I came across answers that said to add self.save_m2m() before self.save(commit=False). But this leads to another error:
Environment:
Request Method: POST
Request URL: http://127.0.0.1:8000/create/
Django Version: 3.2.5
Python Version: 3.9.6
Installed Applications:
['fontawesome_free',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'jquery',
'portfolio',
'blog',
'import_export',
'tinymce',
'hitcount',
'taggit',
'accounts']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Traceback (most recent call last):
File "C:\Users\Babli\AppData\Roaming\Python\Python39\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "C:\Users\Babli\AppData\Roaming\Python\Python39\site-packages\django\core\handlers\base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\Babli\AppData\Roaming\Python\Python39\site-packages\django\contrib\auth\decorators.py", line 21, in _wrapped_view
return view_func(request, *args, **kwargs)
File "C:\Users\Babli\Desktop\My-Projects\MySite\mysite\blog\views.py", line 35, in make_post
form.save_m2m()
File "C:\Users\Babli\AppData\Roaming\Python\Python39\site-packages\django\forms\models.py", line 451, in _save_m2m
f.save_form_data(self.instance, cleaned_data[f.name])
File "C:\Users\Babli\AppData\Roaming\Python\Python39\site-packages\django\db\models\fields\related.py", line 1668, in save_form_data
getattr(instance, self.attname).set(data)
File "C:\Users\Babli\AppData\Roaming\Python\Python39\site-packages\django\db\models\fields\related_descriptors.py", line 536, in __get__
return self.related_manager_cls(instance)
File "C:\Users\Babli\AppData\Roaming\Python\Python39\site-packages\django\db\models\fields\related_descriptors.py", line 851, in __init__
raise ValueError('"%r" needs to have a value for field "%s" before '
Exception Type: ValueError at /create/
Exception Value: "<Post: sadfsadfasdfsadsdfasd>" needs to have a value for field "id" before this many-to-many relationship can be used.
The screenshot of the error message
Here are the code snippets I used:
models.py
class Catagory(models.Model):
title = models.CharField(max_length=50)
slug = models.SlugField(max_length=400, unique=True, blank=True)
class Meta:
verbose_name_plural = "Catagories"
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Catagory, self).save(*args, **kwargs)
def __str__(self):
return self.title
#property
def num_posts(self):
return Post.objects.filter(catagory=self).count()
#property
def last_post(self):
return Post.objects.filter(catagory=self).latest("date")
class Post(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(max_length=100, unique=True, blank=True)
user = models.ForeignKey(Author, on_delete=models.CASCADE)
content = HTMLField(max_length=500)
catagory = models.ManyToManyField(Catagory)
date = models.DateTimeField(auto_now_add=True)
approved = models.BooleanField(default=False)
hit_count_generic = GenericRelation(HitCount, object_id_field='object_pk',
related_query_name='hit_count_generic_relation')
points = models.IntegerField(default=0)
tags = TaggableManager()
comments = models.ManyToManyField(Comment, blank=True)
forms.py
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'catagory', 'tags']
views.py
#login_required
def make_post(req):
context = {}
form = PostForm(req.POST or None)
if req.method == "POST":
if form.is_valid():
author = Author.objects.get(user=req.user)
form.save(commit=False)
form.save_m2m()
new_post = form
new_post.user = author
new_post.save()
return redirect('blog:blog-index')
context.update({
'form': form,
'title': 'Create A Post'
})
return render(req, 'create.html', context)
I don't know why that error is occurring, and it's driving me nuts
From the docs:
Another side effect of using commit=False is seen when your model has a many-to-many relation with another model. If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.
The error is then caused by these two lines:
form.save(commit=False)
form.save_m2m()
form.save(commit=False) will give you an unsaved object, meaning it has no id yet. So calling form.save_m2m() right after it will produce the error you shared.
This means that you need to first save the unsaved object before calling form.save_m2m():
my_obj = form.save(commit=False)
my_obj.save()
form.save_m2m()
So it seems what you intended to do is:
new_post = form.save(commit=False) # unsaved new post object
new_post.user = author
new_post.save()
form.save_m2m()
I've got a model similar to the following (this is a condensed version of a long form):
class UserProfile(models.Model):
display_name = models.CharField()
nationality = models.ForeignKey(Nationality)
religion = models.ForeignKey(Religion)
partner_nationality = models.ManyToManyField(
Nationality,
related_name='partner_nationality',
blank=True)
partner_religion = models.ManyToManyField(
Religion,
related_name='partner_religion',
blank=True)
And the following model form:
class UserProfilePartnerPreferencesForm(ModelForm):
partner_nationality = ModelChoiceField(
queryset=Nationality.objects.order_by('name'),
widget=CheckboxSelectMultiple,
empty_label=None,
required=False,
to_field_name='id')
class Meta:
model = UserProfile
fields = [
'partner_religion',
'partner_nationality',
]
widgets = {
'partner_religion': CheckboxSelectMultiple(),
}
And this generic view:
class UserProfilePartnerPreferencesUpdateView(LoginRequiredMixin,
UpdateView):
model = UserProfile
form_class = UserProfilePartnerPreferencesForm
def get(self, request, *args, **kwargs):
"""
Force the PK of the object to edit to the current user's profile
"""
self.kwargs[self.pk_url_kwarg] = request.user.profile.id
return super(UserProfilePartnerPreferencesUpdateView, self).get(
request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""
Force the PK of the object to edit to the current user's profile
"""
self.kwargs[self.pk_url_kwarg] = request.user.profile.id
return super(UserProfilePartnerPreferencesUpdateView, self).post(
request, *args, **kwargs)
So what I'm doing in the model form is:
Not displaying some fields (the display_name)
Changing the widget on a field
Creating a custom queryset and changing the widget on a field.
This form displays OK but the field where I've passed a custom queryset won't save. If I don't give any values for it, django complains with 'NoneType' object is not iterable while trying to save the field (partner_nationality in this example). If I give it a valid value, it says that the value isn't valid.
So it seems like fields where I supply a custom queryset aren't being applied correctly when the form is saved. If I comment out the customisation (i.e. partner_nationality in the ModelForm), it saves correctly with the default settings.
How can I pass a customised queryset and change the widget for a model's ManyToMany field? Also, bonus points if there's a simpler way (something like passing a parameter to the widgets dict (where partner_religion is defined).
I'm using Django 1.11.1.
Update
Full traceback is as follows when I don't select any options:
Environment:
Request Method: POST
Request URL: http://127.0.0.1:8000/user-profile/edit/preferences
Django Version: 1.11.1
Python Version: 3.5.1
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'dating.dating',
'dating_site']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Traceback:
File "/venv/lib/python3.5/site-packages/django/core/handlers/exception.py" in inner
41. response = get_response(request)
File "/venv/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
187. response = self.process_exception_by_middleware(e, request)
File "/venv/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
185. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/venv/lib/python3.5/site-packages/django/views/generic/base.py" in view
68. return self.dispatch(request, *args, **kwargs)
File "/venv/lib/python3.5/site-packages/django/contrib/auth/mixins.py" in dispatch
56. return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
File "/venv/lib/python3.5/site-packages/django/contrib/auth/mixins.py" in dispatch
116. return super(UserPassesTestMixin, self).dispatch(request, *args, **kwargs)
File "/venv/lib/python3.5/site-packages/django/views/generic/base.py" in dispatch
88. return handler(request, *args, **kwargs)
File "/dating/dating/dating/views/user_profile.py" in post
99. return super(UserProfilePartnerPreferencesUpdateView, self).post(request, *args, **kwargs)
File "/venv/lib/python3.5/site-packages/django/views/generic/edit.py" in post
240. return super(BaseUpdateView, self).post(request, *args, **kwargs)
File "/venv/lib/python3.5/site-packages/django/views/generic/edit.py" in post
183. return self.form_valid(form)
File "/venv/lib/python3.5/site-packages/django/views/generic/edit.py" in form_valid
162. self.object = form.save()
File "/venv/lib/python3.5/site-packages/django/forms/models.py" in save
452. self._save_m2m()
File "/venv/lib/python3.5/site-packages/django/forms/models.py" in _save_m2m
434. f.save_form_data(self.instance, cleaned_data[f.name])
File "/venv/lib/python3.5/site-packages/django/db/models/fields/related.py" in save_form_data
1686. getattr(instance, self.attname).set(data)
File "/venv/lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py" in set
982. objs = tuple(objs)
Exception Type: TypeError at /events/dating/user-profile/edit/preferences
Exception Value: 'NoneType' object is not iterable
In your model, partner_nationality is a ManyToManyField. Therefore you should use a ModelMultipleChoiceField in your form, not a ModelChoiceField.
class UserProfilePartnerPreferencesForm(ModelForm):
partner_nationality = ModelMultipleChoiceField(
queryset=Nationality.objects.order_by('name'),
widget=CheckboxSelectMultiple,
)
If you define the widget in the field definition, you don't have to set it in widgets as well.
Another approach is to change the field's attributes in the __init__ method. For example, you can change the queryset with the following:
class UserProfilePartnerPreferencesForm(ModelForm):
def __init__(self, *args, **kwargs):
super(UserProfilePartnerPreferencesForm, self).__init__(*args, **kwargs)
self.fields['partner_nationality'].queryset = Nationality.objects.order_by('name')
I have three Tables in a many-to-many-relationship, a Product table, a Server table and an intemediary table to link the two objects together.
Each Server can have many products, and each product can be associated with more than one server.
Here are my models
#/myapp/models.py
class Server(TimeStampedModel):
name = models.CharField(max_length=35)
description = models.CharField(max_length=200)
products = models.ManyToManyField('Product', through='ServerProduct',
related_name='products')
class ServerProduct(TimeStampedModel):
server = models.ForeignKey('Server', on_delete=models.CASCADE)
product = models.ForeignKey('Product', on_delete=models.CASCADE)
class Product(TimeStampedModel):
name = models.CharField(max_length=200)
price = models.DecimalField(decimal_places=2, max_digits=11)
servers = models.ManyToManyField(
'Server', through='ServerProduct', related_name='servers')
In my create view I'm pointing to a form to allow users to create a Server, and select it's corresponding products...
Inside form_valid() I'm trying to link each Product to the new Server
#/myapp/views.py
class ServerCreateView(SuccessMessageMixin, CreateView):
model = Server
form_class = ServerForm
....
def form_valid(self, form):
server = form.save(False)
server.save()
for product in form.cleaned_data['products']:
ServerProduct.objects.create(server=server, product=product)
return super(ServerCreateView, self).form_valid(form)
My form looks as follows..
class ServerForm(BlankToRequiredMixin):
class Meta:
model = Server
fields = '__all__'
widgets = {
'name': forms.TextInput(attrs={'autofocus': 'autofocus'}),
}
However when I submit the form django returns the following error:
Cannot set values on a ManyToManyField which specifies an intermediary
model. Use reports.ServerProduct's Manager instead.
In place of ServerProduct.objects.create(server=server, product=product) I've also tried the following (after reading the documentation here) but this returns the same error
prod = ServerProduct(server=server, product=product)
prod.save()
Any idea how I could solve this? (Preferably still using the Generic create view)
EDIT: Full Traceback
Environment:
Request Method: POST
Request URL: http://localhost:8000/server-create/
Django Version: 1.9.7
Python Version: 3.4.2
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_extensions',
'auditlog',
'rest_framework',
'reports.apps.ReportsConfig']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'auditlog.middleware.AuditlogMiddleware']
Traceback:
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
149. response = self.process_exception_by_middleware(e, request)
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
147. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/views/generic/base.py" in view
68. return self.dispatch(request, *args, **kwargs)
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/views/generic/base.py" in dispatch
88. return handler(request, *args, **kwargs)
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/views/generic/edit.py" in post
256. return super(BaseCreateView, self).post(request, *args, **kwargs)
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/views/generic/edit.py" in post
222. return self.form_valid(form)
File "/home/jwe/piesup2/reports/views.py" in form_valid
182. return super(ServerCreateView, self).form_valid(form)
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/contrib/messages/views.py" in form_valid
11. response = super(SuccessMessageMixin, self).form_valid(form)
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/views/generic/edit.py" in form_valid
201. self.object = form.save()
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/forms/models.py" in save
452. self._save_m2m()
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/forms/models.py" in _save_m2m
434. f.save_form_data(self.instance, cleaned_data[f.name])
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/db/models/fields/related.py" in save_form_data
1618. setattr(instance, self.attname, data)
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/db/models/fields/related_descriptors.py" in __set__
481. manager.set(value)
File "/home/jwe/piesup2/venv/lib/python3.4/site-packages/django/db/models/fields/related_descriptors.py" in set
882. (opts.app_label, opts.object_name)
Exception Type: AttributeError at /server-create/
Exception Value: Cannot set values on a ManyToManyField which specifies an intermediary model. Use reports.ServerProduct's Manager instead.
The traceback shows that the error is occurring when you call super() in the form_valid method.
File "/home/jwe/piesup2/reports/views.py" in form_valid
182. return super(ServerCreateView, self).form_valid(form)
You are already saving the form in your form_valid method, so there is no need to call super(). Just redirect to the success url instead.
def form_valid(self, form):
server = form.save(False)
server.save()
for product in form.cleaned_data['products']:
ServerProduct.objects.create(server=server, product=product)
return HttpResponseRedirect(self.get_success_url())
Remember to add the import:
from django http import HttpResponseRedirect
I bought and am Reading the Book Two Scoops of Django:Best Practices for Django 1.5 and in it has a example of Class based views. After this implementation I get the error after submitting the form.
ImproperlyConfigured at /NonProfitCreate/
No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model
Doing research I came along this problem Django - Class Based Generic View - "No URL to redirect to"
I want the get_absolute_url to work in my program
this is my forms.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# npp/forms.py
from django import forms
from .models import NonProfit
class NonProfitCreateForm(forms.ModelForm):
class Meta:
model = NonProfit
fields = ("name","contact_name","email","phone","address","image","tags",)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(NonProfitCreateForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(NonProfitCreateForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
class NonProfitUpdateForm(NonProfitCreateForm):
class Meta:
model = NonProfit
this is my models.py and views files
from django.db import models
from django.contrib.auth.models import User
from django.db.models import permalink
from django_extensions.db.fields import AutoSlugField
from django.contrib import admin
from django.core.urlresolvers import reverse
import tagging
from tagging.models import Tag
# Create your models here.
''' this is for the Non-Profit Proccess '''
class NonProfit (models.Model):
User = models.ForeignKey(User)
name = models.CharField(max_length=100)
contact_name = models.CharField(max_length=100)
email = models.EmailField(max_length=75)
phone = models.CharField(max_length=20)
address = models.TextField(max_length=3000)
image = models.ImageField(upload_to='photos/%Y/%m/%d',blank=True)
slug = models.SlugField(max_length=128)
slug = AutoSlugField(('slug'), max_length=128, unique=True, populate_from=('name',))
tags = tagging.fields.TagField()
def get_absolute_url(self):
return reverse("npp/nonprofit_detail", kwargs={"slug": self.slug})
def __unicode__(self):
return self.name
def get_tags(self):
return Tag.objects.get_for_object(self)
# Create your views here.
# Auction/npp/views.py
from Auction.views import ActionMixin
from django.contrib import messages
from django.views.generic import CreateView, UpdateView, DetailView
from braces.views import LoginRequiredMixin
from forms import NonProfitCreateForm,NonProfitUpdateForm
from models import NonProfit
class NonProfitCreateView(LoginRequiredMixin,ActionMixin,CreateView):
model = NonProfit
action = "created"
form_class = NonProfitCreateForm
class NonProfitUpdateView(LoginRequiredMixin,ActionMixin,UpdateView):
model = NonProfit
action = "updated"
form_class = NonProfitUpdateForm
class NonProfitDetailView(DetailView):
model = NonProfit
# Auction/views.py
class ActionMixin(object):
#property
def action(self):
msg = "{0} is missing action.".format(self.__class__)
raise NotImplementedError(msg)
def form_valid(self, form):
msg = "{0}!".format(self.action)
messages.info(self.request, msg)
return super(ActionMixin, self).form_valid(form)
urls.py
url(
regex=r'^NonProfitCreate/',
view=NonProfitCreateView.as_view(),
name='NonProfitCreate',
),
url(
regex=r'^NonProfit/(?P<slug>[-\w\d]+)/',
view=NonProfitDetailView.as_view(),
name='NonProfit'
)
this is my stacktrace, the django braces is highlighted, and
/home/talisman/projects/Auction/Auction/views.py in form_valid
return super(ActionMixin, self).form_valid(form)
EEnvironment:
Request Method: POST
Request URL: http://127.0.0.1:8000/NonProfitCreate/
Django Version: 1.5.1
Python Version: 2.7.4
Installed Applications:
('django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'django.contrib.admindocs',
'django.contrib.comments',
'django.contrib.sitemaps',
'zinnia',
'tagging',
'mptt',
'south',
'misc',
'adm',
'registration',
'npp',
'blogs')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware')
Traceback:
File "/home/talisman/virt_env/Auction/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/core/handlers/base.py" in get_response
115. response = callback(request, *callback_args, **callback_kwargs)
File "/home/talisman/virt_env/Auction/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/views/generic/base.py" in view
68. return self.dispatch(request, *args, **kwargs)
File "/home/talisman/virt_env/Auction/local/lib/python2.7/site-packages/django_braces-1.0.0-py2.7.egg/braces/views.py" in dispatch
98. **kwargs)
File "/home/talisman/virt_env/Auction/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/views/generic/base.py" in dispatch
86. return handler(request, *args, **kwargs)
File "/home/talisman/virt_env/Auction/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/views/generic/edit.py" in post
199. return super(BaseCreateView, self).post(request, *args, **kwargs)
File "/home/talisman/virt_env/Auction/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/views/generic/edit.py" in post
165. return self.form_valid(form)
File "/home/talisman/projects/auction/Auction/views.py" in form_valid
54. return super(ActionMixin, self).form_valid(form)
File "/home/talisman/virt_env/Auction/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/views/generic/edit.py" in form_valid
128. return super(ModelFormMixin, self).form_valid(form)
File "/home/talisman/virt_env/Auction/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/views/generic/edit.py" in form_valid
65. return HttpResponseRedirect(self.get_success_url())
File "/home/talisman/virt_env/Auction/local/lib/python2.7/site-packages/Django-1.5.1-py2.7.egg/django/views/generic/edit.py" in get_success_url
119. "No URL to redirect to. Either provide a url or define"
Exception Type: ImproperlyConfigured at /NonProfitCreate/
Exception Value: No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model.
This exception is produced because self.object = None when attempting to redirect after a valid edit. Since the value of self.object is the result of a form.save() call, the most likely reason for this error is that you have overridden the save() method in NonProfitCreateForm, but forgotten to return the saved object.
The Form.save() method is expected to return the object that was saved and should not be None.
Your NonProfitCreateForm could be modified as shown below:
class NonProfitCreateForm(forms.ModelForm):
...
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(NonProfitCreateForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
return obj #<--- Return saved object to caller.
The first two lines of your save() method will create a model instance from the entered form data. But because commit=False, the object will not be saved to the database. If self.request is not present on your form instance, a the returned object will not have a database primary key, and get_absolute_url will still fail.
So, you want to ensure that a request parameter is always passed to your form when instantiated. This doesn't happen by default, so you need to arrange your view code to instantiate your form with a request parameter.
Looking through the code for FormMixin you can see that there is a get_form_kwargs function which determines the arguments to pass to any instantiated form. You need to pass request=self.request, so in your view override get_form_kwargs to add the required parameter, something like this:
class NonProfitCreateView(LoginRequiredMixin,ActionMixin,CreateView):
model = NonProfit
action = "created"
form_class = NonProfitCreateForm
def get_form_kwargs(self):
# Ensure the current `request` is provided to NonProfitCreateForm.
kwargs = super(NonProfitCreateView, self).get_form_kwargs()
kwargs.update({ 'request': self.request })
return kwargs
It would probably be a better idea to create a subclass of CreateView with the modified get_form_kwargs function, and have your NonProfitCreateView derive from the subclass.
When you use reverse, use the name of the url pattern you wish to reverse.
You wish to redirect to this url:
url(
regex=r'^NonProfit/(?P<slug>[-\w\d]+)/',
view=NonProfitDetailView.as_view(),
name='NonProfit'
)
Therefore your get_absolute_url method should be:
def get_absolute_url(self):
return reverse("NonProfit", kwargs={"slug": self.slug})
Try to remove the #permalink decorator from your get_absolute_url method. It cannot work together with reverse.
Also, the Django documentation states the following:
The permalink decorator is no longer recommended. You should use reverse() in the body of your get_absolute_url method instead.
You can override your class-based view's get_success_url function. Like this:
def get_success_url(self):
return reverse("NonProfit", kwargs={"slug": self.object.slug})
Solved the problem after reading the first sentence of Austin Phillips:
def form_valid(self, form):
article = form.save(commit=False)
article.author = self.request.user
self.object = article.save()
return super().form_valid(form)
the point is the save article.save()'s result back to self.object
I have two models and im doing a customform so that I may be able to view and save the form from my html into db. but when I try to save then I get this error.
Traceback
Environment:
Request Method: POST
Request URL: http://127.0.0.1:8008/
Django Version: 1.4
Python Version: 2.7.3
Installed Applications:
('django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'SecondBlog.blog',
'django.contrib.admin',
'django.contrib.admindocs')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware')
Traceback:
File "c:\Python27\lib\site-packages\django\core\handlers\base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "f:\apt3\SecondBlog\SecondBlog\blog\views.py" in home
10. if form.is_valid():
File "c:\Python27\lib\site-packages\django\forms\forms.py" in is_valid
124. return self.is_bound and not bool(self.errors)
File "c:\Python27\lib\site-packages\django\forms\forms.py" in _get_errors
115. self.full_clean()
File "c:\Python27\lib\site-packages\django\forms\forms.py" in full_clean
272. self._post_clean()
File "c:\Python27\lib\site-packages\django\forms\models.py" in _post_clean
309. self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
File "c:\Python27\lib\site-packages\django\forms\models.py" in construct_instance
51. f.save_form_data(instance, cleaned_data[f.name])
File "c:\Python27\lib\site-packages\django\db\models\fields\__init__.py" in save_form_data
454. setattr(instance, self.name, data)
File "c:\Python27\lib\site-packages\django\db\models\fields\related.py" in __set__
366. self.field.name, self.field.rel.to._meta.object_name))
Exception Type: ValueError at /
Exception Value: Cannot assign "u'Sasa'": "Book.author" must be a "Author" instance.
models.py
class Author(models.Model):
name = models.CharField(max_length = 30)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length = 100)
def __unicode__(self):
return '%s' % (self.title)
forms.py
class CustomForm(ModelForm):
author = forms.CharField()
def save(self, commit=True):
author = Author.objects.get_or_create(name=self.cleaned_data['author'])
instance = super(CustomForm, self).save(commit=commit)
instance.author = author
if commit:
instance.save()
return instance
class Meta:
model = Book
fields = ('author','title',)
views.py
def home(request):
if request.method == 'POST':
form = CustomForm(request.POST)
if form.is_valid():
print "all validated"
form.save()
return HttpResponseRedirect('index.html')
else:
print "failed"
else:
form = CustomForm()
variables = RequestContext(request, {'form' : form})
return render_to_response('index.html', variables)
Thank you so much.
The issue is not with the saving, but with the validating of your form. Basically, you create a customized version of the model form for Book, with two fields, author and title. By default, the author field would display as a reference browsing widget for the Author type.
However, what you do with author = forms.CharField() is tell the form to display a simple text input field instead of an Author reference selector. When it tries to validate this.. well it can't.
What you need to do is process the author value before validating, you can do this in the clean() function, which you can add to your CustomForm
def clean(self):
self.cleaned_data['author'] = Author.objects.get_or_create(name=self.cleaned_data['author'])
return self.cleaned_data
A very similar question is in how to change form field in method is_valid()
I try your codes and do some tests, no matter what I do I always get that error. Because before the author create, the book execute first which cause the instance error.
I modified your codes. I remove your save method and change it to clean method which is working now.
class CustomForm(forms.ModelForm):
author = forms.CharField()
def clean(self):
cleaned_data = super(CustomForm, self).clean()
author = cleaned_data.get("author")
if not author:
raise forms.ValidationError("Please enter an author")
data = Author.objects.create(name=author)
cleaned_data['author'] = data
return cleaned_data
class Meta:
model = Book
fields = ('author','title',)
The problem is that it isn't even getting to your code that sets the author instance, because the superclass's save method is setting it there using the text field, which is where the error occurs.
You could try either using pop to remove the author value from cleaned_data before calling super, or replacing that value with the Author instance.
Edit for example
def save(self, commit=True):
author = Author.objects.get_or_create(name=self.cleaned_data.pop('author'))
instance = super(CustomForm, self).save(commit=commit)