【问题标题】:Inline formset factory in django (2.0.8) generic Viewdjango(2.0.8)通用视图中的内联表单集工厂
【发布时间】:2019-03-31 19:46:49
【问题描述】:

我正在尝试创建一个内联表单集工厂,这样当我同时创建 Post 时,我可以创建一个 PostVocab,它具有与之关联的 ForeignKey。我看了几个教程,但被这个问题困住了。保存时它告诉我: 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

我的表格:

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
)

我的观点:

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

我的模板:

{% 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 %}

我以为这点

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

实际上将通过将PostVocab 实例与Post 关联来解决此问题。你们知道这段代码有什么问题吗?


更新


改变这一点:

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

现在我得到了Cannot assign "": "PostVocab.post" must be a "Post" instance.

postVocab.post 是“Post”的实例,因为它有一个 ForeignKey 与之关联。为什么会显示此错误?是因为 PostVocab 类在不同的应用程序(词汇表)中吗?


UDPATE(Post 模型中保存方法的改变)

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)

仍然出现错误:Cannot assign "": "PostVocab.post" must be a "Post" instance


UDPATE(视图变化-def post())

@Uroš Trstenjak @art06 当我打印 formset.errors 时,我得到了 [{'post': ['This field is required.']}] 。所以我的代码实际上并没有通过 if 语句 if form.is_valid() and formset.is_valid():

我尝试通过使用将 Formset 中的外键关联到 Post :

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

但是我在这个位上得到一个错误: f['post'] = str(post_title)

错误是'PostVocabForm' object does not support item assignment

【问题讨论】:

  • 我认为,您只需要self.object= form.save() formset.instance=self.object formset.save() 而不是words = formset.save(commit=False) for word in words: word.post = post word.save(),请参阅Django Inline formsets example
  • 改成这样:if form.is_valid() and formset.is_valid(): form.save() self.object = form.save() formset.instance=self.object formset.save() 但现在我得到了NOT NULL constraint failed: vocabulary_postvocab.post_id
  • 应该像if form.is_valid() and formset.is_valid(): self.object = form.save() formset.instance=self.object formset.save()
  • formset 有一个错误,所以代码实际上没有通过这个if form.is_valid() and formset.is_valid(): if 语句。请检查上面的更新,非常感谢!

标签: django django-forms


【解决方案1】:

您已覆盖 Post 模型上的 save 方法,但您仅在 self.picture 不是 None 的情况下保存 Post。首先你应该把super(Post, self).save()放在if语句之前,然后处理图片。

您还应该将post 字段添加到PostVocabFormSet。

PostVocabFormSet = inlineformset_factory(
    Post,
    PostVocab,
    fields=['post', 'word', 'explanation', 'example'],
    extra=1,
    can_delete=True
)

【讨论】:

  • 我改变了这样的安全方法..见上面的更新
  • @seba6m6 检查更新的答案。如果在将 post 添加到 PostVocabFormSet 之前将 print(words.post) 放入 for 循环中,控制台输出是什么?
猜你喜欢
  • 2017-12-29
  • 2014-01-01
  • 2020-09-18
  • 2014-03-19
  • 2019-05-15
  • 2018-09-11
  • 1970-01-01
  • 2011-11-30
  • 2011-07-11
相关资源
最近更新 更多