Day Log App in Django Part 7: Edit a DayLog

This post will discuss how to implement the editing of an existing Daylog through a form.

Table of Contents

Steps

Show the form

Create test

The test will simply create a Daylog instance and expect the response from the server to contain the expected form attributes.

def test_edit_view(self):
    daylog = create_daylog()
    daylog.save()

    self.assertTrue(Daylog.objects.filter(slug = daylog.slug).count())

    response = self.client.get(reverse('daylog:edit', args=(daylog.slug,)))

    self.assertEqual(response.context['action'], 'daylog:edit')
    self.assertEqual(response.context['operation'], 'edit')

Set URL

Add the following to api/urls.py:

url(r'^(?P<slug>.+)/edit$', views.edit, name='edit'),

Create edit method

Same as the implementation of details and delete, the slug from the URL is validated before the form is shown.

def edit(request, slug):
    try:
        daylog = Daylog.objects.get(slug = slug)
    except Daylog.DoesNotExist:
        messages.error(request, "Day Log does not exist.", "danger")
        return redirect('daylog:get_list')

    return render(request, 'daylog/form.html', {
        'daylog': daylog,
        'action': 'daylog:edit',
        'operation': 'edit',
    })

Changed the edit link in daylog/templates/daylog/index.html from:

/daylog//edit

to


{% url 'daylog:edit' daylog.slug %}

Edit Daylog page

The test test_edit_view should pass.

Submit the form

Create test

from .forms import DaylogForm

...

def test_edit_existing_daylog_without_changes(self):
    old_daylog = create_daylog()
    old_daylog.save()

    self.assertTrue(Daylog.objects.filter(slug = old_daylog.slug).count())

    new_daylog = old_daylog.__dict__

    response = self.client.post(reverse('daylog:edit', args=(old_daylog.slug,)),
        new_daylog, follow=True)

    for message in response.context['messages']:
        self.assertEqual(message.message, "Day Log edited.")
        self.assertEqual(message.extra_tags, "info")
        break;

def test_edit_existing_daylog_with_valid_changes(self):
    old_daylog = create_daylog()
    old_daylog.save()

    self.assertTrue(Daylog.objects.filter(slug = old_daylog.slug).count())

    new_daylog = old_daylog.__dict__
    new_daylog['title'] = "New title"

    response = self.client.post(reverse('daylog:edit', args=(old_daylog.slug,)),
        new_daylog, follow=True)

    for message in response.context['messages']:
        self.assertEqual(message.message, "Day Log edited.")
        self.assertEqual(message.extra_tags, "info")
        break;

There is no need to check for existing slug and log_at values because they are disabled for modification during edit.

Update edit method

There are a few key changes in the back-end to make the edit submission work.

First, DaylogForm has a new parameter operation which will help in the validation to skip slug and log_at because they already exist in the database.

Next, is the submitted form passes validation then its values are saved together with the key attributes of the original Daylog object:

  • id
  • created_at
  • modified_at
def edit(request, slug):
    try:
        old_daylog = Daylog.objects.get(slug = slug)

        if request.method == 'POST':
            daylog_form = DaylogForm(request.POST, operation='edit')
            if daylog_form.is_valid():
                new_daylog = daylog_form.instance
                new_daylog.id = old_daylog.id
                new_daylog.created_at = old_daylog.created_at
                new_daylog.modified_at = old_daylog.modified_at
                new_daylog.save()
                messages.info(request, "Day Log edited.", "info")
                return redirect('daylog:get_list')
            else:
                return render(request, 'daylog/form.html', {
                    'daylog': request.POST,
                    'action': 'daylog:edit',
                    'operation': 'edit',
                    'errors': daylog_form.errors
                 })
    except Daylog.DoesNotExist:
        messages.error(request, "Day Log does not exist.", "danger")
        return redirect('daylog:get_list')

    return render(request, 'daylog/form.html', {
        'daylog': old_daylog,
        'action': 'daylog:edit',
        'operation': 'edit',
    })

Add these new methods to the DaylogForm class in daylog/forms.py:

def __init__(self, *args, **kwargs):
    self.operation = kwargs.pop('operation', None)
    super(DaylogForm, self).__init__(*args, **kwargs)

def full_clean(self):
    super(DaylogForm, self).full_clean()

    if self.operation == 'edit':
        if 'slug' in self._errors:
            del self._errors['slug']
        if 'log_at' in self._errors:
            del self._errors['log_at']

    return self.cleaned_data

full_clean is the overriden method where the error messages for the two attributes are just removed during edit.

Update edit form

Go to templates/daylog/index.html and change the edit link from:

/daylog//edit

to


{% url 'daylog:edit' daylog.slug %}

Additionally update the readonly conditional in templates/daylog/form.html of “Date of log” to:


{% if operation == 'view' or operation == 'edit' %}readonly{% endif %}

References

Twitter, LinkedIn