Best practice for Django sites to set up site configuration variables? - django

all! I am writing a Django blog site.
I am new to Django. Since Django has the philosophy of loose coupling, I believe it's best to follow their beliefs when using their framework.
So I am encountering a dilemma here:
I want to set up some variables for my blog, say, the blog title, slogan, and the maximum length of digest on the homepage of each blog I wrote, and how many blog digest should I display on the homepage per page.
I can do this by creating a new app in my Django project, and create models for my site's config variables, then read those variables from other app, but this practice obviously breaks the philosophy of loose coupling.
The only work around I can think of is setting up environment variables in my sites .wsgi file (I use Apache and mod_wsgi to serve Python scripts) But I don't think messing up with environment variable is 'clean' enough.
Can anyone suggest me a better solution?

First thing you can do is set up those variables in your project's settings module, many apps do that:
# settings
BLOG_TITLE = 'My title'
Then, a good practice is to provide settings defaults, so your app should have a settings file
# blog/settings.py
from django.conf import settings
BLOG_TITLE = getattr(settings, 'BLOG_TITLE', 'MY default title')
# Then wherever, views or context processor
from blog import settings # See how we're not importing project's settings
title = settings.BLOG_TITLE
Another alternative is to create a "Blog" model that contains all those variables, this may invite you to make your app to have a blog tied per Django sites
from django.contrib.sites.models import Site
class Blog(models.Model):
site = models.OneToOneField(Site) # Tied up to a Django site
title = models.CharField(max_length=256)
Now you can change those values from your Admin interface and use them in your views or context processor
site = Site.objects.get_current() # Installed Django Sites app and middleware
blog = site.blog
print blog.title

You can create settings or constants file in your app folder and use it. This way all your constants will be bound to your application:
apps/
blog/
__init__.py
models.py
urls.py
constants.py # <-- here it is!
File can look like this:
BLOG_TITLE = 'My super blog'
# ...
And you can use tour constants like this:
import blog.constants
# ...
return render_to_response('index.html', {title: blog.constants.BLOG_TITLE})
And in template:
<title>{{ title }}</title>

In my opinion, the best way to do this is by adding a model related to Site model using inheritance
First add site id to your Django settings file
SITE_ID = 1
now create a model in an app
from django.db import models
from django.contrib.sites.models import Site
class Settings(Site):
field_a = models.CharField(max_length=150, null=True)
field_b = models.CharField(max_length=150, null=True)
class Meta:
verbose_name_plural = 'settings'
db_table = 'core_settings' # core is name of my app
def __str__(self) -> str:
return 'Settings'
then edit apps.py file of that app
from django.apps import AppConfig
from django.db.models.signals import post_migrate
def build_settings(sender, **kwargs):
from django.contrib.sites.models import Site
from .models import Settings
if Settings.objects.count() < 1:
Settings.objects.create(site_ptr=Site.objects.first())
class CoreConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'project.apps.core'
def ready(self) -> None:
post_migrate.connect(build_settings, sender=self)
now every time you run migrations a row will auto-generated in core_settings that have a one to one relationship with your Site model
and now you can access to your settings like this
Site.objects.get_current().settings.access_id
optional: if have only a single site
unregister site model from admin site and disable creating and deleting settings model in admin panel
from django.contrib import admin
from . import models
from django.contrib.sites.models import Site
admin.site.unregister(Site)
#admin.register(models.Settings)
class SettingAdminModel(admin.ModelAdmin):
def has_delete_permission(self, request,obj=None) -> bool:
return False
def has_add_permission(self, request) -> bool:
return False

Related

how to redirect from HomePage(Page) to your custom app model

