Opened 11 years ago
Closed 11 years ago
#24241 closed Bug (wontfix)
state.clone doesn't copy Django models
| Reported by: | Claude Paroz | Owned by: | Marten Kenbeek |
|---|---|---|---|
| Component: | Migrations | Version: | 1.8alpha1 |
| Severity: | Release blocker | Keywords: | |
| Cc: | sokandpal@…, Markus Holtermann | Triage Stage: | Accepted |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
While debugging #24225, I noticed that StateApps.clone(), even if it calls copy.deepcopy(self.all_models), the all_models structure is only partially deecopied.
all_models looks like:
defaultdict(<class 'collections.OrderedDict'>, {'migrations': OrderedDict([('tag', <class 'Tag'>)])})
Unfortunately, the <class 'Tag'> class is identical in the original and the copy (and what's more important, its _meta content), which is a potential cause of difficult-to-debug bugs. Hints?
Change History (16)
comment:2 by , 11 years ago
ipdb> id(self.all_models['migrations']['tag']) 140327793908816 ipdb> id(clone.all_models['migrations']['tag']) 140327793908816
ipdb> other_state.apps.all_models['migrations']['tag'] <class 'Tag'> ipdb> coopy = deepcopy(other_state.apps.all_models['migrations']['tag']) ipdb> id(other_state.apps.all_models['migrations']['tag']) 140635957997264 ipdb> id(coopy) 140635957997264
Deep copying only applies to instance level attributes but not class level.
Maybe we should write own tool for deep copy to be able to copy class (not class instance)?
comment:3 by , 11 years ago
| Cc: | added |
|---|
comment:4 by , 11 years ago
I will try to do some research. But it requires to do some python magic on a model class
UPD:
Unfortunately, __deepcopy__ defines behavior for copy.deepcopy() for instances of class, so it should be resolved in another way.
comment:6 by , 11 years ago
My experience from QuerySet deepcopy removal is that it is better to write a custom method for this. Deepcopy is slow and it is hard to see what exactly gets copied; and if you restrict it, then some other user of deepcopy doesn't actually get a true deepcopy.
comment:7 by , 11 years ago
| Cc: | added |
|---|
comment:8 by , 11 years ago
| Severity: | Normal → Release blocker |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
comment:9 by , 11 years ago
I tried smth like this
models_to_copy = self.all_models
for apps in models_to_copy.values():
for model in apps.values():
model = type(model.__name__, (model,), {'__module__': model.__module__})
But, some models (e.g. ContentType) have __fake__ as module, so it is not present in sys.modules and model metaclass can not work.
And, am I going in the right way? I think we should have an util for this specific issue.
comment:10 by , 11 years ago
More reliable to copy class, but still no ideal
dict_copy = SomeDjangoModel.__dict__.copy() del dict_copy[‘_meta’] type(SomeDjangoModel.__name__, SomeDjangoModel.__bases__, dict_copy)
comment:11 by , 11 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
I've created a patch on github, though it either exposes or creates a bug (I would say expose) when copying a ProjectState that contains models without any managers, not even a default one. ModelState.from_model can't handle models without managers.
I'm quite new to this. Would it be better to submit this patch and create a new ticket for the other bug, or should I wait because this patch causes additional tests to fail?
comment:12 by , 11 years ago
Thanks knbk! The patch looks sensible at the first glance. Could you create if for the master branch (ie 1.9) and open a pull request.
Regarding the manager problem: could you open a new ticket with a failing test case that demonstrates the problem.
comment:13 by , 11 years ago
| Has patch: | set |
|---|---|
| Version: | 1.8alpha1 → master |
Note that I removed the explicit assignment to clone[app_label][model_name]. ModelBase.__new__ calls apps.register_model, in which this assignment is the very first line.
comment:14 by , 11 years ago
| Version: | master → 1.8alpha1 |
|---|
comment:15 by , 11 years ago
| Has patch: | unset |
|---|
comment:16 by , 11 years ago
| Resolution: | → wontfix |
|---|---|
| Status: | assigned → closed |
After more thoughts, I think now that models not being copied is not necessarily a bug, but may be part of the optimization process. As my patch for #24225 shows, state_forwards methods have to take care of re-rendering models which are potentially changed by the migration operation. I think it's the price to pay for migration optimization.
Sorry for the time spent trying to create a patch!
Test to reproduce the problem:
tests/migrations/test_state.py