Add custom page to django admin without a model - python

I'm trying to add a custom page to the admin without a model association.
This is what I achieved so far.
class MyCustomAdmin(AdminSite):
def get_urls(self):
from django.conf.urls import url
urls = super(MyCustomAdmin, self).get_urls()
urls += [
url(r'^my_custom_view/$', self.admin_view(MyCustomView.as_view()))
]
return urls
class MyCustomView(View):
template_name = 'admin/myapp/views/my_custom_template.html'
def get(self, request):
return render(request, self.template_name, {})
def post(self, request):
# Do something
pass
admin_site = MyCustomAdmin()
admin_site.register(MyModel1)
admin_site.register(MyModel2)
# etc...
This is actually working but the problem is that with this solution I loose some apps from the Django admin interface (account, auth, socialaccounts, sites).

This is because your other admins are using the default admin.site. You need to totally replace the default admin.site with your own as explained here (you may also want to read this too).
Or you can just do it piggy-style by monkeypatching the default admin.site.get_urls() method:
from django.contrib import admin
_admin_site_get_urls = admin.site.get_urls
def get_urls():
from django.conf.urls import url
urls = _admin_site_get_urls()
urls += [
url(r'^my_custom_view/$',
admin.site.admin_view(MyCustomView.as_view()))
]
return urls
admin.site.get_urls = get_urls
Legal disclaimer : I won't be held responsible for any kind of any unwanted side-effect of this "solution", including (but not restricted too) your coworkers defenestrating you on the next code review. It's a dirty solution. It's a mess. It stinks. It's evil. You shouldn't do that, really.

Related

Is it required to add custom views in admin page in ModelAdmin class when we can do it normally by adding views in views.py and urls in urls.py?

According to django docs:
class MyModelAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
my_urls = [
url(r'^my_view/$', self.my_view),
]
return my_urls + urls
def my_view(self, request):
# ...
context = dict(
# Include common variables for rendering the admin template.
self.admin_site.each_context(request),
# Anything else you want in the context...
key=value,
)
return TemplateResponse(request, "sometemplate.html", context)
If I am not wrong, we can do the same thing by adding url in urls.py and the views in views.py as it is normally done then, what is the use of introducing this way? I am a newbie to django and I may be missing something here.
Can you please provide an example where we cannot do it in views.py and we must use the above method?
Any guidance/help would be appreciated.
I think I figured out, both of them can be used to do the same thing but the key difference is that the views which you write using above method will belong to admin app while the general views in views.py belongs to the particular app in you have written.
Hence, the url in ModelAdmin need to be called using name admin:url_name since the url goes as admin/my_views/ in given example.

How do you give a wagtail/django Page a custom url to serve at?

In wagtail/django how do you make a basic wagtail Page model, create the html template, and then tell that model to serve as a view for a specific url?
from django.db import models
from wagtail.wagtailcore.models import Page
class MyPage(Page):
title = models.CharField(blank=True, null=True, max_length=255)
#...
I want the url to register like
url(r'^monkey/(?P<slug>[A-Za-z0-9]+)$', ...)
But I don't have a common urls.py folder its stored outside of the project. I tried using the RoutablePageMixin, but I believe that serves for subpages. I also know where to store the html template within the structure so that is not a problem.
You might want something like:
from django.http import Http404
from django.views import View
from wagtail.core.views import serve
class WagtailPageServeView(View):
"""
A default way to serve up wagtail pages in urls
"""
wagtail_slug = None
def get(self, request, *args, **kwargs):
if self.wagtail_slug:
return serve(request, self.wagtail_slug)
else:
raise Http404(
f"WagtailPageServeView missing 'wagtail_slug'. Cannot serve "
f"request.path: {request.path}"
)
Then you can do something like this in urls.py:
urlpatterns += [
re_path(r'^$', WagtailPageServeView.as_view(wagtail_slug='/'), name='home'),
]
In Wagtail, you are creating a page type and then creating instances of the page type in the CMS.
Even if you are only going to use this page once, you still need to make a type. Following this logic, page urls are defined inside the CMS through the page's parent and slug. Most likely, you could achieve what you want through these means.
If you want to override your page template and create a custom url, you can override the get_url_parts method on your model.
Also, if you want to skip Wagtail for this page specifically, remember that Wagtail is just built over Django. You can always create a new module and define the url in that model. If you check your site urls.py, you will see the wagtail url patterns. You can change it to look something like this:
urlpatterns = [
# other urls...
url(r'wagtail/', include(wagtail_urls)),
url(r'^monkey/', ('monkey_app.urls', 'monkey_app')),
]

Django Haystack Custom Search Form

