How to add custom view to custom AdminSite in Django - python

im a beginner with Django and basically want to add a section in Django Admin where if I click on it, I have a view that can process data.
For example: I have models Campaign and Components which I can access through Django Admin and now I also want an Option "Analytics" that work the same way as if it was a model. Maybe this image will make clear what I want:
When I click on "Analytics" I want to call a view that can fill a html template with data that I calculate from my models e.g. build a table that counts my instances of Campaigns and Components.
What I've done so far:
create a custom AdminSite to overwrite the default AdminSite. Problem: I do not see my option appearing.
#admin.py
class MyAdminSite(admin.AdminSite):
def get_urls(self):
urls = super(MyAdminSite, self).get_urls()
custom_urls = [
path(r'myapi/test/', self.admin_view(testview)),
]
return urls + custom_urls
site_header = "My custom site"
admin_site = MyAdminSite(name="myadmin")
#apps.py
class MyAdminConfig(AdminConfig):
default_site = "myapi.admin.MyAdminSite"
#settings.py
INSTALLED_APPS = [
'rest_framework',
#'django.contrib.admin',
"myapi.apps.MyAdminConfig",
#views.py
#api_view(["GET", "POST"])
def testview(request):
print("testmyview")
return render(1, "mytest.html")
#urls.py
urlpatterns = [
path('admin/', admin_site.urls),
path('api/', include('myapi.urls')),
]
I tried to follow this tutorial:
https://adriennedomingus.medium.com/adding-custom-views-or-templates-to-django-admin-740640cc6d42
but I think after overwriting the get_urls() I should see a new option appearing on my Django Admin, which is not the case.

Related

In django how to generate dynamic url after domain name for every page?

I am building a blog website where I set a unique title for every article. I want the article should have url domain_name/<article_title>/.
Suppose I have model A and Moel B:
class A(models.Model):
title = models.CharField(max_length=500,unique=True)
class B(models.Model):
title = models.CharField(max_length=500,unique=True)
app.urls.py file :
urlpatterns = [
path('',view.index,name="index"),
path('contact/', contact, name="contact"),
path('about/', about, name="about"),
path('terms-and-conditions/', terms, name="terms_and_conditions"),
path('privacy/', privacy, name="privacy"),
path('<str:title>/', article_details, name="article_details"),
]
I have view file as follows:
def article_details(request,title):
if 'title_in_model_A':
render 'some_page_A'
if 'title_in_model_B:
render 'some_page_B'
render(request,'app/404.html')
project.urls file:
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('app.urls')),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
handler404 = 'app.views.view_404'
My question is:
Is this type of page rendering good or not?
Does 404 request handles correctly?
The way OP is doing is ok, but if one wills it's possible to simplify the article_details view by using the shortcut get_object_or_404, like
from django.shortcuts import get_object_or_404
def article_details(request,title):
article = get_object_or_404(A, title=title)
In order to customize the 404 view one can use handlers. Here's a good example in the docs.
Hard to say if OP's renders correctly because the question doesn't show that OP has a clothes app with a view_404 in views.py.
As per OP's new requirement, in the case of having two models and wanting to check the existence of instances that have the title matching a specific one, then OP can use exists() as follows
def article_details(request,title):
if A.objects.filter(title=title).exists():
# render page A
elif B.objects.filter(title=title).exists():
# render page A
else:
# 404
Note that this method is good if one doesn't need the model but are just checking the existence. If one does need it as well, then one can include inside of the condition the following (before the render)
my_model = MyModel.objects.get(title=title)

Override the automatic generated view_name in Django Rest Framework

