-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Swappable Models #1160
Comments
I think this is a great early candidate for expanding swappable models beyond auth.User. I'm happy to chip in or liase with the django core team on this. |
Awesome. Like I said I haven't used it myself, so there's a big (naive) question in my head. Is the notion of swappable models something that's a general feature of Django, or is it specific only to its User model? Another naive thought is that even if this isn't a general feature, is there really that much to it, considering that all it might entail is replacing hard-coded references to particular models throughout the code base and having them dynamically loaded via the setting name? |
It's absolutely intended to be used for other models. The author of the patch, Russell Keith-Magee, describes this notion here: https://www.youtube.com/watch?feature=player_detailpage&v=KHg6AoExYjs#t=2165 |
That clears that up then. Thanks! |
Have been thinking about this a lot lately. From a quick inspection I think the Django feature is essentially what you've described above Steve, but with some added bits to support migrations properly. For example I think a swapped-out model won't have its tables created. I haven't watched Russ's talk yet, but there's some interesting discussion about Django's swappable models here, from when they were being developed: https://groups.google.com/forum/#!searchin/django-developers/swappable/django-developers/YwEZUIMuIkE/g9-tAZ15RxMJ Anyway, I think I'll post to django-developers about this tomorrow. There's an opportunity to add something really useful not only to Mezzanine but to Django on the whole. On my wishlist for an official API:
|
Also interesting: https://github.com/wq/django-swappable-models It looks like this is small enough to be vendorised into Mezzanine if need be. |
Is it possible that the new Applications API in Django 1.7+ could be useful here? Specifically I'm thinking Mezzanine could have an app config that lends itself to being extended, like: # mezzanine/apps.py
from django.apps import AppConfig
class MezzanineAppConfig(AppConfig):
label = 'mezzanine'
def get_models(self):
models = super(MezzanineAppConfig, self).get_models()
final_models = []
for model in models:
final_models.append(self.get_model(model.__name__))
return final_models Then to use it in Mezzanine you'd do: # mezzanine/blog/views.py
from django.apps import apps
_mezzanine_app_config = apps,get_app_config('mezzanine')
BlogPost = _mezzanine_app_config.get_model('BlogPost')
## OR ##
BlogPost = apps.get_model('mezzanine', 'BlogPost') And finally, to swap out a model in your project: # myblogapp/models.py
from mezzanine.blog.models import BlogPost
class MyBlogPost(BlogPost):
pass
# myproject/apps.py
from mezzanine.apps import MezzanineAppConfig
from myblogapp.models import MyBlogPost
class MyMezzanineAppConfig(MezzanineAppConfig):
def get_model(self, model_name):
if model_name.lower() == 'blogpost':
return MyBlogPost
else:
return super(MyMezzanineAppConfig, self).get_model(model_name)
# myproject/settings.py
INSTALLED_APPS = [
'myproject.apps.MyMezzanineAppConfig',
# ...
] This is untested, just me writing out some code as a thought experiment. I haven't had a chance to play with Django 1.7+, but doing a cursory check of their GitHub, it seems like they've moved the code for getting models from an app to using My understanding of the code so far would suggest that The timing of when to call Per @AlexHill's suggestion of having a decorator, a modified version of the above code could instead build up a list of swapped models in Seems like an interesting direction to go, if someone has the time to play around with it. A nice '@swap_model(BlogPost)' decorator could be a clean way to trigger the functionality. EDIT: On second thought, the decorator syntax may not be ideal. If there are two apps that both want to add something new to BlogPost, and I want to use both apps, that seems problematic if they both used the decorator. In that case I'd probably need to roll my own AppConfig anyway to resolve the conflict. |
@dsanders11 interesting idea and thankyou for spending the time to think about this. The obstacle is the one you already identified: You can override these things of course, but messing with the internals of app loading and model importing is something we want to do less of – that's the motivation for getting rid of
I think you're right, migrations wouldn't be created for the overridden model. What might happen is that a migration would be created in the |
Overall, I think any solution which makes models "impersonate" models in other apps is a dangerous path to go down. Django's swappable user model avoids this. When you swap out That approach is nice because models stay in their own apps, and because it's explicit: if you really do need |
My 2 cents - sounds like the right thing to do would be to mimic whatever |
Totally agree with that. IMO it's only a question of how much sugar we add on top. |
I'm not sure if this conversation is still ongoing, but I have been looking into how To further explain the Obviously, I'm not sure how much this method makes sense from your guys' point of view, since I'm by no means an experienced developer. Any thoughts? |
@avery-laird sounds right |
I've been doing a bit of preliminary work to get this up and running, but when updating the TypeError: Error when calling the metaclass bases
unicode() argument 2 must be string, not tuple I haven't been able to track down the issue just from my own debugging, has anyone run into this exception before? It seems like it's probably something really simple I'm just overlooking. |
Hi, I have swapped BlogPost on my 4.0.1 branch: bors-ltd@77001a4 It works for my case (inheriting from django.contrib.gs models and admin) but it lacks tests. And I should take a look at that AppConfig thing too. |
Noticed the second warning in the documentation regarding substituting a custom user model which points out that the model being swapped in has to be created in the initial migration for its app. Something to keep in mind if this progresses since it will be a bit of a headache for switching to swapped models since most people would probably use a model from an app which has an initial migration at that point. |
Hello, I'm waking up this rather old issue. I was pretty sure I already opened a pull request for my "swappable_blogpost" branch and people commented it but it seems my memory failed me. Anyway, here is my branch updated against the latest 4.2.2 release (not the master branch): 4.2.2...bors-ltd:4.2.2-swappable_blogpost Do you need I go on with the Page model and make it a "swappable_models" branch? Against what base branch? How do you see tests about it? The tests pass on my branch but I don't have any new test with a custom model. I could probably get somewhere with |
Thanks for waking it up! This is probably the number one feature Mezzanine needs right now, so it's very welcome.
Yes this definitely needs to be complete across all the concrete models before we can accept it (maybe that's why your old PR fell by the wayside, I honestly can't remember either). I'd suggest treating "swappable models" as one entire feature rather than breaking it down into blog, pages, etc, and looking at your current work, it doesn't look like there's too much involved, which lends itself toward doing it all together - so one branch/PR should do.
Give it a try and see how far you get - it would be great to have specific coverage for this, but if we can't, at least having the existing tests pass when swapped models are explicitly used in your current project would definitely suffice for now. Thanks again! |
Just to notify I've started the work. No big issue yet but I'm playing hide and seek everywhere content models are imported. So far I have swapped:
And the first design decision I must report is when, for example, |
(It's marked as WIP because I left some XXX with questions.) Any of the following models can now be swapped by your own version: * Page * RichTextPage * Link * BlogPost * BlogCategory * Form * FormEntry * FieldEntry * Gallery * GalleryImage So you can keep the same features and API but add fields, methods, inherit from other classes (geo models for geo fields, third-party models...), etc. BasePage was merged into AbstractPage, same side effect on inheriting the object manager on Page, Link, etc. BaseGallery was merged into AbstractGallery, no point in keeping it. I didn't change imports or references in the admin modules, I followed UserAdmin conventions here. You'll have to explicitly register, e.g. PageAdmin to your swapped Page model. No docs update yet, let's first agree on code and naming conventions, what will become the public API for users. This includes whether the docstring goes on the abstract model or the vanilla model. I also guess this deprecates the field injection feature, before removing it in a future major version. Besides that, it must be 100% compatible, no test changes apart from imports.
Awesome!
Ideally we can resolve this so that swapping |
(It's marked as WIP because I left some XXX with questions.) Any of the following models can now be swapped by your own version: * Page * RichTextPage * Link * BlogPost * BlogCategory * Form * FormEntry * FieldEntry * Gallery * GalleryImage So you can keep the same features and API but add fields, methods, inherit from other classes (geo models for geo fields, third-party models...), etc. Just make sure you inherit from each respective abstract base class. BasePage was merged into AbstractPage with the same side effect on inheriting the object manager on Page, Link, etc. BaseGallery was merged into AbstractGallery, no point in keeping it. I didn't change imports or references in the admin modules, I followed UserAdmin conventions here. You'll have to explicitly register, e.g. PageAdmin to your swapped Page model. No docs update yet, let's first agree on code and naming conventions, what will become the public API for users. This includes whether the docstring goes on the abstract model or the vanilla model. I also guess this deprecates the field injection feature, before removing it in a future major version. Besides that, it must be 100% compatible, no test changes apart from imports.
Hello again, this was close to be ready for initial review, but there are three tests not passing: Here is the work in progress: https://github.com/bors-ltd/mezzanine/tree/4.2.2-swappable_models I could make these tests pass by modifying There is also the question of swapping Page itself, the root of all content models, and still having I've also left a few XXX, mainly about translations. By the way, how do you run Mezzanine's test suite in the context of a bootstraped project? |
The class AbstractRichTextPage(Page, RichText):
@staticmethod
def get_model():
model_name = getattr(settings, "RICH_TEXT_PAGE_MODEL", "pages.RichTextPage")
return utils.models.get_model(model_name)
RichTextPage = AbstractRichTextPage.get_model() |
Or even more concise: def get_swappable_model(setting_name, default):
model_name = getattr(settings, setting_name, default)
return get_model(model_name)
RichTextPage = get_swappable_model("RICH_TEXT_PAGE_MODEL", "pages.RichTextPage") |
(It's marked as WIP because I left some XXX with questions.) Any of the following models can now be swapped by your own version: * Page * RichTextPage * Link * BlogPost * BlogCategory * Form * FormEntry * FieldEntry * Gallery * GalleryImage So you can keep the same features and API but add fields, methods, inherit from other classes (geo models for geo fields, third-party models...), etc. Just make sure you inherit from each respective abstract base class. BasePage was merged into AbstractPage with the same side effect on inheriting the object manager on Page, Link, etc. BaseGallery was merged into AbstractGallery, no point in keeping it. I didn't change imports or references in the admin modules, I followed UserAdmin conventions here. You'll have to explicitly register, e.g. PageAdmin to your swapped Page model. No docs update yet, let's first agree on code and naming conventions, what will become the public API for users. This includes whether the docstring goes on the abstract model or the vanilla model. I also guess this deprecates the field injection feature, before removing it in a future major version. Besides that, it must be 100% compatible, no test changes apart from imports.
I agree on But I couldn't get rid of these model loading functions. These models must be loaded later in Django's bootstrap process. If I just load all these models early in the process, at best I get "Models are not ready yet", at worst I get obscure errors in tests. I've just pushed forced another version. The At first I got quite many obscure errors, like blog URLs not reversing, but I delayed as much the loading of models and the errors disappeared. Which makes me feel this is quite fragile. But I still get these three tests not passing and I lack a proper debug environment like running a single test and open an (i)pdb when a test fails. |
(It's marked as WIP because tests are far from passing, and we can always discuss the coding style and naming.) Any of the following models can now be swapped by your own version: * Page * RichTextPage * Link * BlogPost * BlogCategory * Form * FormEntry * Field * FieldEntry * Gallery * GalleryImage So you can keep the same features and API but add fields, methods, inherit from other classes (geo models for geo fields, third-party models...), etc. without having to resort to model inheritance. Just make sure you inherit from each respective abstract base class. Contrary to my first submission, I kept the BaseXXX classes for a smoother transition path. I didn't change imports or references in the admin modules, I followed UserAdmin conventions here. You'll have to explicitly register, e.g. PageAdmin to your swapped Page model. No docs update yet, let's first agree on code and naming conventions, what will become the public API for users. This includes whether we provide default values in the template project settings or in the code. I also guess this deprecates the field injection feature, before removing it in a future major version. Besides that, it must be 100% compatible, no test changes apart from imports.
Hi, I reworked my swappable models branch against the latest release: https://github.com/stephenmcd/mezzanine/compare/master...bors-ltd:4.3.1-swappable-models?expand=1 But tests are far from passing for now... Github shows conflicts against the master branch, I don't have any "4.3.x" or "stable" branch to rebase upon. |
(It's marked as WIP because tests are far from passing, and we can always discuss the coding style and naming.) Any of the following models can now be swapped by your own version: * Page * RichTextPage * Link * BlogPost * BlogCategory * Form * FormEntry * Field * FieldEntry * Gallery * GalleryImage So you can keep the same features and API but add fields, methods, inherit from other classes (geo models for geo fields, third-party models...), etc. without having to resort to model inheritance. Just make sure you inherit from each respective abstract base class. Contrary to my first submission, I kept the BaseXXX classes for a smoother transition path. I didn't change imports or references in the admin modules, I followed UserAdmin conventions here. You'll have to explicitly register, e.g. PageAdmin to your swapped Page model. No docs update yet, let's first agree on code and naming conventions, what will become the public API for users. This includes whether we provide default values in the template project settings or in the code. I also guess this deprecates the field injection feature, before removing it in a future major version. Besides that, it must be 100% compatible, no test changes apart from imports.
(It's marked as WIP because tests are far from passing, and we can always discuss the coding style and naming.) Any of the following models can now be swapped by your own version: * Page * RichTextPage * Link * BlogPost * BlogCategory * Form * FormEntry * Field * FieldEntry * Gallery * GalleryImage So you can keep the same features and API but add fields, methods, inherit from other classes (geo models for geo fields, third-party models...), etc. without having to resort to model inheritance. Just make sure you inherit from each respective abstract base class. Contrary to my first submission, I kept the BaseXXX classes for a smoother transition path. I didn't change imports or references in the admin modules, I followed UserAdmin conventions here. You'll have to explicitly register, e.g. PageAdmin to your swapped Page model. No docs update yet, let's first agree on code and naming conventions, what will become the public API for users. This includes whether we provide default values in the template project settings or in the code. I also guess this deprecates the field injection feature, before removing it in a future major version. Besides that, it must be 100% compatible, no test changes apart from imports.
One of the biggest issues people hit with Mezzanine, and likely many other open source Django apps that provide models, is the ability to customize the fields that the models use. Django itself hit this early on with its
User
model, which can now be "swapped" by pointing a setting at the app/model name to use in its place. (Presumably, I've never used it) the swappable model must implement certain members, methods and/or attributes, and then consumers of the model (view functions and the like) can expect these to exist.Mezzanine has the
EXTRA_MODEL_FIELDS
setting that allows fields to be injected, but it has a handful of problems. Primarily database migrations are difficult to deal with, since migrations for an app now spread across the original app and some external app/package. Apparently m2m fields can't be injected. Some related issues are #1031 and #1150.Ideally we can make the core models throughout Mezzanine (and Cartridge) swappable, and remove the
EXTRA_MODEL_FIELDS
setting. This would give developers the power to customize things like:Page
model that underpins all page typesBlogPost
modelProduct
modelThe ability to swap out these three models would take Mezzanine and Cartridge's potential flexibility to a whole other level.
The text was updated successfully, but these errors were encountered: