| 1 | After a long two-day sprint on this, hitting road-block after road-block, we've learned a whole lot. Here's where we are: |
| 2 | |
| 3 | == Impetus == |
| 4 | During the state of Django talk, Adrian put up a slide showing an INSTALLED_APPS setting like this: |
| 5 | {{{ |
| 6 | INSTALLED_APPS = Apps( |
| 7 | app('django.contrib.admin'), |
| 8 | app('project.app', name='polls'), |
| 9 | ) |
| 10 | }}} |
| 11 | The idea is to allow more robustness and extensibility in the INSTALLED_APPS. We came into this thinking it would be rather simple, but after a while, that was seen to be anything but the case. |
| 12 | |
| 13 | == Design Goals == |
| 14 | Allow change of name of third-party app |
| 15 | Allow change of db_prefix of third-party app |
| 16 | Allow multiple instances of an app with different names, db_prefix, etc. |
| 17 | |
| 18 | == Initial Plan == |
| 19 | db.models.loading becomes core.apps. db.models.loading is kept only for backwards compatibility, redirecting to core.apps. |
| 20 | AppCache becomes InstalledApps |
| 21 | App class added to hold name, path, db_prefix, etc. |
| 22 | settings.INSTALLED_APPS becomes instance of InstalledApps, and converts every item to an App instance if it is a string. |
| 23 | {{{ |
| 24 | settings.INSTALLED_APPS = ('django.contrib.auth', |
| 25 | App('django.contrib.admin', name='SuperAdmin'), |
| 26 | App('myapp', name='Wow', db_prefix='pref')) |
| 27 | }}} |
| 28 | |
| 29 | After accessing settings this becomes (pseudo): |
| 30 | {{{ |
| 31 | InstalledApps(App(path='django.contrib.auth', name='auth'), |
| 32 | App(path='django.contrib.admin', name='SuperAdmin'), |
| 33 | App(path='myapp', name='Wow', db_prefix='pref')) |
| 34 | }}} |
| 35 | |
| 36 | For backwards compatibility, set model._meta.app_label to specified name. |
| 37 | Going forward, set model._meta.app to App instance, use model._meta.app.name, model._meta.app.db_prefix, etc. |
| 38 | |
| 39 | Provide signal when InstalledApps is finished loading apps. |
| 40 | |
| 41 | Multiple instances of an app would NOT be solvable with this, so long as we are attaching and consuming this metadata via model._meta (since model classes would still be shared by the instances). No approaches that didn't herald the return of magic were floated for this. This issue is most likely shared by any multiple-database solution that hopes to allow the same models to be used in different contexts against different databases, so that work may provide a useful solution here as well. |
| 42 | |
| 43 | |
| 44 | == Issues Found During Dev == |
| 45 | Today model._meta.app_label is implicitly forced to be the filesystem dir name above models. |
| 46 | |
| 47 | Currently model._meta.app_label is heavily overloaded, acts as: |
| 48 | * Admin app display name (by .title()) |
| 49 | * Admin permissions prefix |
| 50 | * DB table creation prefix |
| 51 | * Dumpdata/Loaddata fixture prefix identifier |
| 52 | * When explicitly set, used to associate models elsewhere in the app structure. |
| 53 | * RelatedObject explicit cross-app identification. |
| 54 | * contrib.contenttypes identification |
| 55 | |
| 56 | Some of these should remain in the developer's control (model association, cross-app model id). |
| 57 | Some of these should be in the installer's control (admin name, db table prefix). |
| 58 | Some of these should use new db_prefix instead (table creation, fixtures?). |
| 59 | Some of these it's not clear (contenttypes, permissions). |
| 60 | |
| 61 | Keeping the interfaces of get_apps, get_models, etc is problematic as some of these just take a models module (and determine the app label by the fs dir name), or return the models module. It would be preferrable if these took and returned App instances (which would have a .module attribute), but with larger compatibility ramifications. |
| 62 | |
| 63 | Initial attempt at resolving these differences was to have InstalledApps keep two app->models dicts, one by module_name (old app_label) and one by app.name (specified in settings). This lead to a separation between get_model and get_model_from_module_name, the latter used for model association etc that should remain under the original app developer's control. |
| 64 | |
| 65 | Populating the by-app.name mapping is hackish, since it's not knowledge that's available to ModelBase.__new__ for register_models. Instead, after loading each app we find models by the module_name and store matching entries in the app.name dict. |
| 66 | |
| 67 | Additionally there are a number of places that use the string paths from INSTALLED_APPS: |
| 68 | * management, translation, templatetags/template loaders (to do further imports) |
| 69 | * test.client (to check if sessions are available) |
| 70 | |
| 71 | These were straightforward to switch to use app.path |
| 72 | |
| 73 | == Unresolved == |
| 74 | |
| 75 | Whether this approach is overall worthwhile given that it does not address installing multiple instances of an app. |
| 76 | Whether contenttypes, permissions, etc should use module_name or app.name or an additionally configurable value (either by app developer or installer). |
| 77 | How much backwards incompatibility is acceptable in terms of get_apps/get_models/etc, iter(settings.INSTALLED_APPS), other third-party overloading of _meta.app_label. |