I've created an app "Blog". In my app I've got several models including "BlogIndex(Page)". When I run local server I find myself at "home_page.html". What I want is to start my local server at "blog_index.html". I know that I can set a root page at settings>site>localhost to make my "blog_index.html" a root page, but I can't do this because in my app I've got some other models that live at the same level as "BlogIndex(Page)" and they are children of the root which is "HomePage" so it would brake my code. So my question is: can I make a redirect from "HomePage(Page)" to my "BlogIndex" so that when i start my server I would be automatically redirected from "HomePage" to "BlogIndex"? How can I do it? How much it will affect the performance of the site and it's optimization?
I know that there is settings>redirect but it works only for inactive pages, but i need "HomePage" to be active.
Thank you.
Perhaps a better approach would be to display your blog posts (and any other models you want) on your homepage. Just override get_context(). See here: Wagtail Views: extra context
Update:
You can redirect by overriding the serve() method. For example, in your model, you would do something like:
# home/models.py
...
from django.http import HttpResponseRedirect
from django.urls import reverse
class HomePage(Page):
body = RichTextField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('body', classname="full"),
]
def serve(self, request):
# Redirect to blog index page
return HttpResponseRedirect('/blog/')
# only do this if you're using urls.py and namespaces
# return HttpResponseRedirect(reverse('blog:index'))
More info: http://docs.wagtail.io/en/latest/reference/pages/model_recipes.html?highlight=serve()#overriding-the-serve-method

Auto register Django auth models using custom admin site

I implemented authentication management using Django auth with the default admin site but then I wanted to use my own AdminSite to rewrite some behaviors:
class OptiAdmin(admin.AdminSite):
site_title = "Optimizer site's admin"
#...Other stuff here
Then registered my own models:
admin_site = OptiAdmin(name='opti_admin')
admin.site.register(MyModel, MyModelAdmin)
#Other stuff here
But when I go to the admin site I am only able to see the models I just registered, which sounds fair to me but I would like to see all the other apps models in this new custom site including the auth's users and groups and I don't know how to do this automatically like the default admin does, pls help :).
Create your own AdminSite with a simple __init__() override.
Import your admin in urls.py.
Replacing the Django Admin and getting the autodiscover() behavior is possible with minimal effort. Here's a project structure generated in the typical django-admin startproject project fashion:
project/
manage.py
project/
__init__.py
settings.py
urls.py
wsgi.py
admin.py # CREATE THIS FILE
project/admin.py: (I think it makes the most sense to do this at the project level.)
from django.contrib.admin import * # PART 1
class MyAdminSite(AdminSite):
site_header = "My Site"
def __init__(self, *args, **kwargs):
super(MyAdminSite, self).__init__(*args, **kwargs)
self._registry.update(site._registry) # PART 2
site = MyAdminSite()
project/urls.py (snippet):
from . import admin # PART 3
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
Part 1 is simple Python. By importing everything from django.contrib.admin into your namespace, it acts as a drop-in replacement. I suppose you don't have to do this, but it helps preserve expectations. Part 3, simply connect up your admin. Part 2 is the real trick. As the documentation says, autodiscover() is called to do the work. All autodiscover does is go through INSTALLED_APPS attempting to import a file called admin.py. Importing runs the code of course and that code is doing the same thing you do to register models (example by decorator and example by method). No magic. You don't have to register your models with your customized admin (as the documentation says).
Autodiscover looks smarter than it is with its register_to kwarg. That indicates you could call autodiscover() yourself passing your own admin. Nope; there's no wiring connected there (future feature?). The assignment happens here and is fixed to the native AdminSite instance here (or here using the decorator). Django contrib models register to that instance and so will any third-party libraries. It's not something you can hook into.
Here's the trick though, _registry is just a dictionary mapping. Let Django autodiscover all the things and then just copy the mapping. That's why self._registry.update(site._registry) works. "self" is your customized AdminSite instance, "site" is Django's instance and you can register your models with either.
(Final note: If models are missing, it's because of import order. All the registration to Django's AdminSite needs to happen before you copy _registry. Registering directly to your customized admin is probably the easiest thing.)
The Django docs suggest using SimpleAdminConfig with a custom admin site.
INSTALLED_APPS = (
...
'django.contrib.admin.apps.SimpleAdminConfig',
...
)
That prevents the models being registered with the default AdminSite.
The docs seem to assume that you will import the models individually and add them to your custom admin site:
from django.contrib.auth.models import Group, User
from django.contrib.auth.admin import GroupAdmin, UserAdmin
admin_site.register(Group, GroupAdmin)
admin_site.register(User, UserAdmin)
This would be very repetitive if you have models in many apps. It doesn't offer any advice how to automatically register models from all your apps with your custom site.
You could try monkey patching admin, and replacing admin.site with your own.
from django.contrib import admin
admin.site = OptiAdmin(name='opti_admin')
Then, when code called admin.site.register(), it would register the model with your admin site. This code would have to run before any models were registered. You could try putting it in the AppConfig for your app, and make sure that your app is above django.contrib.admin.
Adding to JCotton's great answer:
Using django 2.0, overriding site_header and site_title in the custom admin site only works for the index page.
To get it to work with all admin views, extend JCotton's code with the following:
def __init__(self, *args, **kwargs):
super(MyAdminSite, self).__init__(*args, **kwargs)
self._registry.update(site._registry) # PART 2
for model, model_admin in self._registry.items():
model_admin.admin_site = self
Just include init method in your CustomAdminSite class like this.
class CustomAdminSite(admin.AdminSite):
def __init__(self, *args, **kwargs):
super(CustomAdminSite, self).__init__(*args, **kwargs)
self._registry.update(admin.site._registry)

Registering Django system checks in AppConfig's ready() method

In the docs for Django's System check framework it says:
Checks should be registered in a file that’s loaded when your application is loaded; for example, in the AppConfig.ready() method.
None of the examples on that page, or around the AppConfig.ready() method, show how to do this. Given a checking method like:
from django.core.checks import register, Tags
#register(Tags.compatibility)
def my_check(app_configs, **kwargs):
# ... perform compatibility checks and collect errors
return errors
How would you do this in/from the AppConfig.ready() method? Is one called from the other? Which file should the above method go in? Do you add #register(...) to the ready() method?
From reading the examples on this page about the Apps Registry and System Checks Framework, it seems there are (at least) two ways to add your own System Checks. To adapt that page's examples (assuming you're creating an app called myapp):
1) Create a myapp/checks.py file like this:
from django.apps import apps as camelot_apps
from django.core.checks import register, Warning
from django.core.checks import Tags as DjangoTags
class Tags(DjangoTags):
"""Do this if none of the existing tags work for you:
https://docs.djangoproject.com/en/1.8/ref/checks/#builtin-tags
"""
my_new_tag = 'my_new_tag'
#register(Tags.my_new_tag)
def check_taggit_is_installed(app_configs=None, **kwargs):
"Check that django-taggit is installed when usying myapp."
errors = []
try:
from taggit import models
except ImportError:
errors.append(
Warning(
"The django-taggit app is required to use myapp.",
hint=("Install django-taggit"),
# A unique ID so it's easier to find this warning:
id='myapp.W001',
)
)
return errors
And then in myapp/__init__.py (create it if it doesn't exist):
from . import checks
Running this should run that check above:
$ ./manage.py check myapp
2) Or, as I thought in my initial question, you can register that check in your AppConfig. So, keep the above code in myapp/check.py, but remove the #register(Tags.my_new_tag) line.
Then create myapp/apps.py containing this:
from django.core.checks import register
from .checks import Tags, check_taggit_is_installed
class MyappConfig(AppConfig):
name = 'myapp'
def ready(self):
super(MyappConfig, self).ready()
register(Tags.my_new_tag)(check_taggit_is_installed)
And alter myapps/__init__.py so it contains this:
from . import checks
default_app_config = 'myapp.apps.MyappConfig'
The first example seems simpler, with no need for a custom AppConfig.
Django recommends:
Checks should be registered in a file that’s loaded when your application is loaded; for example, in the AppConfig.ready() method.
Therefore, place the checks code in a checks.py file. Then simply in apps.py, as with signals:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'MyApp'
verbose_name = "My App"
def ready(self):
# import myapp.signals
import myapp.checks
My guess is that you can just place this code in ready() method. For example of usage you can take a look at django.contrib.auth (or other contrib app). This app has check in checks.py file, that imported in apps.py and registered in ready() method: checks.py, apps.py.

