On Fri, Apr 9, 2010 at 12:33 AM, Nick Sandford <nick.sandf...@gmail.com> wrote:
> An App Loading mechanism for Django
> ====================================
>
> About Me
> ----------
> Hi everyone,
>
> My name is Nick Sandford, I'm an electrical engineering student at the
> University of Western Australia.
>
> Background
> -----------
>
> I haven't been a particularly active contributor to any open source project -
> here is where I hope to change that. In my current work at Christ
> Church Grammar School we use Django heavily for most of our internal
> projects. I've
> followed django-dev closely for a couple of years and I have poked around with
> its internals on various occasions.

Ok - I clearly need to get a Perth DjUG going...

> Plan
> -----
>
> Implement an improved application loading mechanism into Django.
>
> Rationale
> ---------
>
> Django current application loading code is inflexible in many cases. There
> exists a need for a number of extra features to be added to the way
> applications are handled such as:
>
>  * The ability to internationalise application names.
>  * The ability to customise application names similar to ``verbose_name`` in
>    ``model._meta``
>  * Deploy the same application multiple times in a single project.
>  * Deploy two different applications with the same name.
>  * Manage settings within applications.
>
> Method
> -------
>
> I don't intend to solve the third dot point of the previous list - it seems
> like a difficult problem, possibly to be tackled post-GSoC. What I do intend
> to do is to move towards 'application classes' more akin to
> ``django.contrib.admin``.
>
> New syntax in ``settings.INSTALLED_APPS`` will be introduced to address the
> previous issues. Some examples of accepted syntax:
>
> .. sourcecode:: python
>
>    INSTALLED_APPS = (
>        'django.contrib.auth',
>        app('blog.BlogApplication', 'blog_1', 'My blog', 'app1_'),
>        app('blog.BlogApplication', 'blog_2', 'Some other blog', 'app2_'),
>        app(path='tagging.Tagging', verbose_name='My tagging application'),
>        app('categories', db_prefix='cat_'),
>        app({'path': 'django.contrib.admin.AdminApplication',
>             'label': 'admin',
>             'verbose_name': 'Secret Admin'}),
>    )
>
> The ``app`` function will take four arguments, three of which are optional.
> These are ``path``, ``label``, ``verbose_name``, and ``db_prefix``. It will
> return an instance of an ``Application`` object, which will contain all of an
> installed application's information. ``path`` will be the dotted path to a
> subclass of ``Application``. The downside is that ``settings.py`` requires
> an import, which may be against style rules.
>
> .. sourcecode:: python
>
>    def app(path, label=None, verbose_name=None, db_prefix='')
>        if not path or not isinstance(path, basestring):
>            raise ImproperlyConfigured('Application path must be string.')
>        application_class = import_module(path)
>        return application_class(path, label, verbose_name, db_prefix)
>
> ``INSTALLED_APPS`` will then be a tuple containing strings or ``Application``
> instances. The application loading code will iterate over ``INSTALLED_APPS``
> and construct an internal cache of ``Application`` instances to be used with
> ``get_models``, etc. For backwards compatibility, if an element of the tuple 
> is
> a string, an instance of a base ``Application`` class will be created with 
> sane
> defaults (similar to ``app_label`` at the moment).
>
> The ``Application`` class will be very similar to ``django.contrib.admin``'s
> ``AdminSite`` and will hopefully simplify application settings. If you write
> views and urls directly on the ``Application`` class itself, instead of an
> ``from django.conf import settings`` and subsequent ``settings.API_KEY``, you
> could just reference ``self.api_key`` for instance. This wouldn't be the
> required, just an optional extra.
>
> Model classes will get a ``_meta.app`` attribute, which will be an instance
> of the model's ``Application`` class. Models should only be associated with 
> one
> application.
>
> I agree with moving ``django.db.models.loading`` to ``core.apps``, since we'll
> no longer require applications to have a ``models.py``. A reference to the
> new functions in ``core.apps`` will still live in ``django.db.models.loading``
> for backwards compatibility.
>
> A subclass of ``Application`` might look like:
>
> .. sourcecode:: python
>
>    from django.views.simple import direct_to_template
>    from djang.core.apps import Application
>    from blog.models import Entry, Category
>
>    class BlogApplication(Application):
>        models = [Entry, Category]
>        api_key = 'default'
>
>        def entry_detail(self, slug, request,
> template_name='blog/entry_detail.html'):
>            entry = get_object_or_404(Entry, slug=slug)
>            context = {
>                'entry': entry,
>                'api_key': self.api_key,
>            }
>            return direct_to_template(request, template_name, context)
>
> Hurdles
> --------
>
> There are a list of possible technical issues:
>
>  * Introducing the new ``app`` function requires an import in settings.py
>   which might not be acceptable behaviour.
>  * Two applications with the same label.
>  * Worrying amounts of things that may affect backwards compatibility would
>   probably need addressing.
>
> Solutions
> ----------
>
> We could alternatively introduce a new file into the project,
> ``applications.py`` which contains purely application-related setup. This 
> might
> be handy to manage application settings and to ensure ``settings.py`` doesn't
> get too full of application-specific settings. This could allow us do 
> something
> like:
>
> .. sourcecode:: python
>
>    from django.core import apps
>    from blog import BlogApplication
>
>    class MyBlogApplication(BlogApplication):
>        api_key = 'testing'
>
>    another_blog = BlogApplication(label='blog2',
>                                   verbose_name=_('Another blog'),
>                                   api_key='anothertest')
>
>    apps.register(MyBlogApplication, label='blog', verbose_name=_('My blog'))
>    apps.register(another_blog)
>
> depending on what people would like more. This could allow us not to touch
> ``settings.INSTALLED_APPS`` at all.
>
> To solve the 'two applications named auth' problem, the ``AppCache`` keeps
> track of application instances, not ``app_label``. For the case of two
> applications with the same name, ``get_app`` should return a tuple containing
> both application instances with that name. To ensure a single application
> instance is returned with ``get_app``, another argument - ``path`` should be
> added. ``get_models`` and ``get_model`` would take an application instance
> and return models on that instance. This might affect the admin's handling of
> applications.

I'm not necessarily sold, but this is certainly an interesting idea.
In some respects, it's a variation of a suggestion that Jannis made in
another thread. Jannis suggested that we use dotted notation in
INSTALLED_APPS to point at the application instances. A registration
process is certainly another novel way of approaching the problem.

My concern with using a registration process is that we could end up
with a situation where INSTALLED_APPS no longer contains a useful
description of what is installed. The interaction between
INSTALLED_APPS and an app registration process also needs to be
considered carefully:
 * Does ordering matter? If so, which takes precedence -
INSTALLED_APPS or registrations?
 * Are registered apps added to INSTALLED_APPS as part of the
registration process?
 * Is this something that needs to be handled as a
"APP_LOADER='django.core.apps.loader.legacy'" which uses
INSTALLED_APPS, and
"APP_LOADER='django.core.apps.loader.registration'" which uses the new
approach (i.e., that new projects won't need an INSTALLED_APPS
setting)?

There are two related subproblems that are also worth investigating here:

 * The "Stuff that needs to happen when a Django server instance
starts" problem - you're introducing a new task that needs to happen
at startup. This isn't the only 'on startup' task - admin
registration, signal registration, and the proposed logging interface
all have similar needs. We've been handling this on a per-instance
basis so far, but there's a greater need to address 'startup problems'
in a common/generic way.

 * The "Register app/object/model with system-wide index" problem - we
have many different implementations of this pattern in Django already;
if we're going to add another one, it would be good to find a way to
abstract this into a common codebase, rather than reinventing the
wheel each time.

In addition to these two, there is also a nebulous "enterprise
settings support" issue out there. This essentially amounts to a need
to separate application settings from deployment settings. Having a
non-settings-based way to handle application registrations is
certainly a start down this path.

I know time is running short for you to get your proposal in, but I'd
be interested to see any ideas you have you on how your project could
lead to (or contribute to) solutions (or partial solutions) to these
problems.

> Timeline
> ---------
>
> 1) Implement the ``Application`` class. -- 2 weeks
> 2) Move ``django.db.models.loading`` to ``django.core.apps`` and refactor
>   ``get_app``, ``get_model``, etc. -- 1 week
> 3) Modify the admin, management, translation, templatetags, templateloaders
>   to use ``app.path`` -- 1 week
> 4) Testing and documentation -- 3 weeks
> 5) Bug fixes and backwards incompatibility problems -- 1 week
> 6) Time permitting possibly tackle the multiple instances of the same app
>   problem. (about 2 weeks).

A few comments:

Your estimate for point 2 seems light, especially if taken in light of
the bigger issues of 'startup tasks' and 'registration as a pattern'
problems I mentioned earlier.

Elaboration of point 3 would be nice. What sort of modifications are required?

Point 4 sends up a red flag for me. Testing in particular isn't an
afterthought - it's an integral part of development. If the work of
step (1) doesn't include tests, it's incomplete as designed, IMHO.

Point 6 Ain't Gonna Happen (tm). Let me tell you right now that it's
more than 2 weeks work. The foreign key problem alone will require you
to jump all sorts of technical hurdles. You'd be better served
dropping this from your proposal altogether and concentrate on other
issues.

I also note that this timeline only adds up to 10 weeks. I'm going to
guess that you're allowing for UWA's exam break -- which is fine --
but if this is the case, you need to acknowledge it in your schedule.

> Any feedback would be greatly appreciated, sorry for the late application and
> good luck to all the other applicants :)

On a style issue - your proposal spends a lot of time explaining the
traditional "App() in INSTALLED_APPS" approach, which you then reject
for various (valid) reasons, and then moves into the interesting/novel
part. There's been plenty written on the App() approach; you would be
better served to not mention this at all if it doesn't form part of
what you intend to implement.

2c of advice - There are essentially two major subproblems here:

 1) How to define an App. This bit isn't really controversial - it
needs to be a class of some sort. However, describing exactly what
this class can do and how it interacts with existing _meta structures
is important. Your current proposal covers this, but confuses the
issue with the old App() in INSTALLED_APPS notation.

 2) How to register an App. This is the big beast that isn't
completely solved yet. You've presented a novel approach, but it's
important that you acknowledge in your proposal that this isn't a
solve issue yet, and will require additional work.

You would be well advised to acknowledge this split in your proposal.

Yours,
Russ Magee %-)

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-develop...@googlegroups.com.
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en.

Reply via email to