From 01d751ae4010c2fac39a3de6ffea0a260cb90251 Mon Sep 17 00:00:00 2001
From: Gabe Jackson <gabejackson@cxg.ch>
Date: Thu, 9 Jan 2014 01:51:38 +0100
Subject: [PATCH] Allow generic editing views to use `form_class` and `fields`
together
---
django/forms/models.py | 11 ++++++++---
django/views/generic/edit.py | 41 ++++++++++++++++++++--------------------
tests/generic_views/test_edit.py | 11 +++++++++++
3 files changed, 39 insertions(+), 24 deletions(-)
diff --git a/django/forms/models.py b/django/forms/models.py
index a849436..8e8a886 100644
a
|
b
|
class ModelFormMetaclass(DeclarativeFieldsMetaclass):
|
280 | 280 | opts.widgets, formfield_callback, |
281 | 281 | opts.localized_fields, opts.labels, |
282 | 282 | opts.help_texts, opts.error_messages) |
283 | | |
284 | 283 | # make sure opts.fields doesn't specify an invalid field |
285 | 284 | none_model_fields = [k for k, v in six.iteritems(fields) if not v] |
286 | 285 | missing_fields = (set(none_model_fields) - |
… |
… |
class ModelFormMetaclass(DeclarativeFieldsMetaclass):
|
290 | 289 | message = message % (', '.join(missing_fields), |
291 | 290 | opts.model.__name__) |
292 | 291 | raise FieldError(message) |
| 292 | |
293 | 293 | # Override default model fields with any custom declared ones |
294 | | # (plus, include all the other declared fields). |
295 | | fields.update(new_class.declared_fields) |
| 294 | if opts.fields: |
| 295 | for field_name, field in fields.iteritems(): |
| 296 | if not field or field_name in new_class.declared_fields: |
| 297 | fields[field_name] = new_class.declared_fields[field_name] |
| 298 | fields.update(fields) |
| 299 | else: |
| 300 | fields.update(new_class.declared_fields) |
296 | 301 | else: |
297 | 302 | fields = new_class.declared_fields |
298 | 303 | |
diff --git a/django/views/generic/edit.py b/django/views/generic/edit.py
index 0a8301f..daeeba2 100644
a
|
b
|
from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
|
8 | 8 | from django.views.generic.detail import (SingleObjectMixin, |
9 | 9 | SingleObjectTemplateResponseMixin, BaseDetailView) |
10 | 10 | |
11 | | |
12 | 11 | class FormMixin(ContextMixin): |
13 | 12 | """ |
14 | 13 | A mixin that provides a way to show and handle a form in a request. |
… |
… |
class ModelFormMixin(FormMixin, SingleObjectMixin):
|
95 | 94 | """ |
96 | 95 | Returns the form class to use in this view. |
97 | 96 | """ |
98 | | if self.form_class: |
99 | | return self.form_class |
| 97 | if not self.form_class: |
| 98 | self.form_class = model_forms.ModelForm |
| 99 | |
| 100 | if self.model is not None: |
| 101 | # If a model has been explicitly provided, use it |
| 102 | model = self.model |
| 103 | elif hasattr(self, 'object') and self.object is not None: |
| 104 | # If this view is operating on a single object, use |
| 105 | # the class of that object |
| 106 | model = self.object.__class__ |
100 | 107 | else: |
101 | | if self.model is not None: |
102 | | # If a model has been explicitly provided, use it |
103 | | model = self.model |
104 | | elif hasattr(self, 'object') and self.object is not None: |
105 | | # If this view is operating on a single object, use |
106 | | # the class of that object |
107 | | model = self.object.__class__ |
108 | | else: |
109 | | # Try to get a queryset and extract the model class |
110 | | # from that |
111 | | model = self.get_queryset().model |
112 | | |
113 | | if self.fields is None: |
114 | | warnings.warn("Using ModelFormMixin (base class of %s) without " |
115 | | "the 'fields' attribute is deprecated." % self.__class__.__name__, |
116 | | DeprecationWarning) |
117 | | |
118 | | return model_forms.modelform_factory(model, fields=self.fields) |
| 108 | # Try to get a queryset and extract the model class |
| 109 | # from that |
| 110 | model = self.get_queryset().model |
| 111 | |
| 112 | if self.fields is None: |
| 113 | warnings.warn("Using ModelFormMixin (base class of %s) without " |
| 114 | "the 'fields' attribute is deprecated." % self.__class__.__name__, |
| 115 | DeprecationWarning) |
| 116 | |
| 117 | return model_forms.modelform_factory(model, form=self.form_class, fields=self.fields) |
119 | 118 | |
120 | 119 | def get_form_kwargs(self): |
121 | 120 | """ |
diff --git a/tests/generic_views/test_edit.py b/tests/generic_views/test_edit.py
index be26a14..eac4040 100644
a
|
b
|
from django.views.generic.edit import FormMixin, CreateView
|
13 | 13 | |
14 | 14 | from . import views |
15 | 15 | from .models import Artist, Author |
| 16 | from .test_forms import AuthorForm |
16 | 17 | |
17 | 18 | |
18 | 19 | class FormMixinTests(TestCase): |
… |
… |
class CreateViewTests(TestCase):
|
142 | 143 | |
143 | 144 | self.assertEqual(list(MyCreateView().get_form_class().base_fields), |
144 | 145 | ['name']) |
| 146 | |
| 147 | def test_create_view_with_restricted_fields_and_form_class(self): |
| 148 | |
| 149 | class MyCreateView(CreateView): |
| 150 | model = Author |
| 151 | form_class = AuthorForm |
| 152 | fields = ['name'] |
| 153 | |
| 154 | self.assertEqual(list(MyCreateView().get_form_class().base_fields), |
| 155 | ['name']) |
145 | 156 | |
146 | 157 | def test_create_view_all_fields(self): |
147 | 158 | |