There will be several tests: anonymous user from some pages will be redirected to login page. That's why some helper method was organized.
Now it is TemplateView, then it will be ListView etc.
When I try to transmit a request to the get method of TemplateView subclass, I get this error message: 'HomePageView' object has no attribute 'request'. But the signature of TemplateView's get method is def get(self, request, *args, **kwargs).
Could you give me a kick here?
/photoarchive/general/views.py
class HomePageView(TemplateView):
template_name = "general/home.html"
/photoarchive/general/tests.py
class GeneralTest(TestCase):
def test_anonymous_user_redirected_to_login_page(self, view_instance):
user = User(username='anonymous', email='vvv#mail.ru', password='ttrrttrr')
user.is_active = False
request = HttpRequest()
request.user = user
pdb.set_trace()
response = view_instance.get(request)
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], '/accounts/login/')
def test_anonymous_user_from_home_page_redirected_to_login_page(self):
view_instance = HomePageView()
self.test_anonymous_user_redirected_to_login_page(view_instance)
Traceback:
ERROR: test_anonymous_user_from_home_page_redirected_to_login_page (general.tests.GeneralTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/michael/workspace/photoarchive/photoarchive/general/tests.py", line 49, in test_anonymous_user_from_home_page_redirected_to_login_page
self.test_anonymous_user_redirected_to_login_page(view_instance)
File "/home/michael/workspace/photoarchive/photoarchive/general/tests.py", line 23, in test_anonymous_user_redirected_to_login_page
response = view_instance.get(request)
File "/home/michael/workspace/venvs/photoarchive/lib/python3.5/site-packages/django/views/generic/base.py", line 158, in get
return self.render_to_response(context)
File "/home/michael/workspace/venvs/photoarchive/lib/python3.5/site-packages/django/views/generic/base.py", line 131, in render_to_response
request=self.request,
AttributeError: 'HomePageView' object has no attribute 'request'
----------------------------------------------------------------------
Ran 1 test in 0.028s
Interactive playing at pdb breakpoint:
-> response = view_instance.get(request)
(Pdb) view_instance
<general.views.HomePageView object at 0x7f9270d550f0>
(Pdb) request
<HttpRequest>
(Pdb) view_instance.get(request)
*** AttributeError: 'HomePageView' object has no attribute 'request'
(Pdb)
For reference:
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
A view that renders a template. This view will also pass into the context
any keyword arguments passed by the URLconf.
"""
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
Testing individual CBV methods is tricky. You aren't meant to instantiate the view with HomePageView().
In your urls.py, you do:
url(r'^$', HomePageView.as_view()),
Similarly, in your test, you can do HomePageView.as_view(). This returns the callable view which you can pass the request to.
view_instance = HomePageView.as_view()
response = view_instance(request)
Related
In order to interact with slack, a server needs to be able to validate requests based on some cryptographic hashing. If this check returns false, the server should respond with a 400. It seems sensible to do this as a mixin:
class SlackValidationMixin:
def dispatch(self, request, *args, **kwargs):
if validate_slack_request(request):
return super().dispatch(request, *args, **kwargs)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
This gives the error "accepted_renderer not set on Response"
Based on a SO question, I added the following:
class SlackValidationMixin:
def dispatch(self, request, *args, **kwargs):
if validate_slack_request(request):
return super().dispatch(request, *args, **kwargs)
else:
response = Response(status=status.HTTP_400_BAD_REQUEST)
response.accepted_renderer = JSONRenderer
response.accepted_media_type = "application/json"
response.renderer_context = {}
return response
But this gives the error: AttributeError: 'NoneType' object has no attribute 'get_indent'
Why does it need an accepted_renderer, given that it is only responding with an HTTP status code, with no additional data? What is the easiest way of getting around this?
Following suggestion in answer to make EmptyResponse object inheriting from Response:
Traceback (most recent call last):
File "path/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "path/lib/python3.8/site-packages/django/utils/deprecation.py", line 96, in __call__
response = self.process_response(request, response)
File "path/lib/python3.8/site-packages/django/middleware/common.py", line 106, in process_response
if response.status_code == 404:
AttributeError: 'dict' object has no attribute 'status_code'
At first the solution: your second approach is fine, you only need to instantiate the JSONResponse class (DRF does this in the get_renderers method of views.APIView):
response.accepted_renderer = JSONRenderer()
Background:
Django WSGIHandler (inherited from Basehandler) calls response.render() to render the response
DRF Response (inherited from SimpleTemplateResponse) object has a render method that gets the rendered content via the rendered_content property (which calls the render method of the renderer with the passed data, media type and context)
In the initial content-negotiation stage, the renderer is set according to the DEFAULT_RENDERER_CLASSES/APIView.renderer_classes setting and the Aceept header passed by client; the selected renderer is set in the HttpRequest object as accepted_renderer and the media type as request.accepted_media_type attributes
If the renderer needs any extra context, the Response object also needs the renderer_context attribute; for example, views.APIView sets the current view, request, and arguments as renderer_context dict
Now it should be clear why you need the attributes with Response object -- to get the renderer, media type and to pass any extra context that might be needed by the selected renderer.
You've added an answer, where you're setting the above mentioned attributes and then from the renderer returning an empty dict as response. If you want to follow that route, a much easier and cleaner option would be to create a subclass of Response and return an empty dict from the render method e.g.:
class EmptyResponse(rest_framework.response.Response):
def render(self):
# You can have your own rendered content here
self.content = b''
return self
Now only returning the EmptyResponse object would do, no need to add the renderer related attributes:
class SlackValidationMixin:
def dispatch(self, request, *args, **kwargs):
if validate_slack_request(request):
return super().dispatch(request, *args, **kwargs)
else:
return EmptyResponse(status=status.HTTP_400_BAD_REQUEST)
Now, unless you're adding some custom content, the deferred rendering is not needed; you can directly return HttpResponse object:
from django.http import HttpResponse
class SlackValidationMixin:
def dispatch(self, request, *args, **kwargs):
if validate_slack_request(request):
return super().dispatch(request, *args, **kwargs)
else:
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
And if you want, you can pass the content (as bytes) while initializing HttpResponse. But if for some reason, you need lazy rendering, you need to use Response.render.
Creating a renderer that returns nothing seems to get this to work. I would be surprised if this were the 'correct' way, but it gets the job done.
class NoneRenderer(BaseRenderer):
def render(self, *args, **kwargs):
return {}
class SlackValidationMixin:
def dispatch(self, request, *args, **kwargs):
if validate_slack_request(request):
return super().dispatch(request, *args, **kwargs)
else:
response = Response(status=status.HTTP_400_BAD_REQUEST)
response.accepted_renderer = NoneRenderer
response.accepted_media_type = "*/*"
response.renderer_context = {}
return response
I have looked at other questions regarding this error but can't seem to pinpoint the issue on mine. I know there are better ways to do some of the things I am trying to do in the following code, but right now I'm just trying to pinpoint the culprit causing the following error.
Internal Server Error: /users/create/
Traceback (most recent call last):
File "/home/ubuntu/workspace/venv_mymanagedservices/lib/python3.5/site-packages/django/core/handlers/exception.py", line 39, in inner
response = get_response(request)
File "/home/ubuntu/workspace/venv_mymanagedservices/lib/python3.5/site-packages/django/utils/deprecation.py", line 138, in __call__
response = self.process_response(request, response)
File "/home/ubuntu/workspace/venv_mymanagedservices/lib/python3.5/site-packages/django/middleware/clickjacking.py", line 32, in process_response
if response.get('X-Frame-Options') is not None:
AttributeError: 'str' object has no attribute 'get'
[26/Mar/2017 23:02:14] "POST /users/create/ HTTP/1.1" 500 72448
Here's the view where I am trying to create a new user:
class CustomerUserCreate(LoginRequiredMixin, CreateView):
model = User
template_name = 'users/user_create.html'
fields = ['username', 'name', 'email', 'password']
# Search for which customer the user is assigned to and deny access if not a customer user or superuser
def render_to_response(self, context):
customer_admins = Customer.objects.filter(users=self.request.user)
if not (customer_admins or self.request.user.is_superuser):
return redirect('/403')
else:
return super(CustomerUserCreate, self).render_to_response(context)
def get_form(self, form_class=None):
form = super(CustomerUserCreate,self).get_form(form_class) #instantiate using parent
customer_admins = Customer.objects.get(users=self.request.user)
# Add a field that allows user to select which companies should be added to the Company models' users field.
form.fields['companies'] = forms.models.ModelMultipleChoiceField(Company.objects.filter(customer=customer_admins))
form.fields['companies'].widget = forms.CheckboxSelectMultiple()
return form
def get_success_url(self):
return reverse('users:list')
def form_valid(self, form):
customer_admins = Customer.objects.filter(users=self.request.user)
if not (customer_admins or self.request.user.is_superuser):
return redirect('/403')
companies_data = form.cleaned_data['companies']
del form.cleaned_data['companies']
# Record which customer account this user should be associated with
form.instance.created_by_customer = Customer.objects.get(users=self.request.user)
new_user = User.objects.create_user(username=form.cleaned_data['username'], email=form.cleaned_data['email'], name=form.cleaned_data['name'], password=form.cleaned_data['password'])
if companies_data:
for company in companies_data:
company.users.add(new_user)
else:
messages.add_message(self.request, messages.WARNING, 'You must select at least 1 company for the new user.')
return reverse('users:create')
# Add a success message
messages.add_message(self.request, messages.SUCCESS, 'User Created.')
return reverse('users:list')
Does anyone have a clearer head than I do about this one? I think I may just be confusing myself with all that I'm trying to do and completely missing it.
Okay, as you may have noticed, I was saving the new user in form_valid because I needed to use the create_user function, but then I was returning the form that wasn't necessary anymore. Instead of returning form, I return new_user and that seems to work. Still not sure this is best way to go about letting clients create new users on the front end, but it seems to work.
return super(CustomerUserCreate, self).form_valid(new_user)
A bit of context: I'm trying to create a CreateView page for users to input information whilst working with Django 1.9 which is at URL '/profile/'.
When users are not logged in, and I go to the /profile/ url then it will redirect me to the login page as expected. However, when I am logged in to the site, and then go to /profile/ then it will display this error:
TypeError at /profile/
__ init __() takes 1 positional argument but 2 were given
At the moment, I'm decorating a class with a method_decorator, as mentioned in the docs here: https://docs.djangoproject.com/en/1.10/topics/class-based-views/intro/#decorating-the-class.
This is in my views.py:
#login_required(login_url = "/login/", redirect_field_name = None)
#method_decorator(login_required, name = 'dispatch')
class SpkCreateView(CreateView):
form_class = SpkCreateForm
template_name = 'userprofile/spk_form.html'
def get(self, request):
form = self.form_class(None)
return render(request, self.template_name, {'form': form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
spk_fname = form.cleaned_data['spk_fname']
spk_lname = form.cleaned_data['spk_lname']
The 'name' argument for the method_decorator is also tripping me up so I'm not sure whether it's to do with that.
If anybody has any solutions then thanks!
Edit:
Changed #login_required(login_url = "/login/", redirect_field_name = None)
#method_decorator(login_required, name = 'dispatch')
to #method_decorator(login_required(login_url = "/login/", redirect_field_name = None), name = 'dispatch')
and this is the traceback from Powershell
Internal Server Error: /profile/
Traceback (most recent call last):
File "C:\Users\Programs\Python\Python36-32\lib\site-packag
es\django\core\handlers\base.py", line 149, in get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Users\Programs\Python\Python36-32\lib\site-packag
es\django\core\handlers\base.py", line 147, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
TypeError: __init__() takes 1 positional argument but 2 were given
[28/Sep/2016 22:20:22] "GET /profile/ HTTP/1.1" 500 57532
You're decorating the class with login_required both with and without #method_decorator. You need to pass the decorator you want to use to method_decorator, in this case:
#method_decorator(login_required(login_url="/login/", redirect_field_name=None), name='dispatch')
class SpkCreateView(CreateView):
...
I want to make an AbstractEditForm (Inherited from ModelForm) form, from which there are multiple forms that would be inheriting from it. But I am getting this error :
Here is my forms.py
# This is the abstract form that I want to inherit other forms from
class AbstractEditForms(forms.ModelForm):
def __init__(self, id_fields=None, ref_field=None,
model=None, *args, **kwargs):
self.id_fields = id_fields
self.changed_fields = {}
self.ref_field = ref_field
self.model_ = model
self.ref_id_changed = False
self.check_ref_id()
try:
if 'id_fields' in kwargs.keys():
del kwargs['id_fields']
if 'ref_fields' in kwargs.keys():
del kwargs['ref_fields']
except KeyError as e:
print('Error in AbstractionEditForms :', str(e))
super(AbstractEditForms, self).__init__(*args, **kwargs)
# This is the form that I want to use
class SchemeEditForm(AbstractEditForms):
class Meta:
model = SchemeModel
exclude = ['created_on', 'financial_year']
widgets = {
'as_ref_id': forms.TextInput(attrs={'readonly': 'readonly',
'placeholder': 'Auto Generated '
}),
'admin_sanction_amount': forms.HiddenInput(),
'updated_on': forms.HiddenInput(),
}
views.py :
def edit_scheme_form_view(request, pk=None):
assert pk is not None, 'pk cannot be None, scheme edit form'
instance = get_object_or_404(SchemeModel, pk=pk)
id_fields = ['technical_authority', 'dept_name', ]
model = SchemeModel
ref_field = "as_ref_id"
if request.method == 'GET':
scheme_form = SchemeEditForm(None, id_fields=id_fields, ref_field="as_ref_id",
model=model, instance=instance)
context = {
'form': scheme_form
}
return render(request, 'Forms/forms/SchemeForm.html', context=context)
if request.method == 'POST':
scheme_form = SchemeEditForm(request.POST, id_fields=id_fields, ref_field="as_ref_id",
model=SchemeModel, instance=instance)
if scheme_form.is_valid():
instance = scheme_form.save()
return generate_success_page(request, instance,"Scheme Edit Success",
"Scheme Details - Edited", nav_dict=None,
util_dict=None)
else:
return render(request, 'Forms/forms/SchemeForm.html', {'form': scheme_form})
Error traceback:
Internal Server Error: /edit/admin-sanction-form/1/
Traceback (most recent call last):
File "C:\Python35\lib\site-packages\django\core\handlers\base.py", line 149, in get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Python35\lib\site-packages\django\core\handlers\base.py", line 147, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\karth\PycharmProjects\phc\edit_forms\views.py", line 17, in edit_scheme_form_view
model=model, instance=instance
TypeError: __init__() got multiple values for argument 'id_fields'
[04/Sep/2016 20:41:08] "GET /edit/admin-sanction-form/1/ HTTP/1.1" 500 63681
I understand that this is problem with initialization form either AbstractEditForm or the SchemeEditForm.
Any help would be much appreciated.
SchemeEditForm is interpreting the first positional argument you are passing it as being the id_fields argument. When you later try to pass id_fields by name, it thinks it is getting a duplicate of that argument and you are getting an error.
Try changing your __init__() method to accept arbitrary positional arguments before your keyword arguments like this:
def __init__(self, *args, id_fields=None, ref_field=None, model=None, **kwargs):
I am not sure if this will get you the results you want in terms of how your forms function, but it will get rid of the error you are seeing. Note that this method will not work in Python 2.X, only Python 3.
I'm want to create one page with a form, and every time I submit the form it adds an item to the list below the form.
I can make it work using 2 pages:
one page using the mixin CreateView to add items
one page ListView to have the list.
But I'm trying to have the form and the list on the same page. So I tried to create a class with both mixin:
class FormAndListView(ListView, CreateView):
pass
Then I've used this class:
FormAndListView.as_view(
queryset=PdfFile.objects.order_by('id'),
context_object_name='all_PDF',
success_url = 'listview',
form_class = UploadFileForm,
template_name='textfrompdf/index.html',)),
But when I try to load the page, I get the error: Exception Value: 'FormAndListView' object has no attribute 'object'
Traceback:
File "C:\Program Files\Python_2.7\lib\site-packages\django\core\handlers\base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\base.py" in view
47. return self.dispatch(request, *args, **kwargs)
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\base.py" in dispatch
68. return handler(request, *args, **kwargs)
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\list.py" in get
122. return self.render_to_response(context)
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\base.py" in render_to_response
94. template = self.get_template_names(),
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\list.py" in get_template_names
134. names = super(MultipleObjectTemplateResponseMixin, self).get_template_names()
File "C:\Program Files\Python_2.7\lib\site-packages\django\views\generic\detail.py" in get_template_names
122. if self.object and self.template_name_field:
Exception Type: AttributeError at /PDF/
Exception Value: 'FormAndListView' object has no attribute 'object'
I've no idea how to debug that. Where to start?
I use a lot of views that involve a form and a list of objects. Rather than trying to mixin things I just add the queryset into the context data as below.
class UploadFileView(CreateView):
form_class = UploadFileForm
success_url = 'listview'
template_name = 'textfrompdf/index.html'
def get_context_data(self, **kwargs):
kwargs['object_list'] = PdfFile.objects.order_by('id')
return super(UploadFileView, self).get_context_data(**kwargs)
Do not mix list and update views.
Instead, create two separate views for these tasks:
List view displays the list and a web form with action URL pointing to the create view.
Create view accepts POST data and
displays form with error message in case of failure;
redirects to the list view in case of success.
Also I've tried to use class-based views and found that they are too complex.
I think it is much easier to use old-style function views.
I found the answer, there is 2 problems:
ListView and CreateView are "high level" mixin which aggregate "lower
level" mixins. But these lower level mixins are not compatible together.
The View class calls directly the render_to_response(), but in my scenario, there is 2 view class and render_to_response() should only be called once at the end.
I was able "solve" this issue using the following steps:
Instead of calling ListView and CreateView, I used lower level mixins. Moreover I called explicitly BaseCreateView and BaseListView from which I "extracted" the form and object_list
class FormAndListView(BaseCreateView, BaseListView, TemplateResponseMixin):
def get(self, request, *args, **kwargs):
formView = BaseCreateView.get(self, request, *args, **kwargs)
listView = BaseListView.get(self, request, *args, **kwargs)
formData = formView.context_data['form']
listData = listView.context_data['object_list']
return render_to_response('textfrompdf/index.html', {'form' : formData, 'all_PDF' : listData},
context_instance=RequestContext(request))
It's not clean but it works!
I have made my own class to solve this problem. I don't know if it's better or worse, but it works too. I have tried to use the generic mixins and have tested that validation and pagination work.
The code in GitHub
class ListAppendView(MultipleObjectMixin,
MultipleObjectTemplateResponseMixin,
ModelFormMixin,
ProcessFormView):
""" A View that displays a list of objects and a form to create a new object.
The View processes this form. """
template_name_suffix = '_append'
allow_empty = True
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty and len(self.object_list) == 0:
raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.")
% {'class_name': self.__class__.__name__})
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(object_list=self.object_list, form=form)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
self.object = None
return super(ListAppendView, self).post(request, *args, **kwargs)
def form_invalid(self, form):
self.object_list = self.get_queryset()
return self.render_to_response(self.get_context_data(object_list=self.object_list, form=form))
If you try it and find any errors, please tell me here or in GitHub.
I got into this problem and solve it with the following code, the answer by #jondykeman does not have pagination and other utilities for base classes. the other approaches that are proposed are a little complicated than the following:
class ObjectCreateView(LoginRequiredMixin, MultipleObjectMixin, View):
queryset = Wallet.objects.all()
def get(self, request):
self.object_list = super(ObjectCreateView, self).get_queryset().filter(user=request.user)
allow_empty = super(ObjectCreateView, self).get_allow_empty()
if not allow_empty:
if super(ObjectCreateView, self).get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
is_empty = not self.object_list.exists()
else:
is_empty = not self.object_list
if is_empty:
raise Http404()
context = super(ObjectCreateView, self).get_context_data()
form = CreateObjectForm()
context['form'] = form
return render(request, 'objects/object-list.html', context=context)
def post(self, request):
form = CreateWalletForm(request.POST)
if form.is_valid():
Object.objects.create(name=form.cleaned_data['name'], user=request.user)
messages.success(request, 'Object is created')
else:
messages.error(request, utility.get_form_errors_as_string(form))
return redirect('objects:create')