I am trying to create a inline formset factory so that when i create the Post at the same time i can create a PostVocab which has a ForeignKey associated with it. I watched couple of tutorials but got stucked on this problem.. When saving it shows me : ValueError: save() prohibited to prevent data loss due to unsaved related object 'post'.
My Models:
from django.db import models
from PIL import Image
from django.urls import reverse
from ckeditor.fields import RichTextField
from django.db.models.signals import pre_save
from django.utils.text import slugify
from django.conf import settings
# Create your models here.
def upload_location(instance, filename):
filename = instance.title
return "blog/%s.jpg" %(filename)
class Post(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
pdf = models.FileField(blank=True, null=True)
audio_file = models.FileField(blank=True, null=True)
author = models.CharField(max_length=20)
added = models.DateTimeField(auto_now=False,auto_now_add=True)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
draft = models.BooleanField(default=False)
publish = models.DateField(auto_now=False, auto_now_add=False)
post = RichTextField(blank=True, null=True)
picture = models.ImageField(upload_to=upload_location, blank=True, null=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post_detail' , kwargs={'slug':self.slug})
def save(self, **kwargs):
if self.picture:
super(Post, self).save()
mywidth = 440
image = Image.open(self.picture)
wpercent = (mywidth / float(image.size[0]))
hsize = int((float(image.size[1]) * float(wpercent)))
image = image.resize((mywidth, hsize), Image.ANTIALIAS)
image.save(self.picture.path)
from django.db import models
from blog.models import Post
# Create your models here.
class PostVocab(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
word = models.CharField(max_length = 30)
explanation = models.CharField(max_length = 200)
example = models.CharField(max_length = 100)
def __str__(self):
return self.word
My Forms:
from django import forms
from ckeditor.widgets import CKEditorWidget
from vocabulary.models import PostVocab
from .models import Post
from django.forms.models import inlineformset_factory
class PostForm(forms.ModelForm):
post = forms.CharField(widget=CKEditorWidget())
class Meta:
model = Post
fields = ['title', 'author','picture','post','draft','publish']
PostVocabFormSet = inlineformset_factory(
Post,
PostVocab,
fields=['word','explanation','example'],
extra=1,
can_delete=True
)
My Views:
class CreatePost(View):
form = PostForm
formset = PostVocabFormSet
template_name = 'blog/post_form.html'
def get(self, request):
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
return render(request,self.template_name, {"form":self.form,'formset':self.formset})
def post(self, request):
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
form = self.form(request.POST, request.FILES)
formset = self.formset(request.POST, request.FILES)
print(form.errors)
if form.is_valid() and formset.is_valid():
post = form.save(commit=False)
post.save()
words = formset.save(commit=False)
for word in words:
word.post = post
word.save()
return redirect('blog:main')
return render (request, self.template_name,{"form":self.form,'formset':self.formset})
My template:
{% extends 'main/base.html' %}
{% block content %}
<div class="row">
<div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-10 col-lg-offset-1">
<div class="panel panel-default">
<div class="panel-body">
<form class="form-horizontal" method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
{{ formset.management_form }}
<table role="grid" class="stack hover" style="width:100%">
<thead>
<tr>
<th scope="col" class="text-center" style="width=10%">Order</th>
<th scope="col" class="text-center" style="width=10%">Word</th>
<th scope="col" class="text-center" style="width=10%">Explanation</th>
<th scope="col" class="text-center" style="width=10%">Example</th>
<th scope="col" class="text-center" style="width=10%">Delete</th>
</tr>
</thead>
<tbody class="order">
{% for form in formset %}
<tr class="postvocab-form">
<td>{{ form.id }}</td>
<td>{{ form.word }}</td>
<td>{{ form.explanation }}</td>
<td>{{ form.example }}</td>
{% if form.instance.pk %}<td class="text-center">{{ form.DELETE }}</td>
{% else %}<td class="text-center"></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary" >Submit</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
I thought that this bit
if form.is_valid() and formset.is_valid():
post = form.save(commit=False)
post.save()
words = formset.save(commit=False)
for word in words:
word.post = post
word.save()
return redirect('blog:main')
will actually resolve this problem by associating a PostVocab
instance with a Post
.Do you guys know what's wrong with this code ?
UPDATE
change this bit :
if form.is_valid() and formset.is_valid():
form.save()
words = formset.save(commit=False)
for word in words:
word.post = form
word.save()
return redirect('blog:main')
Now i get the Cannot assign "": "PostVocab.post" must be a "Post" instance.
postVocab.post is the instance of "Post" because it has a ForeignKey associated with it. Why it is showing this Error? Is it because the PostVocab class is in different app (vocabulary) ?
def save(self, **kwargs):
super(Post, self).save()
if self.picture:
mywidth = 440
image = Image.open(self.picture)
wpercent = (mywidth / float(image.size[0]))
hsize = int((float(image.size[1]) * float(wpercent)))
image = image.resize((mywidth, hsize), Image.ANTIALIAS)
image.save(self.picture.path)
Still get the erorr : Cannot assign "": "PostVocab.post" must be a "Post" instance
@Uroš Trstenjak @art06 When i printed formset.errors
i got [{'post': ['This field is required.']}]
. So my code did'nt actually pass the if statement if form.is_valid() and formset.is_valid():
I tried pass associate the Foreign Key in Formset over to the Post by using it :
def post(self, request):
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
form = self.form(request.POST, request.FILES)
formset = PostVocabInlineFormSet(request.POST)
post_title = form['title'].value()
print(post_title)
for f in formset:
f['post'] = str(post_title)
print(f['post'])
print(form.errors)
print(formset.errors)
if form.is_valid() and formset.is_valid():
self.object = form.save()
formset.instance=self.object
formset.save()
return redirect('blog:main')
return render (request,self.template_name,{"form":self.form,'formset':self.formset})
but i get an error in this bit : f['post'] = str(post_title)
The error is 'PostVocabForm' object does not support item assignment
You have overriden the save
method on Post model, but you are only saving the Post in case self.picture
is not None
. First you should put super(Post, self).save()
before the if statement and than process the picture.
You should also add your post
field to the PostVocabFormSet
.
PostVocabFormSet = inlineformset_factory(
Post,
PostVocab,
fields=['post', 'word', 'explanation', 'example'],
extra=1,
can_delete=True
)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.