Creating 2 django sites that share 90% data and code - django

I have two closely related sites, a main site and a mobile site, hosted as a django app. They'll have a lot of the same functionality and need to access the same data. The main difference is the templates will be different and the way the site is structured will be different.
I have two separate virtual hosts, one for each (though I don't have to do it that way). My first thought was that the Django sites framework helps to solve this, but the docs don't seem to describe my use case.
Can someone give me a hint to know if I'm on the right track? The urls.py will need to be different since, for example, the homepage is completely different between the apps. The main goal is that for the data in the two different apps to be shared and the code to manage that does not need to be duplicated.
From the main site:
User submits an item that is stored in the model
From the mobile site:
User views a list of items and see the one just entered on the main site
User gives a 5 star rating on the recently added item
From the main site:
User views a list of highly rated items and the recently added item (which now has a high rating) shows up on the list.

Have a look at this answer to a similar question. Basically you can just use the same views and just return different templates based on the user-agent.
Also, if you structure your application logic so that it is broken up into different "apps" in django terms, then you can re-use them if you need different flows with similar components.
Hopefully this gets you off and running.
UPDATE:
So lets say you have your main site http://www.mainsite.com/ which has it's own urls.py models.py and views.py that makes your functionality for the main site. Then you have http://www.m.mainsite.com/ which has it's own set of urls, and views. Then you can just import the main site's models and use them in the mobile sites views.

OK, both answers are great and contributed to what I chose for my final solution.
In the settings.py file there is an option called ROOT_URLCONF. I created two settings.py files, called settings_desktop.py and settings_mobile.py and in each of these used the following code:
from settings.py import *
ROOT_URLCONF = 'myapp.urls_mobile'
(or in the case of the desktop, myapp.urls_desktop)
This actually gives a lot of cool features such as being able to use different template directories for each site, though really I'm not going to do that.
Then I created two versions of the wsgi file where the only difference was this line:
os.environ['DJANGO_SETTINGS_MODULE'] = 'myapp.settings_mobile'
or
os.environ['DJANGO_SETTINGS_MODULE'] = 'myapp.settings_desktop'
In each of the virtual hosts the only difference is the WSGIScriptAlias line that points to the different wsgi file for each host.
This allowed me to effectively use one django app that could easily accommodate both sites.
Thanks for helping work out a good solution to this.

I did something very similar once. My way of solving this problem of multiple urls.py was something like this:
Create two urlconf, one for each site;
Create a new Middleware:
from django.utils.cache import patch_vary_headers
class DomainMiddleware:
def __init__(self):
pass
def process_request(self, request):
#avoid problems when reaching the server directly trough IP
host = request.META.get('HTTP_HOST', None)
if host is None: return
host = host.split(':')[0] #remove port number
if host is mobile:
urlconf = "mobile.urls"
else:
urlconf = "default.urls"
request.urlconf = urlconf
def process_response(self, request, response):
patch_vary_headers(response, ('Host',))
return response
Check also why you have to do the patch_vary_headers on the docs.

Related

Django: detecting request domain in urls.py

I have a Django app that will serve diferent websites.
Each one will have it´s own domain
Each one will have it´s own sub app with templates and views
They all share the same backend, models and data
My aproach
As I already have the database with the segmentation info I need to show the desired products in each site and I will have different views for each sub app, I don´t need to add another field in the models.
I think that it would be easier just to detect the request domain in my main app urls.py and rout home url to the desired sub app.
Something like:
# urls.py
if "name1" in request.domain:
urlpatterns += [path('', include('app1.urls'))]
if "name2" in request.domain:
urlpatterns += [path('', include('app2.urls'))]
else:
urlpatterns += [path('', include('app3.urls'))]
Maybe I should make a middleware and set a global variable I can access in urls.py? Can I use this kind of if statements in urls.py?
Django sites
I checked the Django Sites Framework, but if I understand ir well, it´s more focused in segmenting the database in the models, not the templates and views. In any case I don´t really catch how the Sites framework detects the incomming URL and roots the request to each sub app.
Other intents
I searched for more info and this article can brief the different articles about the issue I found.
https://medium.com/crowdbotics/how-to-use-dynamic-subdomains-in-django-dc1cb2cac00b
But still I don´t get how can I achieve what I need. Sounds very logical just to use my aproach and don´t mess with my database. If it´s possible.
Any clues welcome. Thanks in advance!

Django - Runtime database switching

In my work we want to run a server with multiple databases. The databases switching should occur when you acces a url like http://myapp.webpage.com or http://other.webpage.com. We want to run only one server instance and at the moment of the HTTP request switch the database and return the corresponding response.
We've been looking for a mantainable and 'Django-friendly' solution. In our investigation we have found possible ways to do this, but we have not enough information about.
Option 1: Django middleware
The django middleware runs each time the server receive a HTTP request.
Making a database switch here could be the best option but using django database routers as far as I know only allow to change the database for a model or group or models.
Another option is to set a django model manager instance in the middleware and force all models to re-assign the objects attribute from an added attribute in the custom middleware.
My last option is to create a new attribute in the request object received by the middleware that return the database alias from settings.py and in each model query use the using method.
Option 2: Class-based View Mixin
Create a mixin that use the past three options, but I the mixin must be set in ALL the Class-based views. If a programmer forget to set the mixin and it comes to a production server, the data could be (or stop being) in the right database, and I don't wanna take the risk.
Option 3: Changing the database settings in runtime
This option works but Is not recommended and is too risky.
UPDATE:
How this works?
middlewares.py
import django.conf as conf
import os.path
class SelectDB(object):
def process_request(self, request):
print request.META['HTTP_REFERER']
file_database = open("booklog/database.txt", "r")
database = file_database.read(10)
file_database.close()
if database != 'default':
conf.settings.DATABASES['default']['NAME'] = database
Any information that help us to solve will be greatly appreciated.
Answer (it worked for me)
The question was already answered here, in stackoverflow. I'd love this functionality were in django. It was a bit hard to find the way to make this possible.
I think that is important to comment the great work that Wilduck made with the django plugin django-dynamic-db-router, it's a great plugin that makes possible this operation in (a bit) different way.
Thanks a lot to #JL Peyret and #ire_and_curses.
And as an answer to #ire_and_curses. At least in this moment, in the project I'm working it's what we need. In previous projects we needed a similar behavior and made one server per instance was terrible to mantain and update each server, even automating the process.

Reversing urls in Django / Celery

I have been asked to send an email on friday with a list of projects matching a set of conditions. The database front end is written in Django and its an in house application. I would like to link to the admin page of the project in the email.
On my development machine I am trying
reverse("admin:index")
and I notice that it is only returning
admin
from the Celery task, whereas in standard Django views this would return
127.0.0.1:8000/admin/
Is there a way around this? I would prefer not to hard code the start of the urls.
I have set up Celery as described in the "first steps with Django" tutorial, and have a
myproject/myproject/celery.py
file defining the app (alongside settings.py and urls.py) and
project/myapp/tasks.py
file with the actual tasks.
reverse() always returns a domain-relative url, i.e. /admin/. One option to increase portability is to use the sites framework.
As you don't have a request object in your Celery task, you have to explicitly set the SITE_ID in your settings and set the site's name and domain in your database. Then you can do:
from django.contrib.sites.models import Site
url = "%s%s" % (Site.objects.get_current().domain, reverse('admin:index'))
Usually URLs in django are relative - i.e. the "127.0.0.1:8000" part you see when you hover the link comes from the fact that you are browsing the site from that location. Try looking at the href value of the URLs generated.
A simple solution to your problem is to concatenate the reverse output with the domain, i.e.
'http://example.com/'+reverse('admin:index')

Can Django-cms pages incorporate the vary_on_headers decorator?

I'm building a site using Django-CMS and we're using MobileESP to detect the user-agent and serve different templates and content depending on what device is being used to the view the site.
The issue is that with django-cms's built in page caching, users are seeing the wrong content depending on which version of the page was cached. For example, if the desktop version of the site is viewed first and then cached, then users visiting the site on a smartphone still see the desktop version instead of the mobile version.
With traditional Django views this could be resolved using the #vary_on_headers('User-Agent') to ensure different versions of the site get correctly cached.
Is it possible to apply this logic to Django-CMS views some how?
I managed to figure out a way to work around this issue by using the patch_vary_headers() functionality in a custom middleware class. I already had a platform detection middleware which added the mobile/desktop setting to the request object. So I just added a process_response function to the middleware and patched it there. So far it appears to be working correctly.
class PlatformDetectionMiddleWare (object):
def process_request(self, request):
user_agent = request.META.get("HTTP_USER_AGENT")
http_accept = request.META.get("HTTP_ACCEPT")
agent = mdetect.UAgentInfo(userAgent=user_agent, httpAccept=http_accept)
request.is_tablet = agent.detectTierTablet()
request.is_mobile = agent.detectTierIphone()
def process_response(self, request, response):
patch_vary_headers(response, ['User-Agent'])
return response
http://www.djangobook.com/en/2.0/chapter15.html I think you can caching like SI Eric suggest and this link talk about caching with vary_header I think it's can help to solve your problem.
I cache on view layer with
#vary_on_headers('User-Agent')
in my project and it run quite well!

How to alter django settings based on current request?

I'm running multiple sites from a single django instance, and expect the framework to eventually serve several hundred sites from one or several installations.
I need to patch some django settings based on the current request. I've written some middleware to monkey patch the settings, but I need these settings to be patched before the middleware gets invoked because other apps aren't taking the monkey-patched changes (i.e. apps get run then middleware gets run so the apps don't use the monkey-patched settings).
I should also add this is mainly for the benefit of third-party apps that I haven't written, so I don't want to go round adding decorators or anything like that because that would mess up my upgrade path.
So:
How can I get access to the current request in an app's init.py file?
Will an app's init.py get called once per request or only once? If it's only once, how else could I do this so I can manipulate the settings once per request?
Is it safe to do this kind of monkey patching? I know it makes code a bit more opaque, but I don't want to use different wsgi files per site because I want to allow users to edit some of these settings and have my monkey patching come from the database.
Is there a better solution that would allow certain settings to be stored in the database?
This module - django-tupperware does what you are asking about: https://bitbucket.org/jiaaro/django-tupperware/
Give it a try.
Never ever ever change settings on the fly. You cannot predict how the application may one day be deployed, and in most of them the project will fail in.. interesting ways.
If you really want to have hundreds of sites which will be dynamically reconfigured all the time, you might try to Djangos django/conf/__init__.py which is used to supply the settings to the rest of the framework. You might be able to alter it in a way it depends on the request and it's Host field. Or you'll get many interesting failures at the very least.
My solution to this problem is to give each request, ALL the settings and update them on the fly with a middleware, how do i do this?
Is rather simple:
This is the middleware that does it all
from django.conf import settings
class DumpObject: pass
class Settings(object):
def process_request(self,request):
request.settings = DumpObject()
for setting in dir(settings):
if not setting.startswith('__'):
setattr(request.settings, setting, getattr(settings,setting))
The DumpObject is just there so i can use the object.property notation, it could have been a dictionary, but i wanted to keep some similarity in the syntax.
This assumes that none of your settings name starts with __ which is a fair assumption.
So if i want to override a particular setting, i don't keep a settings file i do it in this middleware. like this:
class Settings(object):
def process_request(self,request):
request.settings = DumpObject()
for setting in dir(settings):
if not setting.startswith('__'):
setattr(request.settings, setting, getattr(settings,setting))
if 'mydomain' in str(request.host): #this is thanks to django-hosts project
request.settings.GOOGLE_ANALYTICS_ID = '89298393-238'
Of course this doesnt take into account the problem of accessing the settings the old way
from django.conf import settings
settings.GOOGLE_ANALYTICS_ID = 'the value in settings.py'
But it really doesn't matter because you will probably only want to change the settings on the context of having a request object, available.