Django signal get request full path

I wrote a signal in my django project, inside I want to send an email with the full path of my website. But to do so, I need to get the request object inside the signal, how could I do that? Thanks.
#receiver(pre_save, sender=AccessRequest)
def email_if_access_true(sender, instance, **kwargs):
#How can I get the full path of my website here?
pass
If you don’t have access to the request object, you can use the get_current() method of the Site model’s manager.
from django.contrib.sites.models import Site
#receiver(pre_save, sender=AccessRequest)
def email_if_access_true(sender, instance, **kwargs):
current_site = Site.objects.get_current()
if current_site.domain == 'foo.com':
#do other stuff here
else:
pass
you need to ensure that you defined SITE_ID=1 in your settings
If you don’t have access to the request object, you can use the get_current() method of the Site model’s manager.
from django.contrib.sites.models import Site
#receiver(pre_save, sender=MyModel)
def my_function_without_request(sender, instance, **kwargs):
current_site = Site.objects.get_current()
domain = current_site.domain
# Do something
To enable the sites framework, follow these steps:
Add 'django.contrib.sites' to your INSTALLED_APPS setting.
Define a SITE_ID setting: SITE_ID = 1
Run migrate.
Source: https://docs.djangoproject.com/en/3.2/ref/contrib/sites/
To set the correct name and domain for your project, you can use a data migration.
To start, make an empty migration file you can work from (Django will put the file in the right place, suggest a name, and add dependencies for you):
python manage.py makemigrations --empty yourappname
Then, open up the file and create a new function and have RunPython use it.
from django.contrib.sites.models import Site
from django.db import migrations
def set_name_and_domain():
one = Site.objects.all()[0]
one.domain = 'yourdomain.com'
one.name = 'Your Name'
one.save()
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(set_name_and_domain),
]
All of this will save your domain and site name in your database where you can access it without having the request object. So far I have not found a better way to do this.
Source: https://docs.djangoproject.com/en/3.2/topics/migrations/#data-migrations
Put following code in your pre_save signal receiver:
from django.contrib.sites.models import get_current_site
current_site = get_current_site(request=None)
domain = current_site.domain
protocol = "http"
You can generate absolute url to your website in email by passing required context variables to template. If access_request is instance in your case and there is one get_abosulte_url() method in your AccessRequest model, then following line in email template will give you absolute url.
{{ protocol }}://{{ domain }}{% access_request.get_absolute_url %}
Reference - PasswordResetForm in django.contrib.auth.form.

Save in Django Admin sometimes fails

