I will have standard class-based views for CRUD operations that inherit from various generic views like ListView, DetailView and so on.
I will be setting all of their
context_object_name
attribute to the same value.
I was wondering if there is a way to do it more pythonic, to not repeat the operations many times in the code, but to be able to change that variable in one place if necessary?
ps. what comes to my mind is of course further inheritance, but maybe there is some more django-like way?
You can also use a mixin, instead of a middleware app:
class CommonContextMixin(object):
def get_context_data(self, *args, **kwargs):
context = super(CommonContextMixin, self).get_context_data(*args, **kwargs)
context['foo'] = 'bar'
return context
Then use that mixin in your views:
class MyView(TemplateView, CommonContextMixin):
""" This view now has the foo variable as part of its context. """
Relevant Django docs: https://docs.djangoproject.com/en/2.1/topics/class-based-views/mixins/
Middleware can do the trick
class SetContextObjectNameMiddleware:
def process_template_response(self, request, response):
if 'object' in response.context_data:
response.context_data['foo'] = response.context_data['object']
return response
Then add the middleware to your settings.py
It's not really setting the view's context_object_name but it achieves the same outcome.
Related
Let's say I want to use the LoginRequiredMixin and a UserPermissionMixin created by myself and apply them to all the views in an app. This is just an example, I might also have mixins that add some context or do other stuff.
I could do it manually, for example this view:
class MyCreateView(LoginRequiredMixin, UserPermissionMixin, CreateView)
But, since I have many views and I might have other specific mixins for some views, this gets messy and hard to manage.
One solution that came to mind would be to create new classes for the generic views:
class DecoratedCreateView(LoginRequiredMixin, UserPermissionMixin, CreateView):
pass
class DecoratedDetailView(LoginRequiredMixin, UserPermissionMixin, DetailView):
pass
class DecoratedUpdateView(LoginRequiredMixin, UserPermissionMixin, UpdateView):
pass
class DecoratedDeleteView(LoginRequiredMixin, UserPermissionMixin, DeleteView):
pass
and then, use these as my generic views:
class MyCreateView(DecoratedCreateView)
Is this a good approach? Do I have to add any methods in the classes above or do I just leave them blank and it'll work as expected?
Is there any other way to achieve this, maybe in urls.py ?
Your approach is good. I've been doing so for some projects with a slight difference:
myapp/views/generic.py
from django.views.generic import (
CreateView as BaseCreateView,
DetailView as BaseDetailView,
UpdateView as BaseUpdateView,
DeleteView as BaseDeleteView,
)
__all__ = ['MyappMixin', 'CreateView', 'DetailView', 'UpdateView', 'DeleteView']
class MyappMixin(LoginRequiredMixin, UserpermissionMixin):
pass
class CreateView(MyappMixin, BaseCreateView):
pass
class DetailView(MyappMixin, BaseDetailView):
pass
class UpdateView(MyappMixin, BaseUpdateView):
pass
class DeleteView(MyappMixin, BaseDeleteView):
pass
myapp/views/base.py
from .generic import CreateView
class MyCreateView(CreateView):
pass
It works fine, without much hassle, and allows you to easily skip the mixin exceptionally if needed.
According to the usecase, another solution might be to use middlewares or context processors.
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
is_in_myapp = request.resolver_match.app_name == 'myapp'
if is_in_myapp and not request.user.is_authenticated:
response = HttpResponse("Permission denied", status=403)
else:
response = self.get_response(request)
return response
I have been working on this all day.
I am trying to write custom permission for class views to check if user is in a certain group of permissions.
def rights_needed(reguest):
if request.user.groups.filter(Q(name='Admin')).exists():
pass
else:
return HttpResponseRedirect('/account/log-in/')
#method_decorator(rights_needed, name='dispatch')
class AdminView(CreateView):
model = Admin
form_class = AdminForm
def get_template_names(self):
return 'clinic/visitform_list.html'
Could help me know how I can achieve this? Or an easier way around it?
I also tried this (code inside AdminView class):
def dispatch(self, request):
if request.user.groups.filter(Q(name='Admin')).exists():
return super().dispatch(*args, **kwargs)
else:
return HttpResponseRedirect('/account/log-in/')
A decorator is a function that takes a function (a view in this case), and returns another function (a view in this case). At the moment your rights_needed looks like a regular view - it’s returning a response not a function.
Django comes with a user_passes_test method that makes it easy to create decorators like this. Since you are using class based views, it would be even easier to use the UserPassesTest mixin.
Your test function for the mixin would be:
def test_func(self):
return self.request.user.groups.filter(Q(name='Admin')).exists()
In Django, when using class based views, it is commonplace to setup class-level variables such as template_name.
class MyView(View):
template_name = 'index.html'
def get(self, request):
...
I am wondering if modifying these variables during runtime will persist across multiple requests or just the current one.
class MyView(View):
template_name = 'index.html'
def get(self, request):
if only_returns_true_once_function():
self.template_name = 'something.html'
...
Each request creates a new instance of that class, handles the request, and destroys it. The reason for class-based views is not to maintain instances, it's to allow inheritance and mixin composition. This makes it substantially easier to create reusable functionality that spans multiple views.
You can change variables at any point in the class' lifetime. The only point that these variables become important is when the request is handled, specifically during the dispatch() method, which other HTTP action methods like get() and post() wrap.
I strongly encourage you to bookmark the Classy Class-based Views site because it offers an incredibly thorough overview of how class-based views are composed and how they inherit. The most appropriate way to change the template names in a class based view is to override the get_template_names() method on a TemplateView.
class MyView(TemplateView):
def get_template_names(self):
if some_contrived_nonce_function():
return 'something.html'
else:
return super(MyView, self).get_template_names()
The above assumes your view either inherits from TemplateView or implements TemplateResponseMixin.
Modifying this as:
self.template_name = 'something.html'
will definitely only last for that request.
Modifying it as:
type(self).template_name = 'something.html'
will cause new instances to inherit your changes.
I know that there are several generic views like ListView, DetailView, or simply View.
The thing is can I actually get the context data that are declared in a BaseMixin's get_context_data() and use it in a View that doesn't have override get_context_data()?
Example:
class BaseMixin(object):
def get_context_data(self, *args, **kwargs):
context = super(BaseMixin, self).get_context_data(**kwargs)
context['test'] = 1
return context
And the view that extends this BaseMixin:
class FooView(BaseMixin, View):
def foo(self, request):
context = super(BaseMixin, self).get_context_data(**kwargs)
# do something
return
This is not working actually, even after put **kwargs as a parameter in foo(). The error is 'super' object has no attribute 'get_context_data'.
So is there a way to get context data which was set in BaseMixin in FooView ?
Thanks for your answers :)
Thanks to #Sayes and all answer posters, I finally solved this problem.
From what I figured out, the problem is actually in BaseMixin, the inherited class of BaseMixin, which is object, doesn't have a get_context_data() function, just like #Sayes commented.
After replace this object with ContextMixin, everything works perfectly, at least perfectly for now.
Here is the modified BaseMixin:
class BaseMixin(ContextMixin):
def get_context_data(self, *args, **kwargs):
# do something
return context
Basically I want to use a generic view that lists objects based on a username. Now, the question is, how do I do something like:
(r'^resources/$',
ListView.as_view(
queryset=Resources.objects.filter(user=request.user.username),
...
)
)
I couldn't find a way to access the HttpRequest (request) object though... Or do I need to use my own views and do all object selection there?
You could try subclassing the generic view:
class PublisherListView(ListView):
def get_queryset(self):
return Resources.objects.filter(user=self.request.user.username)
Then your urls entry would look like:
(r'^resources/$',
PublisherListView.as_view(
...
)
)
More information on dynamic filtering in class based views can be found here: http://docs.djangoproject.com/en/dev/topics/class-based-views/#dynamic-filtering
If you really want to clutter your URLconf directly, you can do it like so:
(r'^resources/$',
lambda request: ListView.as_view(queryset=Resources.objects.filter(user=request.user.username), ...)(request)
)
Or access the request by subclassing the view:
class MyListView(ListView):
def dispatch(self, request, *args, **kwargs):
self.queryset = Resources.objects.filter(user = request.user.username)
return super(MyListView, self).dispatch(request, *args, **kwargs)