jma
jma

Reputation: 3779

Django: edit existing object, but new on save

In a blog-ish part of my django app, editing an existing entry results in creating a new entry. It should modify the existing entry.

I have a simple model:

class BlogEntry(models.Model):
    ...
    slug = models.SlugField(unique=True)

and a simple form for editing it:

class BlogEntryForm(forms.ModelForm):
...
slug = forms.CharField(required=False, widget=forms.HiddenInput())

class Meta:
    model = BlogEntry
    fields = (..., 'slug')

and the view, somewhat simplified, is also straight-forward:

class BlogEditView(View):
    @method_decorator(login_required)
    def get(self, request, slug=None):
        context = {
            'user': request.user,
        }
        if slug is None:
            print('Creating new entry.')
            context['entry'] = BlogEntryForm()
            context['entry'].publication_date = datetime.datetime.now()
            return render(request, 'blog/edit.html', context)
        print('Using existing entry.')
        entry = get_object_or_404(BlogEntry, slug=slug)
        context['entry'] = BlogEntryForm(instance=entry)
        return render(request, 'blog/edit.html', context)

    @method_decorator(login_required)
    def post(self, request):
        blog_entry_form = BlogEntryForm(request.POST)
        if blog_entry_form.is_valid():
            blog_entry = blog_entry_form.save(commit=False)
            if blog_entry.slug is None or blog_entry.slug == '':
                print('New entry, slug is empty.')
                blog_entry.creation_date = datetime.datetime.now()
                blog_entry.slug = slugify(blog_entry.title) + '_' + hex(random.randint(0, 1e10))
            blog_entry.author = request.user
            blog_entry.save()
            return redirect(reverse('blog/article', args=[blog_entry.slug]))
        ...

In get(), I confirm with the print's that I am taking the correct branch. If the entry exists, I set entry with the instance. In post(), however, I always take the new entry branch.

The layout has the typical hidden element loop.

{% for hidden in form.hidden_fields %}
    {{ hidden }} hidden
{% endfor %}

though, suspiciously, when I look at the served html, I don't see the slug entry, so it's no surprise that it's not getting passed through.

Anyone see what I'm missing?

Upvotes: 0

Views: 1352

Answers (1)

catavaran
catavaran

Reputation: 45575

You should add the slug argument to the post() method as well. This will allow you to get the blog entry to edit from the database and pass this entry to the form as an instance argument:

@method_decorator(login_required)
def post(self, request, slug=None):
    blog_entry = BlogEntry.objects.filter(slug=slug).first()
    blog_entry_form = BlogEntryForm(request.POST, instance=blog_entry)
    ...

UPDATE: To pass the slug argument to the post() method you should use the empty action attribute of the <form> tag:

<form action="" method="POST">

In this case the form will be submitted to the url from which it was loaded. So the slug argument for the POST request will be the same as for the GET.

Upvotes: 2

Related Questions