How can i change the Django admin template on the fly - python

I have the Django Admin model like this
class STUDENTAdmin(ModelAdmin):
change_list_template = "students/student_change_list.html"
Now i want to chnage that dynamically based on some request parameter
something like
if request.GET['foo']:
change_list_template = "students/student_change_list_other.html"
how can i do that

I think you need to override the changelist_view and act on the TemplateResponse() returned from it or change the variable holding that name just before that call.
The original function is like this
def changelist_view(self, request, extra_context=None):
# a lot of stuff happen here
return TemplateResponse(request, self.change_list_template or [
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
'admin/%s/change_list.html' % app_label,
'admin/change_list.html'
], context, current_app=self.admin_site.name)
so I think that a code like
def changelist(self, request, extra_context=None):
if request.GET['foo']:
self.change_list_template = "students/student_change_list_other.html"
return super(STUDENTAdmin, self).changelist_view(request, extra_context)

Related

Is there a 'DetailView' in Django Admin?

I know there is a change/update view in Django admin but is there any detail view that just lists out the record's attributes? Kind of like the DetailView in the Django app?
Or does anyone know any 3rd party packages I can install to provide the same functionality?
I too was investigating this recently.
One approach that works is to create a custom ModelAdmin with a detail_view method that forwards the call to ModelAdmin's changeform_view() method. Then this view is added to the urls list via overriding ModelAdmin.get_urls().
Then, in this method set a class attribute, say __detail_view to True. Then override has_change_permission() method, which returns False, if __detail_view is detected and set to True. This will cause AdminSite to render the fields in readonly mode (using the AdminReadonlyField wrapper fields) instead of the standard AdminField objects.
You can also change the change_form_template to a custom template for detail_view to accommodate custom rendering for detail views.
class CustomModelAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj=None):
if getattr(self, '__detail_view', None):
return False
return super().has_change_permission(request, obj)
def detail_view(self, request, object_id, form_url='', extra_context=None):
setattr(self, '__detail_view', True)
# Custom template for detail view
org_change_form_template = self.change_form_template
self.change_form_template = self.detail_view_template or self.change_form_template
ret = self.changeform_view(request, object_id, form_url, extra_context)
self.change_form_template = org_change_form_template
delattr(self, '__detail_view')
return ret
def get_urls(self):
urls = super().get_urls()
# add detail-view for the object
from django.urls import path
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
# Replace the backwards compatibility (Django<1.9) change view
# for the detail view.
urls[len(urls)-1] = path('<path:object_id>/', wrap(self.detail_view), name='%s_%s_detail' % info)
return urls
I haven't tried the custom template approach, but using the __detail_view object attribute to force readonly rending seems to work.
The default change_form_template still shows the delete button at the bottom, which is okay I guess. But it needs another button to actually take you to the real change page where the object can be changed. Again template customization is the way to go here. Hint: look at {% submit_row %} in admin templates and model a custom inclusion template tag that displays the Change button, if the user has change permission. Take note to call the has_change_permission() here to get the real permission before setting the __detail_view attribute.
Not sure if there are other implications for doing it this way, but it ought to work.
HTH

Best way to write views for multiple queries in Django?

It's a simple question. I've organised my models so that most objects served to the page are of one type - Item. The model contains various attributes which help me serve it in different ways.
I have articles, and videos, which are determined by a 'type' field on the model. Type = 'article' etc.
I have a listview, which shows all the objects in the Item model, sorted by date.
class ItemListView(generic.ListView):
# This handles the logic for the UserForm and ProfileForm - without it, nothing happens.
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
# return Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return Item.objects.all().order_by('-create_date')
I want a view which shows all the articles, sorted by date, and all the videos, sorted by date. I have a feeling I'll be writing many more such views.
Is there a better way to do it than to write a new view for every single query? As, this is what I'm currently doing:
Views.py
class ItemListViewArticle(generic.ListView):
# This handles the logic for the UserForm and ProfileForm - without it, nothing happens.
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
# return Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return Item.objects.filter(type__contains='article').order_by('-create_date')
class ItemListViewVideo(generic.ListView):
# This handles the logic for the UserForm and ProfileForm - without it, nothing happens.
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
# return Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
return Item.objects.filter(type__contains='video').order_by('-create_date')
urls.py
path('new/', views.ItemListView.as_view(), name='new_list'),
path('new/articles', views.ItemListViewArticle.as_view(), name='article_list'),
path('new/videos', views.ItemListViewVideo.as_view(), name='video_list'),
You can use URL querystring(ie request.GET) to get type of the item from url and filter by it. Like this:
path('new/', views.ItemListView.as_view(), name='new_list'),
class ItemListViewArticle(generic.ListView):
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
content_type = self.request.GET.get('type')
return Item.objects.filter(type__contains=content_type).order_by('-create_date')
# usage
localhost:8000/new/?type=article
localhost:8000/new/?type=video
Or you can use URL parameter to get the type of data:
path('new/<str:content_type>/', views.ItemListView.as_view(), name='new_list'),
class ItemListViewArticle(generic.ListView):
def item(self, request, *args, **kwargs):
return index(request)
def get_queryset(self):
content_type = self.kwargs.get('content_type')
return Item.objects.filter(type__contains=content_type).order_by('-create_date')
# usage
localhost:8000/new/article/
localhost:8000/new/video/

Django DeleteView without confirmation template

