博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Tutorial : Implementing Django Formsets
阅读量:6998 次
发布时间:2019-06-27

本文共 6487 字,大约阅读时间需要 21 分钟。

A step-by-step tutorial for setting up and testing a standard Django formset.

 

I’ve noticed on #django IRC that many people need guidance on formsets, and as I’ve now used them in a couple of my projects — most recently,  — I thought I could offer a short how-to based on my own experiences.

Firstly, if you haven’t already, go and . If you’re still confused, or want an end-to-end tutorial, then read on. The code contained in this tutorial has been tested to work with Django 1.7.

Contents

What Does a Formset Do?

Formsets are for dealing with sets of identical data. For example in Connect, I have a form where the user can save multiple links to their public profile, with each link having both a URL and an anchor:

Animation of a formset in actionA Django formset in action.

I also want:

  • The formset to be nested within the user’s profile form.
  • The user to add or remove as many links as they like.
  • Custom validation checking that no anchor or URL is entered more than once.

Django comes with a number of ‘batteries included’ formsets. There are  and .

This how-to, however, is going to focus on creating a standard formset using custom forms.

Step 1. Create Your Forms

First we need to set out our link form. This is just a standard Django form.

forms.py

from django import formsclass LinkForm(forms.Form): """ Form for individual user links """ anchor = forms.CharField( max_length=100, widget=forms.TextInput(attrs={ 'placeholder': 'Link Name / Anchor Text', }), required=False) url = forms.URLField( widget=forms.URLInput(attrs={ 'placeholder': 'URL', }), required=False)

As our formset will need to be nested inside a profile form, let’s go ahead and create that now:

forms.py

class ProfileForm(forms.Form): """ Form for user to update their own profile details (excluding links which are handled by a separate formset) """ def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) super(ProfileForm, self).__init__(*args, **kwargs) self.fields['first_name'] = forms.CharField( max_length=30, initial = self.user.first_name, widget=forms.TextInput(attrs={ 'placeholder': 'First Name', })) self.fields['last_name'] = forms.CharField( max_length=30, initial = self.user.last_name, widget=forms.TextInput(attrs={ 'placeholder': 'Last Name', }))

Step 2. Create Your Formset

For this particular example, we’re going to add some validation to our formset, as we want to ensure that there are no duplicate URLs or anchors.

We also want to verify that all links have both an anchor and URL. We could simply set the fields as required on the form itself, however this will prevent our users from submitting empty forms, which is not the behaviour we’re looking for here. From a usability perspective, it would be better to simply ignoreforms that are completely empty, raising errors only if a form is partially incomplete.

If you don’t want any custom validation on your formset, you can skip this step entirely.

forms.py

from django.forms.formsets import BaseFormSetclass BaseLinkFormSet(BaseFormSet): def clean(self): """ Adds validation to check that no two links have the same anchor or URL and that all links have both an anchor and URL. """ if any(self.errors): return anchors = [] urls = [] duplicates = False for form in self.forms: if form.cleaned_data: anchor = form.cleaned_data['anchor'] url = form.cleaned_data['url'] # Check that no two links have the same anchor or URL if anchor and url: if anchor in anchors: duplicates = True anchors.append(anchor) if url in urls: duplicates = True urls.append(url) if duplicates: raise forms.ValidationError( 'Links must have unique anchors and URLs.', code='duplicate_links' ) # Check that all links have both an anchor and URL if url and not anchor: raise forms.ValidationError( 'All links must have an anchor.', code='missing_anchor' ) elif anchor and not url: raise forms.ValidationError( 'All links must have a URL.', code='missing_URL' )

Step 3. Hook Up Your View

Now we can use Django’s built in formset_factory to generate our formset. As the name suggests, this function takes a form and returns a formset. At its most basic, we only need to pass it the form we want to repeat - in this case our LinkForm. However, as we have created a custom BaseLinkFormSet, we also need to tell our factory to use this instead of using Django’s default BaseFormSet.

In our example, we also want our formset to display all of the existing UserLinks for the logged in user. To do this, we need to build a dict of our user’s links and pass this as our initial_data.

To save our data we can build a list of UserLinks and save this to the user’s profile using the bulk_createmethod. Wrapping this code in a transaction will avoid a situation where the old links are deleted, but the connection to the database is lost before the new links are created.

We are also going to use the  to tell our users whether their profile was updated.

views.py

from django.contrib import messagesfrom django.contrib.auth.decorators import login_required from django.core.urlresolvers import reverse from django.db import IntegrityError, transaction from django.forms.formsets import formset_factory from django.shortcuts import redirect, render from myapp.forms import LinkForm, BaseLinkFormSet, ProfileForm from myapp.models import UserLink @login_required def test_profile_settings(request): """ Allows a user to update their own profile. """ user = request.user # Create the formset, specifying the form and formset we want to use. LinkFormSet = formset_factory(LinkForm, formset=BaseLinkFormSet) # Get our existing link data for this user. This is used as initial data. user_links = UserLink.objects.filter(user=user).order_by('anchor') link_data = [{ 'anchor': l.anchor, 'url': l.url} for l in user_links] if request.method == 'POST': profile_form = ProfileForm(request.POST, user=user) link_formset = LinkFormSet(request.POST) if profile_form.is_valid() and link_formset.is_valid(): # Save user info user.first_name = profile_form.cleaned_data.get('first_name') user.last_name = profile_form.cleaned_data.get('last_name') user.save() # Now save the data for each form in the formset new_links = [] for link_form in link_formset: anchor = link_form.cleaned_data.get('anchor') url = link_form.cleaned_data.get('url') if anchor and url: new_links.append(UserLink(user=user, anchor=anchor, url=url)) try: with transaction.atomic(): #Replace the old with the new UserLink.objects.filter(user=user).delete() UserLink.objects.

转载于:https://www.cnblogs.com/floodwater/p/9996774.html

你可能感兴趣的文章