How to get request params in Django admin custom column? - python

I need to keep information about filtering and searching in Django admin change page.
So when user filters by "?away_team__id__exact=267821", I need to append this query to change page url.
Let's say we filtered objects by query above. This is the url of change list:
http://127.0.0.1:8000/matches/match/?away_team__id__exact=267821
I want to make change column which redirects user to change page of the current object and appends query to the url so instead:
http://127.0.0.1:8000/matches/match/2009/change/
The url will be:
http://127.0.0.1:8000/matches/match/2009/change/?away_team__id__exact=267821
The problem is that I couldn't access request in the custom column method. I tried to do it using template language but without success, I get:
http://127.0.0.1:8000/matches/match/1996/change/?{{%20request.GET.urlencode%20}}
This is the method:
def change(self,obj):
return mark_safe(f"""<a class="changelink" href="{reverse("admin:matches_match_change",args=(obj.pk,))}"""+"?{{ request.GET.urlencode }}\""+"><span class='icon'>ZmeniƄ</span></a>")
Do you know how to do that?
EDIT
This is because I need to create a NEXT and PREVIOUS buttons in change object page so user can step directly to the next object.

You can just store the current request on the admin instance in the change list view to make it available to subsequent methods:
class YourAdmin(ModelAdmin):
def changelist_view(self, request, *args, **kwargs):
self.request = request
return super().changelist_view(request, *args, **kwargs)
def change(self, obj):
request = getattr(self, 'request', None)
if request:
# use request.GET to construct the link

I put together a mixin for extending AdminModels for accessing to the request parameters:
class RequestParameters_AdminMixin(object):
"""adds the request GET parameters in AdminModel variable request_parameters"""
request_parameters={}
def changelist_view(self, request, *args, **kwargs):
self.request_parameters = request.GET
return super().changelist_view(request, *args, **kwargs)
Use:
class TaskModelAdmin(RequestParameters_AdminMixin, original_TaskModelAdmin):
def column_name(self, obj):
if 'is_maintask' in self.request_parameters:
...

Related

How can i route request to specific views according to parameters in Django?

I created an API with Django and i am trying to route requests to different views for optimization purposes.
Here is what i'm trying to do: if the GET request is 127.0.0.1:8000/api/sales/?key=<KEY>&some_query=<some_value>&... that query should be handled by a view called sales. If the GET request contains a specific parameter called sale_value the request should be routed to another view called large_sales, for example 127.0.0.1:8000/api/sales/?key=<KEY>&sale_value=<INT>... should be handled from the large_sales view.
Here is what i tried:
urlpatterns = [
path('sales/', views.sales, name="sales"),
path('sales/sale_value<int:num>', views.large_sales, name="large_sales"),
]
This will route all the requests to sales. How can i do this? Maybe with regex?
Edit: i can't handle this logic directly in the view because i want to cache these two queries with different TTL. Example:
#cache_page(1.5)
def sales(request):
...
#cache_page(10)
def large_sales(request):
...
def sales(request):
try:
sale_value = request.GET['sale_value']
except:
return redirect(large_sales, num=11) # replace 11 with your number.
or try to use if condition (if sale_value != None:)
You can write a view which accesses request.GET and redirects accordingly.
You may also need to put defensive code in the view that you redirect to, to make sure that a user (or attacer) is not getting creative with completely insecure GET parameters!
With a class-based view you can subclass get ...
class SalesView( some_CBV_class):
...
def get( self, request, *args, **kwargs):
if request.GET.get( 'sale_value', None):
target = reverse('app:large_sales', kwargs={...}
) + '?' + request.GET.urlencode()
return HttpResponseRedirect( target)
return super().get( request, *args, **kwargs)
and for safety, maybe
class LargeSalesView( ...)
def get( self, request, *args, **kwargs):
if request.GET.get( 'sale_value', None) is None
target = reverse('app:sales', kwargs={...}
) + '?' + request.GET.urlencode()
return HttpResponseRedirect( target)
return super().get( request, *args, **kwargs)

Override update method of UpdateAPIView

I need to do some actions, before calls update().
my code
class CarView(generics.UpdateAPIView):
permission_classes = (IsAdminUser,)
serializer_class = CarSerializer
def get_queryset(self):
return ...
def update(self, request, *args, **kwargs):
# some actions
super(CarView, self).update(request, *args, **kwargs)
But I'm getting an error
error message
Expected a Response, HttpResponse or HttpStreamingResponse to be
returned from the view, but received a <type 'NoneType'>
How can I fix that?
Like most Django views, your update method on the ViewSet should be returning a response. Right now you aren't returning anything, which is why Django is complaining about receiving NoneType (as that is the default return value).
The issue is coming from the last line of your update method, where you are calling the parent update but aren't returning it.
super(CarView, self).update(request, *args, **kwargs)
If you returned it, the response that came from the update method that is normally defined would be passed down the chain and rendered as you would expect.
return super(CarView, self).update(request, *args, **kwargs)
This is happening because you have not returned anything in your update method. Django views expect a Response object to be returned. Just add a return in your update method.
class CarView(generics.UpdateAPIView):
permission_classes = (IsAdminUser,)
serializer_class = CarSerializer
def get_queryset(self):
return ...
def update(self, request, *args, **kwargs):
# some actions
return super(CarView, self).update(request, *args, **kwargs)
According to docs,
REST framework supports HTTP content negotiation by providing a
Response class which allows you to return content that can be rendered
into multiple content types, depending on the client request.
The Response class subclasses Django's SimpleTemplateResponse.
Response objects are initialised with data, which should consist of
native Python primitives. REST framework then uses standard HTTP
content negotiation to determine how it should render the final
response content.
So, to render the data into different content types, you have to return a response.

