How to hook the admin forms to get the request context? - django

In Django - Overriding get_form to customize admin forms based on request the problem is to select a different form based on the permissions of the user in the request object by hooking the get_form() method.
I would like to actually invoke a method on the object during iteration that uses the request context to output some information.
The documentation lists four ways to hook the form display.
But the function signatures don't include the request object. If they did, you could write something like (note that request is not in fact an argument):
class CustomAdmin(admin.ModelAdmin):
list_display = [ 'name', 'user_specific', ]
#
def user_specific(self, obj, request):
return obj.func1(request)
#
output.short_description = 'UserSpecific'
Overriding get_form() would not be thread safe if used to store the state... So what would be the best way?

In your case, I feel that maybe writing your own view is a better choice than hacking django's admin site.
But if you insist, you can override changelist_view and record the request.
class CustomAdmin(admin.ModelAdmin):
list_display = [ 'name', 'user_specific', ]
def changelist_view(self, request, extra_context=None):
self.request = request
return super(admin.ModelAdmin, self).changelist_view(self, request, extra_context)
def user_specific(self, obj):
return obj.func1(self.request)
output.short_description = 'UserSpecific'

Related

Django: access to user info from admin.py for methods with no request object?

Django 1.11.
In admin.py I have:
class AuditAdmin(DeactivateMixin, admin.ModelAdmin):
"""all the superclass stuff"""
from which I subclass my model's stuff with various custom methods:
class SomeModelAdmin(AuditAdmin):
list_display = ["filed1", "filed2", "field3"]
def get_queryset(self, request):
if request.user.is_superuser:
#do something extra
def inline_add_somemodelattribute1(self, my_object):
#how to access user if I don't have request ?
So inside inline_add_somemodelattribute1 method I need to make decision based on the user but that method does not take request as an argument. How do I get my user data in this case? I did not find anything relevant in self or my_object
Thanks
Easiest way of access current request is using crequest. You can use it like this:
from crequest.middleware import CrequestMiddleware
class SomeModelAdmin(...):
...
def inline_add_somemodelattribute1(self, my_object):
crequest = CrequestMiddleware.get_request()
# your logics here for example: if crequest.user.pk == 1:
You may consider setting some values that you may require in your custom methods related to request user in changelist_view method as :
def changelist_view(self, request, extra_context=None):
# setting is_superuser attribute to be used in custom methods.
setattr(self, 'is_superuser', request.user.is_superuser)
return super().changelist_view(request, extra_context)

Django/Python: How to write Create, List, Update and Delete in a single View or a generic view?

I am trying to write a view in which a post can be created and in the same page, the object_list will be displayed. And even an object can be updated and deleted.
Country Capital
India Delhi UPDATE DELETE
USA Washington UPDATE DELETE
----- ------
I would appreciate helping me in achieve this or suggesting a similar type of question.
What you're looking for are Mixins.
Try creating a detail view class with the following parameters:
mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView
For example:
class ObjectDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
queryset = Object.objects.all()
As has proposed by Daniel, if you like DRF, ViewSets are also a decent alternative. However, they're not exactly succinct so I generally avoid them when possible.
Something like a ModelViewSet, however, is extremely clear-cut and the approach I generally choose.
Here's an example:
class ObjectViewSet(viewsets.ModelViewSet):
queryset = Object.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Beautiful, isn't it?
For more details, see the DRF tutorial: http://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/
You are mixing view and template. View handle requests and template show content and links.
You will have ListView, which will contain list of posts. In template you add forms for update, form for create and forms for delete. Each form will have attribute action with link to proper view. So update forms will have link to url with UpdateView, create forms to CreateView, and delete to DeleteView. In each form you set redirect back to ListView. This way if you want to use only Django.
OR
If you really want to everything handle on one page without refreshing and redirecting. You can use ajax and django-rest-framework and its viewset. In viewset you can handle lists, create, update, push, detail, in one class.
Viewset:
class UserViewSet(viewsets.ViewSet):
"""
Example empty viewset demonstrating the standard
actions that will be handled by a router class.
If you're using format suffixes, make sure to also include
the `format=None` keyword argument for each action.
"""
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass

Readonly View for 10 Django Class Based Views

I have 10 Django Class Based Views and I want to display them read-only to the user.
I want the whole form to be read-only, not only some values. Submitting the form should be disabled on the client (HTML) and a second time on the server (POST not allowed).
Is there a MixIn or an other simple solution?
Here's a mixin that does two simple things:
Sets html attributes for all fields in form for disabled andreadonly.
Overrides the form_valid method of your CBV so that no model saving ever happens; instead, the template is rendered (just as if there was no submitted data). The user, this way, does not cause any action if they submitted the form.
Form field errors may appear next to disabled fields if you are rendering the full form in your template; solve this by either erasing the form's error dictionary or by rendering each field individually without errors.
from django.views.generic.edit import FormMixin, ModelFormMixin
class ReadOnlyModelFormMixin(ModelFormMixin):
def get_form(self, form_class=None):
form = super(ReadOnlyModelFormMixin, self).get_form()
for field in form.fields:
# Set html attributes as needed for all fields
form.fields[field].widget.attrs['readonly'] = 'readonly'
form.fields[field].widget.attrs['disabled'] = 'disabled'
return form
def form_valid(self, form):
"""
Called when form is submitted and form.is_valid()
"""
return self.form_invalid(form)
Extending this concept for a non-model FormView is pretty simple; inherit from class FormMixin instead. :)
To disallow POST requests in general for class-based views you could use the following mixin:
class DisallowPostMixin(object):
def post(self, request, *args, **kwargs):
return self.http_method_not_allowed(self, request, *args, **kwargs)
If you also want to disable certain form fields etc. you could add the get_form method from Ian Price's answer.
You can hack it through middleware. On request - check view name and request method (if post - redirect), on response - add input attrs in response.content. But mixin - best solution.

