Django admin actions in detail view? - python

I have a few admin actions defined in my (fairly standard) Django app. Some of those actions would also make a lot of sense if they were available on the detail page of an object.
Right now, users would need to navigate back to the list, then search for the specific record, then trigger the action.
Is there a way to expose this functionality on the detail page as well?

Here is the answer you're looking for.
Essentially, you create a yourmodel_changeform.html file with following contents:
{% extends 'admin/change_form.html' %}
{% block submit_buttons_bottom %}
{{ block.super }}
<div class="submit-row">
<input type="submit" value="Button Label" name="_your-action-name">
</div>
{% endblock %}
And then override the response_change method on your ModelAdmin class and set the change_form_template attribute.
from django.http import HttpResponseRedirect
class YourModelAdmin(admin.ModelAdmin):
change_form_template = "templatelocation/yourmodel_changeform.html"
def response_change(self, request, obj):
if "_your-action-name" in request.POST:
# do whatever you want the button to do
obj.name = "new name"
obj.save()
return HttpResponseRedirect(".") # stay on the same detail page
return super().response_change(request, obj)
Tested on Django 3.0.3

If I understand you right, what you need to do is to write some separate html and then use it on different pages with {% include '/path/foo.html' %}. Here is more on this topic.
In foo.html you can add any functionality you want to use on different pages(forms, links, etc.).
If you want more detailed answer, it would be nice to see your code and what exactly you want to do.

To elaborate pythad.
1 Add another simple html that extends your admin html's and holds the buttons describing the admin action. you may call it change_form.py
make sure to strt is with:
{% extends "admin/change_form.html" %}
2 you would need to add small functions in your admin.py to conect the buttons to the action functions.
somthong like :
try:
sr = Scenario.objects.filter(pk = pk)
queitem = QueueScript.objects.filter(scriptresult__pk=sr.scriptreport.pk)
#do somtho
return HttpResponseRedirect(request.META["HTTP_REFERER"])
3 you need to elaborate/override the admins get_urls function to know the functions from above:
def get_urls(self, ):
urls = super(ScenarioAdmin, self).get_urls()
my_urls = patterns("",
url(r"(?P<pk>\d+)/stop_my_script/$",
self.stop_my_script),
url(r"(?P<pk>\d+)/run_scenario_standalone/$",
self.run_scenario_standalone),
)
return my_urls + urls
4 and finally elaborate your change_view func also in admin.py
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = {}
extra_context['show_scer_buttons'] = True
extra_context['is_running'] = self.choose_template_buttons(object_id, extra_context)
return super(ScenarioAdmin, self).change_view(request, object_id,
form_url, extra_context=extra_context)

Related

How to call a verification function from views to templates?