Redirecting to external page from django class based view

I'm trying to redirect users based on the referer in the request header. Basically, if the referer is say https://www.google.com, I would like to send them to a page, not on my website. Otherwise, continue processing as usual.
Here is what I have so far
class ArticleAccess(TemplateView, SomeMixin):
http_method_names = ['get']
template_name = 'template.html'
def dispatch(self, request, *args, **kwargs):
return super(ArticleAccess, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(ArticleAccess, self).get_context_data(**kwargs)
item = get_object_or_404(ClientItem.objects.using(self.get_site().name), id=kwargs['article_id'])
if self.request.META.get('HTTP_REFERER') == 'https://www.google.com/':
return redirect(item.item_url)
context['id'] = item.id
context['name'] = item.name
context['html'] = item.description
context['item_url'] = item.item_url
return context
This just stays on the same page instead of redirecting. I have also tried HttpResponseRedirect, but to no avail
alecxe is correct.. you'd have to redirect from a method that is expected to return an HttpResponse.
get_context_data is not expected to return an HttpResponse and isn't ever returned by the view. It's always used to get a data dict to populate say a template. No matter what you return from this method, it will never override the response.
Therefore wherever you write this override, it needs to be in a place that is expected to return a response, such as get, post, dispatch.
The problem now is to determine how to get your object outside of the get_context_data method.
For debugging, I recommend you to start by just using a plain redirect('some_view') without conditions on your dispatch method so you can check if redirection is hit as expected and only then, go for conditions and anything else. #Yuji-tomita-tomita is just right! :) Django-pdb and ipdb are very nice tools.

Django: ListView with post() method?

I am trying to process two forms in a Django class based view. The site contains a form called form (based on GET) for narrowing the list results of the ListView and the second form status_form (based on POST).
Both forms are required since the ListView returns a list of items. Form lets the user restrict the choices and status_forms lets the user flag incorrect items via a modal form (therefore it needs to be in the same template).
My trouble is that ListView does not come with the method post, however FormView does. My class List inherits from both classes, but when I execute the class I get the error message:
Attribute Error: 'List' object has no attribute 'status_form'
How should I change my implementation to allow the second form been processed via the post method?
class List(PaginationMixin, ListView, FormMixin):
model = ListModel
context_object_name = 'list_objects'
template_name = 'pages/list.html'
paginate_by = 10 #how may items per page
def get(self, request, *args, **kwargs):
self.form = ListSearchForm(self.request.GET or None,)
return super(List, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.status_form = StatusForm(self.request.POST or None)
if self.status_form.is_valid():
...
else:
return super(List, self).post(request, *args, **kwargs)
def get_queryset(self):
# define the queryset
...
# when done, pass to object_list
return object_list
def get_context_data(self, **kwargs):
context = super(List, self).get_context_data(**kwargs)
context.update(**kwargs)
context['form'] = self.form
context['status_form'] = self.status_form # Django is complaining that status_form is not existing, result since the post method is not executed
return context
# Django is complaining that status_form does not exist,
# result since the post method is not executed
context['status_form'] = self.status_form
Because you didn't define self.status_from in the first place.
You have defined it in get_context_data, and it's accessible from there.
You can access you object from get_context_data in your post method;
context = self.get_context_data(**kwargs)
status_form = context['status_form']
Also consider that you can define your status_form directly in post method itself without getting it from self or get_context_data.
Redesign you views to separate each Form processing in separate Views then tight them with each-other.
Views redesign:
In nutshell, let each view to do one job. You can create a View just for processing your status_form and name it like StatusFormProcessView then on your List view return it on its post method
class List(ListView);
def post(self, request, *args, **kwargs):
return StatusFormView.as_view()(request) # What ever you need be pass to you form processing view
This is just an example of it, need more work to be real.
For another example; On my website index page I have a search form. when user POST or GET the search form, The processing of searching doesn't exist in my IndexView, instead I handle the whole form stuff in separate view, If form should process on GET method, I'll override get() method, If form should process on POST, I'll override post() method to send search_form data to the view that is responsible for handling of processing the search_form.
Comments response
status_form = context['status_form']
shouldn't it be
context['status_form'] = status_form
after I created it ?
You want to get status_form from context, So you need to
status_form = context['status_form']
Anyway, your form data are available on self.request.POST

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