#25227: Add utility method `get_updated_model()` to `ModelForm`
-------------------------------------+-------------------------------------
     Reporter:  candeira             |                    Owner:  candeira
         Type:                       |                   Status:  assigned
  Cleanup/optimization               |
    Component:  Forms                |                  Version:  master
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  1                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------

Comment (by candeira):

 Hi, Tim, Thanks for your feedback.

 Replying to [comment:5 timgraham]:
 > This seems a bit confusing to me in that it hides the fact that you are
 calling `super()`,

 I started to write a reply telling you why `super()` was incidental yada
 yada and, in justifying it, I found out that there was a bug in my code.

 In my code as it was at the time of your comment, if you subclass a
 ModelForm, override `save()`, then call `get_updated_model()` on an
 instance of the subclass, it will call the `_save()` in the parent and
 ignore the overriden `save()`. That was bad.

 Now that I've fixed it, and added a heap more tests and documentation, I
 have a much better explanation for *not* calling super.

 All three of these chunks of code are equivalent (I've taken care to test
 all three, they are in tests/model_form_sets/tests.py):

 {{{
         class PoemForm_currentAPI(forms.ModelForm):
             def save(self, commit=True):
                 poem = super(PoemForm2, self).save(commit=False)
                 poem.name = "%s by %s" % (poem.name, poem.poet.name)
                 if commit:
                     poem.save()
                     self.save_m2m
                 return poem

         class PoemForm_get_updated_model(forms.ModelForm):
             def save(self, commit=True):
                 poem = self.get_updated_model()
                 poem.name = "%s by %s" % (poem.name, poem.poet.name)
                 if commit:
                     poem.save()
                     self.save_m2m
                 return poem

         class PoemForm_save_via_form_not_model(forms.ModelForm):
             def save(self, commit=True):
                 poem = self.get_updated_model()
                 poem.name = "%s by %s" % (poem.name, poem.poet.name)
                 if commit:
                     super(PoemForm_save_via_form_not_model, self).save()
                     # self.save_m2m called at the superlass's save()!
                 return poem
 }}}

 You call `super()` when you need to access functionality of the same-named
 method in the superclass, because code reuse is better than code
 duplication. But `super()` is optional if you don't need access to such
 functionality.

 Before the current patch, overriding `save()` needed to call
 `save(commit=False)` via the superclass's `save()` method to get the
 updated models. This call is still available for backwards compatibility,
 and about half the tests still use it.

 Since all the functionality of the `save(commit=False)` path of the
 superclass is now available via `get_updated_models()`, so no need to call
 `super()` when reading a form's instance.

 If one is overriding the `commit=False` path, they can do that by calling
 `get_updated_model()` and changing the instance before saving it or by
 overriding  `get_updated_model()`, which is where the `commit=False`
 action is. They would then have to call `super()` to get to the
 superclass's `get_updated_model()`.

 (This is all now in the documentation for this patch)

 Note that the current idiom in use in Django doesn't use the superclass's
 `ModelForm.save()` for actually committing the form's data. It calls the
 model instance's `save()` directly, and then it calls `form.save_m2m()`.
 The functionality that `super()` provides there is not considered
 important enough, and the little bit of duplicated code in calling
 `self.save_m2m()` is considered better for how it expresses the code's
 intent.

 > and I have never found the current API confusing (of course, I may be in
 the minority).

 I only noticed that in the context of doing the djangogirls tutorial as a
 facilitator. I may be new to Django, but I'm not new to programming, so I
 understand why it's done or, at least, what it does under the covers. So
 not confusing to me either.

 It's however interesting to see what a complete newcomer does with the
 same information from the tutorial. They have to take it as a dogma of
 faith, as one more place where computing is counterintuitive and not at
 all DWIM. I overheard one of the other facilitators apologising for the
 `save(but_really_save=False)` contradiction, and I cought myself also
 saying "this isn't good, but...".

 In my opinion, any API that you have to apologise for to a new student is
 worth changing.

 > Could you please write to the DevelopersMailingList to get some more
 opinions?

 I'm doing it now that I've made the changes prompted by akaariai, and
 refactored with more tests to make sure that I haven't introduced a new
 bug.

 Thanks again for your time and attention.

--
Ticket URL: <https://code.djangoproject.com/ticket/25227#comment:9>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/066.41dfaaca4d29df5c2ba8264498512b88%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to