I have a class-based view in Django.
I have implemented a method get_context_data. A user can log in or update his/her data and is redirected to a class-based view template.
I want the information of the logged-in user inside the class-based view. I'm not rendering the view, just re-directing. Is there any approach like saving the data in memory during computation or global variables so that it can be accessed anywhere in the views.py.
if the user is logging in using the authenticate/login method, and you have the SessionMiddleware loaded as you should, then the user information should be in your request.user object.
In a class-based View object you can read the request like so:
class SomeView(View):
def get(self, *args, **kwargs):
user = self.request.user
#recommended: check if the user is authenticated, ie, not anonymous.
if user.is_authenticated:
check_something(user) #do whatever you need.
in the case of TemplateView subclasses (I assume, since you mention get_context_data) is the same:
class SomeTemplateView(TemplateView):
def get_context_data(self, *args, **kwargs):
if self.request.user and self.request.user.is_authenticated:
#do something
Globals and other things won't work in a Django service, it might work on development, but in production, your user's request could be handler by different python processes altogether and memory won't be shared between them.
Related
I am writing a class-based view to allow users to edit their profile. Since I want the users to access this view with a URL of the type my_profile/edit/ rather than something like profile/<int:pk>/edit/, using a view based on UpdateView is quite cumbersome since getting the user profile object requires to access the request object, and get_object does not directly have access to it.
My two questions:
Shall I use UpdateView in this case?
If so, what would be the best way to override get_object(self, queryset=None)? My best attempt so far is the following:
class EditProfileView(UpdateView):
model = UserProfile
_request = None
def get_object(self, queryset=None):
return get_user_profile(self._request)
def dispatch(self, request, *args, **kwargs): # can also override setup() in newer Django versions
self._request = request
return super().dispatch(request, *args, **kwargs)
This looks clean enough to me except that if one day the Django framework decides that get_object should be called early in the workflow of the view generation, this piece of code could break.
You don't need to do this. All class-based views make the request available as self.request.
Your entire code should just be:
class EditProfileView(UpdateView):
model = UserProfile
def get_object(self, queryset=None):
return get_user_profile(self.request)
Note, even if it didn't, you still wouldn't need to define _request at class level. That's just not necessary in Python.
Also, I don't know what your get_user_profile function does but it can probably be replaced with just self.request.user.profile.
I want to handle "confirm form resubmission" pop ups, when the user refresh the login page with incorrect data.
I'm using a class named: UserLogin inherited from LoginView('django built-in'), I know that I need to write a method in my own class to handle this issue, and I did it with HttpRespone for my Registration view. who can I solve it for my Login View?
my urls.py:
path('login/', UserLogin.as_view(template_name='userLogin/login.html'), name='login')
in my views:
class UserLogin(LoginView):
def refresh_page(self):
pass
return HttpResponse('login/')
I don't know How to fill my refresh_page method. besides, I tried :
class UserLogin(LoginView):
def refresh_page(self):
print('hello')
and I noticed that the method doesn't called at all! because it didn't print "hello" on my console
I think you can do it on different ways, one of them, just send post data to your view where you can handle user action and react to it. For example, like this:
class UserLogin(LoginView):
def post(self, request, *args, **kwargs):
if request.data.get('refresh'):
self.refresh_page()
return super().post(self, request, *args, **kwargs)
def refresh_page(self):
return HttpResponse('login/')
I have a FormView (currently a WizardView as I have multiple forms) which takes input from the (anonymous) user and passes the cleaned data to be used in a request to an external api.
However I'm not clear how best to pass the data recieved from one view to the next view?
The data received from the api is to be displayed in the next view.
(If the data was held internally (rather than in an external api) I would probably try a ListView with the relevant parameters passed from the FormView to the ListView via a session)
However the api returned data will have many pages of data.
What's the best way to manage this in django? (This doesn't need to be async as the returned data is required before the user can proceed.)
class QuotesWizard(SessionWizardView):
def get_template_names(self):
return [QUOTES_TEMPLATES[self.steps.current]]
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated():
return redirect(settings.LOGIN_REDIRECT_URL)
return super().dispatch(request, *args, **kwargs)
def done(self, form_list, form_dict, **kwargs):
postcode = form_dict['postcode'].cleaned_data['postcode']
service = form_dict['service'].cleaned_data['service']
usage = form_dict['usage'].cleaned_data['usage']
usage = convert_usage(usage)
# get data from external api
data = get_quotes(postcode, usage_ofgem, service_type)
# redirect to quotes page - how to manage transfer of data?
return redirect('quotes')
I have a similar approach in a module, also not surprisingly with address data.
I've chosen to store the results in the database, so I've created a model for it. One reason is that number of calls have a price, so each call I'm not making, I'm saving money. Another is that I'm not bound by the whims of the remote API: My API is consistent for the browser and Django views and if something changes remotely, I have to fix it only in one place.
The crux is to make a QuerySet in which you override the get method and in your case also the filter method in a similar way:
class PostcodeApiQuerySet(QuerySet):
# fields, other methods
def get(self, *args, **kwargs):
try:
return super(PostcodeApiQuerySet, self).get(*args, **kwargs)
except self.model.DoesNotExist:
self._validate_lookup_kwargs(kwargs)
pc = self._get_field_lookup_value('pc', kwargs)
house_nr = self._get_field_lookup_value('house_nr', kwargs)
house_nr_add = self._get_field_lookup_value('house_nr_add', kwargs)
try:
address = self._fetch_one(
pc, house_nr, house_nr_add=house_nr_add
)
except ObjectDoesNotExist as e:
raise self.model.DoesNotExist(e.args[0])
except KeyError as e:
raise self.model.DoesNotExist(e.args[0])
except ValueError as e:
raise self.model.DoesNotExist(e.args[0])
street = self._handle_foreign_for_address(address)
instance = self.model(street=street, **address)
instance.full_clean()
instance.save()
instance.refresh_from_db()
return instance
The methods called here that are not included, deal with the remote API and mapping of fields to API filters, but it should give you a general idea. The filter method will be a bit more complex, as you will have to figure out what you don't have and as such what you need to fetch remotely.
Once you've got this done, you can reduce your pages of data to a list of ID's, which should easily fit in a session. Then you can use ListView and override get_queryset() to teach it about the list of ID's to get from the session.
EDIT
So the flow is:
FormView (GET): render selection form
FormView (POST), in form_valid: Apply filters to overridden filter method of the custom queryset (Also see Manager.from_queryset) and store ID's in session
FormView (POST), in form_valid creates redirect to success_url
ListView (GET) invokes get_queryset which you've taught to deal with ID's in session
Good luck!
There are multiple ways to accomplish this:
Pass the data using GET params while redirecting. This way the resultant view would be cacheable and stateless. (recommended)
Use django sessions. Add the variable to session, which can be
retrived globally. Sessions act as global variables, so use them
only as last resort.
Use django messages if you want to store variables in session only for next view.
I'm building a job application form. A logged-in user is permitted to apply to the job only once (there's only one job). At the moment, a user is able to directly access the job application (FormView), by typing in its specific URL, and an error message is thrown AFTER the user submits the form. To achieve this, I'm doing a couple of things:
(1) Wrapping the job application view in login_required()
(2) Using the form_valid() method to check whether or not a user has already submitted an application to this specific job via:
def form_valid(self, form):
form = form.save(commit=False)
form.user = self.request.user
if Application.objects.filter(user=user_id):
messages.error(self.request, 'Sorry, our records show that you have already applied to this job.')
return redirect('/')
But I'd rather not permit them to reach the page at all. Instead, I want to check whether or not a user has already applied (during the request), and redirect them away from the form, if they have already applied. I have limited access to logged-in users that pass a test in the past, using something like:
def job_application_view(request):
active_user = request.user
if Application.objects.filter(user=active_user):
return HttpResponse("Some response.")
However, I can't seem to figure out how to access request via the FormView Class-Based View. I'm sure I'm missing something simple. Perhaps another method of FormView I'm missing?
You can still use decorators on class-based views, but they are slightly more difficult to apply than function-based views.
class ApplicationView(FormView):
# ...
#method_decorator(user_passes_test(job_application_view))
def dispatch(self, *args, **kwargs):
return super(ApplicationView, self).dispatch(*args, **kwargs)
Answering specific parts of your post...
I have limited access to logged-in users that pass a test in the past
With class-based views, you need to decorate the url or decorate the dispatch method with any decorators you are interested in applying.
However, I can't seem to figure out how to access request via the FormView Class-Based View. I'm sure I'm missing something simple. Perhaps another method of FormView I'm missing?
You can access the request with self.request
I am using django-tokenapi to allow for authentication of an Android project that is using Django as a backend. The project also has a web interface.
django-tokenapi uses the #token_required decorator to protect certain views. Django uses the #login_required decorator to protect certain views.
What I would like is to have only one view that is protected by #login_required OR #token_required so it can be used with either the webapp or Android app.
So, ideally, it would look like this:
#token_required
#login_required
def names_update(request):
....
....
However that does not work. Is there a better method of doing this? Or is the correct thing to have two views, one the webapp and one for Android, that are protected by the proper decorator, and then lead to the same method.
No there's no easy way, if possible at all, to write an generalized OR decorator according to you described.
However, you can write a new decorator that does exactly what you want:
from functools import wraps
from django.contrib.auth import authenticate, login
from django.views.decorators.csrf import csrf_exempt
def token_or_login_required(view_func):
"""
Decorator which ensures the user is either logged in or
has provided a correct user and token pair.
"""
#csrf_exempt
#wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
user = request.REQUEST.get('user')
if user and user.is_authenticated:
return view_func(request, *args, **kwargs)
token = request.REQUEST.get('token')
if user and token:
user = authenticate(pk=user, token=token)
if user:
login(request, user)
return view_func(request, *args, **kwargs)
return HttpResponseForbidden()
return _wrapped_view
this decorate combines both token_required and login_required decorators, and will allow access to the view either if the user is logged in, or the token is valid.
You could try assigning a new view variable to the old view:
#token_required
def names_update_token(request):
...
#login_required
names_update_login = names_update_token
This should have the effect of creating a second view named names_update_login which is just a pointer to the first so that the code remains the same.
EDIT:
Another thought, and one I have used, is to create a more "generic" view and call it from each of the other views:
def update_token(request):
...
#token_required
def names_update_token(request):
update_token(request)
#login_required
def names_update_login(request):
update_token(request)
This gets around the issue you mentioned while still maintaining only a single copy of the actual code that implements the view.
It is possible to use more than one decorator on a single view. But in this case I think you should seperate the view and apply the decorators seperately. Otherwise token_required decorator will try to authenticate using token which won't be available on a request made using browser.