Django: Modify Class Based View Context (with **kwargs) - python

I've a Function definition that works perfect, but I need to update to a Class Based View.
function def:
def ProdCatDetail(request, c_slug, product_slug):
try:
product = Product.objects.get(category__slug=c_slug, slug = product_slug)
except Exception as e:
raise e
return render(request, 'shop/product.html', {'product':product})
So far, I've read that to modify the context of a Class Based View (CBV) I need to overwrite the def get_context_data(self, **kwargs) in the CBV.
So, I've done this:
Class Based View:
class ProdCatDetailView(FormView):
form_class = ProdCatDetailForm
template_name = 'shop/product.html'
success_url = 'shop/subir-arte'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['product'] = Product.objects.get(category__slug=c_slug, slug = product_slug)
return context
How should I pass the arguments c_slug, product_slug to the get_context_data definition for this CBV to work as the Function definition?

A class based view is, by the .as_view basically used as a function-based view. The positional and named parameters, are stored in self.args, and self.kwargs respectively, so we can use:
class ProdCatDetailView(FormView):
# ...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['product'] = Product.objects.get(
category__slug=self.kwargs['c_slug'],
slug =self.kwargs['product_slug']
)
return context

Related

make decorator Only the creator of Content can manipulate it

I have created Two decorator to block anyone to Access to specific content
like:
#method_decorator(login_required(login_url='core:login'), name='dispatch')
#method_decorator(allowed_users(allowed_roles=['writer']), name='dispatch')
class BookDeleteView(BSModalDeleteView):
model = Book
template_name = 'book/book_delete.html'
success_message = 'Success: book was deleted.'
success_url = reverse_lazy('core:book_list')
i want to create decorator seems like this
book=Book.objects.get(id=pk)
if request.user==book.writer.profile.user:
If you want to use a decorator:
def writers_only(function):
#wraps(function)
def wrap(request, *args, **kwargs):
if request.user == Book.objects.get(id=kwargs.get('pk')).writer.profile.user:
return function(request, *args, **kwargs)
else:
# Do something else
return wrap
Then add this to your class based view:
# Other decorators
#method_decorator(writers_only, name='dispatch')
class BookDeleteView(BSModalDeleteView):
# Your code
Otherwise you can also override BookDeleteView.get_object (you can create a mixin to reuse this implementation of get_object in various views):
#method_decorator(login_required(login_url='core:login'), name='dispatch')
#method_decorator(allowed_users(allowed_roles=['writer']), name='dispatch')
class BookDeleteView(BSModalDeleteView):
model = Book
template_name = 'book/book_delete.html'
success_message = 'Success: book was deleted.'
success_url = reverse_lazy('core:book_list')
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
if obj.writer.profile.user != self.request.user:
raise PermissionDenied() # Or do something else
return obj
You don't need a decorator for that. What you need is queryset defined on your model view that will restrict the query:
class BookDeleteView(BSModalDeleteView):
def get_queryset(self):
return self.model.objects.filter(user=self.request.user)
In case it has to be done for delete only you may also set it dynamically depending on request:
class BookDeleteView(BSModalDeleteView):
def get_queryset(self):
if self.request.method == 'DELETE':
return self.model.objects.filter(user=self.request.user)
return self.model.objects.all()

get context data in get_queryset

