Wagtail multisite on same domain

Answering an old question for future enquirers. @gasman has nailed the issue with this ask in his comment above:

In Wagtail's terminology, sites and domains are exactly the same thing, so "multiple sites on the same domain" doesn't make sense.

However, I was able to work around this using the following steps.

I needed this kind of arrangement so that different site-wide settings could be used for mainsite.com and mainsite.com/subsite (example names).

While I can always enforce different settings for different parts of my website in the code I've written, I wanted this to work for third-party packages as well such as wagtailmenus etc.

E.g. I wanted the menu-items for all pages on mysite.com to be different from those for all pages under and including mainsite.com/subsite.

Note that this is a crude approach, and there can be unforeseen issues with this. But, so far, it has worked fine for me.

  1. Create a new site in Wagtail admin for your subsite.
    Use the same hostname as was used for the mainsite, but add a dot in the end for satisfying uniqueness. We'll not use this hostname anyway.
    Also, use the same root page as mainsite's. This is for generating the URLs correctly.

  2. Create a small middleware:

    if request.path.startswith('/subsite'):
        # find subsite and mainsite using Site.objects.filter(site_name='') queries
        subsite.hostname = mainsite.hostname
        request._wagtail_site = subsite
    

    This will make sure that all requests coming to the subsite get their specific _wagtail_site attribute but with the correct hostname. This will let your code and all other code in third-party packages to determine the site as subsite. But, if a third-party package uses Django's site instead of Wagtail's Site, I'm not sure if this will still work.

  3. Generally, you would not want the subsite to be set as default website. To enforce this, include the following in any of your apps' admin.py (I added some extra validations as well):

    def clean_subsite(self):
        instance = self
        if instance.site_name != 'Subsite': return
        super(Site, self).clean()
        if instance.is_default_site:
            raise ValidationError({'is_default_site': ["'Subsite' cannot be made the default website."]})
    
        mainsite = Site.objects.filter(site_name='Mainsite').first()
        if not mainsite:
            raise ValidationError({'hostname': ["'Mainsite' must exist before saving 'Subsite'."]})
    
        if instance.hostname != (mainsite.hostname + '.'):
            raise ValidationError({'hostname': ["Hostname for Subsite is just a placeholder (Subsite uses the same domain as Mainsite does)." +
            " Use the Mainsite's hostname with a '.' as suffix for satisfying uniqueness constraint."]})
    
    from wagtail.core.models import Site
    Site.add_to_class("clean", clean_subsite)
    

    As the Site filter queries above are using site_name='', do not change the names of Mainsite and Subsite in the Admin (without modifying these queries in the code).

  4. Lastly, be warned that the query Page.objects.in_site() will work incorrectly for both mainsite and subsite.
    Either do not use in_site(), or override it. This function uses:

    self.descendant_of(site.root_page, inclusive=True)
    

    For mainsite, we need to exclude the descendants of subsite's root page. For subsite, we need to include only the descendants of subsite's root page.