I've long been curious about what
admin.autodiscover()
actually do. The document didn't say much about it:
Above we used admin.autodiscover() to automatically load the INSTALLED_APPS admin.py modules.
and
There is really no need to use autodiscover when using your own AdminSite instance since you will likely be importing all the per-app admin.py modules in your myproject.admin module.
If I don't uncommnet
# admin.autodiscover()
what functionality I will lose?
And for what consideration should I use or not use autodiscover?
As u said: autodiscover() load all admin.py from the apps folders. So you have in the /admin/ all the models that you use (from your own app or not).
I recommend to use autodiscover() if you are going to use the admin app.
P.D. additionally some app have their on autodiscover with more functionalities.
admin.py is executed whenever your django loads URLconf from urls.py, the autodiscover() will search for all the apps in INSTALLED_APPS one by one and executes the code in that file.
Related
Situation: I have a Django project with multiple applications, each of which have one or more models. I have added custom admin actions for some of the models and got the "Delete selected" action for free. I want to disable this for all models in all applications.
I'm aware how to disable the "Delete selected" action for the whole project, i.e. by using
admin.site.disable_action('delete_selected')
but I simply don't know where to put it. I've put it in the admin.py file of one application and that definitely works, but that bugs me as (in my opinion) a site-wide change, also affecting other applications, shouldn't be caused by the admin configuration of a single application. I was hoping to find some way to have a "project-level" admin.py file, but it seems that only fully customizing the AdminSite will do the trick, which I think is a bit overkill if I only want to disable the "delete_selected" action globally.
Any thoughts?
It's somewhat up to you, but to do that where you are already importing the necessary module would make sense.
It's likely you could do this in your project urls.py, and taking one of mine as an example you'd do;
from django.contrib import admin
from django.urls import re_path, path, include
from django.views.decorators.cache import cache_page
from django.views.i18n import JavaScriptCatalog
try:
admin.site.disable_action('delete_selected')
except KeyError:
pass
urlpatterns = [
path(
'jsi18n/',
cache_page(60 * 60, cache='pages')(
JavaScriptCatalog.as_view()
),
name='javascript-catalog'
),
path(
'admin/',
admin.site.urls
)
]
I've wrapped it in a try/except because I've seen a KeyError when testing this.
I think it makes sense to organize my Django apps in a different way (by protocol):
📂myapp
apps.py
models.py
utilities.py
📂html
admin.py
urls.py
views.py
📂rest
serializers.py
urls.py
views.py
📂graphql
etc
This would move the admin.py file into the html folder, but sadly autodiscover does not seem to find it anywhere else than in the myapp folder. Is there a way to point to the correct location: perhaps in apps.py? I am sure a symlink would work, but not really what I am after.
There's nothing magic about either admin.py or autodiscover. All admin.py does is run any register calls on import, and all autodiscover does is look for an admin.py file in each installed app and import it. There's nothing to stop you importing your admin.py in its custom location from somewhere else, eg the models or views files.
This was somewhat tricky, but the comment by #Joran Beasley set me in the right direction.
For admin.py to be autodiscovered it needs to be either in the top level app folder, or be imported in a file. However... not every file works.
If you do from .html import admin
into __init__.py or apps.py: django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
into models.py: Results in an importerror from models into admin. I think due to the circular nature of the imports.
However the following works: from . import admin inside the urls.py. Make sure to add a comment that this import is needed for autodiscover purposes.
So I have 2 apps for my django project. In the default mysite folder, you have the urls.py file. I cant seem to find out how to write all my urls for all the apps in just the one urls.py file.
I have tried this:
from reviews.models import *
however that isnt working.
Thanks for any help!
Just realised that i was importing the models instead of the views.
the code above should be from reviews.views import *
Try and prevent confusion with explicit view names, explicit is better than implicit
from reviews import views as reviews_views
from other_app import views as other_app_views
I have many apps and i want to activate the admin for all the models in my all apps.
I remember few months one of my friend did something that enables the admin site without any admin.py file
he did something in settings.py files with INSTALLED_APPS and all of the apps showed in the admin section
I am now not able to find that. Any one??
Your friend probably did something like...
from django.db.models import get_models
for model in get_models():
admin.site.register(model)
In one of his admin.py files.
I dunno, I'd only do this to test stuff. It's a bit too magical. Remember you'll have to explicitly unregister any models you may want to register again.
Just follow the instructions in the docs. They describe how to activate the django admin site here. You need to modify urls.py. That's it.
There are even comments in that file that tell you which three lines to uncomment.
I recommend you do include an admin.py in your apps because it is the Django convention and that some explicitness in what you trying to achieve is a good thing; the solution I use is the following default admin.py template for all new apps I start:
# Auto registers any new models with the admin, eventually you will want a tailored admin.py
from django.contrib import admin
current_app = models.get_app(__package__)
for model in models.get_models(current_app):
admin.site.register(model, admin.ModelAdmin)
Which is the best way to implement my own django.contrib.admin.sites.AdminSite?
Actually I get a problem with the registration of INSTALLED_APPS in django.contrib.admin.autodiscover. If I use my custom AdminSite class in urls.py, there were no apps displayed on the admin page.
I fixed this with a litte hack. I wrote this class:
from django.contrib.admin.sites import site as default_site
class AdminSiteRegistryFix( object ):
'''
This fix links the '_registry' property to the orginal AdminSites
'_registry' property. This is necessary, because of the character of
the admins 'autodiscover' function. Otherwise the admin site will say,
that you havn't permission to edit anything.
'''
def _registry_getter(self):
return default_site._registry
def _registry_setter(self,value):
default_site._registry = value
_registry = property(_registry_getter, _registry_setter)
And implement my custom AdminSite like this:
from wltrweb.hacks.django.admin import AdminSiteRegistryFix
from django.contrib.admin import AdminSite
class MyAdminSite( AdminSite, AdminSiteRegistryFix ):
# do some magic
pass
site = MyAdminSite()
So I can use this site for urls.py.
Anyone knows a better way? Since I access a var starting with a underscore it is no more than a hack. I don't like hacks.
Edit: Another way would be to rewrite the django.contrib.admin.autodiscover function, but in this case I would have redundant code.
The Problem
Using a custom class derived from django.contrib.admin.AdminSite for the admin site of a project, without having to write custom registration code to register models with the new class. When I use 3rd party apps with their own models, I'd rather not have to edit custom registration code only because models were added or removed from these apps.
The Solution
You have to switch the instance created with the default class used for the admin site to your own instance, created with your own class before django.contrib.admin's autodiscover function is called. I do this by:
Having an app that will perform the switch. (I use my project-specific app named core for my own purposes.)
Two choices:
Django 1.6 to 1.9: use __init__ of the app to perform the switch. In Django 1.8, you will get a deprecation warning due to the change in Django 1.9 quoted below. Note that this method will work with 1.9 too because the Django modules loaded by the code shown below have been changed in 1.9 so that they no longer load models. When I use this method my core/__init__.py file contains:
from django.contrib import admin
from django.contrib.admin import sites
class MyAdminSite(admin.AdminSite):
pass
mysite = MyAdminSite()
admin.site = mysite
sites.site = mysite
Django 1.9 and over: use the app configuration of the app to perform the switch. As of Django 1.9, as the release notes state:
All models need to be defined inside an installed application or declare an explicit app_label. Furthermore, it isn’t possible to import them before their application is loaded. In particular, it isn’t possible to import models inside the root package of an application.
I prefer to limit the imports I do at the root level to avoid the risk of loading models. While as of version 1.9 using the __init__ method above will work, there's no telling if 1.10 or a later version will introduce a change that will cause problems.
When I use this method the core/__init__.py sets default_app_config = "core.apps.DefaultAppConfig" and I have a core/apps.py like this:
from django.apps import AppConfig
class DefaultAppConfig(AppConfig):
name = 'core'
def ready(self):
from django.contrib import admin
from django.contrib.admin import sites
class MyAdminSite(admin.AdminSite):
pass
mysite = MyAdminSite()
admin.site = mysite
sites.site = mysite
While it is possible to use this method with versions 1.7 and 1.8, it is a bit risky to use it with those versions. See the notes below.
Placing this app earlier than django.contrib.admin in the INSTALLED_APPS list. (This is absolutely necessary for 1.7 and later. In earlier versions of Django, it might work okay even if the app is later than django.contrib.admin. However, see the notes below.)
Notes and Caveats
The app that performs the switch should really be the first app in the INSTALLED_APPS list so as to minimize the chance that something else will grab the value of site from django.contrib.admin before the switch is made. If another app manages to get that value before the switch is done, then that other app will have a reference to the old site. Hilarity will surely ensue.
The method above won't work nicely if two apps are trying to install their own new default admin site class. This would have to be handled on a case-by-case basis.
It is possible that a future release of Django could break this method.
For version prior to 1.9, I preferred using __init__ to do site switch over using the app configuration because the documentation on initialization indicates that the ready() method of the app configurations is called relatively late. Between the time an app's module is loaded, and the time ready() is called, models have been loaded, and in some case, it could mean that a module has grabbed the value of site from django.contrib.admin before ready is called. So as to minimize the risk, I have the app's __init__ code do the switch.
I believe the risk that existed in version 1.7 and 1.8 and that I avoided by using __init__ to perform the site switch as early as possible does not exist in 1.9. Everybody is prohibited from loading modules before all the applications are loaded. So doing the switch in the ready callback of the first application listed in INSTALLED_APPS should be safe. I've upgraded a large project to 1.9 and used the app configuration method, without any problem.
From Django 2.1, there is an 'out-of-the-box' solution: https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#overriding-the-default-admin-site
from django.contrib import admin
class MyAdminSite(admin.AdminSite):
...
Swapping the custom admin site is now done by adding your own AdminConfig to installed apps.
from django.contrib.admin.apps import AdminConfig
class MyAdminConfig(AdminConfig):
default_site = 'myproject.admin.MyAdminSite'
INSTALLED_APPS = [
...
'myproject.apps.MyAdminConfig', # replaces 'django.contrib.admin'
...
]
Note the difference between AdminConfig and SimpleAdminConfig, where the latter doesn't trigger admin.autodiscover(). I'm currently using this solution in a project.
Having encountered the same kind of issue while implemeting site-wide custom AdminSite in Django 3.2, I found a workaround to make it work.
It seems the documentation should be updated accordingly.
from django.contrib.admin.apps import AdminConfig
class MyAdminConfig(AdminConfig):
default_site = 'myproject.admin.MyAdminSite'
This raises an exception:
RuntimeError: 'myproject.apps' declares more than one default AppConfig: 'AdminConfig', 'MyAdminConfig'.
It is solved by importing django.contrib.admin.apps instead of django.contrib.admin.apps.AdminConfig:
from django.contrib.admin import apps
class MyAdminConfig(apps.AdminConfig):
default_site = 'myproject.admin.MyAdminSite'
Then the exception moves on:
django.core.exceptions.ImproperlyConfigured: Application labels aren't unique, duplicates: admin
This is caused by the settings.py configuration:
INSTALLED_APPS = [
'myproject.apps.MyAdminConfig', #replaces django.contrib.admin
...
It is solved by removing the 'MyAdminConfig' from the settings:
INSTALLED_APPS = [
'myproject.apps', #replaces django.contrib.admin
...
Quoting from https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#customizing-the-adminsite-class
If you'd like to set up your own administrative site with custom behavior, however, you're free to subclass AdminSite and override or add anything you like. Then, simply create an instance of your AdminSite subclass (the same way you'd instantiate any other Python class), and register your models and ModelAdmin subclasses with it instead of using the default.
I guess that is the most explicit approach, but it also means that you need to change the register code in your apps admin.py files.
There is really no need to use autodiscover when using your own AdminSite instance since you will likely be importing all the per-app admin.py modules in your myproject.admin module.
The assumption seems to be, that once you start writing your custom admin site, it becomes pretty much project specific and you know beforehand which apps you want to include.
So if you don't want to work with the hack above, I only really see these two options. Replace all register calls to your custom admin site or register the models explicitly in your adminsite module.