Hi.
The motivation to this question is that if I want to implement
"copies" of an app within an website (for instance, a stackoverflow-like
where each app is a Q&A for its own, but with modifications on
templates, urls, etc.), as far as I'm understanding: (1) each "copy" has
to change its name, names of all calls of templates, views, etc due to
name collision and (2) each copy must be an app on its own without any
dependencies on other apps with models/templates/urls, because otherwise the dependent
apps would also have to be "copied", names changed, etc.
In a previous post I suggested generalizing templates and url search to
be used within subapps (apps with folder inside another app), which I found was a way of allowing what I wanted to do.
There
was a criticism that during the discussion that I (mostly) ignored,
but while re-reading it, I understood its meaning and its
importance to a discussion by itself.
The criticism was from Shai Berger, that correctly pointed out that Django uses unique app names [1], and what I didn't understood at the time was that this forbids any possibility of using subapps the way I was suggesting or in any way that might help.
Motivated by that criticism, I want to ask: why apps have to have unique names?
To try to answer this question on the code, I did a simple search on Django code. This is far from rigorous, but gives some nice results. The search was simple: regex of r"settings.INSTALLED_APP" and regex of r".split('.')[-2]". Afterwards, I read the code to understand which dependencies it has on the labeling. Here are the results:
## Usage of settings.INSTALLED_APP (regex: settings.INSTALLED_APP)
# To check admin existence, uses full path (import_module)
/django/contrib/admin/__init__.py:24: for app in settings.INSTALLED_APPS:
# To check specific app existence, uses full path (import_module)
/django/contrib/admin/templatetags/admin_static.py:6: if 'django.contrib.staticfiles' in settings.INSTALLED_APPS:
/django/contrib/comments/__init__.py:16: if comments_app not in settings.INSTALLED_APPS:
/django/contrib/gis/tests/__init__.py:113: settings.INSTALLED_APPS = list(self.old_installed) + new_installed
/django/contrib/gis/tests/__init__.py:125: settings.INSTALLED_APPS = self.old_installed
/django/contrib/messages/tests/base.py:16: 'django.contrib.auth' not in settings.INSTALLED_APPS,
/django/contrib/messages/tests/base.py:214: lambda app:app!='django.contrib.messages', settings.INSTALLED_APPS),
/django/contrib/messages/tests/base.py:239: lambda app:app!='django.contrib.messages', settings.INSTALLED_APPS),
/django/contrib/sitemaps/tests/flatpages.py:8: @skipUnless("django.contrib.flatpages" in settings.INSTALLED_APPS,
/django/contrib/sitemaps/tests/http.py:88: @skipUnless("django.contrib.sites" in settings.INSTALLED_APPS,
# for finding static; use full path of the app
/django/contrib/staticfiles/finders.py:122: apps = settings.INSTALLED_APPS
# for binding commands to the management. Requires unique app to avoid commands collision.
/django/core/management/__init__.py:101: apps = settings.INSTALLED_APPS
# for binding commands to the management. Requires unique app to avoid commands collision.
/django/core/management/__init__.py:319: options += [(a.split('.')[-1], 0) for a in settings.INSTALLED_APPS]
# for importing the 'management' module within each installed app, to register dispatcher events.
/django/core/management/commands/flush.py:35: for app_name in settings.INSTALLED_APPS:
# for importing the 'management' module within each installed app, to register dispatcher events (lacks DRY principle: command used is the same as previous file.)
/django/core/management/commands/syncdb.py:38: for app_name in settings.INSTALLED_APPS:
# for the 'models' of the db. It uses django.utils.importlib.import_module on the full app's path. Class uses _label_for for defining a "name" for the app, which requires unique app
/django/db/models/loading.py:61: for app_name in settings.INSTALLED_APPS:
/django/db/models/loading.py:143: for app_name in settings.INSTALLED_APPS:
# To check whether the model is installed. It uses full paths of the package
/django/db/models/options.py:71: self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
# To find templatetags. It stores the full path of the module
/django/template/base.py:1271: for app_module in ['django'] + list(settings.INSTALLED_APPS):
# To find templates. It stores the full path of the module
/django/template/loaders/app_directories.py:19: for app in settings.INSTALLED_APPS:
# To find templates. Depends on the pkg_resources.resource_string to tell wether it returns app name of full app name
/django/template/loaders/eggs.py:23: for app in settings.INSTALLED_APPS:
# To check specific app existence, uses full path (import_module)
/django/test/client.py:353: if 'django.contrib.sessions' in settings.INSTALLED_APPS:
/django/test/client.py:501: and 'django.contrib.sessions' in settings.INSTALLED_APPS:
# for finding "locale/". Why it uses reverse of settings.INSTALLED_APPS ? Uses full app (import_module) path
/django/utils/translation/trans_real.py:159: for appname in reversed(settings.INSTALLED_APPS):
# for building the debug page. Not relevant.
/django/views/debug.py:745: {{ settings.INSTALLED_APPS|pprint }}
/django/views/debug.py:936: {{ settings.INSTALLED_APPS|pprint }}
# for translating javascript. Uses full path (import_module)
/django/views/i18n.py:189: packages = [p for p in packages if p == 'django.conf' or p in settings.INSTALLED_APPS]
## usage of app by app.__name__.split('.')[-2] (regex: split('.')[-2])
# deprecated
/django/core/management/commands/reset.py:31: app_name = app.__name__.split('.')[-2]
# for listing apps and models for syncdb. It requires unique app name.
/django/core/management/commands/syncdb.py:67: (app.__name__.split('.')[-2],
# Only used for printing in case verbosity >= 2
/django/core/management/sql.py:184: app_name = app.__name__.split('.')[-2]
# Used for labeling app "app_label" of model.
# Notice that app_label is also used in '/django/db/models/loading.py'/django/db/models/base.py:54: kwargs = {"app_label": model_module.__name__.split('.')[-2]}
/django/db/models/loading.py:77: return app_mod.__name__.split('.')[-2]
At first glance, I would say that the way django "finds" the app could be transformed into a function to simplify code; there are a lot of calls "app_mod.__name__.split('.')[-2]" that could be transformed into a function e.g. in django.utils.get_app_label(app)', should this deserve a ticket?
Secondly, by the code, the unique app constraint seems to be a design decision and not something that django's current implementation could not properly handle.
This leads to the question, what is the reasoning for this design decision?
Thanks,
Jorge
[1]
https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps