Code

Opened 8 years ago

Closed 7 years ago

#2380 closed defect (wontfix)

ChangeManipulator's and ManyToManyFields (do not show up in resulting FormWrapper)

Reported by: scanner@… Owned by: jacob
Component: Documentation Version:
Severity: normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

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.

Attachments (0)

Change History (4)

comment:1 Changed 8 years ago by scanner@…

fyi - the code I use in the "GET" to get around this is:

   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__

        # Now, new_data will not contain any of our editable ManyToManyField
        # data since those fields are not part of obj.__dict__, so go through
        # the fields in our model, and for any field that is ManyToMany _and_
        # editable get the set of all objects and put it in that field.
        #
        for field in klass._meta.many_to_many:
            if isinstance(field, models.fields.related.ManyToManyField) and field.editable:
                new_data[field.name] = getattr(obj,field.name).all()

comment:2 Changed 7 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 7 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 7 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.

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.