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 c开发者_JAVA百科orresponding 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))
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
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})
...
Override the save
method of the ModelForm. This way you won't have to repeat yourself if you need to use the form in multiple places.
See this answer for more details:
https://stackoverflow.com/a/40822731/2863603
精彩评论