How to override the request method (from 'PUT' into 'POST') in Django Rest Framework

I'm coding a plugin-wrapper for provide a app to restfull, problem is the app don't process PUT request, then I get from resfull a PUT request and should change request's method into a POST request.
I have tried this:
self.context['request'].method = 'POST'
but obviously don't works
How I could do it?
Thanks
Update:
Serializer code:
class PageEditSerializer(serializers.ModelSerializer):
"""
Serializer Class to edit and delete a Page.
"""
raw = serializers.CharField()
def save(self):
#no works
self.context['request'].method = 'POST'
"""call to waliki new function"""
views.edit(self.context['request'], self.instance.slug)
class Meta():
model = Page
fields = ('id', 'title', 'slug', 'raw', )
read_only_fields = ('id', 'slug', )
View code:
class PageEditView(generics.RetrieveUpdateAPIView):
"""
A simple View to edit a Page.
"""
lookup_field = 'slug'
serializer_class = PageEditSerializer
permission_classes = (permissions.AllowAny, )
def get_queryset(self):
return Page.objects.filter(slug=self.kwargs['slug'])
I will divide my answer into 3 parts.
PART-1: Does Django Rest Framework (DRF) explicitly supports request method overriding?
Yes, DRF has provided 2 ways in which the request method can be overridden.
Using a hidden form field:
DRF supports browser-based PUT, DELETE and other methods, by overloading POST requests using a hidden form field.
For example:
<form action="/some/url" method="POST">
<input type="hidden" name="_method" value="PUT">
</form>
In the above example, we have overloaded the POST method with PUT by using the hidden form field with name as _method.
request.method will now return PUT instead of POST.
HTTP header based method overriding:
DRF also supports method overriding via the X-HTTP-Method-Override header. Just set the X-HTTP-Method-Override header with the value of the desired method.
For example, making a PATCH request via POST in jQuery:
$.ajax({
url: '/myresource/',
method: 'POST',
headers: {'X-HTTP-Method-Override': 'PATCH'},
...
});
This approach is useful when working with non-form content such as JSON or when working on an older web server and/or hosting provider that doesn't recognise particular HTTP methods such as PATCH.
PART-2: Why your code is not working?
Request method is stored in a variable _method and the property method is used to access the value of it.
Your way of setting value of request method would have worked had there been a setter defined for the request's _method attribute. Since it is not defined, you can't set the value of request method in the way you are doing.
PART-3: How to override the request method in your case?
Solution-1(Quick): Directly set the _method attribute
You can directly set the _method to your desired value which will override the request method.
class PageEditSerializer(serializers.ModelSerializer):
"""
Serializer Class to edit and delete a Page.
"""
raw = serializers.CharField()
def save(self):
self.context['request']._method = 'POST' # override the request method
"""call to waliki new function"""
views.edit(self.context['request'], self.instance.slug)
class Meta():
model = Page
fields = ('id', 'title', 'slug', 'raw', )
read_only_fields = ('id', 'slug', )
This will override the request method to POST.
Solution-2: Set X-HTTP-Method-Override header in request via middleware
When initialize_request() is called in DRF view, DRF checks for X-HTTP-Method-Override header in the Django request. If it is set, it overrides the current request method to the method defined in that header.
So before DRF initializes and prepares a DRF request, we can set the X-HTTP-Method-Override header to our desired method which will override the request method.
We create a PutRequestMethodOverrideMiddleware class and set the X-HTTP-Method-Override header in the request in case the request is PUT and this particular header is not already set.
class PutRequestMethodOverrideMiddleware(object):
def process_request(self, request):
override_method = request.META.get('X-HTTP-Method-Override')
if request.method=='PUT' and not override_method:
# Override method in case of PUT requests and header not already set
request.META['X-HTTP-Method-Override'] = 'POST' # set to desired method value
return
Then in your settings, add this middleware to your MIDDLEWARE_CLASSES.
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
...
...
# your custom middleware
'my_project.middlewares.PutRequestMethodOverrideMiddleware',
)
But remember, this method will override all the PUT requests method to POST method.
Here, PutRequestMethodOverrideMiddleware will be executed at the end. You can put it at the position you want it to execute.
As of version 3.3, DRF has dropped support for the method overloading mechanisms via both _method form field and X-HTTP-Method-Override header. You need to implement it yourself for the latest version.
The document provides an example by adding a custom middleware:
METHOD_OVERRIDE_HEADER = 'HTTP_X_HTTP_METHOD_OVERRIDE'
class MethodOverrideMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.method == 'POST' and METHOD_OVERRIDE_HEADER in request.META:
request.method = request.META[METHOD_OVERRIDE_HEADER]
return self.get_response(request)
Another solution is to override dispatch method in each of your APIView:
class MyView(APIView):
def dispatch(self, request, *args, **kwargs):
if request.method == 'POST' and METHOD_OVERRIDE_HEADER in request.META:
request.method = request.META[METHOD_OVERRIDE_HEADER]
return super().dispatch(request, *args, **kwargs)

request in ModelForm admin django

I want to create a default value in admin "add" form based on request (user attributes), searching I found that I can create default value overriding init method of ModelForm, however I canĀ“t access to request here. Note: I tried self.request = kwargs.pop('request') and didn't work. Any Ideas, thaks.
Try overriding the render_change_form method on your model admin:
def render_change_form(self, request, context, *args, **kwargs):
# your code here, modifying the values of whatever fields you have
return super(YourModelAdmin, self).render_change_form(
request, context, args, kwargs)