I am using Django DeleteView in a template and I've created a url & view.
Is it possible to skip the process of loading the _confirm_delete template and just post the delete immediately.
DeleteView responds to POST and GET requests, GET request display confirmation template, while POST deletes instance.
You can send POST request, without confirmation with form like this:
<form method="POST" action="{% url "your_delete_url_name" %}">
{% csrf_token %}<input type="submit" value="DELETE">
</form>
If you do not want to have a link instead form button, use some javascript to make invisible form, that will be submitted on link click.
It is not good practice to use GET request for updating or deleting, but if you really insist you can shortcut get method in your class view to post, ie:
def get(self, *args, **kwargs):
return self.post(*args, **kwargs)
Or you can redefine get() method in your DeleteView:
class YourDeleteView(DeleteView):
model = YourModel
success_url = '<success_url>'
def get(self, request, *args, **kwargs):
return self.post(request, *args, **kwargs)
But be careful with that, ensure that this doesn't affect other functionality.
Yes, just change the next parameter. In your return response, make sure that the dictionary that you pass in is has something like this : { 'next': '/<your_path_here>}/' }, make sure you commit the changes before adding the next parameter. You might want to change your view's get and post functions.
Or you could only allow HTTP request method delete by routing the request directly to the delete method of your class.
from django.views.generic import DeleteView
from django.http import HttpResponseForbidden
class PostDeleteView(DeleteView):
model = Post
http_method_names = ['delete']
def dispatch(self, request, *args, **kwargs):
# safety checks go here ex: is user allowed to delete?
if request.user.username != kwargs['username']:
return HttpResponseForbidden()
else:
handler = getattr(self, 'delete')
return handler(request, *args, **kwargs)
def get_success_url(self):
username = self.kwargs.get('username')
success_url = str(reverse_lazy('post:user_home', kwargs={'username': username}))
return success_url
Let's say your URL looks like this:
path('posts/delete/<int:pk>/', PostDeleteView.as_view(), name='post_delete'),
For clarity why this works, you have to analyze the post and delete methods.
def post(self, request, *args, **kwargs):
return self.delete(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""
Call the delete() method on the fetched object and then redirect to the
success URL.
"""
self.object = self.get_object()
success_url = self.get_success_url()
self.object.delete()
return HttpResponseRedirect(success_url)
post just calls delete and delete gets the object and success URL, deletes the object, then redirects to the success URL you provided. pk_url_kwarg = 'pk' is why I showed the <int:pk> part in the URL.
You can override the get() method to behave exactly like the delete() method:
def get(self, request, *args, **kwargs):
return self.delete(request, *args, **kwargs)
See CCBV here: https://ccbv.co.uk/projects/Django/4.1/django.views.generic.edit/DeleteView/
All you have to do is override the get_success_url method of your delete view. Then it will directly delete the object from the DB. Eg:
class YourView(DeleteView):
model = YourModel
def get_success_url(self):
return reverse('your_redirect_view')

django haystack - how to use with current view having some context

I have a view that puts some context and renders the template from this context. I want view display all the things if no search query is there, and show searhed things, if anything searched.
class MyCurrentView(View):
def get(self, request):
data = { 'users': User.objects.all() }
return render_to_response("mytemp.html", ..
urls.py:
url(r'^search/$', MyCurrentView.as_view())
Now, I am integrating this with SEarchView like this:
class MyCurrentView(SearchView): (If u observe, I subclassed SEarchView).
template = 'mytemp.html'
def get(self, request):
data = { 'users': User.objects.all() }
return render_to_response...
def get_context_data(self, ...):
print "....this should print" #But not printing. So, unable to add data
return data
urls.py:
url(r'^search/$', MyCurrentView.as_view())
# as_view() gave me error, so I did MyCurrentView() , may be thats y, get_context_data not calling.
Will provide more information if necessary.
New Django-style class based views were added via PR #1130 to django-haystack:
https://github.com/toastdriven/django-haystack/pull/1130
You can now use search views like you normally would with django (see changes in pull request).
from haystack.generic_views import SearchView
class MySearchView(SearchView):
def get_context_data(self, **kwargs):
# do whatever you want to context

Render Django view class to either string or response

I have a template that I want to be able to both serve directly and embed in arbitrary other templates in my Django application. I tried to create a view class for it that looks like this:
class TemplateView(View):
def get(self, request):
context = self._create_context(request)
return render_to_response('template.html', context)
def get_string(self, request):
context = self._create_context(request)
return render_to_string('template.html', context)
def _create_context(self, request):
context = {}
# Complex context initialization logic...
return context
I've wired get to my Django URLs. However, I haven't been able to figure out how to instantiate TemplateView so that I can call get_string from other views.
There must be a better way to go about doing this. Ideas?
Update: I've seen some folks talking about making a request internally and using response.content, which would save me from having to write the get_string method. So, perhaps a better question is: How do I make a request to TemplateView from another view?
I'd follow in django's CBV pattern: it determines via dispatch what method to return. By default based on request.method. Why not based on any other argument passed to dispatch()?
So subclass dispatch and give it a way to determine whether or not to return get_string.
def dispatch(self, request, *args, **kwargs):
if 'as_string' in kwargs:
return self.get_string(request)
return super(TemplateView, self).dispatch(request, *args, **kwargs)
response = TemplateView.as_view()(request, as_string=True)

Categories