• Aucun résultat trouvé

A Form for Adding Code Snippets

Dans le document Projects Django (Page 195-198)

So now you have a pretty good idea of how to write a form for adding instances of your Snippet model. You’ll simply set up fields for the things you want users to fill in, and then give it a save()method, which creates and saves the new snippet.

But there’s one new thing you have to handle here. The authorfield on your Snippet model has to be filled in, and it has to be filled in correctly, but you don’t want to show it to your users and let them choose a value. If you did that, any user could effectively pretend to be any other by filling in someone else’s name on a snippet. So you need some way to fill in that field without making it a public part of the form.

Luckily, this is easy to do: a form is just a Python class. So you can add your own custom __init__()method to it and trust that the view function that processes the form will pass in the correct, authenticated user, which you can store and refer back to when it’s time to save the snippet. So let’s get started.

Go into the cabdirectory and create a file called forms.py. In it you can start writing your form as follows:

from django import newforms as forms

Note that, aside from accepting an extra argument—author, which you store for later use—you’re doing two important things here:

• In addition to the authorargument, you specify that this method accepts *argsand

**kwargs. This is a Python shorthand for saying that it will accept any combination of positional and keyword arguments.

• You use super()to call the parent class’s __init__()method, passing the other argu-ments your custom __init__()accepted. This ensures that the __init__()from the base Formclass gets called and sets everything else up properly on your form.

Using this form—accepting *argsand **kwargsand passing them on to the parent method—is a useful shorthand when the method you’re overriding accepts a lot of arguments, especially if a lot of them are optional. The __init__()method of the base Formclass actually accepts up to seven arguments, all of them optional, so this is a handy trick.

Now you can add the fields you care about:

title = forms.CharField(max_length=255)

description = forms.CharField(widget=forms.Textarea()) code = forms.CharField(widget=forms.Textarea()) tags = forms.CharField(max_length=255)

Note that once again you’re relying on the fact that you can change the widget used by a field to alter its presentation. Where Django’s model system uses two different fields—

CharFieldand TextField—to represent different sizes of text-based fields (and has to, because they work out to different data types in the underlying database columns), the form system only has a CharField. To turn it into a <textarea>in the eventual HTML, you simply change its widget to a Textarea, in much the same way that you used the PasswordInputwidget in the example user signup form.

And that takes care of everything except the language, which is suddenly looking a little bit tricky. What you’d like to do is show a drop-down list (an HTML <select>element) of the available languages and validate that the user picked one of those. But none of the field types you’ve seen so far can handle that, so you’ll need to turn to something new.

One way you could handle this is with a field type called ChoiceField. It takes a list of choices (in the same format as a model field that accepts choices—you’ve seen that already in, for example, the statusfield on the weblog’sEntrymodel) and ensures that the submitted value is one of them. But setting that up properly so that the form queries for the set of lan-guages each time it’s used (in case an administrator has added new lanlan-guages to the system) would require some more hacking in the __init__()method. And representing a model rela-tionship like this is an awfully common situation, so you’d expect Django to provide an easy way to handle this.

As it turns out, Django does provide an easy solution: a special field type called ModelChoiceField. Where a normal ChoiceFieldwould simply take a list of choices, a ModelChoiceFieldtakes a Django QuerySet, and dynamically generates its choices from the result of the query (executed fresh each time). To use it, you’ll need to change the model import at the top of the file to also bring in the Languagemodel:

from cab.models import Snippet, Language And then you can simply write:

language = forms.ModelChoiceField(queryset=Language.objects.all())

For this form, you don’t need any special validation beyond what the fields themselves give you, so you can just write the save()method and be done:

def save(self):

snippet = Snippet(title=self.cleaned_data['title'],

description=self.cleaned_data['description'], code=self.cleaned_data['code'],

tags=self.cleaned_data['tags'],

language=self.cleaned_data['language']) snippet.save()

return snippet

Since creating an object and saving it all in one step is a common pattern in Django, you can actually shorten that a bit. The default manager class Django provides will include a method called create(), which creates, saves, and returns a new object. Using that, your save()method is a couple lines shorter:

def save(self):

return Snippet.objects.create(title=self.cleaned_data['title'],

description=self.cleaned_data['description'], code=self.cleaned_data['code'],

tags=self.cleaned_data['tags'],

language=self.cleaned_data['language']) And now your form is complete:

from django import newforms as forms from cab.models import Snippet, Language

class AddSnippetForm(forms.Form):

def __init__(self, author, *args, **kwargs):

super(AddSnippetForm, self).__init__(*args, **kwargs):

self.author = author

title = forms.CharField(max_length=255)

description = forms.CharField(widget=forms.Textarea()) code = forms.CharField(widget=forms.Textarea())

tags = forms.CharField(max_length=255)

language = forms.ModelChoiceField(queryset=Language.objects.all()) def save(self):

return Snippet.objects.create(title=self.cleaned_data['title'],

description=self.cleaned_data['description'], code=self.cleaned_data['code'],

tags=self.cleaned_data['tags'],

language=self.cleaned_data['language'])

Dans le document Projects Django (Page 195-198)