Simplest way to add a view to my Django admin UI? - django

What is the simplest way to add a view to my Django (3.2) admin UI? That is, add a URL mysite.com/admin/my-view so that visiting that URL acts like the rest of admin (in particular, requires similar permissions).
There is a whole page on this, but it's not obvious to me how to piece it together.
https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#adding-views-to-admin-sites says you can add a get_urls to your AdminSite class. Okay, so I need my own AdminSite class. And I need to register it in apps, maybe?
I did this:
class MyAdminSite(admin.AdminSite):
def get_urls(self):
urls = super().get_urls()
my_urls = [
path('my-view', self.my_view, name='my-view')
]
return my_urls + urls
def my_view(self, request):
# do something ..
admin_site = MyAdminSite(name='my_admin')
and this in urls.py:
from .admin import admin_site
urlpatterns = [
path('admin/', admin_site.urls),
and this in unchanged in INSTALLED_APPS in settings:
'django.contrib.admin',
But, now it only shows the admin for the one app, instead of for all the apps. So how do I get it to auto-discover all the apps like it used to? Or is there a simpler way?
P.S. There is also a question on this, but the answer didn't have enough detail for me to use it.
EDIT: I'm reading Make new custom view at django admin and Django (1.10) override AdminSite ..
EDIT 2: This was a hack, but it works for me:
from django.contrib.admin import site
admin_site._registry.update(site._registry)
I had to update in the right place (the project urls.py), perhaps when all the other admin stuff is already loaded.

Related

Django - URL problem for user profile view and edit profile view

In my Django project, specifically in my accounts app, I have 2 simple views, one allows users to view their profile (or other users' profiles), and the other one allows users to edit their own profile.
My account.urls looks like this
from django.urls import path
from accounts.views import ProfileView, EditProfileView
app_name = 'accounts'
urlpatterns = [
path('<slug:username>/', ProfileView.as_view(), name='profile'),
path('edit/', EditProfileView.as_view(), name='edit'),
]
Everything works great, but if an user creates an accounts with the word edit as their username, they will NEVER be able to edit their profile, since the first URL pattern will always be matched.
What's the best way of solving this problem in Django?
I know I could change my URLs to something like:
urlpatterns = [
path('<slug:username>/', ProfileView.as_view(), name='profile'),
path('<slug:username>/edit/', EditProfileView.as_view(), name='edit'),
]
But I prefer to NOT edit my URLs. I was wondering if there is another solution I don't know.

Django: how to route URL requests to django.contrib.admin functions

I would like Django to return the same response for requests to /myapp/add as /admin/myapp/mymodel/add.
myproject/myapp/models.py defines the model and myapp/admin.py registers with django.contrib.admin.
myproject/myapp/models.py:
from django.db import models
class MyModel(models.Model):
...
myproject/myapp/admin.py:
from django.contrib import admin
from .models import MyModel
admin.site.register(MyModel)
I am stuck on how to route the request to django.contrib.admin in the project's urlpatterns:
myproject/myproject/urls.py:
urlpatterns = [
url(r'^$', views.home_page, name='home'),
url(r'^admin/', admin.site.urls),
url(r'^myapp/add', ??????),
]
From printing the return from resolve('/admin/myapp/mymodel/add/') this looks like part of the answer:
ResolverMatch(func=django.contrib.admin.options.add_view, args=(), kwargs={}, url_name=myapp_mymodel_add, app_names=['admin'], namespaces=['admin'])
Well let me say that seems like a weird thing to do, but anyway:
in the file: django.contrib.admin.options.py we see:
class ModelAdmin(BaseModelAdmin):
we see that the add view returns
_changeform_view()
which uses the template
django/contrib/admin/templates/change_form.html
So you would want to render that template in your view.
but it would be missing a bunch of context items,
so you would basically have to re-implement the django.admin.options._changeform_view
and then copy the template django/contrib/admin/templates/change_form.html to your apps' template directory
def admin_add(request):
# admin.changeform_view code here
return render(request, "myapp/change_form.html", {context{)
ps. I assume the admin site view assumes the user is the "superuser" and not a normal user so you would want to account for that as well..

How to reverse match multiple Django admin sites (Custom admin site namespaces)

When you extend AdminSite to create another admin site how do you go about being able to reverse match each site? It seems the admin namespace is hardcoded reverse('admin:index'), is there a way to supply a custom namespace?
You may be confused with the namespace in django. If you are interested to clarify that confusion, you may read up the discussion here.
If you want to try solve your problem, there is a specific documentation for multiple admin sites.
Below are example solutions mostly copied from official documentation
Example Solution:
# urls.py
from django.conf.urls import url
from .sites import basic_site, advanced_site
urlpatterns = [
url(r'^basic-admin/', basic_site.urls),
url(r'^advanced-admin/', advanced_site.urls),
]
And
# sites.py
from django.contrib.admin import AdminSite
class MyAdminSite(AdminSite):
site_header = 'Monty Python administration'
basic_site = MyAdminSite(name='myadminbasic')
advanced_site = MyAdminSite(name='myadminadvanced')
Reversing
reverse('myadminbasic:index') # /basic-admin/
reverse('myadminadvanced:index') # /advanced-admin/

How can I not use Django's admin login view?

I created my own view for login. However if a user goes directly to /admin it brings them to the admin login page and doesn't use my custom view. How can I make it redirect to the login view used for everything not /admin?
From http://djangosnippets.org/snippets/2127/—wrap the admin login page with login_required. For example, in urls.py:
from django.contrib.auth.decorators import login_required
from django.contrib import admin
admin.autodiscover()
admin.site.login = login_required(admin.site.login)
You probably already have the middle two lines and maybe even the first line; adding that fourth line will cause anything that would have hit the admin.site.login function to redirect to your LOGIN_URL with the appropriate next parameter.
While #Isaac's solution should reject majority of malicious bots, it doesn't provide protection for professional penetrating. As a logged in user gets the following message when trying to login to admin:
We should instead use the admin decorator to reject all non-privileged users:
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib import admin
[ ... ]
admin.site.login = staff_member_required(admin.site.login, login_url=settings.LOGIN_URL)
To the best of my knowledge, the decorator was added in 1.9.
I found that the answer above does not respect the "next" query parameter correctly.
An easy way to solve this problem is to use a simple redirect. In your site's urls file, immediately before including the admin urls, put a line like this:
url(r'^admin/login$', RedirectView.as_view(pattern_name='my_login_page', permanent=True, query_string=True))
Holá
I found a very simple solution.
Just tell django that the url for admin login is handle by your own login view
You just need to modify the urls.py fle of the project (note, not the application one)
In your PROJECT folder locate the file urls.py.
Add this line to the imports section
from your_app_name import views
Locate this line
url(r'^admin/', include(admin.site.urls))
Add above that line the following
url(r'^admin/login/', views.your_login_view),
This is an example
from django.conf.urls import include, url
from django.contrib import admin
from your_app import views
urlpatterns = [
url(r'^your_app_start/', include('your_app.urls',namespace="your_app_name")),
url(r'^admin/login/', views.your_app_login),
url(r'^admin/', include(admin.site.urls)),
]
http://blog.montylounge.com/2009/07/5/customizing-django-admin-branding/
(web archive)
I'm trying to solve exactly this problem and I found the solution at this guys blog. Basically, override the admin template and use your own template. In short, just make a file called login.html in /path-to-project/templates/admin/ and it will replace the admin login page. You can copy the original (django/contrib/admin/templates/login.html) and modify a line or two. If you want to scrap the default login page entirely you can do something like this:
{% extends "my-login-page.html" %}
There it is. One line in one file. Django is amazing.
I had the same issue, tried to use the accepted answer, but has the same issue as pointed in the comment above.
Then I've did something bit different, pasting here if this would be helpful to someone.
def staff_or_404(u):
if u.is_active:
if u.is_staff:
return True
raise Http404()
return False
admin.site.login = user_passes_test(
staff_or_404,
)(admin.site.login)
The idea is that if the user is login, and tried to access the admin, then he gets 404. Otherwise, it will force you to the normal login page (unless you are already logged in)
In your ROOT_URLCONF file (by default, it's urls.py in the project's root folder), is there a line like this:
urlpatterns = patterns('',
...
(r'^admin/', include(admin.site.urls)),
...
)
If so, you'd want to replace include(admin.site.urls) with the custom view you created:
(r'^admin/', 'myapp.views.myloginview'),
or if your app has its own urls.py, you could include it like this:
(r'^admin/', include(myapp.urls)),
This is my solution with custom AdminSite class:
class AdminSite(admin.AdminSite):
def _is_login_redirect(self, response):
if isinstance(response, HttpResponseRedirect):
login_url = reverse('admin:login', current_app=self.name)
response_url = urllib.parse.urlparse(response.url).path
return login_url == response_url
else:
return False
def admin_view(self, view, cacheable=False):
inner = super().admin_view(view, cacheable)
def wrapper(request, *args, **kwargs):
response = inner(request, *args, **kwargs)
if self._is_login_redirect(response):
if request.user.is_authenticated():
return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
else:
return redirect_to_login(request.get_full_path(), reverse('accounts_login'))
else:
return response
return wrapper
You can redirect admin login url to the auth login view :
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('your_app.urls')),
path('accounts/', include('django.contrib.auth.urls')),
path('admin/login/', RedirectView.as_view(url='/accounts/login/?next=/admin/', permanent=True)),
path('admin/', admin.site.urls),
]
As of August 2020, django.contrib.admin.sites.AdminSite has a login_template attribute. So you can just subclass AdminSite and specify a custom template i.e.,
class MyAdminSite(AdminSite):
login_template = 'my_login_template.html'
my_admin_site = MyAdminSite()
Then just use my_admin_site everywhere instead of admin.site.

Django static page?

I want to make a static page which will be shown to the user only if he/she clicks on a link provided in one of my models. I can do this by making a Python page alone and calling it, but I want it be called from Django. The user interface should be constructed using the Django API only.
Any suggestions?
With the class-based views in newer Django versions, one can use this in urls.py:
from django.views.generic import TemplateView
url(r'^about',
TemplateView.as_view(template_name='path/to/about_us.html'),
name='about'),
Bypassing views to render a static template, add this line in "urls.py". For example "About Us" page could be
(r'^about', 'django.views.generic.simple.direct_to_template', {'template': 'path/to/about_us.html'}),
Do you mean something like Django's flatpages app? It does exactly what you describe.
If you want to make a static page the flatpages is a good choice. It allows you to easily create static content. Creating static content is not harder than creating a view really.
On Django 2.2.6, loosely following David's answer, I added the path in urls.py:
from django.views.generic import TemplateView
urlpatterns = [
.... .... ....
path('about',
TemplateView.as_view(template_name='path/to/about_us.html'),
name='about'),
And I needed to adjust settings.py to specify the template directory:
TEMPLATES = [{
... ... ...
'DIRS': [os.path.join(BASE_DIR, 'template')],
Then I saved the actual content in template/path/to/about_us.html