I've got a basic django-haystack SearchForm working OK, but now I'm trying to create a custom search form that includes a couple of extra fields to filter on.
I've followed the Haystack documentation on creating custom forms and views, but when I try to view the form I can only get the error:
ValueError at /search/calibration/
The view assetregister.views.calibration_search didn't return an HttpResponse object. It returned None instead.
Shouldn't basing this on SearchForm take care of returning a HttpResponse object?
forms.py
from django import forms
from haystack.forms import SearchForm
class CalibrationSearch(SearchForm):
calibration_due_before = forms.DateField(required=False)
calibration_due_after = forms.DateField(required=False)
def search(self):
#First we need to store SearchQuerySet recieved after / from any other processing that's going on
sqs = super(CalibrationSearch, self).search()
if not self.is_valid():
return self.no_query_found()
#check to see if any date filters used, if so apply filter
if self.cleaned_data['calibration_due_before']:
sqs = sqs.filter(calibration_date_next__lte=self.cleaned_data['calibration_due_before'])
if self.cleaned_data['calibration_due_after']:
sqs = sqs.filter(calibration_date_next__gte=self.cleaned_data['calibration_due_after'])
return sqs
views.py
from .forms import CalibrationSearch
from haystack.generic_views import SearchView
from haystack.query import SearchQuerySet
def calibration_search(SearchView):
template_name = 'search/search.html'
form_class = CalibrationSearch
queryset = SearchQuerySet().filter(requires_calibration=True)
def get_queryset(self):
queryset = super(calibration_search, self).get_queryset()
return queryset
urls.py
from django.conf.urls import include, url
from . import views
urlpatterns = [
....
url(r'^search/calibration/', views.calibration_search, name='calibration_search'),
....
]
Haystack's SearchView is a class based view, you have to call .as_view() class method when adding a urls entry.
url(r'^search/calibration/', views.calibration_search.as_view(), name='calibration_search'),
This helped me.
"removing the "page" prefix on the search.html template did the trick, and was a good temporary solution. However, it became a problem when it was time to paginate the results. So after looking around, the solution was to use the "page_obj" prefix instead of "page" and everything works as expected. It seems the issue is that the haystack-tutorial assumes the page object is called "page", while certain versions of django its called "page_obj"? I'm sure there is a better answer - I'm just reporting my limited findings."
See this: Django-Haystack returns no results in search form

Login Required Decorator with Many Possible HTML's for Same URL

I would like to restrict access to urls that are served by django generic views. I have researched the login required decorator, but have only had partial success in getting it to work as I have a complication that is not addressed in the docs (or at least I couldn't find it).
Before adding the decorator, in urls.py I have the following:
url(r'^search/(?P<search_type>\w+)', search)
The above named search function is slightly complex in that depending on various conditions, it will render one of four possible html pages.
I don't see in the docs a way to handle multiple html pages using the decorator, and I can't seem to figure out the correct syntax.
I have tried using the decorator with one of the four html pages, and it does work for that one html page:
from django.views.generic import TemplateView
url(r'^search/(?P<search_type>\w+)',
login_required(TemplateView.as_view(template_name='search_form.html',), search)),
But how do I require login for all the possible html's? For example, I tried things like:
url(r'^search/(?P<search_type>\w+)',
login_required(TemplateView.as_view(template_name='search_form.html',), TemplateView.as_view(template_name='search_form_manual.html',), search)),
I also tried subclassing the generic views:
//in view.py
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name_1 = "search_form.html"
template_name_2 = "search_form_manual.html"
template_name_3 = "search_results.html"
template_name_4 = "tag_search_results.html"
//in urls.py
from views import AboutView
url(r'^search/(?P<search_type>\w+)',
login_required(AboutView.as_view(template_name_1, template_name_2,), search)),
But I get errors that template_name_1 and template_name_2 do not exist...
Any help is appreciated.
Use that with class view
from django.views.generic import TemplateView
class AboutView(TemplateView):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)
template_name_1 = "search_form.html"
template_name_2 = "search_form_manual.html"
template_name_3 = "search_results.html"
template_name_4 = "tag_search_results.html"

Django custom list view in admin

One of the objects I am managing in my django site will only ever have one instance in the database. I therefore want to change the list view to simply redirect to the 'edit' page for this first object.
So basically when you hit /admin/my_site/widgets I want to redirect to /admin/my_site/widget/1. I have tried a custom view, a custom template, etc, but I can't find an easy way of doing this (or any way of doing this for that matter).
It's almost like I want to do something like this (doesn't work because I can't figure out how to change the list view):
class WidgetAdmin(admin.ModelAdmin):
def list_view(self, request):
widget = Widget.objects.all()[0]
return HttpResponseRedirect('/admin/my_site/widget/%s' % widget.id)
I've also tried change the url's to match the list request and do a redirect there, but I can't seem to match the list request with anything other than a complete blanket regex, i.e. (r/^.*$/) which means I just get an infinite loop redirect.
I needed the same thing. I solved it slighty different using the changelist_view from ModelAdmin. Using your example it would look somthing like:
class MySingleEditAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
object, created = self.get_my_object()
url = reverse('admin:%s_%s_change' %(object._meta.app_label, object._meta.module_name), args=[object.id] )
return HttpResponseRedirect(url)
class WidgetAdminAdmin(MySingleEditAdmin):
def get_my_object(self):
return Widget.objects.get_or_create(pk=1, ...default_data...)
Ok this is how I sorted it out.
class WidgetAdmin(admin.ModelAdmin):
def list_view(self, request):
widget = Widget.objects.all()[0]
return HttpResponseRedirect('/admin/my_site/widget/%s' % widget.id)
def get_urls(self):
from django.conf.urls.defaults import *
urls = super(WidgetAdmin, self).get_urls()
my_urls = patterns('',
(r'^$', admin.site.admin_view(self.list_view))
)
return my_urls + urls

Categories

Resources