在 Django inlineformset_factory 中更改图像将其放在列表的末尾

Changing an image in Django inlineformset_factory puts it to the end of the list

假设我正在制作一个 "How to" Django webapp,用户可以在其中制作 post关于如何-做不同的事情。

你懂的。当成员使post.They向post

添加额外的图像时,我已经使post为this.Now创建视图

例子: "How to"做一根绳子

现在他们必须一步一步地展示绳子是如何制作的

我使用 Django 表单集和我的 post 模型来实现这一点。在创建视图中一切都工作得很好。没问题。但在更新视图中,事情发生了变化。

The Problem

问题是当用户想要编辑他们的 post 并将图像编号 2 从他们的 post 切换到不同的图像时。即使他们改变了第二张图片。该图像现在位于列表的最后。使用户 re-upload 所有图像。带回订单。让我的应用看起来有问题。

示例: 假设用户具有以下 post

main post Title 
" Some description "
Main Image = Post_image.jpg  

1st Image = A.jpg
   Image Title
   Image description
2nd Image = B.jpg
   Image Title
   Image description
3rd Image = C.jpg
   Image Title
   Image description
4st Image = D.jpg
    Image Title
     Image description
5th Image = E.jpg
     Image Title
     Image description
6th Image = F.img
     Image Title
     Image description

Now if I changed 2nd image B.jpg to b.jpg b.jpg moves to the very end of the list and you have the order as A, C, D, E, F, b

以下是我的模型:

 class Post(models.Model):
    user = models.ForeignKey(User, related_name='posts')
    created_at = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=250, unique=True)
    slug = models.SlugField(allow_unicode=True, unique=True,max_length=500)
    post_image = models.ImageField()
    message = models.TextField()

class Prep (models.Model): #(Images)
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='post_prep')
    image = models.ImageField(upload_to='images/', blank=True, null=True, default='')
    image_title = models.CharField(max_length=100, default='')
    image_description = models.CharField(max_length=250, default='')
    sequence = models.SmallIntegerField() ###########################ADDED THIS

    class Meta:    ###########################ADDED THIS
    unique_together = (('post', 'sequence'),) ###########################ADDED THIS
    ordering = ['sequence']  ###########################ADDED THIS

我的 post 创建视图

def post_create(request):
    ImageFormSet = modelformset_factory(Prep, fields=('image', 'image_title', 'image_description'), extra=12, max_num=12,
                                        min_num=2)
    if request.method == "POST":
        form = PostForm(request.POST or None, request.FILES or None)
        formset = ImageFormSet(request.POST or None, request.FILES or None)
        if form.is_valid() and formset.is_valid():
            instance = form.save(commit=False)
            instance.user = request.user
            instance.save()
            post_user = request.user
            for index, f in enumerate(formset.cleaned_data): #######CHANGED THIS
                try: ##############CHANGED THIS
                    photo = Prep(sequence=index, post=instance, image=f['image'], 
                             image_title=f['image_title'], image_description=f['image_description'])
                    photo.save()
                except Exception as e:
                    break

            return redirect('posts:single', username=instance.user.username, slug=instance.slug)
    else:
        form = PostForm()
        formset = ImageFormSet(queryset=Prep.objects.none())
    context = {
        'form': form,
        'formset': formset,
    }
    return render(request, 'posts/post_form.html', context)

我的 post 编辑视图:

class PostPrepUpdate(LoginRequiredMixin, UpdateView):
    model = Post
    fields = ('title', 'message', 'post_image')
    template_name = 'posts/post_edit.html'
    success_url = reverse_lazy('home')

    def get_context_data(self, **kwargs):
        data = super(PostPrepUpdate, self).get_context_data(**kwargs)
        if self.request.POST:
            data['prep'] = PrepFormSet(self.request.POST, self.request.FILES, instance=self.object)
        else:
            data['prep'] = PrepFormSet(instance=self.object)
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        prep = context['prep']
        with transaction.atomic():
            self.object = form.save()

            if prep.is_valid():
                prep.instance = self.object
                prep.save()
        return super(PostPrepUpdate, self).form_valid(form)

我的Forms.py

class PostEditForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ('title', 'message', 'post_image', 'group', )


class PrepForm(forms.ModelForm): #####################CHANGED THIS
    class Meta:
        model = Prep
        fields = ('image', 'image_title', 'image_description', 'sequence')


PrepFormSet = inlineformset_factory(Post, Prep, form=PrepForm, extra=5, max_num=7, min_num=2)

***Need help fixing this issue. Example if they change Image 2. Then it should stay at Number 2 position and not move to the end of the list

目前您不保存图像的顺序,因为它们的显示顺序与创建顺序相同。在 Prep 中添加一个包含图像在图像序列中的位置的字段将有助于:

class Prep (models.Model):
    # ...
    nr = SmallIntegerField()
    #...
    class Meta:
        unique_together = (('post', 'nr'),)

unique_together 约束确保每个数字在每个 post 中只使用一次。这也允许在 post 中重新排序图像,而无需删除和重新创建所有 Prep 个对象。

在显示 post 时,您必须按 nrPrep 个对象进行排序。


至于填充新列,由于没有有意义的单一默认值,最简单的方法可能是:

  • 添加没有unique_together约束的nr字段,首先是null=True;迁移更改。
  • 迁移后,按当前顺序遍历每个Post现有的Prep个对象,并为它们分配升序编号。
  • 之后删除 null=True,添加 unique_together 并再次迁移。

unique_together需要字符串参数(无法访问外层class中的字段);感谢您收听。


在编辑表单中,您希望包含新字段,以便用户可以通过交换索引来交换两个图像的顺序。如果他们使用重复索引,您只需向他们提供有意义的错误消息。

然而,在创建时,您的用户不需要明确指定顺序,因为它隐含在表单集中的图像序列中。所以我建议像这样更改您的 for 循环:

for index, f in enumerate(formset.cleaned_data):
    # ...
    photo = Prep(nr=index,
                 # add all other fields
                )

对以 1 开头的人性化索引使用 nr = index + 1。事实上,indeximage_index 可能是比 nr 更好的字段名称。