Fixed #9475 -- Allowed RelatedManager.add(), create(), etc. for m2m with a through model.#8981
Conversation
066b8ae to
e42d74d
Compare
|
Very promising! |
967256d to
e0be1e9
Compare
|
Ok I added some docs and added a test for a case where the through table requires a value. |
55f841d to
151e5c1
Compare
|
Shouldn't line 955 be |
…anyManager because that seems easier than trying to fix the problem in __set__ which is deprecated for 2.0
151e5c1 to
f84e32e
Compare
|
@airstandley Nice catch! Fixed. |
b674ee5 to
d1ddf46
Compare
|
I just rebased this patch, updated version numbers, and added fail and success tests for add, create, set, get_or_create, and update_or_create. The failed mariadb test looks like a fluke. |
|
How about some helpful message if |
|
Thanks Collin! This would be a nice addition for 2.2, hopefully we can commit it before feature freeze, even if we have to polish some aspects during the alpha phase. |
|
Hmm... I don't think it's possible to detect before the fact if through_defaults is needed, because the field could get auto filled in via a custom save() method, so the only way to know if it will work is to catch the error. But, it seems wrong to me to catch the I'm hoping that letting the error message But maybe catching and raising a new error is helpful? Do you think something like the code below would help? |
|
Ohh, I see now we're using Even still, I don't see a clean way of detecting the issue beforehand. |
tests/m2m_through_regress/tests.py
Outdated
There was a problem hiding this comment.
Are these tests checking something that the m2m_through tests aren't? It looks odd that the results of these calls aren't verified.
There was a problem hiding this comment.
Yeah, good point. I was mostly just converting "can't" test into "can" tests. Should I just delete them these?
There was a problem hiding this comment.
I didn't look closely but I don't think they add anything (but please audit test coverage as I said in my other comment).
There was a problem hiding this comment.
Ok, I removed these tests.
There was a problem hiding this comment.
Please check test coverage carefully. I commented out through_defaults here and don't see a test failure.
There was a problem hiding this comment.
Ok, turns out there's still a "fields.E332 Many-to-many fields with intermediate tables must not be symmetrical." check which i'll just leave in place. So, I removed the through_defaults on this code path.
85d3a30 to
38e1e60
Compare
… m2m with a through model.
ngnpope
left a comment
There was a problem hiding this comment.
A few last minute observations...
| # If this is a symmetrical m2m relation to self, add the mirror | ||
| # entry in the m2m table. `through_defaults` aren't used here | ||
| # because of the system check error fields.E332: Many-to-many | ||
| # fields with intermediate tables mus not be symmetrical. |
| # Add the ones that aren't there already | ||
| self.through._default_manager.using(db).bulk_create([ | ||
| self.through(**{ | ||
| self.through(**dict(through_defaults, **{ |
There was a problem hiding this comment.
Can replace **dict(through_defaults, **{…}) and use unpacking generalisations: **{**through_defaults, **{…}}.
There was a problem hiding this comment.
I guess **through_defaults, **{…} would work as well given surfacing a TypeError if any of the ids properties are provided through through_defaults is probably a better idea than simply dropping them.
| "intermediary model. Use %s.%s's Manager instead." % | ||
| (opts.app_label, opts.object_name) | ||
| ) | ||
| def add(self, *objs, through_defaults=None): |
There was a problem hiding this comment.
I noticed that we use keyword-only arguments for def set(self, objs, *, clear=False, through_defaults=None):
Do we want to so something similar for other methods:
def add(self, *objs, *, through_defaults=None):def create(self, *, through_defaults=None, **kwargs):def get_or_create(self, *, through_defaults=None, **kwargs):def update_or_create(self, *, through_defaults=None, **kwargs):
There was a problem hiding this comment.
Sure, I will push those change, except the suggestion for add() gives invalid syntax.
There was a problem hiding this comment.
Oh. Strange. Not sure why that doesn't work. Thanks.
| # Add the ones that aren't there already | ||
| self.through._default_manager.using(db).bulk_create([ | ||
| self.through(**{ | ||
| self.through(**dict(through_defaults, **{ |
There was a problem hiding this comment.
I guess **through_defaults, **{…} would work as well given surfacing a TypeError if any of the ids properties are provided through through_defaults is probably a better idea than simply dropping them.
| self.through(**{ | ||
| self.through(**dict(through_defaults, **{ | ||
| '%s_id' % source_field_name: self.related_val[0], | ||
| '%s_id' % target_field_name: obj_id, |
There was a problem hiding this comment.
Also the %s_id is probably going to break for fields that define different db_columns. This wasn't an issue before because this branch could only be reached with auto-created intemediary models but now that we allow all forms of through we should use attname or directly *_name here.
There was a problem hiding this comment.
My assumptions here seems to be wrong, this should work fine.
django/django/db/models/fields/related.py
Lines 916 to 917 in d212bc0
https://code.djangoproject.com/ticket/9475