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()
Related
I've been trying to pass a UUID4 id into the url for a specific detail page.
After browsing Stackoverflow and other sites, here's what I've tried so far:
Passing the url path as path('car/<uuid:id>/', views.CarDetailView.as_view(), name='car-detail'),
But this raises the error: Generic detail view CarDetailView must be called with either an object pk or a slug in the URLconf..
Since the uuid field is composed of both letters and numbers, I can't use an int.
So I used this:
path(r"^(?P<car_model>\w+)/$", views.CarDetailView.as_view(), name='car-detail'),
which returns the messy and broken url: showroom/%5E(%3FP09c32f72-5863-49fa-a42a-1d0fed274c4e%5Cw+)/$
Then I tried reverting to the original, but using a def_object method in the View class.
def get_object(self):
object = get_object_or_404(CarInstance,title=self.kwargs['car_model'])
return object
But this returns the error: "KeyError at /showroom/car/09c32f72-5863-49fa-a42a-1d0fed274c4e/
'car_model'"
models.py
class CarInstance(models.Model):
manufacturer = models.ForeignKey('Manufacturer', on_delete=models.SET_NULL, null=True)
car_model = models.CharField('Model', max_length=50, null=True)
views.py
class CarDetailView(generic.DetailView):
model = CarInstance
template_name = 'car_detail'
def get_queryset(self):
return CarInstance.objects.all()
def get_object(self):
object = get_object_or_404(CarInstance,title=self.kwargs['car_model'])
return object
def get_absolute_url(self):
return reverse('showroom:car-detail', args=[str(self.pk)])
The urls should be be formatted as showroom/car/09c32f72-5863-49fa-a42a-1d0fed274c4e/, which brings up the detail view for the specific object.
Any ideas?
Update
According to the answer below, I changed the get_object override to
slug_field = 'title'
slug_url_kwarg = 'car_detail'
But I'm still getting the same urlconf must be called with slug or int error. Should I define the slugh in the models?
Update 2
I've changed the urlconf, but it's raising the same error. Here's the full traceback
Environment:
Request Method: GET
Request URL: http://localhost:8000/showroom/car/09c32f72-5863-49fa-a42a-1d0fed274c4e/
Django Version: 2.2.5
Python Version: 3.7.4
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'showroom']
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:\Users\USER\Envs\torque\lib\site-packages\django\core\handlers\exception.py" in inner
34. response = get_response(request)
File "C:\Users\USER\Envs\torque\lib\site-packages\django\core\handlers\base.py" in _get_response
115. response = self.process_exception_by_middleware(e, request)
File "C:\Users\USER\Envs\torque\lib\site-packages\django\core\handlers\base.py" in _get_response
113. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\USER\Envs\torque\lib\site-packages\django\views\generic\base.py" in view
71. return self.dispatch(request, *args, **kwargs)
File "C:\Users\USER\Envs\torque\lib\site-packages\django\views\generic\base.py" in dispatch
97. return handler(request, *args, **kwargs)
File "C:\Users\USER\Envs\torque\lib\site-packages\django\views\generic\detail.py" in get
106. self.object = self.get_object()
File "C:\Users\USER\Envs\torque\lib\site-packages\django\views\generic\detail.py" in get_object
47. "pk or a slug in the URLconf." % self.__class__.__name__
Exception Type: AttributeError at /showroom/car/09c32f72-5863-49fa-a42a-1d0fed274c4e/
Exception Value: Generic detail view CarDetailView must be called with either an object pk or a slug in the URLconf.
** Another update **
Thanks to help from #ruddra, I changed the path to match the slug_url_kwarg = 'car_detail' It looks like this now:
path('car/<slug:car_detail>/', views.CarDetailView.as_view(), name='car-detail')
However, now the page raises a 404 error.
Page not found (404)
Request Method: GET
Request URL: http://localhost:8000/showroom/car/09c32f72-5863-49fa-a42a-1d0fed274c4e/
Raised by: showroom.views.CarDetailView
No car instance found matching the query
You don't need to override get_object() method. You can simply use slug_url_kwarg and slug_field. Like this:
class CarDetailView(generic.DetailView):
model = CarInstance
template_name = 'car_detail'
slug_field = 'title'
slug_url_kwarg = 'car_model'
More information can be found in get_object() documentation.
Create get_absolute_url() in models.py:
def get_absolute_url(self):
return reverse('car-detail', args=[str(self.<yourUUIDFieldName>)]) # self.car_model
Set URL in urls.py:
urlpatterns = [
path('car/<uuid:yourUUIDFieldName>/', # 'car:<uuid:car_model'>
views.CarDetailView.as_view(), name='car-detail'),
]
Try to change the views:
class CarDetailView(DetailView):
model = CarInstance
slug_field = '<uuid:yourUUIDFieldName>' # -> 'car_model'
slug_url_kwarg = '<uuid:yourUUIDFieldName>' # -> 'car_model'
template_name = 'car_detail.html'
In addition try this if you like:
import uuid
car_model = models.UUIDField(default=uuid.uuid4, unique=True)
Replace yourUUIDFieldName with car_model check if this works,
in my way, i have no idea, i'm just a beginner like others, hope u get something out of it
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.
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 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)