text: Opened 9 years ago
  Closed 8 years ago
  #2380 closed defect (wontfix)
  ChangeManipulator's and ManyToManyFields (do not show up in resulting FormWrapper)
  Description
  I have a model that has in it a ManyToManyField that is editable.
  I need to present a form that lets you add and remove items from this ManyToManyField.
  I figured using the rote ChangeManipulator would work but something was wrong. My form field for the ManyToMany field never had the default data from the object in it. After much head scratching I had a 'duh' momment. The example code goes like this:
  def view(...): try: manipulator = klass.ChangeManipulator(obj_id) except ObjectDoesNotExist: raise Http404 obj = manipulator.original_object if request.POST: .... else: errors = {} new_data = obj.__dict__ form = forms.FormWrapper(manipulator, new_data, errors) ...
  and the new_data = obj.dict is the problem. Since this is a ManyToManyField it actually has no key in obj.dict. Copying the dictionary will not copy the existing data for this field.
  Now, for a kludge I have:
  ... else: # This was NOT a POST. We want to just display the object with any # form fields filled in from the actual object's data, with no errors # in the form. # errors = {} new_data = obj.__dict__ new_data['fixed_address'] = obj.fixed_address.all() ...
  and voila, my data appears pre-filled in in the form. I see and know how this works but I have to think that something is wrong here. For now, since this is in a generic
  function I am going to iterate over the fields in obj.class.meta.fields look for ones that are ManyToManyField and editable = True and push getattr(obj, field_name).all() in to the new_data dictionary, but am I missing how I am supposed to be doing this? If nothing else I am thinking that the docs should mention that you can not use the example if you have an editable ManyToManyField.
  Change History (4)
  comment:1 Changed 9 years ago by scanner@…
  comment:2 Changed 9 years ago by Jeremy Dunck <jdunck@…>
  On 0.91, this is as follows (uglier due to magic get_*_list stuff and the use of raw IDs in manipulator vs. objects in the cache list.)
  import re m2m_fetcher = re.compile('get_.*_list') ... new_data = profile.__dict__ #HACK #just forcing the m2m _caches to be populated so we can fill in the #manipulator using field names. [getattr(profile, p)() for p in dir(profile) if m2m_fetcher.match(p)] #the following mess does: # for each m2m field on the profile, # set the manipulator data to # the list of pk values for each item found in the m2m cache #On the bright side, this is fully generalized, so any other #m2m manipulator code can follow this pattern. for field in profile._meta.many_to_many: new_data[field.name] = [getattr(item, item._meta.pk.attname) \ for item in getattr(profile, field.get_cache_name())]
  comment:3 Changed 9 years ago by Jeremy Dunck <jdunck@…>
  Or, um, where profiles is a model module,
  manip = profiles.ChangeManipulator(id) new_data = manip.flatten_data()
  I feel like an idiot.
  comment:4 Changed 8 years ago by MichaelRadziej <mir@…>
  - Resolution set to wontfix
  - Status changed from new to closed
  The FormWrapper is a part of the oldforms API which will soon be history and there won't be any more bug fixes. Since newforms works completely different, the ticket is no more relevant, so I close it.
  fyi - the code I use in the "GET" to get around this is: