I have several models similar to these (example from Django docs):
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
And I have HTML form in which (if we continue the analogy with the example from the docs) there are three separate input fields for:
- entering the
nameof thePerson; - specifying the
date_joined; - specifying the
invite_reason
And button "Add member". The user fills in all these fields and clicks on this button. JS code adds hidden input fields (filled in with the entered values) to the html page (so that all entered data will be included in the POST-request data). And also JS adds an span-element to the page with information about the added member with a button "Delete" next to it. Then three visible fields are cleared so that you can enter data for the next person. It looks like this:
Now I see two ways to handle this situation on the Django side:
- Put all the logic for working with memberships in the form:
class MembershipForm(forms.ModelForm): class Meta: model = Membership fields = ('person', 'date_joined', 'invite_reason') class GroupForm(forms.ModelForm): class Meta: model = Recipe fields = ('name',) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.memberships = self.instance.membership_set.all() def clean(self): super().clean() self.memberships = [] memberships_data = self.get_memberships_data(self.data) for name, date_joined, invite_reason in memberships_data: data = { 'person': name, 'date_joined': date_joined, 'invite_reason': invite_reason } membership_form = MembershipForm(data) if membership_form.is_valid(): self.memberships.append(membership_form.instance) else: membership_errors = sum(membership_form.errors.values(), []) self.add_error(None, membership_errors) # There may be other checks here as well. # For example, checks for the presence of at least one group member def _save_m2m(self): super()._save_m2m() self.instance.members.clear() self.instance.membership_set.set(self.memberships, bulk=False)
- Use
inlineformset_factory(example for CreateView)
MembershipFormSet = forms.inlineformset_factory(Group, Membership, fields=('person', 'date_joined', 'invite_reason')) class CreateGroupView(CreateView): model = Group ... def get(self, request, *args, **kwargs): self.object = None form_class = self.get_form_class() form = self.get_form(form_class) membership_formset = MembershipFormSet() return self.render_to_response( self.get_context_data( form=form, membership_formset=membership_formset ) ) def post(self, request, *args, **kwargs): self.object = None form_class = self.get_form_class() form = self.get_form(form_class) membership_formset = MembershipFormSet(self.request.POST) if (form.is_valid() and membership_formset.is_valid()): return self.form_valid(form, membership_formset) else: return self.form_invalid(form, membership_formset) def form_valid(self, form, membership_formset): self.object = form.save(commit=False) self.object.author = self.request.user self.object.save() membership_formset.instance = self.object membership_formset.save() return HttpResponseRedirect(self.get_success_url()) def form_invalid(self, form, membership_formset): return self.render_to_response( self.get_context_data( form=form, membership_formset=membership_formset ) )
In this case, all the logic for processing forms will be in view-functions (or View classes). Also, this approach assumes a certain format of hidden input fields, which allows the formset to retrieve data without additional settings. And it imposes additional restrictions on the js code (in terms of removing items from the list). However, this approach seems like a cleaner solution to me.
I would like to hear the opinion of experienced developers which approach would be most suitable here. Or both the first and the second are bad, but may be there is another, more appropriate for this situation.
If the problem is not clear, I am ready to answer any clarifying questions
Thanks for any help.
from Django: form with fields for creating related objects

No comments:
Post a Comment