I am trying to develop a ModelManager with an operation similar to the Sites Framework. Depending on a user's field, the ModelManager returns a queryset. I tried to imitate the operation of the Sites Framework but I do not understand how the SITE_ID is obtained dynamically with this function:
def get_queryset(self):
return super(CurrentSiteManager, self).get_queryset().filter(
**{self._get_field_name() + '__id': settings.SITE_ID})
It seems to be static :/.
I capture the user's field through a Middleware and assign it to request.field. How can I retrieve that field in the ModelManager and perform the query?
I think you are missing the dynamic way to get the current site instance.
From the documentation example:
from django.contrib.sites.shortcuts import get_current_site
def article_detail(request, article_id):
try:
a = Article.objects.get(id=article_id,
sites__id=get_current_site(request).id)
except Article.DoesNotExist:
raise Http404("Article does not exist on this site")
# ...
You should use the get_current_site method to get the current site.
It should be noted that it's not working if you actually define the current site in the settings like SITE_ID=1.
It looks up the current site based on request.get_host() if the SITE_ID setting is not defined.
You should read this part of the documentation actually explaining how django can get the current site in a dynamic fashion:
shortcuts.get_current_site(request)
A function that checks if django.contrib.sites is installed and returns either the current Site object or a RequestSite object based on the request. It looks up the current site based on request.get_host() if the SITE_ID setting is not defined.
Both a domain and a port may be returned by request.get_host() when the Host header has a port explicitly specified, e.g. example.com:80. In such cases, if the lookup fails because the host does not match a record in the database, the port is stripped and the lookup is retried with the domain part only. This does not apply to RequestSite which will always use the unmodified host.
And here is the code of the get_current actually called by get_current_site:
def get_current(self, request=None):
"""
Return the current Site based on the SITE_ID in the project's settings.
If SITE_ID isn't defined, return the site with domain matching
request.get_host(). The ``Site`` object is cached the first time it's
retrieved from the database.
"""
from django.conf import settings
if getattr(settings, 'SITE_ID', ''):
site_id = settings.SITE_ID
return self._get_site_by_id(site_id)
elif request:
return self._get_site_by_request(request)
raise ImproperlyConfigured(
"You're using the Django \"sites framework\" without having "
"set the SITE_ID setting. Create a site in your database and "
"set the SITE_ID setting or pass a request to "
"Site.objects.get_current() to fix this error."
)
Related
Django==1.11.7
django-tenant-schemas==1.8.0
django-allauth==0.34.0
Multi tenant site using django-tenant-schemas (postgres).
On different tenants, different settings are required.
More specifically, different setting is required for ACCOUNT_EMAIL_VERIFICATION
1 tenant needs ACCOUNT_EMAIL_VERIFICATION = "optional" while another one needs ACCOUNT_EMAIL_VERIFICATION ="mandatory"
Looking in the source code, the setting looks not customisable, it is fixed for the whole django site.
-> How can this be done?
You can compute the settings at runtime, since it's simply a python code.
Set that specific code programmatically, using your preferred way. One example:
# predefine the settings per tenant
ACCOUNT_EMAIL_VERIFICATION_PER_TENANT = {
"tenant_x": "mandatory",
"tenant_y": "optional",
}
# implement get_tenant
def get_tenant():
# here be tenant logic
pass
this_tenant = get_tenant()
ACCOUNT_EMAIL_VERIFICATION = ACCOUNT_EMAIL_VERIFICATION_PER_TENANT[get_tenant()]
Or you can have multiple settings files and join them as you wish. Here's how django does.
Oh and if you want to separate the logic from the settings file and have it run before evaluating the settings perhaps, you can inspect what is the trail of execution when you launch your server (e.g. starting from manage.py and insert your get_tenant logic somewhere in between). Most probably it will be somewhere starting from the wsgi.py file - where the application instance gets created and all the django fun begins.
When it comes to programming, you are always in control.
Solved in following way:
In settings.py:
try:
ACCOUNT_EMAIL_VERIFICATION = os.environ['ACCOUNT_EMAIL_VERIFICATION_OVERRIDE']
except KeyError:
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
In wsgi.py file of the tenant where e-mail verification is optional:
os.environ['ACCOUNT_EMAIL_VERIFICATION_OVERRIDE'] = 'optional'
wsgi files for the other tenants remain unchanged.
Gave the bounty to Adelin as he suggested to look into the wsgi file.
I stumbled upon this situation, and my dynamic solution is a middleware as follows without any hardcoding tenant's names
from django.conf import settings
from django.db import connection
from django_tenants.utils import get_public_schema_name, get_tenant_model
class TenantSettingsMiddleWare:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
self.request = request
self.overload_settings()
response = self.get_response(request)
return response
def overload_settings(self):
current_schema_obj = get_tenant_model().objects.get(schema_name=connection.schema_name)
settings.DEFAULT_FROM_EMAIL = 'admin#{}'.format(current_schema_obj.domains.last())
Cheers 🍻🍻🍻
I am trying to set my Django app work with multiple domains (while serving slightly different content)
I wrote this middleware:
class MultiSiteMiddleware(object):
def process_request(self, request):
host = request.get_host()
host_part = host.split(':')[0].split('.com')[0].split('.')
host = host_part[len(host_part)-1] + '.com'
site = Site.objects.get(domain=host)
settings.SITE_ID = site.id
settings.CURRENT_HOST = host
Site.objects.clear_cache()
return
In views I use this:
def get_site(request):
current_site = get_current_site(request)
return current_site.name
def view(request, pk):
site = get_site(request)
if site == 'site1':
# serve content1
...
elif site == 'site2'
# serve content2
...
But now there are 404 errors (I sometimes find them in logs, don't see them while browsing my site manually) where they aren't supposed to be, like my site sometimes is serving content for wrong domains, can they happen because of some flaw in the above middleware and view code or I should look somewhere else?
I had a similar requirement and decided not to use the django sites framework. My middleware looks like
class MultiSiteMiddleware(object):
def process_request(self, request):
try:
domain = request.get_host().split(":")[0]
request.site = Site.objects.get(domain=domain)
except Site.DoesNotExist:
return http.HttpResponseNotFound()
then my views have access to request.site
If you're seeing 404's for sites that aren't yours in your logs it would seem like somebody has pointed their domain at your servers IP address, you could use apache/nginx to filter these out before they hit your app, but your middleware should catch them (though possibly by raising an uncaught 500 error instead of a 404)
Serve multiple domain from one website (django1.8 python3+)
The goal is, obviously, to serve multiple domains, from one Django instance. That means, we use the same models, the same database, the same logic, the same views, the same templates, but to serve different things.
Searching the interwebs, I came to the idea of using the sites framework. The sites framework was designed to do exactly that thing. In fact, the sites framework has been used to do exactly that. But I haven't been able to know on which version of Django it was, and actually, I came to the idea the sites framework was just a vestigial module. Basically, it is just a table, with SITE_ID and SITE_URL, that you can easily access with a function. But I've been unable to find how, from that, you can do a multi-domain website.
So my idea has been, how to modify the url resolver. The idea behind all these is easy : www.domain1.com/bar is resolved to /domain1/bar, and www.domain2.foo is resolved to /domain2/foo. This solves the problem because, if you want to serve multiple domains, you just have to serve multiple folders.
In Django, to achieve this, you have to modify two things :
* the way Django routes requests
* the way django write urls
The way Django routes requests
Django routes requests with middlewares. That's it. So we just have to write a middleware that re-route requests.
To make it easier, middlewares can have a process_request method that process requests (WOW), before requests are handled. So let's write a DomainNameMiddleware
#!python
#app/middleware.py
class DomaineNameMiddleware:
"""
change the request path to add the domain_name at the first
"""
def process_request(self, request):
#first, we split the domain name, and take the part before the extension
request_domain = request.META['HTTP_HOST'].split('.')[-2]
request.path_info = "/%s/%s" % (request_domain, request.path.split('/')[1:])
The way Django writes URL
When I'm talking about django writing url, i'm principally thinking of the {% url %} template tag, the get_absolute_url methods, the resolve and resolve_lazy functions, and those basic Django thing. If we rewrite the way Django handle url, we have to tell Django to write url that way.
But basically it's pretty easy, thanks to Django.
You can easily rewrite basic Django function by just rewriting them, typically in init.py files of modules you added as apps. So :
#!python
#anyapp/__init__.py
from django.core import urlresolvers
old_reverse = urlresolvers.reverse
def new_reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
"""
return an url with the first folder as a domain name in .com
"""
TLD = 'com'
old_reverse_url = old_reverse(viewname, urlconf, args, kwargs, current_app)
# admin will add itself everytime you reload an admin page, so we have to delete it
if current_app == 'admin':
return '/%s' % old_reverse_url[len('admin'):].replace('adminadmin', 'admin')
return '//%s.%s/%s' % (app, TLD, path)
How to use it ?
I use it with base urls.py as dispatchedr.
#!python
#urls.py
from django.conf.urls import include, url
from domain1 import urls as domain1_urls
from domain2 import urls as domain2_urls
urlpatterns = [
url(r'^domain1/', include(domain1_urls, namespace='domain1')),
url(r'^domain2/', include(domain2_urls, namespace='domain2)),
]
Django's Sites framework has built-in middleware to accomplish this.
Simply enable the Sites framework and add this to your MIDDLEWARE:
'django.contrib.sites.middleware.CurrentSiteMiddleware'
This automatically passes a request object to Site.objects.get_current() on every request. It solves your problem by giving you access to request.site on every request.
For reference, the code as of 1.11 is:
from django.utils.deprecation import MiddlewareMixin
from .shortcuts import get_current_site
class CurrentSiteMiddleware(MiddlewareMixin):
"""
Middleware that sets `site` attribute to request object.
"""
def process_request(self, request):
request.site = get_current_site(request)
I will suggest you to use django-multisite .It will fulfill your requirement.
Try using the "sites" framework in django to get the domain name. You already know this I guess.
Take a look here: https://docs.djangoproject.com/en/1.7/ref/contrib/sites/#getting-the-current-domain-for-full-urls
See this:
>>> Site.objects.get_current().domain
'example.com'
Without the https://www or http://www.. Probably your domains will end in .org or some country .pe .ru etc not just .com.
There might be a case when people don't point to your domain but to your IP address for some reason, maybe development of testing so you should always raise an exception with Site.DoesNotExist
I'm working on a Django site hosted on an Apache server with mod_wsgi.
The site is only on https as we have Apache redirect any http requests to https.
The project I'm working on is called Skittle.
I have a custom user model called SkittleUser which inherits from AbstractBaseUser and is set as the AUTH_USER_MODEL in our settings.py file.
os.environ['HTTPS'] = "on" is set in the wsgi.py file.
SESSION_COOKIE_SECURE = True and CSRF_COOKIE_SECURE = True are both set in settings.py
The issue that we are having right now is that logging in as a user is unreliable.
When you go to the login page, some times it works while other times it doesn't.
Then while browsing the site, you will suddenly lose your session and be kicked down to an anonymous user.
We are currently running our test site here if anybody wants to take a look:
https://skittle.newlinetechnicalinnovations.com/discover/
Our production site is at www.dnaskittle.com but does not yet incorporate user logins as the feature doesn't work.
A test user:
email: test#dnaskittle.com
password: asdf
If the login does not work, you will see in the top right "Welcome, Login" in which case, just try clicking on Login again and use the same credentials.
It may take 5-6 times of doing that process before you will actually get logged in.
You will know it works when you see "Welcome Tester, Logout, My Genomes"
After you are logged in, it may stick for a while, but browsing around to other pages will eventually kick you back off.
There is no consistent amount of pages that you can go through before this happens, and it doesn't happen on any specific page.
Any insights on this would be greatly appreciated.
Also of note, going to the Django admin page (which is not our code, but base django code) has the same issue.
I've gotten this issue sorted out now.
Users can not login while on HTTPS using while using the listed setup.
What I did:
In settings.py add:
SESSION_SAVE_EVERY_REQUEST = True
SESSION_COOKIE_NAME = 'DNASkittle'
I also wiped the current django_sessions database in case that was causing issues with old lingering data.
I did not setup extra middleware or SSLRedirect, and everything is working all ship shape.
It's little longer and complex the SSL system. to handle the session/login properly with https you can set a configuration with the session_cookies.
settings.py:
ENABLE_SSL=False #in the debug mode in the production passed it to True
MIDDLEWARE_CLASSES = (
'commerce.SSLMiddleware.SSLRedirect', #)
# the session here is to use in your views when a user is connected
SESSION_COKIE_NAME='sessionid'
#the module to store sessions data
SESSION_ENGINE='django.contrib.sessions.backends.db'
#age of cookie in seconds (default: 2 weeks)
SESSION_COOKIE_AGE=7776000 # the number of seconds in 90 days
#whether a user's session cookie expires when the web browser is closed
SESSION_EXPIRE_AT_BROWSER_CLOSE=False
#whether the session cookie should be secure (https:// only)
SESSION_COOKIE_SECURE=False
SSLMiddleware is a file you going to define in your project like.
SSLMiddleware.py:
from django.conf import settings
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
SSL='SSL'
class SSLRedirect:
def process_view(self,request,view_func,view_args,view_kwargs):
if SSL in view_kwargs:
secure =view_kwargs[SSL]
del view_kwargs[SSL]
else:
secure=False
if not secure == self._is_secure(request):
return self._redirect(request,secure)
def _is_secure(self,request):
if request.is_secure():
return True
if 'HTTP_X_FORWARD_SSL' in request.META:
return request.META['HTTP_X_FORWARD_SSL'] == 'on'
return False
def _redirect(self,request,secure):
protocol = secure and "https" or "http"
newurl ="%s://%s%s" % (protocol, request, request.get_full_path())
if settings.DEBUG and request.method=="POST":
raise RuntimeError, \
return HttpResponsePermanentRedirect(newurl)
Now in your urls which should handle your logins or connection add the line
urls.py:
from project import settings
urlpatterns += patterns('django.contrib.auth.views',
(r'^login/$','login',{'template_name':'registration/login.html','SSL':settings.ENABLE_SSL},'login'),
Try to adapt this to your code. and don't forget to turn ENABLE_SSL to True
For users login with HTTPS, you have to enable SSL and use code that matches your case to use it. For the user session you can use this:
First check if you have in settings.py:
INSTALLED_APPS= ('django.contrib.sessions',)
and use the request.sessions in your file.py:
request.session['id']='the_id_to_store_in_browser' or 'other_thing'
Here you use request.session like a special SessionStore class which is similar to python dictionary.
In your views.py for example before rendering a template or redirecting test the cookie with the code:
if :
# whatever you want
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponseRedirect(up-to-you)
else:
# whatever you want
request.session.set_test_cookie()
return what-you-want
with this code, the user session will stick a wide, depending on your SESSION_COOKIE_AGE period.
I have a number of sites under one Django application that I would like to implement site wide caching on. However it is proving to be a real hassle.
what happens is that settings.CACHE_MIDDLEWARE_KEY_PREFIX is set once on startup, and I cannot go ahead and change it depending on what the current site is. As a result if a page of url http://website1.com/abc/ is cached then http://website2.com/abc/ renders the cached version of http://website1.com/abc/. Both these websites are running on the same Django instance as this is what Django Sites appears to allow us to do.
Is this an incorrect approach? Because I cannot dynamically set CACHE_MIDDLEWARE_KEY_PREFIX during runtime I am unable to cache multiple sites using Django's Site wide caching. I also am unable to do this for template and view caching.
I get the impression that the way this really needs to be setup is that each site needs its own Django instance which is pretty much identical except for the settings file, which in my case will differ only by the value of CACHE_MIDDLEWARE_KEY_PREFIX. These Django instances all read and write to the same database. This concerns me as it could create a number of new issues.
Am I going down the right track or am I mistaken about how multi site architecture needs to work? I have checked the Django docs and there is not real mention of how to handle caching (that isn't low level caching) for Django applications that serve multiple sites.
(Disclaimer: the following is purely speculation and has not been tested. Consume with a pinch of salt.)
It might be possible to use the vary_on_headers view decorator to include the 'Host' header in the cache key. That should result in cache keys that include the HTTP Host header, thus effectively isolating the caches for your sites.
#vary_on_headers('Host')
def my_view(request):
# ....
Of course, that will only work on a per-view basis, and having to add a decorator to all views can be a big hassle.
Digging into the source of #vary_on_headers reveals the use of patch_vary_headers() which one might be able to use in a middleware to apply the same behaviour on a site level. Something along the lines of:
from django.utils.cache import patch_vary_headers
class VaryByHostMiddleware(object):
def process_response(self, request, response):
patch_vary_headers(response, ('Host',))
return response
I faced this problem recently. What I did based on the documentation was to create a custom method to add the site id to the key used to cache the view.
In settings.py add the KEY_FUNCTION argument:
CACHES = {
'default': {
'BACKEND': 'path.to.backend',
'LOCATION': 'path.to.location',
'TIMEOUT': 60,
'KEY_FUNCTION': 'path.to.custom.make_key_per_site',
'OPTIONS': {
'MAX_ENTRIES': 1000
}
}
}
And my custom make_key method:
def make_key_per_site(key, key_prefix, version):
site_id = ''
try:
site = get_current_site() # Whatever you use to get your site's data
site_id = site['id']
except:
pass
return ':'.join([key_prefix, site_id, str(version), key])
You need to change get_full_path to build_absolute_uri in django.util.cache
def _generate_cache_header_key(key_prefix, request):
"""Returns a cache key for the header cache."""
#path = md5_constructor(iri_to_uri(request.get_full_path()))
path = md5_constructor(iri_to_uri(request.build_absolute_uri())) # patch using full path
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
key_prefix, path.hexdigest())
return _i18n_cache_key_suffix(request, cache_key)
def _generate_cache_key(request, method, headerlist, key_prefix):
"""Returns a cache key from the headers given in the header list."""
ctx = md5_constructor()
for header in headerlist:
value = request.META.get(header, None)
if value is not None:
ctx.update(value)
#path = md5_constructor(iri_to_uri(request.get_full_path()))
path = md5_constructor(iri_to_uri(request.build_absolute_uri()))
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
key_prefix, request.method, path.hexdigest(), ctx.hexdigest())
return _i18n_cache_key_suffix(request, cache_key)
Or create you own slightly changed cache middleware for multisite.
http://macrotoma.blogspot.com/2012/06/custom-multisite-caching-on-django.html
I have to assign to work on one Django project. I need to know about the URL say, http://....
Since with ‘urls.py’ we indeed have ‘raw’ information. How I come to know about the complete URL name; mean with
http+domain+parameters
Amit.
Look at this snippet :
http://djangosnippets.org/snippets/1197/
I modified it like this :
from django.contrib.sites.models import RequestSite
from django.contrib.sites.models import Site
def site_info(request):
site_info = {'protocol': request.is_secure() and 'https' or 'http'}
if Site._meta.installed:
site_info['domain'] = Site.objects.get_current().domain
site_info['name'] = Site.objects.get_current().name
else:
site_info['domain'] = RequestSite(request).domain
site_info['name'] = RequestSite(request).name
site_info['root'] = site_info['protocol'] + '://' + site_info['domain']
return {'site_info':site_info}
The if/else is because of different versions of Django Site API
This snippet is actually a context processor, so you have to paste it in a file called context_processors.py in your application, then add to your settings :
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + (
'name-of-your-app.context_processors.site_info',
)
The + is here to take care that we d'ont override the possible default context processor set up by django, now or in the future, we just add this one to the tuple.
Finally, make sure that you use RequestContext in your views when returning the response, and not just Context. This explained here in the docs.
It's just a matter of using :
def some_view(request):
# ...
return render_to_response('my_template.html',
my_data_dictionary,
context_instance=RequestContext(request))
HTTPS status would be handled differently by different web servers.
For my Nginx reverse proxy to Apache+WSGI setup, I explicitly set a header that apache (django) can check to see if the connection is secure.
This info would not be available in the URL but in your view request object.
django uses request.is_secure() to determine if the connection is secure. How it does so depends on the backend.
http://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.is_secure
For example, for mod_python, it's the following code:
def is_secure(self):
try:
return self._req.is_https()
except AttributeError:
# mod_python < 3.2.10 doesn't have req.is_https().
return self._req.subprocess_env.get('HTTPS', '').lower() in ('on', '1')
If you are using a proxy, you will probably find it useful that HTTP Headers are available in HttpRequest.META
http://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.META
Update: if you want to log every secure request, use the above example with a middleware
class LogHttpsMiddleware(object):
def process_request(self, request):
if request.is_secure():
protocol = 'https'
else:
protocol = 'http'
print "%s://www.mydomain.com%s" % (protocol, request.path)
Add LogHttpsMiddleware to your settings.py MIDDLEWARE_CLASSES