I have BaseContext and Listview which is for Searching in multiple models, Search Class inherits from BaseContext. I set the current user to context and want to use it in my def get_queryset method, But it doesn't work. I think in Search CBV get_context_data execute after get_queryset that's why, self.user is None.
class BaseContext(ContextMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
request = self.request
if request.COOKIES.get('token') is not None:
...
user = Users.objects.filter(user_id=user_id).first()
context.update({'current_user': user})
context.update({'is_logged_in ': True})
else:
context.update({'is_logged_in ': False})
return context
class Search(BaseContext, ListView):
template_name = 'search.html'
context_object_name = "results"
paginate_by = 15
user = None
def get_queryset(self):
query = self.request.GET.get('search', None)
if query is not None and self.user is not None:
...
return queryset_chain
return faqModel.objects.none()
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
if 'is_logged_in' in context and context['is_logged_in']:
self.user = context['current_user']
else:
redirect("index")
return context
My question is how can I get context data in def get_queryset(self)?
for Listview get_quersyset() is called before get_contex_data() , hence getting context data is not possible in get_queryset()

Django class-based-view access context data from get_queryset

class ProfileContextMixin(generic_base.ContextMixin, generic_view.View):
def get_context_data(self, **kwargs):
context = super(ProfileContextMixin, self).get_context_data(**kwargs)
profile = get_object_or_404(Profile, user__username=self.request.user)
context['profile'] = profile
return context
class CourseListView(ProfileContextMixin, generic_view.ListView):
model = Course
template_name = 'course_list.html'
object_list = None
def get_queryset(self):
profile = self.get_context_data()['profile']
return super(CourseListView, self).get_queryset().filter(creator=profile)
I have the following two class-based-views. CourseListView inherits ProfileContextMixin which I wrote so that I don't have to repeat overriding get_context_data to get the profile every time in my other views.
Now in my CourseListView, I need to filter the result based on the creator argument, which is the same one retrieved in get_context_data
I know my get_queryset works, and it will call get_context_data() to get the profile, but this will also cause my get_context_data to be called twice, executing the same SQL two times.
Is there a way I can access the context efficiently?
UPDATE:
After reading ListView method flowchart, I ended up doing this, but not sure if it's the best way. Feedback is appreciated.
object_list = []
context = None
def get_context_data(self, **kwargs):
return self.context
def get_queryset(self):
self.context = super(CourseListView, self).get_context_data()
profile = self.context['profile']
queryset = super(CourseListView, self).get_queryset()
queryset = queryset.filter(creator=profile)
self.context['object_list'] = queryset
return queryset
You can move getting profile out of get_context_data to upper function, like dispatch, or use cached_property decorator. This way your profile will be stored in _profile argument of view and you will not do second get to DB after calling self.profile second time.
from django.utils.functional import cached_property
class ProfileContextMixin(generic_base.ContextMixin, generic_view.View):
#cached_property
def profile(self):
return get_object_or_404(Profile, user__username=self.request.user)
def get_context_data(self, **kwargs):
context = super(ProfileContextMixin, self).get_context_data(**kwargs)
context['profile'] = self.profile
return context
class CourseListView(ProfileContextMixin, generic_view.ListView):
model = Course
template_name = 'course_list.html'
object_list = None
def get_queryset(self):
return super(CourseListView, self).get_queryset().filter(creator=self.profile)

Django: How to pass in context variables for registration redux?

here is the views.py for registration redux. i am a little bit confused and i don't really understand how to pass in context variables.
class RegistrationView(BaseRegistrationView):
SEND_ACTIVATION_EMAIL = getattr(settings, 'SEND_ACTIVATION_EMAIL', True)
success_url = 'registration_complete'
def register(self, request, form):
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(request)
if hasattr(form, 'save'):
new_user_instance = form.save()
else:
new_user_instance = UserModel().objects.create_user(**form.cleaned_data)
new_user = RegistrationProfile.objects.create_inactive_user(
new_user=new_user_instance,
site=site,
send_email=self.SEND_ACTIVATION_EMAIL,
request=request,
)
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=request)
return new_user
BaseRegistrationView inherits from FormView, which has in its inheritance chain django.views.generic.base.ContextMixin. This defines the get_context_data method, which returns context as a dict. You can override that method and add in your own variables like so:
class RegistrationView(BaseRegistrationView):
...
def get_context_data(self, **kwargs):
context = super(RegistrationView, self).get_context_data(**kwargs)
context['key'] = value
return context

Modify context after using form

I have a view that shows a list of objects that have specific tag.
class AllView(ListView):
context_object_name = 'facts'
template_name = 'facts_blog/all_facts.html'
def get_context_data(self, **kwargs):
context = super(AllView, self).get_context_data(**kwargs)
if 'TagForm' not in context:
context['TagForm'] = TagForm()
return context
def get_queryset(self):
form = TagForm(self.request.GET)
if form.is_valid():
context = RequestContext(self.request)
return self.send_results(form.cleaned_data['tag'])
else:
return Fact.objects.all()
def send_results(self, tag):
return Fact.objects.filter(tags__slug=tag)
I want to return form.cleaned_data['tag'] to template, but i have already used get_context_data... What should I do to do this?
If I'm understanding what you're saying, you want to include the "tag" in your context dict while the ListView queryset will return the "Fact" instances associated with the tag. Correct?
If you look at Django's BaseListView the get method will call get_queryset first, then right before it calls render_to_response it will call your get_context_data method:
class BaseListView(MultipleObjectMixin, View):
"""
A base view for displaying a list of objects.
"""
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
# [snip]
# some code not related to this omitted
# [snip]
context = self.get_context_data()
return self.render_to_response(context)
What you can do is set an attribute (self.object) in your get_queryset method to store the "tag" from your validated form, then retrieve it in your get_context_data method so you can stuff it in the context dict:
def get_queryset(self):
form = TagForm(self.request.GET)
if form.is_valid():
context = RequestContext(self.request)
# set self.object to the "tag"
self.object = form.cleaned_data['tag']
return self.send_results(self.object)
else:
# set self.object to None since the form is not validated
self.object = None
return Fact.objects.all()
def get_context_data(self, **kwargs):
context = super(AllView, self).get_context_data(**kwargs)
if 'TagForm' not in context:
context['TagForm'] = TagForm()
# put self.object in the context dict
context['tag'] = self.object
return context
Your template will then have a var called tag with either the "tag" value or None.
Assuming that the form's action returns to the AllView view via GET request, you can modify the get_context_data in this way:
def get_context_data(self, **kwargs):
context = super(AllView, self).get_context_data(**kwargs)
if 'TagForm' not in context:
context['TagForm'] = TagForm()
tag_form = TagForm(self.request.GET)
if tag_form.is_valid():
context['tag'] = tag_form.cleaned_data.get('tag')
return context
Hope it does the trick.

Categories

Resources