6👍
I’ve recently developed an e-commerce system with similar requirements — many instances running from the same project sharing almost everything. The previous version of the system was a bunch of independent installations (~30) so it was pretty unmaintainable. I’m sure the requirements still differ from yours (for example, all instances shared the same models in my case), but it still might be useful to share my experience.
You are right that Django doesn’t help with scenarios like this out of the box, but it’s actually surprisingly easy to work it around. Here is a brief description of what I did.
I could see a synergy between what I wanted to achieve and django.contrib.sites
. Also because many third-party Django apps out there know how to work with it and use it, for example, to generate absolute URLs to the current site. The major problem with sites
is that it wants you to specify the current site id in settings.SITE_ID
, which a very naive approach to the multi host problem. What one naturally wants, and what you also mention, is to determine the current site from the Host
request header. To fix this problem, I borrowed the hook idea from django-multisite
: https://github.com/shestera/django-multisite/blob/master/multisite/threadlocals.py#L19
Next I created an app encapsulating all the functionality related to the multi host aspect of my project. In my case the app was called stores
and among other things it featured two important classes: stores.middleware.StoreMiddleware
and stores.models.Store
.
The model class is a subclass of django.contrib.sites.models.Site
. The good thing about subclassing Site
is that you can pass a Store
to any function where a Site
is expected. So you are effectively still just using the old, well documented and tested sites
framework. To the Store
class I added all the fields needed to configure all the different stores. So it’s got fields like urlconf
, theme
, robots_txt
and whatnot.
The middleware class’ function was to match the Host
header with the corresponding Store
instance in the database. Once the matching Store
was retrieved, It would patch the SITE_ID
in a way similar to https://github.com/shestera/django-multisite/blob/master/multisite/middleware.py. Also, it looked at the store
‘s urlconf
and if it was not None, it would set request.urlconf
to apply its special URL requirements. After that, the current Store
instance was stored in request.store
. This has proven to be incredibly useful, because I was able to do things like this in my views:
def homepage(request):
featured = Product.objects.filter(featured=True, store=request.store)
...
request.store
became a natural additional dimension of the request
object throughout the project for me.
Another thing that was defined on the Store
class was a function get_absolute_url
whose implementation looked roughly like this:
def get_absolute_url(self, to='/'):
"""
Return an absolute url to this `Store` or to `to` on this store.
The URL includes http:// and the domain name of the store.
`to` can be an object with `get_absolute_url()` or an absolute path as string.
"""
if isinstance(to, basestring):
path = to
elif hasattr(to, 'get_absolute_url'):
path = to.get_absolute_url()
else:
raise ValueError(
'Invalid argument (need a string or an object with get_absolute_url): %s' % to
)
url = 'http://%s%s%s' % (
self.domain,
# This setting allowed for a sane development environment
# where I just set it to ".dev:8000" and configured `dnsmasq`.
# The same value was also removed from the `Host` value in the middleware
# before looking up the `Store` in database.
settings.DOMAIN_SUFFIX,
path
)
return url
So I could easily generate URLs to objects on other than the current store, e.g.:
# Redirect to `product` on `store`.
redirect(store.get_absolute_url(product))
This was basically all I needed to be able to implement a system allowing users to create a new e-shop living on its own domain via the Django admin.