views:

73

answers:

2

Suppose I have the following models -

class Item(models.Model):
    name = models.CharField(max_length=150)
    value = models.DecimalField(max_digits=12,decimal_places=2)

class Organization(models.Model):
    name = models.CharField(max_length=150)
    items = models.ManyToManyField(Item, through='Customizable')

class Customizable(models.Model):
    organization = models.ForeignKey(Organization)
    item = models.ForeignKey (Item)
    value = models.DecimalField(max_digits=12,decimal_places=2)

More often than not, when items are "assigned" to an organization, they will have the same value as originally recorded in the related Item object. But in certain cases, an item assigned to an organization may have an overridden value (hence the intermediary model). Since overriding the original value happens rarely (but it does happen) I want to allow the user to simply select desired items from a list of Item instances to assign them to an organization instance. The user will then have the option of overriding individual values later after bulk assignment is complete.

So I have the following simple ModelForm -

class AssignItemsForm(forms.ModelForm):
    items = forms.ModelMultipleChoiceField(queryset=Item.objects.all(),required=False,widget=forms.CheckboxSelectMultiple)
    class Meta:
        model = Organization
        exclude = ('name',)

Now since I have a through model, a simple form.save() won't work. I need to (i) save Customizable instances corresponding to the items selected by the user and (ii) make sure the persisted Customizable instances have the proper value taken from the corresponding value taken from the item instance related by foreignkey .

I am trying to handle it in a view (but my mind is blocked) -

def assign_items(request, oid):
    organization = Organization.objects.get(id=oid)
    if request.method == 'POST':
        form = AssignItemsForm(data=request.POST, instance=organization)
        if form.is_valid():
            current_organization = form.save(commit=False)
            #
            #placeholder to save Customizable instances here
            #
            return HttpResponseRedirect(reverse('redirect-someplace-else'))
    else:
        form = AssignItemsForm(instance=organization,)
    return render_to_response("assign_items.html", {"form": form,}, context_instance=RequestContext(request))
A: 

I'd approach this in a different way. You have an intermediary model for your m2m. Hence I'd argue that AssignItemsForm should be backed by this intermediary model. Therefore I'd change it as follows:

# forms.py
class AssignItemsForm(forms.ModelForm):
    value = forms.DecimalField(max_digits=12, decimal_places=2, required = False)

    class Meta:
        model = Customizable

Next, the matter of allowing users to choose a different value. In order to do this I've made the value field of the model optional (required = False). I then check if the user has supplied an explicit value. If not I assume that the Item's default value is to be used. For this I am overriding the clean method of the form:

    def clean(self):
        super(AssignItemsForm, self).clean()
        value, item = self.cleaned_data.get('value'), self.cleaned_data.get('item')
        if not value:
            value = item.value
        self.cleaned_data['value'] = value
        return self.cleaned_data

And finally I tested this in admin.

# admin.py
from app.forms import AssignItemsForm

class CAdmin(admin.ModelAdmin):
    form = AssignItemsForm

admin.site.register(Item)
admin.site.register(Organization)
admin.site.register(Customizable, CAdmin)

This way you can continue to use form.save() thereby avoiding custom manipulation in the view. You'll have to change your view a bit to make sure that the organization is auto selected for assigning items.

# views.py
def assign_items(request, oid):
    organization = Organization.objects.get(id=oid)
    if request.method == 'POST':
        form = AssignItemsForm(data=request.POST.copy())
        form.save()
    else:
        form = AssignItemsForm(initial = {'organization': organization})
    ...
Manoj Govindan
Since overriding of values does not happen that often, I really want the user to be able to assign many items to an organization at one go. I think it would be just more convenient for the user in this case.
chefsmart
+1  A: 

You would have to use save_m2m method:

def assign_items(request, oid):
    organization = Organization.objects.get(id=oid)
    if request.method == 'POST':
        form = AssignItemsForm(data=request.POST, instance=organization)
        if form.is_valid():
            current_organization = form.save(commit=False)

            current_organization.save()

            form.save_m2m()

            return HttpResponseRedirect(reverse('redirect-someplace-else'))
    else:
        form = AssignItemsForm(instance=organization,)
    return render_to_response("assign_items.html", {"form": form,}, context_instance=RequestContext(request))

Look here for more info:

http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method

Dominik Szopa
Not really. It's the stuff that I need to do *before* save_m2m is what I'm banging my head against.
chefsmart