How Do I Capture the Subdomain in a Django URL Pattern? - django

I have a Django app that currently supports multiple languages. I'd like to add subdomain support so that going to 'de.mysite.com' queries articles in German, while 'mysite.com' queries things in English (the default language). There will be about 20 subdomains all pointing to the same Django app.
I have an abstract model with all of the fields for my data and a derived model for each language. Each language has its own database table, like so:
class ArticleBase(models.Model):
title = models.CharField(max_length=240, unique=True)
date_added = models.DateField(auto_now_add=True)
class Meta:
abstract = True
# This is English, the default.
class Article(ArticleBase):
pass
class Article_de(ArticleBase):
pass
I can get articles like this (I have this working today):
def article(request, title, language=None):
if language:
mod = get_model('app', 'Article_' + language)
items = mod.filter(title=title)
else:
items = Article.objects.filter(title=title)
This is my current URL pattern:
url(r'^article/(?P<title>[a-zA-Z_-]+)/$", 'app.views.article', name='article'),
How can I parse the subdomain prefix in a URL pattern so it can be passed into the article view? Or should I be getting this info from the request when I'm processing the view?

The URLS in django don't have access to the domain section of the URL, so that is not an option.
Doing it manually in each view is doable but not so easy to maintain.
I think that a custom middleware solution is probably better where you check the request headers in the middleware and then load the appropriate language (or you replace the accept-language headers and let django do it's magic)
The localemiddleware can make the language available which you can then easily use to pass through to the models/queries as needed.
Also if I may ask: why store every entry in a separate table/model? Can't you just add a language field to the model and store everything in one table? Because going for 20 or so models for articles can become harder to maintain than just 1 model.
UPDATE:
I've played around a little bit and the middleware required is quite simple (although my testing was quite limited):
class SubDomainLanguage(object):
def process_request(self, request):
try:
request.session['django_language'] = request.META['HTTP_HOST'].split('.')[0]
except KeyError:
pass
is the middleware and then change the settings to include the following:
MIDDLEWARE_CLASSES = (
...
'django.contrib.sessions.middleware.SessionMiddleware',
'subdomainmiddleware.SubDomainLanguage',
'django.middleware.locale.LocaleMiddleware',
...
)
This way you can then leverage the normal Django builtin i18n support.
In the views the language is then available in request.LANGUAGE_CODE

I have a case that I have subdomains with periods such as 1.2.3.mydomain.net. I know my domain and set it in my local_settings.py as:
DOMAIN = 'EXAMPLE.net'
TEST_SUBDOMAINS = ["127.0.0.1", "localhost", "testserver"]
I then check that I don't do anything if its for test purposes.
After that I get my sub domain as follows:
if (DOMAIN in TEST_SUBDOMAINS):
return HttpResponse("test only")
subdomain = request.META['HTTP_HOST'].replace((DOMAIN), "")[:-1]
# -1 is to remove the trailing "." from the subdomain
if subdomain and subdomain != "www" :
# whatever you need...

You can just use this easy to use django-subdomains library. It also support subdomain-based URL routing and reversing.

Related

Django : proper way to make website title editable in admin

I'm building a website with Django. I have a main title for the website in my header template, currently hard-coded in it.
How can I make it editable in the admin by the website administrator (or any user with the right credentials) ? Ideally I'd also like to be able to make more such site-wide (or app-wide) attributes editable in the admin (such as site - or app- description, for instance).
I have in mind something like WordPress' bloginfo() . A practical example of this is washingtonpost.com who changed their moto in their header to "Democracy dies in darkness" a few weeks ago.
Of course once the title (or any other attribute) has been edited in the admin I need to be able to get it from within my template.
You can create a simple model to store dynamic website parameters like this for example:
class WebsiteParam(models.Model):
key = models.CharField(max_length=50)
val = models.CharField(max_length=1024)
Then define custom template context processor settings_processor.py
def settings_processor(request):
vals = {x.key: v.val for x in WebsiteParam.objects.all()}
return {'website_settings': vals}
Add this processor into your django settings.py something like:
from django.conf import global_settings
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
"myapp.settings_processor.settings_processor",
)
And you will be able to use your settings in all of your templates like
<html><head><title>{{website_settings.title}}</title>...
if you have settings with title key added into the database
You should of course add caching into request context processor and other conditions if necessary

Django sites framework country separation

I'm developing a web site with Django where we list products and some comments for them. I'd like to have separate sites for each country with different product objects. For example:
us.domain.com - products in U.S are listed and the page is English.
fr.domain.com - products in France are listed and the page is in French etc.
1) What came to my mind is to alter settings.SITE_ID depending on the subdomain requested in a middleware, but this is said to be dangerous. What else can I do? I don't want to run separate instances or settings.py file for each country.
My solution so far:
class SubdomainMiddleware:
def process_request(self, request):
host = request.META.get('HTTP_HOST', '')
try:
site = Site.objects.get(domain=host)
except:
site = Site.objects.get(domain='www.domain.com')
request.site = site
products = Product.objects.filter(site_id=request.site)
Here, below doesn't work because CurrentSiteManager uses settings.SITE_ID, not the request.site:
on_site = CurrentSiteManager()
Product.on_site.all()
I have to explicitly pass site_id=request.site at each query. Is this safe, or are there any other alternatives?
2) If I add site = models.ForeignKey(Site) field to the Product model, is it enough? Or should I add that field to all other models like Comment, etc.?