I have a portfolio page with django, and i'm noticing a strange behaviour when a try to save an edited entry. It saves, and then it seems that the redirect fails to catch the ID and then goes to a 404 page that says domain.com/admi/internal_error.html/ in the URL (note the admi, sometimes is ad or adm). It happens with both 'Save' and 'Save and continue editing'.
This is my model
from django.db import models
from ramonlapenta.portfolio.managers import EntryManager
from ramonlapenta.slughifi import slughifi
class Type(models.Model):
type = models.CharField(max_length=100)
def __unicode__(self):
return self.type
class Entry(models.Model):
type = models.ForeignKey(Type)
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100)
slug = models.SlugField()
description = models.TextField()
requirement = models.TextField()
solution = models.TextField()
url = models.CharField(blank=True, max_length=64)
published = models.BooleanField(db_index=True, default=True)
logo = models.ImageField(upload_to="logos")
image = models.ImageField(upload_to="portfolio", height_field='height', width_field='width')
height = models.CharField(max_length=3)
width = models.CharField(max_length=3)
objects = EntryManager()
def __unicode__(self):
return u"%s - %s" % (self.title, self.created)
class Meta:
verbose_name_plural = "entries"
def save(self, *args, **kwargs):
self.slug = slughifi(self.title)
super(Entry, self).save(*args, **kwargs)
And this is the admin.py
from django.contrib import admin
from ramonlapenta.portfolio.models import Type
from ramonlapenta.portfolio.models import Entry
from django import forms
class EntryAdmin(admin.ModelAdmin):
fieldsets = [
('Title', {'fields' : ['title']}),
('Address', {'fields' : ['url']}),
('Description', {'fields' : ['description','requirement','solution']}),
('Type', {'fields' : ['type']}),
('Image', {'fields' : ['logo', 'image']}),
]
list_display = ('title', 'created')
list_filter = ['created', 'type']
admin.site.register(Type)
admin.site.register(Entry, EntryAdmin)
This is my entry manager, where i only filter the results.
from django.db import models
class EntryManager(models.Manager):
def published(self):
return self.model.objects.filter(published=True).order_by('-id')
This is the general urls.py
from django.conf.urls.defaults import patterns, include, url
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^', include('site.pages.urls')),
(r'^', include('site.blog.urls')),
(r'^', include('site.portfolio.urls')),
(r'^admin/', include(admin.site.urls)),
)
And the portfolio urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('ramonlapenta.portfolio.views',
url(r'^portfolio/$', 'index', name="port-main"),
url(r'^portfolio/(?P<item_slug>[a-zA-Z0-9_.-]+)/$', 'detail', name="port-slug"),
url(r'^portfolio/type/(?P<type>[a-zA-Z0-9_.-]+)/$', 'type', name="port-type")
)
Thos is what i see with HttpFox:
Note: everything else works fine, even there it sometimes works fine, and the public site works fine too. The problem is just editing a portfolio entry.
Does anybody knows why is this happening and how can i fix it?
My guess is that the main url.py is catching some of the admin data (being that any admin action will be considered by all the other urlconfs.
Try to put the admin urls at the top:
urlpatterns = patterns('',
(r'^admin/', include(admin.site.urls)),
(r'^', include('site.pages.urls')),
(r'^', include('site.blog.urls')),
(r'^', include('site.portfolio.urls')),
)
Edit: second guess
The key information is that second 302 in the request list. Someone (probably dreamost's apache) is eating the response and returning a redirect to a custom page.
In this case setting DEBUG = True is of no help, because the response would have anyway the same error.
This means two things:
You have an error somewhere in your Model's code, probably some attribute/field/property that's used to display the object.
To isolate the error, try to find a pattern on which specific values trigger the error and which values allows to load the admin form.
You can't see the django error page unless you either work locally in the development server (manage.py runserver) or disable custom error pages in dreamhost as other suggest.
Knowing your setup and what appears in server logs at the time you're saving an entry might help us locate a problem. It's likely that the cause is outside Django (since the bug is not reproducible under Django's development server).
Anyway, you might find some of these solutions relevant:
Django on dreamhost problem (old post, but symptoms described there are similar, might be relevant if you're running Django on FastCGI).
getting 500 internal server error with django on dreamhost (in short: just try re-create the whole setup and see if that helps).
Or you may want simply to google dreamhost django 500 in case you haven't already, seems like people frequently encounter similar problem while setting up Django on Dreamhost.
I'll be curious to see your urls.py . My guess is that the regex for the admin site is bogus.
For sure an error is happening when you save those entries and you should check your logs. Or try reproduce it with DEBUG=True
My guess is that you are using dreamhost and Dreamhost allows you to define custom error pages and "internal_error.html" is for the status 500 error page (see : http://wiki.dreamhost.com/Creating_custom_error_pages).
Try to have a look at your dreamhost error.log file.
The sure thing is that the problem comes from your dreamhost configuration and not your django code (since it is working properly in development with Django 1.2)