I want to share single code-base Django (2.2) over 2 URLs : foo.domain.ltd
& bar.domain.ltd
.
- on
foo.domain.ltd
: all the Django URLs are available - on
bar.domain.ltd
: only some URLs are available, all other return 403 error
I expected using the sites framework (django.contrib.sites
) to reach this goal :
- 1 code base
- 2 domains configured in the sites framework
- 2
gunicorn
instances with appropriate configuration :core.settings.foo
withSITE_ID = 1
core.settings.bar
withSITE_ID = 2
- 1
nginx
server listening clients and forwarding requests to the appropriategunicorn
instance depending thexxx.domain.ltd
used
The issue I encountered is that sites framework looks like model oriented : You can associate models
to one, two (…) sites
.
I try to set urls
depending on the sites
, but I broke the ./manage migrate
tool…
1) Settings the sites framework to my project and a dualsite
app to host associated code :
core.settings
:
INSTALLED_APPS = [
# (…)
"django.contrib.sites",
"dualsite.apps.DualsiteConfig",
]
core.settings.foo
:
from core.settings import *
SITE_ID = 1
core.settings.bar
:
from core.settings import *
SITE_ID = 2
dualsite.migration.0001_set_sites
:
from django.db import migrations
def my_sites(apps, schema_editor):
Site = apps.get_model("sites", "Site")
initial_site = Site.objects.get(id=1)
initial_site.domain = "foo.domain.ltd"
initial_site.name = "foo"
initial_site.save()
Site.objects.create(domain="bar.domain.ltd", name="bar")
class Migration(migrations.Migration):
dependencies = [("sites", "0002_alter_domain_unique")]
operations = [migrations.RunPython(my_sites)]
At this point ./manage.py migrate
fails :
Applying sites.0001_initial... OK
Applying sites.0002_alter_domain_unique... OK
Applying dualsite.0001_set_sites...Traceback (most recent call last):
File "./manage.py", line 21, in <module>
main()
File "./manage.py", line 17, in main
execute_from_command_line(sys.argv)
File ".venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
utility.execute()
File ".venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 375, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File ".venv/lib/python3.7/site-packages/django/core/management/base.py", line 323, in run_from_argv
self.execute(*args, **cmd_options)
File ".venv/lib/python3.7/site-packages/django/core/management/base.py", line 364, in execute
output = self.handle(*args, **options)
File ".venv/lib/python3.7/site-packages/django/core/management/base.py", line 83, in wrapped
res = handle_func(*args, **kwargs)
File ".venv/lib/python3.7/site-packages/django/core/management/commands/migrate.py", line 234, in handle
fake_initial=fake_initial,
File ".venv/lib/python3.7/site-packages/django/db/migrations/executor.py", line 117, in migrate
state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
File ".venv/lib/python3.7/site-packages/django/db/migrations/executor.py", line 147, in _migrate_all_forwards
state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
File ".venv/lib/python3.7/site-packages/django/db/migrations/executor.py", line 245, in apply_migration
state = migration.apply(state, schema_editor)
File ".venv/lib/python3.7/site-packages/django/db/migrations/migration.py", line 124, in apply
operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
File ".venv/lib/python3.7/site-packages/django/db/migrations/operations/special.py", line 190, in database_forwards
self.code(from_state.apps, schema_editor)
File "dualsite/migrations/0001_set_sites.py", line 6, in my_sites
initial_site = Site.objects.get(id=1)
File ".venv/lib/python3.7/site-packages/django/db/models/manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File ".venv/lib/python3.7/site-packages/django/db/models/query.py", line 408, in get
self.model._meta.object_name
__fake__.DoesNotExist: Site matching query does not exist.
…but it works if I split the migration : ./manage.py migrate sites; ./manage.py migrate
2) Now that my DB is set, it is time to restrict URLs depending the SITE_ID
set in core.settings.xxx
:
dualsite.utils
:
from django.contrib.sites.models import Site
from django.views.generic import TemplateView as tw
def check_site(view_asked):
view_returned = tw.as_view(template_name="403.dhtml")
if Site.objects.get_current().name == "foo":
view_returned = view_asked
return view_returned
core.urls
:
from django.urls import include, path
from django.views.generic import TemplateView as tw
from dualsite.utils import check_site
urlpatterns = [
# restricted : only foo.domain.ltd
path("", check_site(tw.as_view(template_name="base.dhtml")), name="home"),
path("hellofoo/", check_site(include("hellofoo.urls"))),
# full access : foo.domain.ltd & bar.domain.ltd
path("about/", tw.as_view(template_name="about.dhtml"), name="about"),
path("helloworld/", include("helloworld.urls")),
]
Hooray : The URLs restriction works!!!
but ./manage.py migrate sites
is now broken :
Traceback (most recent call last):
File "./manage.py", line 21, in <module>
main()
File "./manage.py", line 17, in main
execute_from_command_line(sys.argv)
File ".venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
utility.execute()
File ".venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 375, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File ".venv/lib/python3.7/site-packages/django/core/management/base.py", line 323, in run_from_argv
self.execute(*args, **cmd_options)
File ".venv/lib/python3.7/site-packages/django/core/management/base.py", line 361, in execute
self.check()
File ".venv/lib/python3.7/site-packages/django/core/management/base.py", line 390, in check
include_deployment_checks=include_deployment_checks,
File ".venv/lib/python3.7/site-packages/django/core/management/commands/migrate.py", line 65, in _run_checks
issues.extend(super()._run_checks(**kwargs))
File ".venv/lib/python3.7/site-packages/django/core/management/base.py", line 377, in _run_checks
return checks.run_checks(**kwargs)
File ".venv/lib/python3.7/site-packages/django/core/checks/registry.py", line 72, in run_checks
new_errors = check(app_configs=app_configs)
File ".venv/lib/python3.7/site-packages/django/core/checks/urls.py", line 13, in check_url_config
return check_resolver(resolver)
File ".venv/lib/python3.7/site-packages/django/core/checks/urls.py", line 23, in check_resolver
return check_method()
File ".venv/lib/python3.7/site-packages/django/urls/resolvers.py", line 398, in check
for pattern in self.url_patterns:
File ".venv/lib/python3.7/site-packages/django/utils/functional.py", line 80, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File ".venv/lib/python3.7/site-packages/django/urls/resolvers.py", line 579, in url_patterns
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
File ".venv/lib/python3.7/site-packages/django/utils/functional.py", line 80, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File ".venv/lib/python3.7/site-packages/django/urls/resolvers.py", line 572, in urlconf_module
return import_module(self.urlconf_name)
File "/usr/lib/python3.7/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
File "<frozen importlib._bootstrap>", line 983, in _find_and_load
File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 728, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "core/urls.py", line 7, in <module>
path("", check_site(tw.as_view(template_name="base.dhtml")), name="home"),
File "dualsite/utils.py", line 9, in check_site
if Site.objects.get_current().name == "foo":
File ".venv/lib/python3.7/site-packages/django/contrib/sites/models.py", line 58, in get_current
return self._get_site_by_id(site_id)
File ".venv/lib/python3.7/site-packages/django/contrib/sites/models.py", line 30, in _get_site_by_id
site = self.get(pk=site_id)
File ".venv/lib/python3.7/site-packages/django/db/models/manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File ".venv/lib/python3.7/site-packages/django/db/models/query.py", line 402, in get
num = len(clone)
File ".venv/lib/python3.7/site-packages/django/db/models/query.py", line 256, in __len__
self._fetch_all()
File ".venv/lib/python3.7/site-packages/django/db/models/query.py", line 1242, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File ".venv/lib/python3.7/site-packages/django/db/models/query.py", line 55, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File ".venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1100, in execute_sql
cursor.execute(sql, params)
File ".venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 99, in execute
return super().execute(sql, params)
File ".venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 67, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File ".venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 76, in _execute_with_wrappers
return executor(sql, params, many, context)
File ".venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File ".venv/lib/python3.7/site-packages/django/db/utils.py", line 89, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File ".venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File ".venv/lib/python3.7/site-packages/django/db/backends/sqlite3/base.py", line 383, in execute
return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such table: django_site
At this point I decided to get help on StackOverflow…
Question : Is the Django sites framework an adequate tool to restrict URLs access depending on domain names used ?
Bonus question :
- if yes : Where am I wrong to broke migrate tool?
- if no : Which tool can I use to keep access rules in Django (instead of putting it in Nginx)
Thanks in advance !