The cookbook has this:
http://code.djangoproject.com/wiki/CookBookManipulatorWithPostpopulatedFields
This sucks for obvious reasons -- you have to go through every field in
your model and copy it's data into the new_data dictionary, at the same
time converting it all to string values (which involves knowing how
Django does this internally, which becomes pretty complicated with
DateTimeFields etc).
The other main method is custom manipulators with only a few fields.
This sucks because it violates DRY - you have to tell the custom
manipulator the information that AutomaticManipulators deduce
automatically from the model definition. You then have to copy your
information from the new_data dict to the model object - a pain if it
contains DateTimeFields, since you have to combine multiple pieces of
data. Plus, if you want FileUpload fields, it also requires copying a
few chunks of code from AutomaticManipulator.
In short, I was hoping to do something like this:
class MyManipulator(MyModel.ChangeManipulator):
limit_to = ['my_text_field', 'my_date_field', 'my_upload_field']
...or, at the very least write some code that would have the same
result, and only have to write the guts of it once, and be reasonably
sure it would continue working.
But I haven't been able to do this, and after a few hours trying to Do
It Right, I almost gave up went to manual form creation and processing.
I eventually worked out that the custom manipulator method isn't too
bad, and that's what I'm using now, but it's far from ideal.
This seems like a fairly common use case i.e. a create/update form, but
with some fields that can't be edited. Would it be possible to fix
AutomaticManipulator and co to make this easier? Are there any
gotchas people can think of (I imagine validators that check multiple
fields might be one)? If I came up with a patch to make this easier,
would the core devs be interested?
Cheers,
Luke
--
"The one day you'd sell your soul for something, souls are a glut."
Luke Plant || L.Plant.98 (at) cantab.net || http://lukeplant.me.uk/
>Has anyone tried to use Django's manipulators and forms for the case
>where you only want a few fields to be editable, and the rest to keep
>the original values?
>
I'm doing it very often. I have some frankenstein user model which is
editable partly in one form, partly in another and third form edits data
from my User model and contrib.auth's User model in together :-)
>In short, I was hoping to do something like this:
>
>class MyManipulator(MyModel.ChangeManipulator):
> limit_to = ['my_text_field', 'my_date_field', 'my_upload_field']
>
>
You are looking for undocumented "follow":
class MyManipulator(MyModel.ChangeManipulator):
def __init__(self, id):
MyModel.ChangeManipulator.__init__(self, id, follow={
'my_text_field': False,
'my_date_field': False,
'my_upload_field': False,
})
Manipulator will look only for fields in its "follow" dict where their
values evaluates to "true". A default manipulator has its "follow"
prefilled with editable fields from a model and a "follow" parameter
that is passed in the constructor will update those values.
Similarly this way you can include non-editable fields that don't get in
by default, just pass them with "True" value.
> You are looking for undocumented "follow":
>
> class MyManipulator(MyModel.ChangeManipulator):
> def __init__(self, id):
> MyModel.ChangeManipulator.__init__(self, id, follow={
> 'my_text_field': False,
> 'my_date_field': False,
> 'my_upload_field': False,
> })
>
> Manipulator will look only for fields in its "follow" dict where their
> values evaluates to "true". A default manipulator has its "follow"
> prefilled with editable fields from a model and a "follow" parameter
> that is passed in the constructor will update those values.
After sending my post, and trawling through the code, I realised I
could use 'follow' to do this, like you say (I had assumed it was only
for related objects), and felt like a bit of an idiot. However, I had
problems. But I've forgotten what they were now - when I get back from
work I'll have another go, and find out if it was just me being stupid
again.
Sorry for the noise,
Luke
> However, I had
> problems. But I've forgotten what they were now
I remembered what my problem was:
Manipulator.get_validation_errors() ignores the 'follow' parameter, so
you get validation errors for fields that aren't included. Also, it's
not simple to strip them out, since you have things like 'mydate_date'
and 'mydate_time' if you have a DateTimeField called mydate.
Also, 'follow' is sub-optimal -- you have to calculate (either by hand
or automatically) the fields that you want to *exclude*, rather than
pass in simply the ones you want to include. Maybe some utility
function on AutomaticManipulator might help.
My aim in all of this, is that if I add any type of field to MyModel, I
shouldn't have to alter MyCustomManipulator at all.
Luke
>Manipulator.get_validation_errors() ignores the 'follow' parameter, so
>you get validation errors for fields that aren't included.
>
Just tested it and it shows that you don't. I have a required field
switched off with "follow" that is not in the form. It doesn't give an
error. When I include it in follow with "True" it does give an error.
My quick guess (I'm a bit lazy to check :-) ) is that automatic
manipulator just don't create field objects based on follow and this is
why it works for validation.
> Also, it's
>not simple to strip them out, since you have things like 'mydate_date'
>and 'mydate_time' if you have a DateTimeField called mydate.
>
>
Follow works on model fields, not manipulator field objects. I
nonetheless checked it out, this is the beginning of automatic
manipulator constructor:
def __init__(self, follow=None):
self.follow = self.opts.get_follow(follow)
self.fields = []
for f in self.opts.fields + self.opts.many_to_many:
if self.follow.get(f.name, False):
self.fields.extend(f.get_manipulator_fields(self.opts,
self, self.change)) # Here's when your date and time fields come into
existance
>Also, 'follow' is sub-optimal -- you have to calculate (either by hand
>or automatically) the fields that you want to *exclude*, rather than
>pass in simply the ones you want to include. Maybe some utility
>function on AutomaticManipulator might help.
>
>
Yes... But I should say that I needed to exclude fields from
manipulators more often.
>My aim in all of this, is that if I add any type of field to MyModel, I
>shouldn't have to alter MyCustomManipulator at all.
>
>
Hm... But there are cases when you want include a new field in this
particular manipulator and there are when you don't. I don't think there
can be a default behavior.
An example. I have a User model and it has some fields that only an
administrator can touch (priveleges, rating) and fields editable by a
user (birth_date, location, etc..). I have two manipulators for this
model: administrative one with excluded personal fields and a personal
one with excluded administrative fields. And I can't predict in which
manipulator any new field should go. I will have to decide it for each
field anyway.
Here's my version ... I also think that this approach has some room left
to improve ;-)
def follow_only(model_cls, field_list):
"""builds the "follow" dict that specifies which fields to take from
the POST data.
Manipulators are passed a dict { fieldname : Boolean } as `follow`.
`follow_only()` builds this dict from the model class and a list of
field names (containing the fields to take from the POST data.
This should really go into a library module or to the django
contributions.
"""
res = dict.fromkeys(model_cls._meta.get_follow().keys(), False)
res.update(dict.fromkeys(field_list,True))
return res
Michael
I must have made a typical late night error, or maybe I'm confusing
what actually happened with various aborted attempts (that didn't use
'follow'), and another error I was having (upload fields not working
correctly -- the example usage in the custom manipulator docs doesn't
handle file uploads). Thanks for checking that for me.
> My quick guess (I'm a bit lazy to check :-) ) is that automatic
> manipulator just don't create field objects based on follow and this is
> why it works for validation.
Yep, you are right, it does just that.
> >My aim in all of this, is that if I add any type of field to MyModel, I
> >shouldn't have to alter MyCustomManipulator at all.
> >
> >
> Hm... But there are cases when you want include a new field in this
> particular manipulator and there are when you don't. I don't think there
> can be a default behavior.
>
> An example. I have a User model and it has some fields that only an
> administrator can touch (priveleges, rating) and fields editable by a
> user (birth_date, location, etc..). I have two manipulators for this
> model: administrative one with excluded personal fields and a personal
> one with excluded administrative fields. And I can't predict in which
> manipulator any new field should go. I will have to decide it for each
> field anyway.
I see your point, but when you decide to add the field to, for example,
your administrative fields, it's easy to remember to change that
manipulator, because you'll have to change the template anyway (I
presume), and you're quite likely to actually test the code. At the
moment you have to remember to change all the *other* manipulators --
in your case this is fairly easy to remember, because you happen to
have 2 manipulators that cover everything, but in other cases (e.g. 1
manipulator or more than 2) it seems like the current interface is more
bug prone that its inverse.
I guess my suggestion would be an alternative keyword argument
'limit_to_fields' to AutormaticAdd/ChangeManipulator.__init__(),
mutually exclusive with 'follow', that would calculate the 'follow'
argument for you. The implementation would be pretty much like the
code Michael posted.
Luke