Is there a 'DetailView' in Django Admin? - python

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

Related

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.

How to PATCH a single field using Django Rest Framework?

I have a model 'MyModel' with many fields and I would like to update a field 'status' using PATCH method. I'm using class based views. Is there any way to implement PATCH?
Serializers allow partial updates by specifying partial=True when initializing the serialzer. This is how PATCH requests are handled by default in the generic views.
serializer = CommentSerializer(comment, data=request.data, partial=True)
This will allow you to update individual fields in a serializer, or all of the fields if you want, without any of the restrictions of a standard PUT request.
As Kevin Brown stated you could use the partial=True, which chefarov clarified nicely.
I would just like to correct them and say you could use generics freely, depending on the HTTP method you are using:
If you are using PATCH HTTP method like asked, you get it out of the box. You can see UpdateModelMixin code for partial_update:
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
For any HTTP method different from PATCH, this can be accomplished by just overriding the get_serializer method as follows:
def get_serializer(self, *args, **kwargs):
kwargs['partial'] = True
return super(YOUR_CLASS, self).get_serializer(*args, **kwargs)
This will create the serializer as partial, and the rest of the generics will work like a charm without any manual intervention in the update/partial_update mechanism.
Under the hood
I used the generic: generics.UpdateAPIView which uses the UpdateModelMixin which has this code:
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
…
So, if you override the get_serializer function, you can actually override the partial argument and force it to be true.
Please note, that if you want it partial only for some of the HTTP methods, this makes this approach more difficult.
I am using djangorestframework==3.5.3
It does seem to be supported out of the box. On your browser API, navigate to a model detail page, at the bottom next to the HTML Form tab click Raw data, delete everything from the JSON string except the ID field and the field you wish to change, and click PATCH. A partial PATCH update is performed.
I'm using djangorestframework==3.2.4, and haven't had to do anything to my ViewSets and Serializers to enable this.
In this exampe we are updating the bool status_field field of the model, and I'm using jquery 2.2.1. Add the following to the <head>:
<script src="{% static 'my_app/jquery.min.js' %}"></script>
<script>
$(document).ready(function(){
var chk_status_field = $('#status_field');
chk_status_field.click(function(){
$.ajax({url: "{% url 'model-detail' your_rendering_context.id %}",
type: 'PATCH', timeout: 3000, data: { status_field: this.checked }
})
.fail(function(){
alert('Error updating this model instance.');
chk_status_field.prop('checked', !chk_status_field.prop('checked'));
});
});
});
</script>
Then in a <form>:
<input type="checkbox" id="status_field" {% if your_rendering_context.status_field %}
checked {% endif %} >
I chose to permit the checkbox to change, then revert it in the case of failure. But you could replace click with mousedown to only update the checkbox value once the AJAX call has succeeded. I think this will lead to people repeatedly clicking the checkbox for slow connections though.
If anyone is still planning to find a simple solution using ModelSerializer without changing much of your views, you can subclass the ModelSerializer and have all your ModelSerializers inherit from that.
class PatchModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
kwargs['partial'] = True
super(PatchModelSerializer, self).__init__(*args, **kwargs)
class ArticleSerializer(PatchModelSerializer):
class Meta:
model = Article
I struggled with this one for a while, but it is a very straightforward implementation using generic views or a combination of generic views and mixins.
In the case of using a generic update view (generics.UpdateAPIView), just use the following code, making sure the request type is PATCH:
class UpdateUser(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
There's nothing else to it!
If you're using an update mixin (mixins.UpdateModelMixin) in combination with a generic view (generics.GenericAPIView), use the following code, making sure the request type is PATCH:
class ActivateUser(mixins.UpdateModelMixin, generics.GenericAPIView):
serializer_class = UserSerializer
model = User
lookup_field = 'email'
def get_queryset(self):
queryset = self.model.objects.filter(active=False)
return queryset
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
The second example is more complex, showing you how to override the queryset and lookup field, but the code you should pay attention to is the patch function.

How does class based view with multiple methods work with urls in django?

I have a created a view class "class Demo", which has 3 functions
def update_time()
def get_context()
def before_response()
urls.py : url(r'^demo/$', Demo.as_view(),name='demo_class'),
When i'll enter url /demo/ how will it determine which function to call from "class Demo" ?
Because Django’s URL resolver expects to send the request and associated arguments to a callable function, not a class, class-based views have an as_view() class method which serves as the callable entry point to your class. The as_view entry point creates an instance of your class and calls its dispatch() method. dispatch looks at the request to determine whether it is a GET, POST, etc, and relays the request to a matching method if one is defined, or raises HttpResponseNotAllowed if not.
just read the docs
Basically class based views are recommended when you need to handle both get and post requests at one point. For example in get method of class Register, you can render the registration form and in its post method, you can handle the form submission. If its a get request it will automatically invoke the get() method in the class, same for post request. Also you can write any common code in the dispatch() method which will be invoked for every request.eg:
class Register(View):
def dispatch(self, *args, **kwargs):
'''
common code here
'''
return super(Register, self).dispatch(*args, **kwargs)
def get(self, request):
registration_form = RegistrationForm()
return render(request, 'new.html', { 'form': registration_form })
def post(self, request):
registration_form = RegistrationForm(request.POST or None)
if registration_form.is_valid():
#save form
return HttpResponseRedirect(reverse('success-show'))
return render(request,new.html', { 'form': registration_form })
For references you can check this website.
You need to subclass a class based views, and depending on that it will have one or another method.
For example TemplateView renders a template you pass in the template_name attribute.
All class based views care about is that the attributes needed to work properly are setted. That is done via different methods. You can check the django's documentation for specifics.
For example, if you want to render a form in your template view, you will need to pass the form in the context, so you can override get_context_data() like:
def get_context_data(self, **kwargs):
context = super(DemoClass, self).get_context_data(**kwargs)
context['form'] = MyForm()
return context
There are different methods to handle different things, like querysets, objects, etc.
They are not complicated, but they are specific. If a class based view does not fit what you need, it may be easier to implement the functionality from a more general view (View, TemplateView) than forcing a more specific one to do things it is not intended for.
slightly change the url
add numbers one to three in url and put the condition in your view.
Ex.
url(r'^abc/(?P<newid>.*)$', 'class_demo'),
so your url will be like abc/1 or abc/2 or abc/3
view
def class_demo(requests, newid):
if newid==1:
update_time()
elif newid==2:
get_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)

Define a Custom Form for use in Django's ModelAdmin Add View

I'm trying to expose a Django model in admin using the ModelAdmin class. ModelAdmin seems to assume you use the same form for add and change. I'd like the add_view to use a simplified form that only lists a handful of required fields. After submission, it'll redirect to the change_view and use ModelForm's default form to render nearly all fields.
What's the easiest way to do this? I've inspected the code, but I don't see a clear way. ModelAdmin tends to refer to a single self.form reference in both the add_view and change_view. I'm thinking of overriding add_view(), but I don't want to reimplement all the code. It might be more efficient to override get_form(), but I don't see how to detect whether get_form() is being called during add_view or change_view.
get_form() is passed an obj parameter when called during change_view. Simply detect that return the new form/tweak parameters as needed.
For example:
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
# hide every other field apart from url
# if we are adding
if obj is None:
kwargs['fields'] = ['url']
return super(MyModelAdmin, self).get_form(request, obj, **kwargs)
Will force the form to only display the "url" field when adding and everything else otherwise.

Categories