I have a function to check if the user is a staff:
class VerificateUser():
def allowed_user(request):
if request.user.is_staff:
return True
In my template, I’m going to show this section only for staff users, but this is not working.
{% url if not allowed_user %}
<h1>
Welcome to the show
</h1>
If do something like it, works:
```html
{% if not request.user.is_staff %}
<h1>
Welcome to the show
</h1>
But I need to use a view function to clean my code because I’m probably going to add more conditionals.
Since you are using a class-based view then I would suggest updating the context dictionary which you could use in the html template to determine whether the user is allowed or not. For example, Within the views.py.
class VerificateUser():
# Update the context
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Call the allowed_user() and return whatever value, passing that value it to the context variable
context['allowed_user'] = self.allowed_user()
return context
# Checking if the user is allowed here
def allowed_user():
if self.request.user.is_staff:
return True
return False
Now within the html file, you can reference that allowed_user from the context variable.
{% if allowed_user %}
<h1>Hi, you are allowed to see here...</h1>
{% endif %}
That should do the trick.
You can do this sort of thing in many ways, but simply you can do by the following way-
{% if request.user.is_authenticated %}
<p>Welcome,{{request.user.first_name}} </p>
{% endif %}
request object is by default available in all of your Django templates context.

How can I delete the answers (code in body)?

I am creating a Q&A website for practice, I created the answer and the question model and linked them together, however I can not access the template that I set for the deletion of the answer model, I created a DeleteView to delete the question. Here is the code:
views.py:
class Politics_post_details(DeleteView):
model = PoliticsPost
context_object_name = 'politicsposts'
pk_url_kwarg = 'qid'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# now you can get any additional information you want from other models
question = get_object_or_404(PoliticsPost, pk=self.kwargs.get('qid'))
context['answers'] = Answer.objects.filter(post=question).order_by('-date_posted')
return context
class AnswerDelete(UserPassesTestMixin,DetailView):
model = Answer
success_url = reverse_lazy('Lisk home')
pk_url_kwarg = 'aid'
def test_func(self):
answer = self.get_object()
if self.request.user ==answer.author:
return True
return False
urls.py(not root):
path('politicspost/<int:qid>/createanswer/',views.CreateAnswer.as_view(template_name='lisk_templates/createanswer.html'),name = 'Answer'),
path('politicspost/<int:qid>/answer/<int:aid>/delete/',views.AnswerDelete.as_view(template_name = 'lisk_templates/answer_delete.html'),name='Answer_Delete'),
path('politicspost/<int:qid>/',views.Politics_post_details.as_view(template_name='lisk_templates/politics_post_details.html'),
I created the template but whenever I try to access it, it gives me an error as follows:
NoReverseMatch at /politicspost/29/
Reverse for 'Answer_Delete' with arguments '(36,)' not found. 1 pattern(s) tried: ['politicspost/(?P<qid>[0-9]+)/answer/(?P<aid>[0-9]+)/delete/$']
Thanks in advance.
answer_delete.html:
{%extends "lisk_templates/base.html"%}
{% block title %}
Page title
{% endblock title %}
{% block body%}
<div class="feed" style="background-color:lightred;"><form method="POST">
{% csrf_token %}
<h3>Are you sure that you want to delete this answer: <br>
{{ object.content }}</h3>
<button id="signin" type="submit">Yes</button> No
</form>
</div>
{% endblock body %}
You have a number of issues. The biggest one is that you have switched DeleteView and DetailView (your DeleteView is a DetailView and vice versa).
And then you should either rename your template to answer_confirm_delete or add the template_name_suffix to your DeleteView. See the documentation for further details:
https://docs.djangoproject.com/en/3.0/ref/class-based-views/generic-editing/#django.views.generic.edit.DeleteView
If you use Django's DeleteView, you don't need to specify a url in the template, it will do all the work for you. Just make sure you specify the url correctly in your urls.py. Fix these issues and see if it works then.
May Be This Might Work For You, Because You Never Used The Import Of DeleteView
from django.views.generic import DeleteView
"""This Is From My Blog App, That I Used For My Blog App, Hope This Will Help"""
class PostDeleteView(LoginRequiredMixin, DeleteView):
model = Post
success_url = reverse_lazy('post_list')
Happy Coding, let me know if there are any issues with this I'am Up for you!

Method get_context_data is called twice when using template tags in django

I have a template_tag.py:
from django import template
from myapp.views import RenderView
register = template.Library()
#register.inclusion_tag("template_tag.html")
def render_myapp():
rv=RenderView()
return rv.get_context_data()
and myapp.views.py:
from django.views.generic.base import TemplateView
class RenderView(TemplateView):
template_name = "test.html"
def get_context_data(self, **kwargs):
context = super(RenderView, self).get_context_data(**kwargs)
context["test"] = 1 # this is hit twice in the debugger
return context
template_tag.html:
{% if test %}
{{ test|safe }}
{% endif %}
base.html (different app):
{% load template_tag %}
{% render_myapp %}
I wonder why RenderView().get_context_data() is hit twice in the debugger? I don't call it twice in my template. It's probably because TemplateView already calls get_context_data and then I call it again rv.get_context_data(). But then how should my template_tag.py look like to not call get_context_data() again?
There seem to be two situations here.
In one case, the template tag is always in a template that is rendered by your RenderView. In which case, there doesn't seem to be any need for a tag; you should just either include template_tag.html or put its code directly into test.html.
In the other case, the tag is in another template, or in a range of templates that may or may not be rendered by RenderView. In which case, why is the context data for that page defined in RenderView? It should be defined directly in render_myapp().

django form submit not working

I have created for interface for users to filter the content from database by values.
Class in View
class FilterCommentsUIView(TemplateView,FormView):
template_name = 'allcomments.html'
form_class = CommentFilterForm
def get_context_data(self, **kwargs):
context = super(FilterCommentsUIView, self).get_context_data(**kwargs)
logger.debug("Entered context data")
logger.debug(self.request.method)
if self.request.method == 'POST':
form = CommentFilterForm(self.request.POST)
loggeer.debug("Entered POST")
if form.is_valid():
logger.debug("Entered is valid")
parenttype = self.request.POST['pchoice']
users = self.request.POST.get('users')
tags = self.request.POST.get('tags')
fdate = self.request.POST.get('fdate')
tdate = self.request.POST.get('tdate')
typeuser = self.request.POST.get('typeuser')
query = self.request.POST.get('query')
userid = User.objects.get(username ='pavan')
comments = Item.objects.all().filter(user = userid.id).order_by('-created')
paginator = Paginator(comments,20)
page = self.request.GET.get('page')
try:
comments = paginator.page(page)
except PageNotAnInteger:
comments = paginator.page(1)
except EmptyPage:
comments = paginator.page(paginator.num_pages)
context['comments']=comments
context['form']=CommentFilterForm
else:
logger.debug("Entered isvalid else")
logger.debug(form.errors)
else:
logger.debug("Entered Else of POST")
form = CommentFilterForm()
comments = Item.objects.all().order_by('-created')
paginator = Paginator(comments, 20)
page = self.request.GET.get('page')
try:
comments = paginator.page(page)
except PageNotAnInteger:
comemnts = paginator.page(1)
except EmptyPage:
comments = paginator.page(paginator.num_pages)
context['comments']=comments
context['form']= form
return context
When i click on submit button it does not enter the POST if section. It always goes to else part. What am i doing wrong ?
Thanks in advance for help.
Pavan
As others have said you have issues with the fact all your code is in get_context_data. In all actuality you don't even need to use get_context_data to accomplish what you are trying to do.
From what I can see you are trying to get comments on the page. Then submit a form back to the page and get a filtered out set of comments returned, and rendered. Here is a view solution that better does what you want except using generic class based views more fully.
class FilterCommentsUIView(FormMixin, ListView):
template_name = 'allcomments.html'
form_class = CommentFilterForm
paginate_by = 20
model = Item
context_object_name = 'comments'
qs_kwargs = {}
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_queryset(self):
qs = super(FilterCommentsUIView, self).get_queryset()
if self.qs_kwargs:
return qs.filter(**self.qs_kwargs)
return qs
def form_valid(self, form):
parenttype = form.cleaned_data['pchoice']
users = form.cleaned_data('users')
tags = form.cleaned_data('tags')
fdate = form.cleaned_data('fdate')
tdate = form.cleaned_data('tdate')
typeuser = form.cleaned_data('typeuser')
query = form.cleaned_data('query')
userid = User.objects.get(username ='pavan')
# put logic here on what to expand into a filter on orm
self.qs_kwargs['user'] = userid.id
self.render_to_response(self.get_context_data())
I haven't run this code, but this is how it should work.
This uses the FormMixin to give us form functionality along with the ListView with your Item class. This takes care of querying for your objects and paginating them, along with a basic get method.
Also the ListView inherits from TemplateResponseMixin which has your template code so you don't need to inherit from TemplateView so in the code above I removed that.
Based on the very nature of the base View class which ListView uses the dispatch method detects whether you are doing a GET, POST, PUT, DELETE, etc... As such it calls the appropriate method on the view. In this case since you are doing a POST it will call the post method in the view. The FormView's implementation of post is this:
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Since we aren't using FormView, but are using FormMixin We need to include it.
So all it does is get the form class you set in self.form_class instantiates it, then checks if it is valid. If so calls the form_valid method. Thus we overrode the form_valid method above. Since it passes in the form with it and it has cleaned all the data we can use the normal django form of form.cleaned_data. This helps us with security so we should use that to get back information.
In the last part of the form_valid method we are returning render_to_response. Normally this just redirects to self.success_url, but we want to render the page so we just do a render_to_response and pass it in our context data. We call self.get_context_data() because that is what builds all the data for paginating our comments that we need.
Now comes a bit of the magic. if you notice there is self.qs_kwargs. This is something that is not normally in GCBV's, but something I added for this implementation. Here is where you would put logic to build a dictionary of filters to run in your orm call in get_queryset. We pack it into the dictionary, because in the get_queryset method above we unpack it all, if needed, to do our filtering.
So if you have:
qs_kwargs = {'user': user.id, 'created__gt': somedate}
And you do:
qs.filter(**qs_kwargs) or Item.objects.filter(**qs_kwargs)
That is roughly the same as doing:
qs.filter(user=user.id, created__gt=somedate) or Item.objects.filter(user=user.id, created__gt=somedate)
Finally, if you pay attention to the get_queryset method you notice it returns back all results, like you want, unless qs_kwargs is populated which happens from the form_valid method. So it takes in to account both GET and POST on the page.
Also to keep things simple your html should look something like this:
<html>
<head></head>
<body>
<div>
{% for comment in comments %}
{{ comment }}
{% endfor %}
<ul class="pagination">
{% if page_obj.has_previous() %}
<li>Previous</li>
{% endif %}
{% for pg in paginator.page_range %}
{% if page_obj.number == pg %}
<li class="active">{{ pg }}</li>
{% else %}
<li>{{ pg }}</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next() %}
<li>Next</li>
{% endif %}
</ul>
</div>
<div>
<form action="" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="submit" />
</form>
</div>
</body>
</html>
Hope that helps.
This needs to be done in the form_valid() method, not the get_context_data method. get_context_data is just used to provide extra data (aka context) to the template during the request.

Django's get_context_data function not working?

I'm practicing with Django's Class Based View.
It seems like my overridden get_context_data function is not working properly, but I have no idea what is wrong :(
My code:
urls.py
url(r'^user/(?P<pk>\d+)/posts/$', UserPosts.as_view(), name='user_post'),
views.py
class UserPosts(ListView):
template_name = 'app_blog/user_posts_page.html'
context_object_name = 'post_list'
def get_queryset(self):
self.user = get_object_or_404(User, id=self.kwargs['pk'])
return self.user.post_set.order_by('-id')
def get_context_data(self, **kwargs):
context = super(UserPosts, self).get_context_data(**kwargs)
context['user'] = self.user
return context
user_post_page.html
{% block content %}
<div class="main_content">
<h2>Welcome to {{user.username}}'s User Post Page!</he>
<ul>
{% for post in post_list %}
<li>{{post.post_title}}</li>
{% endfor %}
</ul>
</div>
{% endblock %}
The html page correctly displays the user's post_list, BUT the h2 tag displays:
Welcome to 's User Post Page!
I'm pretty sure I passed the 'user' variable in the get_context_data function, but the html page does not displa the user.username... Any idea why this is happening :(??
Thanks
Use another name that is not user. It seems like RequestContext overwrite user variable.
Please see the default TEMPLATE_CONTEXT_PROCESSORS, which set the django.contrib.auth.context_processors.auth.
If TEMPLATE_CONTEXT_PROCESSORS contains this processor, every RequestContext will contain these variables:
user – An auth.User instance representing the currently logged-in user (or an AnonymousUser instance, if the client isn’t logged in).
perms – An instance of django.contrib.auth.context_processors.PermWrapper, representing the permissions that the currently logged-in user has.
So you'd better give your user variable another name.
As the other answers said, don't use the variable name user. But even more importantly, in this particular case the ListView is not the best generic class to use; instead, you're better off using the DetailView, which makes your view much simpler:
class UserPosts(DetailView):
model = User
template_name = 'app_blog/user_posts_page.html'
context_object_name = 'listed_user'
The only change in your template is to use listed_user instead of the user variable. Since DetailView already looks into the pk URL argument, it will select and return the right User automatically.

Categories

Resources