I'm creating a Restful API with Django REST Framework. What I have is a project with two different apps. One app is called project/catalog and one is called project/environment. Both of the apps have a model called Device and their own url.py file, which will be included in the project/url.py file.
The urls from the apps are included with a namespace in project/url.py:
# project/url.py
urlpatterns = [
path(r'api/', include(('environment.urls', 'environment'))),
path(r'api/', include(('catalog.urls', 'catalog')))
]
The basename for the urls are automatic generated because in the viewSet I have a attribute queryset
My question is:
Is it possible to override the automatic view_name generation to use the pattern with a namespace? What it says in the documentation is that the view_name is generated like this (model_name)-detail for example. What I want is that the view_name will be generated like this (app_name):(model_name)-detail
I can use the namespace when I create the HyperlinkedIdentityField by my self and add the view_name attribute, like below:
# project/catalog/serializer.py
class DeviceSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='catalog:device-detail')
class Meta:
model = models.Device
fields = [
'url',
...
# project/environment/serializer.py
class DeviceSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='environment:device-detail')
class Meta:
model = models.Device
fields = [
'url',
...
But once I remove the example above it throws the Could not resolve URL for hyperlinked relationship using view name "device-detail" exception. Because the automatic generated view_name excpects device-detail, but I want it to expect catalog:device-detail for the catalog app or environment:device-detail for the environment app.
Some solutions I know about are:
Removing the namespace in project/url.py, but then there are two apps urls which has the same basename. In project/catalog the basename will be device-detail as same for project/environment. This will give issues because it will only look at one basename.
Change the HyperlinkedModelSerializer to just ModelSerializer, but that doesn't really fix my problem for my use case
Define every HyperlinkedIdentityField and add the view_name for every serializer, but that will be a pain for maintenance
My app url files
# project/catalog/url.py
from django.urls import include, path
from rest_framework import routers
from catalog import views
router.register(r'devices', views.DeviceViewSet)
urlpatterns = [
path('catalog/', include(router.urls)),
]
# project/environment/url.py
from django.urls import include, path
from rest_framework import routers
from environment import views
router.register(r'devices', views.DeviceViewSet)
urlpatterns = [
path('environment/', include(router.urls)),
]
EDIT
I have for example the AssemblySerializer in the app environment project/environment/serializer.py. Because I have two apps which can have the same urls I have to use namespaces, which is OK and what I want.
class AssemblySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Assembly
fields = '__all__'
extra_kwargs = {
'url': {'view_name': 'environment:installedassembly-detail'},
'device': {'view_name': 'environment:device-detail'},
'parent': {'view_name': 'environment:installedassembly-detail'},
'catalog_assembly': {'view_name': 'catalog:assembly-detail'}
}
What I don't want is to set for each serializer the view_name like in the example above. I want that it will automatic look at the namespace of the app environment so that I only have to define a view_name when its outside the app, like the example below.
class AssemblySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Assembly
fields = '__all__'
extra_kwargs = {
'catalog_assembly': {'view_name': 'catalog:assembly-detail'}
}
You want to use namespaces in this case.
If you are using recent Django then you should specify an app namespace in your urls.py using app_name="catalog". You can override this in include() if needed.
urlpatterns = [
path(r'api/environment/', include('environment.urls')),
path(r'api/catalog/', include('catalog.urls')),
]
Your urls.py can then be simplified like this:
app_name = 'catalog'
urlpatterns = router.urls
And you can reverse the lookup using that namespace (they can also be nested). Read up on setting the view_name on HyperlinkedModelSerializer:
class AccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Account
fields = ['account_url', 'account_name', 'users', 'created']
extra_kwargs = {
'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
'users': {'lookup_field': 'username'}
}
If you need to add custom urls outside the router, then just add them as normal.
urlpatterns += [
path('other/', some_view_func, )
path('ship/', include(("other_thing.urls", "ship"))
]
I highly recommend you install django_extensions installed so you can view your routes:
$> ./manage.py show_urls | column -t
...
PATH VIEW FUNC VIEW NAME
/catalog/items/ app.ItemViewSet catalog:item-list
/catalog/items/<pk>/ app.ItemViewSet catalog:item-detail
...
The include function is a little strange, I'll give you that. I have a helper that I use which makes it consistent, and usually removes the need for routers:
urlpatterns = [
route('layouts/', LayoutViewSet, name="layouts"),
route('admin/', admin_urls, name='admin'),
route('catalog/', 'apps.catalog.urls', name='catalog'),
etc
]

django admin remove login page

Is there anyway to delete django admin login page (mySite.com/admin) and use the user session which has logged in in main site (mySite.com)?
If any code is needed please tell me to add.
My middleware in settings.py is:
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
.
.
.
.
.
]
update:
the reason is I want the admin first logs in with his account in website then open admin page. Other users would see admin link, but after clicking that they would see a message you don't have permission to see or change anything and they see nothing else. I just want myWebsite.com/admin be redirected to admin:index if the user is logged in and to myWebsite.com if he is not.
You can easily do this using your main urls.py file. Just redirect the admin login URL to your custom log-in page on your website. In the below sample you will notice the normal admin.site.urls being used AFTER admin/login and admin/logout (I assume you'll also have a custom log-out page). With these custom views being first they will take precedence and be used instead of the ones in admin.site.urls.
urlpatterns = [
url(r'^admin/login', your_custom_login_view, name='custom_login_page_admin'),
url(r'^admin/logout', your_custom_logout_view, name='custom_logout_view_admin'),
url(r'^admin/', admin.site.urls),
]
Create a custom subclass of AdminSite and overwrite the login() method. Something like this:
class CustomAdminSite(admin.AdminSite):
def login(self, request, extra_context=None):
if not request.user.is_authenticated:
# not authenticated, redirect to main login page
login_path = reverse('login')
return HttpResponseRedirect(login_path)
if self.has_permission(request):
# Already logged-in, redirect to admin index
index_path = reverse('admin:index', current_app=self.name)
return HttpResponseRedirect(index_path)
else:
# Logged in, but doesn't have required permissions
return render(...) # render a template with your error message
Follow the Django documentation on how to customize the AdminSite class.
Why you want to do that? Admin Page is for admin purposes, just dont access /admin/ path anymore or just remove admin from your urls.py, doign that you will not be able to access admin pages anymore...
But if you want to make your users access your django admin native pages using your custom login page, just make sure to tag your users with is_staff so they can access native django pages...
models.py
from django.contrib.auth.models import User
class CustomUser(User):
... # Your new fields
views.py
def create_user(request):
...
user, created = CustomUser.objects.get_or_created(
... # Your Custom + User Django fields
is_staff = True # This will allow this user to access the admin page
)
If you want allow all your users to access your django admin pages without need to use Django Login Page you can override your CustomUser model to set all users with is_staff
class CustomUser(User):
... # Your new fields
def save(self, *args, **kwargs):
if not self.id: # This indentify if the registry is new...
self.is_staff = True
super(CustomUser, self).save(*args, **kwargs)
Obs.: Make sure your User models extends the Django User Auth, there 2... one more complex and one simple, check the docs
https://docs.djangoproject.com/en/2.0/ref/contrib/auth/
All mentioned solutions here might do a good job, but my solution would be a little different: If you want to redirect to your default login page and hide the django-admin, sth. simple like this is a good workaround:
urlpatterns = [
path('admin/login/', RedirectView.as_view(pattern_name='account_login', permanent=True)),
]
Of course the target route (here account_login) can be changed as desired.
I prefer this approach:
on urls.py
from .settings import LOGIN_URL
from django.views.generic import RedirectView,
from django.urls import path, include
from django.contrib import admin
urlpatterns = [
path('admin/login/', RedirectView.as_view(url=LOGIN_URL)),
path('admin/', admin.site.urls),
....
]
To disable admin just remove url(r'^admin/', admin.site.urls), from your main urls.py. Another things to clean are 'django.contrib.admin' from INSTALLED_APPS.
A real easy way to accomplish this is by doing 2 things:
Head to your settings.py file and add: ADMIN_ENABLED = False
Head to your main urls.py file which should have something that looks like:
from django.urls import path, include
urlpatterns = [
# path('admin/', admin.site.urls),
path('', include("landing.urls")),
]
and comment out the path to admin like in the above snippet.
Cheers!

Django - custom admin page not related to a model

I am using Django 1.7 with Mezzanine. I would like to have some page in admin, where the staff can call some actions (management commands etc.) with buttons and other control elements.
I would also like to avoid creating new model, or manually create a template and add link to it (if possible).
What is the most common/clean ways how to achieve that?
Actually it is simpler. Just before urlpatterns in urls.py patch admin urls like that:
def get_admin_urls(urls):
def get_urls():
my_urls = patterns('',
url(r'^$', YourCustomView,name='home'),
)
return my_urls + urls
return get_urls
admin.autodiscover()
admin_urls = get_admin_urls(admin.site.get_urls())
admin.site.get_urls = admin_urls
ModelAdmin.get_urls let you add a url to the admin url's. So you can add your own view like this:
class MyModelAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
my_urls = patterns('',
(r'^my_view/$', self.my_view)
)
return my_urls + urls
def my_view(self, request):
# custom view which should return an HttpResponse
pass
https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_urls
I didn't try this out, but it seems to me that you can subclass a build-in admin view and let your custom template extend the build-in admin templates.

Override AdminSite to append custom urls

I overrode default AdminSite class as described in manual, though it's too pure with information about this part over there.
My gs/admin.py file:
from django.contrib.admin import AdminSite
from django.conf.urls import patterns, url
from gs.views import *
class AdminSiteGs(AdminSite):
def get_urls(self):
urls = super(AdminSiteGs, self).get_urls()
urls += patterns('',
url(r'^my_admin_view/$', self.admin_view(my_admin_view))
)
return urls
admin_site_gs = AdminSiteGs()
gs it's my application and project name.
gs/urls.py file:
from django.conf.urls import patterns, include, url
from page import views
from gs.admin import admin_site_gs
urlpatterns = patterns('',
url(r'^admin/', include(admin_site_gs.urls)),
)
and I have application named page, where I place admin.py file:
from gs.admin import admin_site_gs
from page.models import Page, Menu
from django.contrib import admin
class PageAdmin(admin.ModelAdmin):
list_display = ('name', 'url', 'page_type')
class MenuAdmin(admin.ModelAdmin):
list_display = ('name', 'code')
admin_site_gs.register(Page, PageAdmin)
admin_site_gs.register(Menu, MenuAdmin)
So nothing here is working =( Neither /admin/my_admin view ( it returns 404 ), nor main admin page /admin. I don't see my models I registered in page/admin.py file.
It may sounds fun, but I tried all staff working in 3-4 hours =)) As you might guess I'm completely newbie both in Django and Python... All I want to know now is how to append custom URLs and views to my overriden class of AdminSite?
I removed autodiscover method, so now seems Django doesn't see nothing about file page/admin.py.
But the first question is more interesting, why I've had 404 error on trying to access the /admin/my_admin page ...
PS why my greeting at the beginning has been cutted o_O
According to the docs, any URL patterns you define for custom admin views must be occur before the admin patterns: https://docs.djangoproject.com/en/1.4/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_urls
Try:
def get_urls(self):
urls = super(AdminSiteGs, self).get_urls()
my_urls = patterns('',
url(r'^my_admin_view/$', self.admin_view(my_admin_view))
)
return my_urls + urls
You shouldn't need to include these patterns like this:
urlpatterns = patterns('',
url(r'^admin/', include(admin_site_gs.urls)), # not needed
)
In my case, I had to override the default 'add url' in order to redirect to a custom Django admin page when clicking '+Add' button in the admin.
So if I just override get_urls() in the way #Brandon said, it will return a list with a duplicate 'add' url (the custom one and the one retrieved from the super).
def get_urls(self):
info = self.model._meta.app_label, self.model._meta.model_name
urls = super(RetailerAdmin, self).get_urls()
# We need to remove the original 'add_url' in order to use the custom one.
urls.remove(urls[1])
custom_url = [
url(r'^batch/$', self.admin_site.admin_view(self.batch_upload_retailers),
name='%s_%s_add' % info),
]
return custom_url + urls
To solve this, I removed the original 'add' url (notice that the 'add' url is always in position 1).

Categories

Resources