Muestra las diferencias entre dos versiones de la página.
| Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previa | ||
|
wiki2:python:django:forms [2018/11/10 17:00] alfred [Render a form] |
wiki2:python:django:forms [2020/05/09 09:25] (actual) |
||
|---|---|---|---|
| Línea 8: | Línea 8: | ||
| </form> | </form> | ||
| </code> | </code> | ||
| + | |||
| + | <code> | ||
| + | <form method="post" novalidate> | ||
| + | {% csrf_token %} | ||
| + | {{ form }} | ||
| + | <button type="submit">Submit</button> | ||
| + | </form> | ||
| + | </code> | ||
| + | |||
| + | Fíjate aquí, con ''novalidate'' no haría la validación de datos local. | ||
| <code> | <code> | ||
| Línea 37: | Línea 47: | ||
| </code> | </code> | ||
| <code> | <code> | ||
| - | # In form_snippet.html: | + | <form method="post" novalidate> |
| - | {% for field in form %} | + | {% csrf_token %} |
| - | <div class="fieldWrapper"> | + | |
| - | {{ field.errors }} | + | {{ form.non_field_errors }} |
| - | {{ field.label_tag }} {{ field }} | + | |
| - | </div> | + | {% for hidden_field in form.hidden_fields %} |
| + | {{ hidden_field.errors }} | ||
| + | {{ hidden_field }} | ||
| + | {% endfor %} | ||
| + | |||
| + | <table border="1"> | ||
| + | {% for field in form.visible_fields %} | ||
| + | <tr> | ||
| + | <th>{{ field.label_tag }}</th> | ||
| + | <td> | ||
| + | {{ field.errors }} | ||
| + | {{ field }} | ||
| + | {{ field.help_text }} | ||
| + | </td> | ||
| + | </tr> | ||
| + | {% endfor %} | ||
| + | </table> | ||
| + | |||
| + | <button type="submit">Submit</button> | ||
| + | </form> | ||
| + | </code> | ||
| + | |||
| + | ==== How does it work? ==== | ||
| + | |||
| + | |||
| + | When we write ''<nowiki>{{ form }}</nowiki>'' in a template, it’s actually accessing the ''<nowiki>__str__</nowiki>'' method from the BaseForm class. We can use other three methods to render it (as table, as p, as ul tags): as_table(), as_ul(), as_p(). | ||
| + | |||
| + | The as_table() and as_ul() methods do not create the <table> and the <ul> tags, so we have to add it by ourselves. | ||
| + | <code> | ||
| + | <form method="post" novalidate> | ||
| + | {% csrf_token %} | ||
| + | <table border="1"> | ||
| + | {{ form }} | ||
| + | </table> | ||
| + | <button type="submit">Submit</button> | ||
| + | </form> | ||
| + | </code> | ||
| + | |||
| + | ==== The form definition ==== | ||
| + | <code python> | ||
| + | class ColorfulContactForm(forms.Form): | ||
| + | name = forms.CharField( | ||
| + | max_length=30, | ||
| + | widget=forms.TextInput( | ||
| + | attrs={ | ||
| + | 'style': 'border-color: blue;', | ||
| + | 'placeholder': 'Write your name here' | ||
| + | } | ||
| + | ) | ||
| + | ) | ||
| + | email = forms.EmailField( | ||
| + | max_length=254, | ||
| + | widget=forms.EmailInput(attrs={'style': 'border-color: green;'}) | ||
| + | ) | ||
| + | message = forms.CharField( | ||
| + | max_length=2000, | ||
| + | widget=forms.Textarea(attrs={'style': 'border-color: orange;'}), | ||
| + | help_text='Write here your message!' | ||
| + | ) | ||
| + | </code> | ||
| + | |||
| + | ==== The validation process... ==== | ||
| + | |||
| + | Converts all the data to proper values in ''cleaned_data'' if they are, for sure, valid. An example of a Boolean field: | ||
| + | <code python> | ||
| + | class MyForm(forms.Form): | ||
| + | has_code = forms.BooleanField(widget= forms.CheckboxInput()) | ||
| + | |||
| + | def my_form_view(request): | ||
| + | if request.method == 'POST': | ||
| + | my_form = MyForm(request.POST) | ||
| + | if my_form.is_valid(): | ||
| + | # my_form.cleaned_data['has_code'] is now a bool | ||
| + | </code> | ||
| + | |||
| + | ===== Gotchas ===== | ||
| + | |||
| + | <code python> | ||
| + | unique_together = ('accounting_year', 'number',) | ||
| + | </code> | ||
| + | ===== Widgets ===== | ||
| + | You can define widgets on for the fields on the same form: | ||
| + | <code> | ||
| + | class PurchaseOrderForm(forms.ModelForm): | ||
| + | class Meta: | ||
| + | model = PurchaseOrder | ||
| + | fields = '__all__' | ||
| + | widgets = { | ||
| + | 'bill_price': EuroWidget(), | ||
| + | 'total_amount': EuroWidget(), | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | ==== Add a widget to a field ==== | ||
| + | <code> | ||
| + | city = forms.CharField(widget=forms.TextInput(attrs={'autocomplete':'off'})) | ||
| + | </code> | ||
| + | También lo puedes añadir en el meta: | ||
| + | <code> | ||
| + | 'widgets': {'city': forms.TextInput} | ||
| + | </code> | ||
| + | ===== Fields ===== | ||
| + | ==== Choice ==== | ||
| + | === From DB === | ||
| + | IF you are saving cities into the database, you can use ModelChoiceField: | ||
| + | <code> | ||
| + | class SelectCityForm(forms.Form): | ||
| + | cities = forms.ModelChoiceField(queryset=City.objects.all()) | ||
| + | </code> | ||
| + | |||
| + | |||
| + | ==== Create your own field ==== | ||
| + | You need the field and the widget: | ||
| + | |||
| + | <code python> | ||
| + | class SelectMultipleChecks(forms.Widget): | ||
| + | template_name = 'widgets/multiple_checks.html' | ||
| + | |||
| + | def __init__(self, *args, choices=(), defaults=(), **kwargs): | ||
| + | super().__init__(*args, **kwargs) | ||
| + | self.choices = choices | ||
| + | |||
| + | def get_context(self, name, value, attrs): | ||
| + | context = super().get_context(name, value, attrs) | ||
| + | context['choices'] = self.choices | ||
| + | return context | ||
| + | |||
| + | |||
| + | class SelectMultipleChecksField(forms.Field): | ||
| + | widget = SelectMultipleChecks | ||
| + | |||
| + | def __init__(self, *args, choices=(), defaults=(), **kwargs): | ||
| + | super().__init__(*args, **kwargs) | ||
| + | self.choices = choices | ||
| + | |||
| + | def _get_choices(self): | ||
| + | return self._choices | ||
| + | |||
| + | def _set_choices(self, value): | ||
| + | if callable(value): | ||
| + | value = CallableChoiceIterator(value) | ||
| + | else: | ||
| + | value = list(value) | ||
| + | self._choices = self.widget.choices = value | ||
| + | |||
| + | choices = property(_get_choices, _set_choices) | ||
| + | |||
| + | def to_python(self, value): | ||
| + | import json | ||
| + | values = json.loads(value) | ||
| + | i_values = [int(v) for v in values] | ||
| + | return [choice for choice in self._choices if choice.pk in i_values] | ||
| + | |||
| + | def validate(self, value): | ||
| + | assert len(value) > 0 | ||
| + | |||
| + | def valid_value(self, value): | ||
| + | return value | ||
| + | </code> | ||
| + | |||
| + | With this we can have the template ''widgets/multiple_checks.html'': | ||
| + | <code html> | ||
| + | <div style="display: flex; justify-content: space-around;"> | ||
| + | <input type="hidden" id="{{ widget.attrs.id }}" value="[{{ selected|join:"," }}]"> | ||
| + | {% for choice in choices %} | ||
| + | <div><input | ||
| + | type="checkbox" {% if choice.pk in selected %}checked{% endif %} | ||
| + | name="{{widget.attrs.id}}_check" | ||
| + | data-id="{{choice.pk}}" | ||
| + | > {{ choice }}</div> | ||
| {% endfor %} | {% endfor %} | ||
| + | </div> | ||
| + | </code> | ||
| + | |||
| + | === Set rendered properties === | ||
| + | |||
| + | |||
| + | You can set properties to render like this: | ||
| + | <code> | ||
| + | city = forms.CharField(widget=forms.TextInput(attrs={'autocomplete':'off'})) | ||
| </code> | </code> | ||