openwisp / django-swappable-models Goto Github PK
View Code? Open in Web Editor NEWSwapper - The unofficial Django swappable models API. Maintained by the OpenWISP project.
Home Page: http://openwisp.org
License: MIT License
Swapper - The unofficial Django swappable models API. Maintained by the OpenWISP project.
Home Page: http://openwisp.org
License: MIT License
Hello,
I have a bunch of apps namespaced in a django_omop
module, meaning each app_label
contains a dot .
, which causes problems with the swap setting definition because it also includes the dot .
.
For instance, when using swapper.get_model_name('django_omop.vocabulary', 'Concept')
, it tries to fetch the setting DJANGO_OMOP.VOCABULARY_CONCEPT
.
While such a setting could be defined by modifying globals()
, this is really not a good solution.
This could easily be fixed by modifying swapper.swappable_setting
and replacing dot .
with something like double underscore __
.
I can propose a fix in a PR if you want.
Add CHANGES.rst
as we have in the rest of OpenWISP modules. For the previous versions we can include a brief summary while we'll be more detailed with the new versions.
Add python 3.8 and python 3.9, remove python 3.6 from GitHub actions testing.
Fix any bug that may appear.
Traceback (most recent call last):
File "/home/cfpp/workspace/cerebromix/src/dev-backend/src/manage.py", line 14, in <module>
execute_from_command_line(sys.argv)
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
utility.execute()
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/core/management/__init__.py", line 354, in execute
django.setup()
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/__init__.py", line 21, in setup
apps.populate(settings.INSTALLED_APPS)
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/apps/registry.py", line 108, in populate
app_config.import_models(all_models)
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/apps/config.py", line 202, in import_models
self.models_module = import_module(models_module_name)
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/importlib/__init__.py", line 109, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 2231, in _gcd_import
File "<frozen importlib._bootstrap>", line 2214, in _find_and_load
File "<frozen importlib._bootstrap>", line 2203, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 1200, in _load_unlocked
File "<frozen importlib._bootstrap>", line 1129, in _exec
File "<frozen importlib._bootstrap>", line 1448, in exec_module
File "<frozen importlib._bootstrap>", line 321, in _call_with_frames_removed
File "/home/cfpp/workspace/cerebromix/src/dev-backend/src/boss/apps/base_partners/models.py", line 37, in <module>
class AbstractPartner(models.Model):
File "/home/cfpp/workspace/cerebromix/src/dev-backend/src/boss/apps/base_partners/models.py", line 41, in AbstractPartner
Partner = load_model("boss_base_partners", "PartnerTag")
File "/home/cfpp/workspace/cerebromix/src/dev-backend/src/boss/swapper.py", line 81, in load_model
cls = get_model(app_label, model)
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/db/models/__init__.py", line 54, in alias
return getattr(loading, function_name)(*args, **kwargs)
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/apps/registry.py", line 199, in get_model
self.check_models_ready()
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/apps/registry.py", line 131, in check_models_ready
raise AppRegistryNotReady("Models aren't loaded yet.")
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
Rise this error!!!
The alias will be dropped in Django 1.9. Just need to update swapper.load_model
to use django.db.models.loading.get_model
instead.
Let's make sure this module can run with Django 3.2 and Django 4.0 alpha.
After the changes for my reusable app which already had its test cases working before using django-swappable-models, the test cases have started to fail.
Here is the stacktrace when running the tests for the reusableapp (in this case it is django-blog-zinnia)
Traceback (most recent call last):
File "./bin/test", line 30, in <module>
sys.exit(nose.main(argv=['nose', '--with-sfd', '--with-progressive', '--nologcapture', '-s']+sys.argv[1:]))
File "/home/ubuntu/django-blog-zinnia/eggs/nose-1.3.7-py2.7.egg/nose/core.py", line 121, in __init__
**extra_args)
File "/usr/lib/python2.7/unittest/main.py", line 94, in __init__
self.parseArgs(argv)
File "/home/ubuntu/django-blog-zinnia/eggs/nose-1.3.7-py2.7.egg/nose/core.py", line 145, in parseArgs
self.config.configure(argv, doc=self.usage())
File "/home/ubuntu/django-blog-zinnia/eggs/nose-1.3.7-py2.7.egg/nose/config.py", line 347, in configure
self.plugins.begin()
File "/home/ubuntu/django-blog-zinnia/eggs/nose-1.3.7-py2.7.egg/nose/plugins/manager.py", line 99, in __call__
return self.call(*arg, **kw)
File "/home/ubuntu/django-blog-zinnia/eggs/nose-1.3.7-py2.7.egg/nose/plugins/manager.py", line 167, in simple
result = meth(*arg, **kw)
File "/home/ubuntu/django-blog-zinnia/eggs/nose_sfd-0.4-py2.7.egg/sfd/sfd.py", line 41, in begin
django.setup() # Django >= 1.7
File "/home/ubuntu/django-blog-zinnia/eggs/Django-1.10.5-py2.7.egg/django/__init__.py", line 27, in setup
apps.populate(settings.INSTALLED_APPS)
File "/home/ubuntu/django-blog-zinnia/eggs/Django-1.10.5-py2.7.egg/django/apps/registry.py", line 115, in populate
app_config.ready()
File "/home/ubuntu/django-blog-zinnia/eggs/Django-1.10.5-py2.7.egg/django/contrib/admin/apps.py", line 23, in ready
self.module.autodiscover()
File "/home/ubuntu/django-blog-zinnia/eggs/Django-1.10.5-py2.7.egg/django/contrib/admin/__init__.py", line 26, in autodiscover
autodiscover_modules('admin', register_to=site)
File "/home/ubuntu/django-blog-zinnia/eggs/Django-1.10.5-py2.7.egg/django/utils/module_loading.py", line 50, in autodiscover_modules
import_module('%s.%s' % (app_config.name, module_to_search))
File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
__import__(name)
File "/home/ubuntu/django-blog-zinnia/zinnia/admin/__init__.py", line 6, in <module>
from zinnia.admin.category import CategoryAdmin
File "/home/ubuntu/django-blog-zinnia/zinnia/admin/category.py", line 7, in <module>
from zinnia.admin.forms import CategoryAdminForm
File "/home/ubuntu/django-blog-zinnia/zinnia/admin/forms.py", line 16, in <module>
Entry = swapper.load_model("zinnia", "Entry")
File "/home/ubuntu/Env/zinnia/lib/python2.7/site-packages/swapper/__init__.py", line 94, in load_model
"Could not find {name}!".format(name=join(app_label, model))
django.core.exceptions.ImproperlyConfigured: Could not find zinnia.Entry!
make: *** [test] Error 1
The code for admin/forms.py looks somewhat like this:
Entry = swapper.load_model("zinnia", "Entry")
class EntryAdminForm(forms.ModelForm):
"""
Form for Entry's Admin.
"""
...
...
...
def __init__(self, *args, **kwargs):
super(EntryAdminForm, self).__init__(*args, **kwargs)
Entry = swapper.load_model("zinnia", "Entry")
self.fields['categories'].widget = RelatedFieldWidgetWrapper(
self.fields['categories'].widget,
Entry.categories.field.remote_field,
self.admin_site)
class Meta:
"""
EntryAdminForm's Meta.
"""
model = Entry
fields = forms.ALL_FIELDS
...
The documentation states that swapper.load_model works only when the django has loaded all the models so probably defining the model = Entry in the class Meta seems incorrect to me.
In that case will I need to import the default Entry model in the admin/forms.py and in the init method of EntryAdmin, reload the Entry model via swapper.load_model (as already done) ?
If so it would be great if we could have the documentation updated about this.
We should update the change log to add the latest feature #39 and mention the compatibility to django 3.2 and django 4.0a (#37).
@codesankalp please send a PR with the change log changes.
Then we can issue a new release on pypi and git tag.
When I try to register an model in Django Admin, rise the error
from django.contrib import admin
from boss.swapper import load_model
Country = load_model("boss_base_addresses", "Country")
State = load_model("boss_base_addresses", "State")
Locality = load_model("boss_base_addresses", "Locality")
Geolocation = load_model("boss_base_addresses", "Geolocation")
Address = load_model("boss_base_addresses", "Address")
class CountryAdmin(admin.ModelAdmin):
search_fields = ('name', 'code')
class StateAdmin(admin.ModelAdmin):
search_fields = ('name', 'code')
class LocalityAdmin(admin.ModelAdmin):
search_fields = ('name', 'postal_code')
class AddressAdmin(admin.ModelAdmin):
search_fields = ('name',)
class GeolocationAdmin(admin.ModelAdmin):
search_fields = ('latitude', 'longitude',)
admin.site.register(Country, CountryAdmin)
admin.site.register(State, StateAdmin)
admin.site.register(Locality, LocalityAdmin)
admin.site.register(Geolocation, GeolocationAdmin)
admin.site.register(Address, AddressAdmin)
Traceback (most recent call last):
File "/home/cfpp/workspace/cerebromix/src/dev-backend/src/manage.py", line 14, in <module>
execute_from_command_line(sys.argv)
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
utility.execute()
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/core/management/__init__.py", line 354, in execute
django.setup()
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/__init__.py", line 21, in setup
apps.populate(settings.INSTALLED_APPS)
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/apps/registry.py", line 115, in populate
app_config.ready()
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/contrib/admin/apps.py", line 22, in ready
self.module.autodiscover()
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/contrib/admin/__init__.py", line 23, in autodiscover
autodiscover_modules('admin', register_to=site)
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/utils/module_loading.py", line 74, in autodiscover_modules
import_module('%s.%s' % (app_config.name, module_to_search))
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/importlib/__init__.py", line 109, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 2231, in _gcd_import
File "<frozen importlib._bootstrap>", line 2214, in _find_and_load
File "<frozen importlib._bootstrap>", line 2203, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 1200, in _load_unlocked
File "<frozen importlib._bootstrap>", line 1129, in _exec
File "<frozen importlib._bootstrap>", line 1448, in exec_module
File "<frozen importlib._bootstrap>", line 321, in _call_with_frames_removed
File "/home/cfpp/workspace/cerebromix/src/dev-backend/src/boss/apps/base_addresses/admin.py", line 34, in <module>
admin.site.register(Country, CountryAdmin)
File "/home/cfpp/workspace/cerebromix/src/dev-backend/lib/python3.4/site-packages/django/contrib/admin/sites.py", line 78, in register
for model in model_or_iterable:
TypeError: 'NoneType' object is not iterable
Add run-qa-checks script like we have in other modules, ensure flake8, isort and black formatting issues are fixed.
EDIT totally re-written for reasons of brevity and clarity
This is a possible duplicate of issue #10, but it wasn't written in a way which was concrete enough for me to be sure. I'll try and make this as concrete and minimal as possible. I have 3 questions (below).
Consider reusableapp
which consists of
reusableapp/models.py
from django.db import models
import swapper
class FooBase(models.Model):
some_field = models.CharField(max_length=32)
class Meta:
abstract = True
class Foo(FooBase):
# default (swappable) implementation of FooBase
class Meta:
swappable = swapper.swappable_setting("reusableapp", "Foo")
reusableapp/migrations/0001_initial.py
from __future__ import unicode_literals
from django.db import migrations, models
import swapper
class Migration(migrations.Migration):
dependencies = [
swapper.dependency('reusableapp', 'Foo')
]
operations = [
migrations.CreateModel(
name='Foo',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('some_field', models.CharField(max_length=32)),
],
options={
'swappable': swapper.swappable_setting("reusableapp", "Foo"),
},
),
]
and 'myapp' which only consists of:
myapp/models.py
from django.db import models
from reusableapp.models import FooBase
class Bar(models.Model):
name = models.CharField(max_length=32)
class Foo(FooBase):
bar = models.ForeignKey('Bar', related_name='foo', null=True)
question 1 should myapp/model.py:Foo also have:
class Meta:
swappable = swapper.swappable_setting("reusableapp", "Foo")
I think the answer is yes
, as I don't see why this implementation is any different than the default
implementation in reusableapp
. Also, I seem to get field lookup errors, as mentioned in issue #10 if this isn't declared.
question 2 does the migration for myapp which introduces it's own implementation of FooBase
need to be updated to use swapper
instead of settings
as in reusableapp/migrations/0001_initial.py
?
question 3 if the answer to question 2 is yes
, should it declare:
dependencies = [
swapper.dependency("reusableapp", "Foo")
]
This can be useful in scenarios like openwisp/openwisp-radius#328 (review).
I think we should add an optional latest=False
keyword argument to this function:
django-swappable-models/swapper/__init__.py
Lines 46 to 53 in 4d31c82
If latest
is True
, we should call a new function which works identically to django's swappable_dependency with the difference that it points to __latest__
instead.
We should document this feature and warn against it's possible drawbacks, recommending users to use it with caution and only if the default usage doesn't work.
In some cases, apps we depend on have data migrations we must depend on which are not included in the initial migration but may be included in the successive migrations.
Depending on latest doesn't fix that and may introduce more harm than good.
In these cases we simply have to depend on the specific migration and recommend anyone wanting to swap that particular app to create a migration with the same name in their swapped models.
I think we should change dependency
as follows:
django-swappable-models/swapper/__init__.py
Lines 47 to 55 in 6698b20
version
attribute which defaults to None
and if present is returned similary to how we return __latest__
nowWe should then update the docs about latest and replace those with this concept.
@codesankalp what do you think?
PS: see also openwisp/openwisp-users#300.
I always get a failed field lookup during makemigrations if the base app ('reusableapp') already has a migration in place, and the derived app ('myapp') did not previously provide a swapped version of a swappable model, but now does provide one, and there is another (swappable?) model which has a foreign key to this newly added model. It does not matter whether the other model was original or swapped.
The error is a lookup failure for the swappable model in the init function of the StateApps class in Django (django/db/migrations/state.py). In that location the exception is ignored if the 'ignore_swappable' parameter is True but ONLY if also app_label == settings.AUTH_USER_MODEL
.
Removing the limitation on app_label from Django seems to result in makemigrations completing successfully and correctly.
Running makemigrations only for 'myapp' does not matter.
Changing the reusableapp migration file as in the instructions does not make a difference. Django version also does not matter, anything from 1.7 up to 1.9 does this.
Swapping Child rather than Parent does not result in the same problems (note that there is no foreign key pointing to it).
Making additional changes to either model after successfully creating the initial ones does not pose any problems either (perhaps unless another foreign key is added but I haven't tested).
So it seems that using swapper with migrations already provided with the 'reusableapp' forces you to manually make an initial migration for the app/project which uses it. Unless I am missing something, in which case the README is incomplete.
We're out of credits on travis-CI and moving everything, so we shall move this too to Github Actions.
@sheppard suggests using https://github.com/ymyzk/tox-gh-actions to replace tox-travis.
Hello!
First of all, many thanks for this repo! I hope Django will ship this feature in its core in not such a distant future.
I think that migrations don't really work with this system (except for simple cases).
Consider 3 models with ForeignKey relationships, A -> B -> C (C depends on B which depends on A). If you swap model B for your own, you will run into a problem:
If you generated the migrations file in the reusable app, you basically end up with a circular import.
django.db.migrations.exceptions.CircularDependencyError: other_app.0001_initial, reusable_app.0001_initial
You can actually see the problem with the following example:
models.py
in the reusable app:import swapper
from django.db import models
class BaseA(models.Model):
class Meta:
abstract = True
class A(BaseA):
class Meta:
swappable = swapper.swappable_setting('reusable_app', 'A')
class BaseB(models.Model):
a = models.ForeignKey(swapper.get_model_name('reusable_app', 'A'), on_delete=models.CASCADE)
class Meta:
abstract = True
class B(BaseB):
class Meta:
swappable = swapper.swappable_setting('reusable_app', 'B')
class BaseC(models.Model):
b = models.ForeignKey(swapper.get_model_name('reusable_app', 'B'), on_delete=models.CASCADE)
class Meta:
abstract = True
class C(BaseC):
class Meta:
swappable = swapper.swappable_setting('reusable_app', 'C')
models.py
in the other app:from django.db import models
from reusable_app.models import BaseB
class B(BaseB, models.Model):
name = models.CharField("extra field for B", max_length=255)
In this first case, your other app is not in INSTALLED_APPS, and you only make migrations for your reusable app, so that it can create a proper database schema when installed.
So you run:
$ ./manage.py makemigrations reusable_app
Migrations for 'reusable_app':
reusable_app/migrations/0001_initial.py:
- Create model A
- Create model B
- Create model C
You get a nice migrations file:
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-19 08:21
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='A',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'swappable': 'REUSABLE_APP_A_MODEL',
},
),
migrations.CreateModel(
name='B',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('a', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.REUSABLE_APP_A_MODEL)),
],
options={
'swappable': 'REUSABLE_APP_B_MODEL',
},
),
migrations.CreateModel(
name='C',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('b', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.REUSABLE_APP_B_MODEL)),
],
options={
'swappable': 'REUSABLE_APP_C_MODEL',
},
),
]
You can, of course, modify it according to your documentation in order to avoid blindly reading configuration values from django.conf.settings which may not be defined:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import swapper
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='A',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'swappable': swapper.swappable_setting('reusable_app', 'A'),
},
),
migrations.CreateModel(
name='B',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('a', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=swapper.get_model_name('reusable_app', 'A'))),
],
options={
'swappable': swapper.swappable_setting('reusable_app', 'B'),
},
),
migrations.CreateModel(
name='C',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('b', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=swapper.get_model_name('reusable_app', 'B'))),
],
options={
'swappable': swapper.swappable_setting('reusable_app', 'C'),
},
),
]
And this works fine:
$ ./manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, reusable_app, sessions
Running migrations:
Applying reusable_app.0001_initial... OK
But now, lets say I want to use my other app that ships with its own version of the "B" model.
I add other_app
to INSTALLED_APPS
and specify REUSABLE_APP_B_MODEL = 'other_app.B'
. Now it doesn't work anymore…
$ ./manage.py makemigrations other_app
Traceback (most recent call last):
File "./manage.py", line 22, in <module>
execute_from_command_line(sys.argv)
File ".../django/core/management/__init__.py", line 367, in execute_from_command_line
utility.execute()
File ".../django/core/management/__init__.py", line 359, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File ".../django/core/management/base.py", line 294, in run_from_argv
self.execute(*args, **cmd_options)
File ".../django/core/management/base.py", line 345, in execute
output = self.handle(*args, **options)
File ".../django/core/management/commands/makemigrations.py", line 173, in handle
migration_name=self.migration_name,
File ".../django/db/migrations/autodetector.py", line 47, in changes
changes = self._detect_changes(convert_apps, graph)
File ".../django/db/migrations/autodetector.py", line 132, in _detect_changes
self.old_apps = self.from_state.concrete_apps
File ".../django/db/migrations/state.py", line 180, in concrete_apps
self.apps = StateApps(self.real_apps, self.models, ignore_swappable=True)
File ".../django/db/migrations/state.py", line 249, in __init__
raise ValueError("\n".join(error.msg for error in errors))
ValueError: The field reusable_app.C.b was declared with a lazy reference to 'other_app.b', but app 'other_app' isn't installed.
Well, Django is lying in a way. Because other_app
IS
installed:
$ ./manage.py check
System check identified no issues (0 silenced).
$ ./manage.py shell
Python 3.5.1 (default, Jan 22 2016, 08:54:32)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import swapper
>>> swapper.load_model('reusable_app', 'B')
<class 'other_app.models.B'>
I guess that Django doesn't see the other_app
being installed because it reads the reusable_app.0001_initial
migration file, which doesn't depend on anything, so it loads it before loading the other_app
, and see references to other_app
that it cannot resolve.
I tried to add fill the dependencies
parameter in reusable_app.0001_initial
, I was hoping that it would trigger an import of the other_app
, but it didn't change anything:
dependencies = [
swapper.dependency('reusable_app', 'A'),
swapper.dependency('reusable_app', 'B'),
swapper.dependency('reusable_app', 'C'),
]
So since generating migrations for the reusable app first doesn't work, there is a workaround. To clean things up, I just deleted the migrations file from the reusable app.
Now the only way to make the whole thing work is to type the following:
$ ./manage.py makemigrations
Migrations for 'other_app':
other_app/migrations/0001_initial.py:
- Create model B
other_app/migrations/0002_b_a.py:
- Add field a to b
Migrations for 'reusable_app':
reusable_app/migrations/0001_initial.py:
- Create model A
- Create model B
- Create model C
When I apply these migrations, they are executed in the following order:
I guess that Django splits migrations in order to resolve the circular dependencies issue.
In my real-life project, I have a much more complicated setup with 12 inter-dependent swappable models. I guess it can lead to even more complicated inter-depndencies…
I have the following models for my reusableapp:
class BaseParent(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField('reusableapp.Author')
class Meta:
abstract = True
class Parent(BaseParent):
class Meta:
swappable = swapper.swappable_setting('reusableapp', 'Parent')
class Author(models.Model):
name = models.CharField(max_length=32)
Here are the migrations for the reusableapp:
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Author',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32)),
],
),
]
from __future__ import unicode_literals
from django.db import migrations, models
import swapper
class Migration(migrations.Migration):
dependencies = [
('reusableapp', '0001_initial'),
swapper.dependency('reusableapp', 'Parent')
]
operations = [
migrations.CreateModel(
name='Parent',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32)),
],
options={
'swappable': swapper.swappable_setting('reusableapp', 'Parent')
},
),
migrations.AddField(
model_name='parent',
name='authors',
field=models.ManyToManyField(to='reusableapp.Author'),
),
]
Now in myapp I have:
from django.db import models
from reusableapp.models import BaseParent
class Parent(BaseParent):
extra_field = models.CharField(max_length=32)
And in myapp settings I have:
REUSABLEAPP_PARENT_MODEL = 'myapp.Parent'
On running makemigrations it generates the following migration for myapp:
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('reusableapp', '0002_auto_20170414_1447'),
]
operations = [
migrations.CreateModel(
name='Parent',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32)),
('extra_field', models.CharField(max_length=32)),
('authors', models.ManyToManyField(to='reusableapp.Author')),
],
options={
'abstract': False,
},
),
]
But it leads to circular dependency as can be seen when running migrate on myapp:
django.db.migrations.exceptions.CircularDependencyError: myapp.0001_initial, reusableapp.0002_auto_20170414_1447
As per the documentation I have added the dependency on Parent in my 0002_auto... migration:
swapper.dependency('reusableapp', 'Parent')
If I remove this dependency, then my migrations run fine. Either I am missing something or the documentation requires some changes?
Python 3.7 is nowadays the most commonly used python3 version. But the tests fail:
[ 8s] File "/home/abuild/rpmbuild/BUILD/django-swappable-models-1.1.0/tests/default_app/models.py", line 18, in Item
[ 8s] type = models.ForeignKey(swapper.get_model_name('default_app', "Type"))
[ 8s] TypeError: init() missing 1 required positional argument: 'on_delete'
Could you please make the software compatible with Python 3.7? Thank you.
Support for django 3
Hi,
This API seems like the perfect fit for a new project of mine, but I'm wondering if it has been abandoned? No official 2.x support yet nor any commits in the main branch for 2 years. Should I assume that if we decide to use it we'll have to maintain it ourselves?
Thanks!
Hi, this is from a question of mine that i posted at StackOverflow. Since nobody has answered in a while, i thought that i should asked it here also.
Excerpt from StackOverflow
Django - How to allow inheritance of swappable model?
I am using swappable to make a reusable app (named Meat
) that provides models that Developers can swap for their own. That model is a super class of other models.
from django.db.models import Model, CharField
from swapper import swappable_setting
class AbstractMeat(Model):
class Meta:
abstract = True
name = CharField(max_length=16)
class Meat(AbstractMeat):
class Meta:
swappable = swappable_setting("cyber", "Meat")
class Pork(Meat):
pass
class Fish(Meat):
pass
To test this, i created the real
app and set MEAT_MEAT_MODEL
.
# settings.py
MEAT_MEAT_MODEL = "real.RealMeat"
# real/models.py
from django.forms import IntegerField
from cyber.models import AbstractMeat
class RealMeat(AbstractMeat):
price = IntegerField()
Running runserver
i get this error:
meat.Fish.meat_ptr: (fields.E301) Field defines a relation with the model 'meat.Meat', which has been swapped out.
HINT: Update the relation to point at 'settings.MEAT_MEAT_MODEL'.
meat.Pork.meat_ptr: (fields.E301) Field defines a relation with the model 'meat.Meat', which has been swapped out.
HINT: Update the relation to point at 'settings.MEAT_MEAT_MODEL'.
This error arises on Django 1.9 to 1.11, but for my purpose only 1.11 is critical.
I tried overriding the meat_ptr
as instructed in Multi-table inheritance like so:
from swapper import get_model_name
from django.db.models import OneToOneField, CASCADE
class Pork(Meat):
meat_ptr = OneToOneField(
get_model_name("meat", "Meat"), CASCADE,
parent_link=True)
But it gives me this error on 1.11 and 1.10 (but not 1.9):
django.core.exceptions.FieldError: Auto-generated field 'meat_ptr' in class 'Pork' for parent_link to base class 'Meat' clashes with declared field of the same name.
In conclusion, how do i make this happen?
What if I want to write a model that inherit a swapped model?
I can't use
class Child(swapper.get_model_name('reusableapp', 'Parent')):
...
because it won't accept a string as model base.
I can't use swapper.load_model(...)
neither (as explained in the README).
Do you have any idea on how to do this?
I want people that use my app to be able to add custom fields in a base model that is inherited by other models of my app. I can't ask them to swap all of the models 🐹
In a specific case, we have a series of django apps all based on swappable, all models are loaded using swappable except this case:
DeviceData
inherits BaseDevice
which is a concrete model imported with the classic from .. import
.
If we try to load BaseDevice
with swappable, the following error is raised:
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
However, I was able to load the model with swappable anyway with a small patch:
BaseDevice = load_model('config', 'Device', require_ready=False)
require_ready
would be a new argument that is just passed to django's get_model()
, which already has this argument.
I think it's safe to add this argument to load_model
, defaulting to False
.
PS: require_ready
is only available since django 2.0
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.