9 | | A validation aware model alone cannot complete this task as information needs to be provided that details the sorts of fields that should be used, and processes are needed to convert from string / post data to the pythonic data. Furthermore many manipulation and form tasks require form fields that do not map directly to a model field, for instance a password changing form that requires a password to be given twice. This example also illuminates how a form field might map directly to a model field, but may not want to give its direct value to the field. As in a password, one probably wouldn't want: {{{user.password = data['password']}}}, but rather: {{{user.set_password(data['password'])}}}. |
| 9 | A validation aware model alone cannot complete these tasks as information needs to be provided that details the sorts of fields that should be used, and processes are needed to convert from string / post data to the pythonic data. Furthermore many manipulation and form tasks require form fields that do not map directly to a model field, for instance a password changing form that requires a password to be given twice. This example also illuminates how a form field might map directly to a model field, but may not want to give its direct value to the field. As in a password, one probably wouldn't want: {{{user.password = data['password']}}}, but rather: {{{user.set_password(data['password'])}}}. |
29 | | As you can see, our form is now more of an exception. And our manipulation process is very simple, most of it takes place in the constructor itself, if durring that time a form is raised, it is presented. Otherwise call "save()" on our manipulator. In this case it creates and returns a new article. It will also raise a form if the model validation returns an error. If no form is raised, we are done and return our Response. |
30 | | |
| 29 | As you can see, our form is now more of an exception. And our manipulation process is very simple, most of it takes place in {{{process()}}}, and if at any point a form is raised, it is presented. Otherwise the manipulator calls and returns {{{save()}}}. In this case it creates and returns a new poll object. It will also raise a form if the model validation returns an error. If no form is raised, we are done and return our Response. |
| 30 | |
| 31 | There is a lot more code to this, as FormWrapper and FormFieldWrapper and some other pieces have been rewritten. Here is the rub, though: |
65 | | |
66 | | def __init__(self, request): |
67 | | """ |
68 | | Initializes the Manipulator and instigates the process. |
69 | | Mainly you will want to define your fields here. |
70 | | """ |
71 | | self.fields = ( |
72 | | SlugField("slug", attributes={'class': 'slug'}), |
73 | | TextField("title", label="Headline", maxlength=60, attributes={'class': 'title'}), |
74 | | LargeTextField("body", label="Story", attributes={'class': 'body'}), |
75 | | DateTimeField("pub_date", label="This article will publish on this date"), |
76 | | ) |
77 | | self.process(request.POST) |
| 66 | |
| 67 | def __getitem__(self, k): |
| 68 | "Allows access of the fields via the brackets, like: manipulator['field_name']" |
| 69 | for f in self.fields: |
| 70 | if (f.name == k): |
| 71 | return f |
| 72 | raise KeyError, "Could not find field with the name %r." % k |
| 73 | |
| 74 | def __repr__(self): |
| 75 | return "Manipulator <%r, %r, %r>" % (self.fields, self.data, self.errors) |
| 76 | |
| 77 | def __init__(self): |
| 78 | # Properties that must be created: |
| 79 | self.fields = () |
| 80 | # Properties that can optionally be created: |
| 81 | self.data = {} # The default data given |
| 82 | self.errors = {} |
96 | | processed. Usually this will entail updating an model instance. To aid in |
97 | | that task, there is a convenience self.update <see below>. |
98 | | """ |
99 | | pass |
100 | | |
101 | | ### Utility Functions ### |
102 | | def update(self, object, data): |
103 | | """ |
104 | | Updates and saves a model object. This will raise a form if there are any errors. |
105 | | """ |
106 | | self.errors = object.update(data) |
107 | | object.save() |
108 | | if (self.errors): |
109 | | raise Form |
110 | | return object |
111 | | |
112 | | def add_errors(self, field, errors): |
113 | | "A convenience function to add errors to a field's error list." |
114 | | errors = list(errors) |
115 | | self.errors.setdefault(field.name, []).extend(errors) |
| 101 | processed. Usually this will entail creating or updating an model instance. |
| 102 | To aid in that task, there are convenience functions edit() and create(), |
| 103 | see below for more details. |
| 104 | """ |
| 105 | raise NotImplementedError |
152 | | for validator in field.validators: |
153 | | try: |
154 | | validator(self.data[field.name]) |
155 | | except (validators.ValidationError, validators.CriticalValidationError), e: |
156 | | self.add_errors(e.messages) |
| 155 | name = field.name |
| 156 | #~ for validator in field.validators: |
| 157 | #~ try: |
| 158 | #~ validator(self.data[name]) |
| 159 | #~ except (validators.ValidationError, validators.CriticalValidationError), e: |
| 160 | #~ self.errors.setdefault(name, []).extend(e.messages) |
| 161 | errors = field.validate(self.data) |
| 162 | if (errors): |
| 163 | self.errors.setdefault(name, []).extend(errors) |
179 | | |
180 | | def update_manipulator(model_cls): |
181 | | "Factory, making a UpdateManipulator for a specified model class" |
182 | | |
183 | | class UpdateManipulator(Manipulator): |
184 | | def __init__(self, request, object): |
| 192 | |
| 193 | def edit_manipulator(model_cls): |
| 194 | class EditManipulator(Manipulator): |
| 195 | def __init__(self, object): |
| 196 | self.fields = forms.generate_fields(model_cls) |
186 | | self.process(request.POST) |
187 | | self.fields = model_cls._meta.update_fields |
188 | | |
189 | | def save(self): |
190 | | self.update(self.object, data) |
191 | | |
192 | | return UpdateManipulator |
| 198 | |
| 199 | def save(self, data): |
| 200 | return forms.edit(self.object, data) |
| 201 | return EditManipulator |
| 202 | |
| 203 | def create(Model, data): |
| 204 | """ |
| 205 | Usefull in the .save of a custom manipulator, this will quickly create an object for you. |
| 206 | """ |
| 207 | m = Model(**data) |
| 208 | # In a validation aware model, any problems would arise here. |
| 209 | m.save() |
| 210 | return m |
| 211 | |
| 212 | def edit(i, data): |
| 213 | """ |
| 214 | Like create() above, this will update an object for you. |
| 215 | """ |
| 216 | i.__dict__.update(data) |
| 217 | # Also validation errors would arise here. |
| 218 | i.save() |
| 219 | return i |
201 | | self.fields = ( |
202 | | SlugField("slug", attributes={'class': 'slug'}), |
203 | | TextField("title", label="Headline", maxlength=60, attributes={'class': 'title'}), |
204 | | LargeTextField("body", label="Story", attributes={'class': 'body'}), |
205 | | DateTimeField("pub_date", label="This article will publish on this date"), |
206 | | ) |
207 | | self.process(request.POST) |
| 228 | self.fields = generate_fields(Poll) # Generate fields creates default fields for a Model |
| 229 | |
| 230 | # Fields have an "attributes" attribute, that adds itself to the tag that is created for it. |
| 231 | # Here, we change our class to "vPopupDateField" so that a javascript function will know to |
| 232 | # add a Date popup window for when the field gets focus. |
| 233 | self.fields['pub_date'].attributes = {'class': 'vPopupDateField'} |
212 | | |
| 238 | |
| 239 | ### Custom views ### |
| 240 | def create_poll(request): |
| 241 | try: |
| 242 | m = Poll.CreateManipulator() |
| 243 | poll = m.process(request) |
| 244 | return HttpResponseRedirect('/polls/%d/' % poll.id) |
| 245 | except Form, form: |
| 246 | return render_to_response('polls/create.html', {'form': form}) |
| 247 | |
| 248 | def update_poll(request, id): |
| 249 | poll = get_object_or_404(Poll, pk=id) |
| 250 | try: |
| 251 | m = Poll.EditManipulator(request, poll) |
| 252 | m.process(request) |
| 253 | return HttpResponseRedirect('/polls/%d/' % poll.id) |
| 254 | except Form, form: |
| 255 | return render_to_response('polls/update.html', {'form': form, 'poll': poll}) |
| 256 | |
261 | | ('^articles/create/%d/$', 'django.views.generic.create_update.create_view', {'model':Article}) |
262 | | ('^articles/update/$', 'django.views.generic.create_update.update_view', {'model':Article}) |
263 | | |
264 | | ### Custom views ### |
265 | | def create_article(request): |
266 | | try: |
267 | | m = Article.CreateManipulator(request) |
268 | | article = m.save() |
269 | | return HttpResponseRedirect('/articles/update/%d/' % article.id) |
270 | | except Form, form: |
271 | | return render_to_response('articles/create.html', {'form': form}) |
272 | | |
273 | | def update_article(request, id): |
274 | | article = get_object_or_404(Article, pk=id) |
275 | | try: |
276 | | m = Article.UpdateManipulator(request, article) |
277 | | m.save() |
278 | | return HttpResponseRedirect('/articles/%d/' % article.id) |
279 | | except Form, form: |
280 | | return render_to_response('articles/update.html', {'form': form, 'object': object}) |
| 311 | ('^polls/create/%d/$', 'django.views.generic.create_update.create_view', {'model':Poll}) |
| 312 | ('^polls/update/$', 'django.views.generic.create_update.update_view', {'model':Poll}) |
| 313 | |
| 316 | == Benefits == |
| 317 | The benefits of this system include: |
| 318 | * Custom form views become very simple, and intuitive. |
| 319 | * Validation aware models are harnessed in this system. |
| 320 | * Custom Manipulators could be defined in the model itself, which hightens the sense of everything in one place. Likewise, it can be seperated out if it's too much code in one spot. |
| 321 | * Defining Custom Manipulators allows one greater control of how the Admin deals with objects. Having a field automatically update with the author's User object would be a snap. Also, some options could move from the {{{class Admin:}}} into the Custom Manipulator, which would tighten things up a great deal. |
| 322 | * Making your own Admin app now becomes a lot easier. People often want to be able to simply plop a form down, and this gives them that ability. |
| 323 | * Because the form is raised, like an exception, it allows the writer to assume the data is correct the whole way through. But when there is a validation error, it is handled easily. |
| 324 | |
284 | | There would be some changes needed in some other django code for all of this to work out right: |
285 | | * The '''FormWrapper''' class would be destroyed, I believe, and would be supplanted by '''Form'''. |
286 | | * Form fields would alter somewhat, most of it would be cosmetic (e.g. changing "html2python" to "to_python" and adding a "label" and "attributes" keyword arguments in the constructor) |
287 | | * Models would gain an "update" function that took a dictionary, applied it to the properties, and then validated. |
288 | | * Meta in the Models would gain a "update_fields" and "create_fields" attribute. Here you could define the fields used for your model's default Manipulators. |
| 326 | There would be many changes needed in some other django code for all of this to work out right: |
| 327 | * The '''FormWrapper''' class would be destroyed, and would be supplanted by the easier '''Form'''. |
| 328 | * Form fields would be altered, both cosmetically "html2python" -> "to_python", and functionally for optimization / simplicity |
| 329 | * Models would have to gain the validation awareness that we've been talking about. |
| 330 | * The Model metaclass would need to create default Create and Edit manipulators if they weren't supplied (this would be very easily done). |