I am learning Class Based Views in Django and have learnt to work with the base generic views like View and TemplateView.
I was playing with the generic view like ListView and DetailView, then I stumbled to a problem.
How can we add custom header in a Class Based View that inherits from one of the view classes like ListView and DetailView.
I searched it on the but all the answers of function based views.
I have been able to set headers in the Class Base Views that inherit from the View class.
class MyView(View):
http_method_names=['post','get']
message='<div id="myview">This is a class based view response.</div>'
content_type='text/html'
charset='utf-8'
template_name='base.html'
#method_decorator(gzip_page)
#method_decorator(condition(etag_func=None,last_modified_func=None))
def get(self,request,*args,**kwargs):
response=TemplateResponse(request,self.template_name,{'number':1,'number_2':2})
response.__setitem__('x-uuid',uuid.uuid4().hex) #set header explicitly
response.__setitem__('status',200)
response.__setitem__('page_id',str(uuid.uuid4().hex))
patch_vary_headers(response,['Etag','Cookie','User-Agent'])
patch_cache_control(response)
return response
#Override this method to tell what to do when one of the methods in http_method_names is called
def http_method_not_allowed(request, *args, **kwargs):
response=HttpResponse("Method not allowed",status=405)
response.__setitem__('x-uid',uuid.uuid4().hex) #set header explicitly
response.__setitem__('status',405)
response.__setitem__({'hello':'word'})
return response
#return any type of response here
# return JsonResponse(status=405,data={'not_allowed':True})
Can anybody tell me how to add a custom header in Class Based View that inherits from ListView or any other View like DetailView.
class GetParticularUserView(ListView):
http_method_names=['get']
template_name='one_user.html'
# context_object_name='user' # set context either by this or by get_context_data
'''queryset=User.objects.all()''' # one way to set the data in the context
def get_queryset(self):
# define the query set to be used here.
self.user=get_object_or_404(User,id=self.kwargs['id'])
return self.user
def get_context_data(self,**kwargs):
context=super(GetParticularUserView,self).get_context_data(**kwargs)
context['user']=self.user
return context
I would override the dispatch method. Call super() to get the response, set the headers, then return the response. Note you shouldn't need to call __setitem__ - just treat the response as a dictionary.
class GetParticularUserView(ListView):
#method_decorator(gzip_page)
#method_decorator(condition(etag_func=None,last_modified_func=None))
def dispatch(self, *args, **kwargs):
response = super(GetParticularUserView, self).dispatch(*args, **kwargs)
response['x-uid'] = uuid.uuid4().hex # set header
return response
Related
I have generic ListAPIView with following list request function. Here category is a query parameter. I am trying to achieve such that the swagger ui also shows the query parameter in the swagger ui doc. How to achieve this? I have the following code.
#extend_schema(
parameters=[
OpenApiParameter(name='category',location=OpenApiParameter.QUERY, description='Category Id', required=False, type=int),
],
)
def list(self, request, *args, **kwargs):
category_id = request.query_params.get('category')
....
return Response(data)
The correct entrypoint for annotations on ApiView (here ListAPIView) is the get method, not the list method.
This is because get is the actual method and list is only wrapped to proxy to the mixin.
class ListAPIView(mixins.ListModelMixin, GenericAPIView):
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
FAQ entry.
Annotate the get method and call self.list() yourself, and the parameter will show up.
I agree with Insa but you can also decorate your View,
to avoid implement/override get method just to decorate it.
#extend_schema_view(
get=extend_schema(
parameters=[
OpenApiParameter(name='category', description='Category Id', type=int),
]
)
)
class YourListView(ListAPIView):
...
I dropped OpenApiParameter location and required arguments as they equal default values.
I have a Django application that uses a JSON API as its data source.
Here's a simplified example of use in one of my views.py:
class GroupsList(LoginRequiredMixin):
def get(self, request, **kwargs):
# Get file list and totals
try:
group_list = group_adapter.list() # makes an API call and ALSO populates a meta info class
except APIAccessForbidden:
return HttpResponseRedirect(reverse('logout'))
return render(request, 'groups/index.html', {
# can I make a mixin to add data here gained from the API call?
'group_list': group_list,
})
This line:
The group_adapter.list() call populates some meta information into another class, that's not related to the group_list itself. I'd like to pass that data to the template. Ordinarily I'd use a context_processor, but when the context processor is called, the API call hasn't been made yet. I could manually check the information and add it to the render() method, but then I'd need to do that in dozens of different views.
Potential Solution #1: Create a Mixin For It
Can I use a mixin here that adds this information to context AFTER the view code runs but BEFORE render passes information to the template?
In other words is there a way to do this:
class GroupsList(LoginRequiredMixin, AddMetaInfoToContextMixin):
and then create a mixin something like this?
class AddMetaInfoToContextMixin(ContextMixin):
def get_context_data(self, **kwargs):
# self.request
context = super().get_context_data(**kwargs)
context['global_meta_information'] = get_global_meta_information()
return context
Potential Solution #2: Make an overridden templateview
Commenter Melvyn pointed out that I can potentially subclass TemplateView and override get_context_data(), so would something like this work?
class TemplateViewWithMeta(TemplateView):
def get_context_data(self, *args, **kwargs):
context = super(Home. self).get_context_data(*args, **kwargs)
context['global_meta_information'] = get_global_meta_information()
return context
class GroupsList(LoginRequiredMixin, TemplateViewWithMeta):
[...]
The typical workflow for a Django generic TemplateView is:
get()
get_context_data()
render_to_response()
So in your case keeping with the spirit of generic views, you could do it like this:
from django.views import generic
class BaseRemoteApiView(generic.TemplateView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.group_list = None
def get(self, request, *args, **kwargs):
try:
self.group_list = group_adapter.list() # makes an API call and ALSO populates a meta info class
except APIAccessForbidden:
return HttpResponseRedirect(reverse('logout'))
return super().get(request, *args, **kwargs)
class RemoteApiContextMixin(generic.base.ContextMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["group_list"] = self.group_list
context["meta_information"] = get_global_meta_information()
return context
class ConcreteRemoteApiView(RemoteApiContextMixin, BaseRemoteApiView):
pass
Of course, you don't have to make 3 classes and can just combine the 3 into one - depends on how mixable you want to be.
I have the following event feed (using Django-ical)
class EventFeed(ICalFeed):
def items(self):
meeting_id = self.kwargs['meeting_id']
return Meeting.objects.get(id=meeting_id)
I am trying to pass the meeting_id through the URL conf like so:
url(r'^meetings/(?P<meeting_id>\d+)/$', EventFeed()),
However this returns 'EventFeed' object has no attribute 'kwargs'?
As Daniel points out in the comments, this is a syndication framework. Therefore we need to use get_object, and to filter not get on the Meeting object:
class EventFeed(ICalFeed):
def get_object(self, request, *args, **kwargs):
return int(kwargs['meeting_id'])
def items(self, meeting_id):
return Meeting.objects.filter(id=meeting_id)
I can't understand the class based views, so I am trying to figure it out with an example. Here is what I have so far:
#urls.py
url(r'^(?P<langcode>[a-zA-Z-]+/about/$', about, name='about')
#views.py
def about(request, langcode):
languages = Language.objects.values_list('code', flat=True)
language = get_object_or_404(Language, pk=langcode)
return render(request, 'about.html', {
'languages': languages,
'language': language
})
I also have some other functional views which contain the first 2 lines of about:
languages = Language.objects.values_list('code', flat=True)
language = get_object_or_404(Language, pk=langcode)
So, what I want to do now is:
create a class BaseView (or however you want to call it) which extends
something from django.generic.views and which will determine the language and languages parameters for the context based on the langcode input parameter
Create the class AboutView(BaseView) (so extending the BaseView) which will somehow define the template name about.html to be used for rendering.
I will further have another class based view, also to extend BaseView and which is simillar to AboutView, but which sets one more context parameter called region depending as well on the langcode input parameter
Can someone show me exactly how to code this stuff? thank you
Here's a simple way to achieve what you want:
You first define the common logic, using the TemplateView generic view:
class MyBaseView(TemplateView):
def dispatch(self, request, *args, **kwargs):
# dispatch takes care of "reading" the parameters from the url
self.language = get_object_or_404(Language, pk=kwargs.pop("langcode")) # I would use some kind of default value to prevent exception, but its up to your logic
return TemplateView.dispatch(self, request, *args, **kwargs)
def get_context_data(self, **kwargs):
# get_context_data creates the context
context = TemplateView.get_context_data(self, **kwargs)
context.update({"language": self.language,
"languages": Language.objects.values_list('code', flat=True)})
return context
Then, you don't even need an AboutView because all you want is to control the template_name, so in your urls.py:
# in urls,py you can just use this, instead of defining an AboutView, you pass the template_name you want to use, this is sufficient because TemplateView expects a template_name attribute
url('^(?P<langcode>[a-zA-Z]+)/about/$', MyBaseView.as_view(template_name="about.html"), name='about')
and finally, for the other view that uses region you can just inherit from the MyBaseView, and add the context you want:
class AboutViewWithRegion(MyBaseView):
def get_context_data(self, **kwargs):
context = MyBaseView.get_context_data(self, **kwargs)
context.update({"region": <<your logic here>>})
return context
Hope that helps!
I am trying to use generic CreateView class to handle forms for a set of models inherited from the same base class.
class BaseContent(models.Model):
...
class XContent(BaseContent):
...
class YContent(BaseContent):
...
To keep things DRY, I want to define one CreateView class that will handle all inherited classes from BaseContent.
The url pattern for that view is:
url(r'^content/add/(?P<model_name>\w+)/$', ContentCreateView.as_view(), name='content_add')
Something like this should work:
class ContentCreateView(CreateView):
template_name = 'content_form.html'
def get_model(self, request):
# 'content' is the name of the application; model_name is 'xcontent', 'ycontent', ...
return ContentType.objects.get_by_natural_key('content', self.model_name)
But I am getting this exception:
ContentCreateView is missing a queryset. Define ContentCreateView.model, ContentCreateView.queryset, or override ContentCreateView.get_object().
This suggestion does not seem to hold as I am not willing to set a class attribute like model or queryset to keep the model form generated dynamic. Overriding the get_object does not seem relevant for creating an object.
I tried overriding get_queryset() but this method does not accept the request parameter, nor have access to self.model_name which comes from the url pattern.
Long story short, how can I make a CreateView use a dynamic form based on a parameter passed from the url?
Thanks.
You could set the model attribute from your your urls.py, depending on the url being called:
url(r'^content/add/x/$',
ContentCreateView.as_view(model=XContent), name='x_content_add'),
url(r'^content/add/y/$',
ContentCreateView.as_view(model=YContent), name='y_content_add')
I admit it's not perfect as you are repeating yourself a bit, but therefore you have the advantage of having different names for the same view, depending on the model! Besides that you could also do something similar with overriding form_class...
Had this problem for some time, but found the solution. You need to override the dispatch method, defined in as_view() (django.views.generic.base), something like this:
class ContentCreateView(CreateView):
def dispatch(self, request, *args, **kwargs):
for app in ['foo', 'bar']:
model = models.get_model(app, kwargs['modelname'])
if model:
self.model = model
break
return super(GenericEdit, self).dispatch(request, *args, **kwargs)
...
...