Well, let me explain this.
I am working on a simple django admin project.
In the admin.py file, I have the following admin classes:
class A_Admin(admin.ModelAdmin):
#some stuff
class B_Admin(admin.ModelAdmin):
#some stuff
I want to override the get_urls() method of A_Admin that if I click a button on A_Admin instance change page, it will redirect the page to B_Admin changelist page.
(I know there are many ways to do what I want and what I mentioned above is not the best, but this is what I want. So let skip the discussion why I insist on this solution.)
I want to the following:
def get_urls(self):
#django's code
#inside the urlpattern
urlpattern = (
#default urls from django admin
.....
url(r'^some_url$',
wrap(super(B_Admin, self).changelist_view),
name='%s_%s_delete' % info),
....)
return urlpatterns
This is not working, since 'self' is a A_Admin class object rather than B_Admin obejct.
So is there any way to get the proxy of calss A_Admin inside B_Admin?
I just wanna override changelist_view of A and call it inside B.
Is this possible?
Thanks in advance
You should just instantiate B_Admin and use its method.
I believe the following code should work:
from django.contrib import admin
from my_app.models import B_Model # The model for which B_Admin is used
def get_urls(self):
#django's code
#inside the urlpattern
urlpattern = (
#default urls from django admin
.....
url(r'^some_url$',
wrap(B_Admin(B_Model, admin.site).changelist_view),
name='%s_%s_delete' % info),
....)
return urlpatterns
UPDATE: Most probably, B_Admin was already instantiated when you called
admin.site.register(B_Model, B_Admin)
So instead of doing
B_Admin(B_Model, admin.site)
again you can just get it from the AdminSite's registry:
admin.site._registry[B_Model]
Related
There is a class in admin.py:
class NewsAdmin(TranslationAdmin):
form = NewsForm
list_display = ('title', 'date' 'show_url')
def show_url(self, obj):
return format_html("<a href='http://www.host.ru/news/{url}' target='_blank'>view</a>", url=obj.id)
show_url.short_description = "View"
Need a link http://www.host.ru replace with a relative one, something like this: protocol + domain name + news/{url}
How can this be done?
I think you are better off making it hardcoded value in your settings, like BASEURL = 'http://www.host.ru'
then having different settings.py for production and development (dev with BASEURL = 'https://localhost:8080' for testing)
and then you can fetch it with:
def show_url(self, obj):
from django.conf import settings
return format_html("<a href='{base}/news/{url}' target='_blank'>view</a>", base=settings.BASEURL, url=obj.id)
# or use reverse so it's dynamically linked to your urls.py (this is what I do)
def show_url_with_reverse(self, obj):
from django.conf import settings
from django.urls import reverse
return format_html("<a href='{base}{url}' target='_blank'>view</a>",
base=settings.BASEURL,
url=reverse('myviewname', args=[obj.id])
)
This isn't the answer you wanted, but I'm throwing it in as a way. I hope someone smarter than me has found a better way, but this is a fallback.
Reasoning
This is all moot because you don't have access to the request object inside that method.
I've found that you need the request object to dynamically get the host, the www.host.ru part, with request.get_host().
But that still leads the http:// and https://, which is not available anywhere (that I've found), so I have that hardcoded in my settings.py.
So either way something will have have to be hardcoded, so might as well be the entire url. It's gross, I hate it, but w/e it gets the job done
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)
firstly, sorry if this problem was already solved, but I couldn't find solution anywhere.
I created two admin instances in Django, here's my admin.py sample code:
class ConferenceRoomAdmin(admin.ModelAdmin):
readonly_fields = ('token', 'image_tag')
class ConferenceRoomSuperAdmin(admin.ModelAdmin):
readonly_fields = ('token', 'image_tag')
class ConferenceAdmin(admin.AdminSite):
def get_urls(self):
urls = super(ConferenceAdmin, self).get_urls()
return urls
admin_site = ConferenceAdmin()
admin_site.register(ConferenceContext)
admin_site.register(ConferenceRoom, ConferenceRoomAdmin)
class ConferenceSuperAdmin(admin.AdminSite):
pass
super_admin_site = ConferenceSuperAdmin()
super_admin_site.register(ConferenceRoom, ConferenceRoomSuperAdmin)
and urls.py file:
url(r'^admin/', include(admin.admin_site.urls)),
url(r'^myadmin/', include(admin.super_admin_site.urls)),
url(r'^login/', views.login_view),
url(r'^test/', views.test_view),
I'm able to login to both admin instances, however, if I log into 'myadmin' instance and click on any link (i.e. change password or edit users, etc. etc) I'm redirected to 'admin' site. What may be wrong here?
Thanks in advance!
Try adding a name attribute to the second admin site, like so:
super_admin_site = ConferenceSuperAdmin(name = 'super_admin')
That is the only difference between your code and mine, as far as I can tell.
The task is simple:
If user visits site root then:
if user is authenticated then:
redirect to /dashboard/
else:
redirect to settings.LOGIN_URL
There are many ways to implement that, but I wonder if there is such way in which I do need to use only urls.py.
I found a solution with RedirectView login_required(RedirectView.as_view(url=my_url)), however then I can only write static my_url instead of reverse(), which is not flexible.
You could use reverse_lazy (Django 1.4) in you url configuration, like so:
from django.conf.urls.defaults import url, patterns
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import redirect
urlpatterns = patterns('',
url(r'^/$', lambda request: return redirect(reverse_lazy('url_name')),
)
Another possibility is to define LOGIN_URL using reverse_lazy, so you could continue to use settings.LOGIN_URL in your redirects.
Code is untested, might have a typo somewhere.
You just need to mixin LoginRequired to your view. You can find an example of the mixin here:
http://djangosnippets.org/snippets/2442/
Then where you define that view, you just do:
class RedirectView(LoginRequiredMixin, DetailView):
....
Or whatever Class Based View you're inheriting from. Hope that helps!
Is there a way to extend another apps ModelAdmin?
I have a project that uses functionality offered by django.contrib.comments.
The CommentsAdmin ModelAdmin class has:
actions = ["flag_comments", "approve_comments", "remove_comments"]
I would like to extend the CommentsAdmin ModelAdmin in my project to include an action ban_user.
I've tried creating my own NewCommentsAdmin(CommentsAdmin) object in my admin.py file and registering it, but I get a notice 'AlreadyRegistered at /admin/' 'The model Comment is already registered'.
class NewCommentAdmin(CommentAdmin):
actions = ['ban_user']
def ban_user(self, request, queryset):
pass
admin.site.register(Comment, NewCommentAdmin)
Is there a way to do this without modifying the original django.contrib.comments code?
Here's how I do it in one project for the User model. In the admin.py for my app:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
class MyUserAdmin(UserAdmin):
# ...
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
Unregister the Comment model first.
I guess you have something like this at the top of your file:
from django.contrib.comments.admin import CommentAdmin
This import executes the registration of the model (at the very bottom of this admin file) again.
One idea that doesn't look very nice (I actually haven't tried it) could be:
from django.contrib.comments.models import Comment
from django.contrib import admin
from django.contrib.admin.sites import NotRegistered
# Try to unregister the Comment model
# that was registered via the auto_discover method
try:
admin.site.unregister(Comment)
except NotRegistered:
pass
# Now we can load the CommentAdmin (which reregisters the admin model)
from django.contrib.comments.admin import CommentAdmin
# We have to unregister again:
try:
admin.site.unregister(Comment)
except NotRegistered:
pass
# Now your stuff...
I guess this could be done better but it should work. To make this approach work, the application that contains this file has to be after the comments application in INSTALLED_APPS.
Now to your class. I think if you write actions = ['ban_user'] you actually overwrite all the actions in the parent class. I think it is the easiest way to override the get_actions method:
class NewCommentAdmin(CommentAdmin):
def get_actions(self, request):
actions = super(NewCommentAdmin, self).get_actions(request)
# Do some logic here based on request.user if you want
# to restrict the new action to certain users
actions.append('ban_user')
return actions
def ban_user(self, request, queryset):
pass
admin.site.register(Comment, NewCommentAdmin)
Hope that helps (or at least gives an idea) :)
Have a look at https://github.com/kux/django-admin-extend
It offers some easy to use functions and decorators that implement the functionality you're requesting in a very flexible manner. The documentation does a pretty good job at explaining why using this approach is better than direct inheritance.
It also has support for injecting bidirectional many to many fields.