简体   繁体   中英

Inline formset factory in django (2.0.8) generic View

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) ?


UDPATE (with the changed save method in Post model)

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


UDPATE (changes in views - def post())

@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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM