On Fri, Apr 9, 2010 at 2:59 PM, Russell Keith-Magee
<freakboy3...@gmail.com> wrote:
> 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?

That was the initial idea, yes.

>  * 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)?

I quite like this approach, so I've incorporated it into the proposal.
Thanks! This makes it a kind-of all or nothing approach, you either
have settings.INSTALLED_APPS or you have the registration method.
Definitely simplfies trying to keep INSTALLED_APPS and the AppCache in
sync. The legacy loader can still be 'Application'-aware and you can
still iterate over INSTALLED_APPS.

>
> 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?

Some of these are described in the updated proposal, not an in-depth
look but at least acknowledging the modifications that 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.

I figured if i didn't explicitly SAY that there would be testing and
documentation, then the comment would have been more akin to "where's
the time set aside for testing and documentation?" But yes, I agree
testing and documentation are an integral part of the development
process.

>
> 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.
>

Noted and dropped from my proposal :-).

> 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.
>

Yeah, exams are in early June. I've added that to the timeline section
of my proposal.

>> 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.

Certainly. Now for some mario kart... and constant checking of gmail.

Cheers,
Nick

-- 
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