Django: Allow admin user to edit site-wide settings?

I have a Django site, and I'd like to allow a couple of site-wide settings:
ADMIN_EMAIL - email address for an administrative user
REMINDER_TEXT - email text for an email that the site will generate once a week
to be edited in the admin interface.
What is the most sensible way to do this? Should I store these values in settings.py, or somewhere in the database?
This previous SO question seems to be related, but I don't think it's ever been fully answered.
Thanks!
I've tried dbsettings (as well as the newer fork of it on github), and they are useful - but a bit too heavy-weight for my needs. Instead, I implemented a simpler method. I did it using a JSONField (from Postgres 9.3) - but you might prefer to use a TextField (use max_length=1000 or somesuch) as it's simpler to explain to people how to enter a string instead of JSON:
1) First, build your own Settings Model (maybe in core/models.py):
class Setting(models.Model):
"""
Model for site-wide settings.
"""
name = models.CharField(max_length=200, help_text="Name of site-wide variable")
value = JSONField(null=True, blank=True, help_text="Value of site-wide variable that scripts can reference - must be valid JSON")
def __unicode__(self):
return self.name
2) Then, add an admin interface (in core/admin.py):
class SettingAdmin(admin.ModelAdmin):
list_display = ['name', 'value']
admin.site.register(Setting, SettingAdmin)
3) Build and run your migrations:
python manage.py schemamigration core --auto
python manage.py migrate core
4) Add to the top of base.html:
<script type="text/javascript">
//Auto-exported site-wide settings
{% autoescape off %}var site_settings = {{ settings|safe|default:"{}" }};{% endautoescape %}
</script>
5) Build an 'injector' that adds code to every request (likely in core/contextprocessors.py)
from models import Setting
import json
def app_settings(request):
"""Global values to pass to templates"""
settings_dict = dict()
settings = dict()
for obj in Setting.objects.all():
settings[obj.name] = obj.value
settings_dict['settings'] = json.dumps(settings)
return settings_dict
5) Then, add the context processors to your site settings.py:
TEMPLATE_CONTEXT_PROCESSORS = (
...,
'<yourapp>.core.contextprocessors.app_settings',
)
6) Log into your admin page via browser and create a setting:
http://yoursite/admin/core/setting/
Add a new setting, something like:
cell_status_colors
with a value of:
["green","red"] (note this has to be valid JSON, so use quotes around strings)
or:
daily_header
with a value of:
{"text":"Tomorrow only, free shipping!"}
7) And finally, each of your javascript files should be able to call these via standard JS:
var colors = site_settings.cell_status_colors || ["green","red"];
var msg_text = site_settings.daily_header || {"text":""};
Ideally, settings.py has to be accessed very rarely, because each access consists in exposing important data (db password for instance). So the best place to store extra settings is definitely a database table - a two column table, with the settings key and the actual settings data will suffice.
There are some applications, that allows django admins to define and use some settings through site-wide... One of those such applications(which i used) is dbsettings...
dbsettings applicaton...

Django - Affiliate links setup with URL redirection

Does anyone know of any better ways to replace long affiliate URL's with cloaked links or short, such that users only see shortened links.
Something like this :
"anothersite.com/offer.html/affiliate_id=001"
cloaked like this:
"http://site.com/click/offer"
Assuming if there more affiliate links like this, setting up redirection would easily fill up urls.py with more patterns. Django’s built-in generic views provide ways to setup (external) URL redirection applications but just wondering whether there are any better ways to do this without filling the .htaccess or urls.py.
If you want to be able to build on this in the future, you could create your own simple model:
class AffiliateLink(models.Model):
slug = models.SlugField(unique=True)
full_url = models.URLField()
Then create a view to do the redirection:
def affiliate_link(request, slug):
link = get_object_or_404(AffiliateLink, slug=slug)
return redirect(link.full_url)
Then setup the urls file:
(r'^affiliates/(?P<slug>[^/]+/', 'myapp.views.affiliate_link'),
And that it.
Django comes with an additional redirects app.

One admin for multiple sites

I have two sites with different SITE_IDs, but I want to have only one admin interface for both sites.
I have a model, which is just an extended FlatPage:
# models.py
class SFlatPage(FlatPage):
currobjects = CurrentSiteManager('sites')
galleries = models.ManyToManyField(Gallery)
# etc
# admin.py
class SFlatPageAdmin(FlatPageAdmin):
fieldsets = None
admin.site.register(SFlatPage, SFlatPageAdmin)
admin.site.unregister(FlatPage)
I don't know why, but there are only pages for current site in admin interface. On http://site1.com/admin/ I see flatpages for site1, on http://site2.com/admin/ I see flatpages for site2. But I want to see all pages in http://site1.com/admin/ interface! What am I doing wrong?
It's because of CurrentSiteManager. According to the documentation "it's a model manager that automatically filters its queries to include only objects associated with the current Site."
Remove the line and everyting should work as expected. Or if you make use of currobjects somewhere else in your code note that admin interface always use the first manager specified, so you need to specify the standard manager first, like this:
# models.py
class SFlatPage(FlatPage):
objects = models.Manager() # Is first, so will be used by admin
currobjects = CurrentSiteManager('sites') # your alternative manager
galleries = models.ManyToManyField(Gallery)