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

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...

Related

Django Getting Data from Template

We have a notification system in place (model extract below) and every time the site needs to notify any user about anything, it creates a notification. Now we want to show that on every site (we use a global template) a counter of the unread messages without changing every view to deliver it to the template. Is there any way to do this right?
class Notification(models.Model):
n_id = models.AutoField(primary_key=True)
n_body = models.CharField(max_length=1000, null=True)
n_recipient = models.ForeignKey(User, related_name='Recipient', on_delete=models.CASCADE)
n_read_status = models.BooleanField(default=False)
Our query would be Notification.objects.filter(n_recipient=request.user, n_read_status=False).count() but we don't want to call it in every view manually.
You can use 2 options:
1 - Using template tags:
Custom template tags and filters
Basically call a custom template tag with user object and get the notification data when you need it.
2 - Using django middleware:
Django docs: Middleware
Add your notification data to request context. Example here:
Django - How to modify template context from middleware
Option one makes more sense cause you might not need notification data everywhere and it requires to check for user objects and ... which can be extra unnecessary process and error handling is kinda harder and if you don't handle it properly, it can cause errors on every page while template tags only used when needed.
Like #DanielRoseman and #Navid2zp pointed out, the correct solution would probably be a template tag which is created by adding a templatetags folder (more info in Django docs). Our working code below:
HTML template
{% load notifications %}
{{ messages|getmessages:request.user }}
notifications.py
from django import template
from Toolbox.models import Notification
from django.contrib.auth.models import User
register = template.Library()
#register.filter
def getmessages(value, user):
number = Notification.objects.filter(n_recipient=user, n_read_status=False).count()
return '' if number == 0 else ' (' + str(number) + ')'

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

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

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.

Django 1.4 Creating an Edit User Profile Account Settings View

Some background,
I've been designing a user profile for our Django database system and am currently trying to create a views page to allow the user to edit their account settings (outside of the admin page). I have searched numerous sources, most of which recommend using .get_profile(), but this has not worked in my program no matter what approach I try (to my knowledge and research done). So far I have used the django main pages for .get_profile() to see how to properly use .get_profile() and found the AUTH_PROFILE_MODULE setting ( I will specify my settings in a bit).
Using this site:
http://www.turnkeylinux.org/blog/django-profile I found out some other methods of using .get_profile() but still nothing has worked. (I won't add the rest of the links i've tried as I could go on for a while)
Back to the question, can anyone recommend a method that would work so that I can obtain the users information to set up some default values to maintain their current settings if they choose not to edit them and only update the ones with new submitted values.
So far I have: (hopefully all the relevant parts)
file directory:
~/Documents/project1
settings.py
AUTH_PROFILE_MODULE = "accounts.Account"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`
File location:
project1/accounts
models.py
# additional model to incorporate our custom fields to the auth user model
class Account(models.Model):
userLink = models.OneToOneField(User) #link (pointer) to the users other information in User model
birthdate = models.DateField(blank = True) # True makes this field optional
gender = models.CharField(max_length = 1, choices = GENDER_CHOICE, null = True)
def __unicode__(self): # define a unicode for the user to access
return u'%s %s' % (self.userLink.first_name, self.userLink.last_name) # return first and last name
# custom Form to change user account information (can't be a modelForm)
class EditAccountForm(forms.Form):
gender = forms.CharField(max_length = 1)#, choices = GENDER_CHOICE, null = True)
birthdate = forms.DateField(widget = SelectDateWidget()) # True makes this field optional
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
views.py
# Account settings view
#login_required
def AccountSettings(request):
sluggedSettingError = request.GET.get('error', '') # error message with slugged character
settingError = sluggedSettingError.replace('-', ' ')
settingForm = AdditionalForm(request.POST or None) # variable is called in edit_user.html
# might want to have some way to create a new profile if the account doesn't exist
userSettings = Account.objects.filter(userLink=request.user)
# userSettings = request.user.get_profile() # returns the current settings of the users profile
#### Currently trying to get it to let me use get_profile() ########
print "userLink = %s" % userSettings#.user
# print "Gender = %s" % userSettings.gender # testing print
if request.method == 'POST':
# if settingForm.is_valid():
# settingForm.save();
return HttpResponseRedirect('/')
return render_to_response("user_settings.html", {'settingForm': settingForm, 'settingError': settingError}, context_instance = RequestContext(request))
# gender = request.POST['gender'] # pass in the gender variable from post
# birthdate = request.POST['birthdate'] # pass in the birthdate from post
As the code is currently I gave up on the .get_profile() approach and tried doing a query through the available accounts until a matching userLink was found and then saving that to a variable "userSettings". The approach I was taking was to use this variable as userSettings.gender, userSettings.birthdate etc. to access each of the users settings and have them changed if updated and set to the previous value if not (with a series of if statements or some built in Django function).
I'm not sure if this is the most efficient method, or if I should revert back to the get_profile() method. When I was using get_profile() (as it is typed currently in the commented out line) I got the following error
Cannot resolve keyword 'user' into field. Choices are: birthdate, gender, id, userLink
I also tried this approach with a new database using userLink defined as "user" instead to match what the django.docs website specified but still got an error about the Account not existing. I tried creating a try: and except: approach from
https://bitbucket.org/filmaster/filmaster-test/src/1618b18003ed/film20/userprofile/views.py
to handle this error but this also did not yield any positive results.
I am out of ideas on how to get .get_profile() to work, and can't think of anything to allow me to access the users stored information so that it can be edited in a form by them. I'm not sure I understand how to utilize it properly at this point and feel like I'm just running around in circles at this point. If anyone can help me with this I'd appreciate it. I tried to give as much relevant information as I could, but if you need something else to help me out let me know and I will try to include it as soon as I can.
Thanks in advance!
To get the profile attached to a user object, you just need to call get_profile() on the user object:
user.get_profile()
For example,
request.user.get_profile()
The docs on it are in Django Docs. Its worth noting that
The method get_profile() does not create a profile if one does not exist.
So you have to have a trigger to create profiles on creating users ...
user.get_profile() is now depreciated, instead, assuming you have a OneToOne with the user model, you can just use user.profile

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)