Difference between reverse and reverse_lazy? - python

I read more projects in Django however I didn't understand!
class SignUpView(generic.CreateView):
form_class = UserCreationForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
like this

reverse(…) [Django-doc] is a function that, for a given view name and parameters, can construct the path. But there is a problem with resolve: it can only work if the urls.py is loaded.
Typically the views.py file is imported by the urls.py, so that means that the views.py is loaded before the urls.py. This thus means that if you call reverse(…) at the class level or in a decorator like #login_required(login_url=reverse('view-name')), it is immediately processed when you the file is loaded. At that point the paths are not yet defined, so that will error.
A solution to this is to postpone evaluation. reverse_lazy(…) [Django-doc] does this. Instead of immediately evaluating it, it simply stores the name of the view, the parameters, etc. and promises to later call reverse when necessary.
If you have a view where the reverse is called in a function for example, then this is usually not a problem, unless the function is called when you load the module of course. So one can for example define a function:
from django.urls import reverse
class SignUpView(generic.CreateView):
form_class = UserCreationForm
template_name = 'signup.html'
def get_success_url(self, *args, **kwargs):
return reverse('login')
This works because here .get_success_url(…) is only called when the view is invoked and was successful. This is long after the paths are loaded.

Related

Accessing request object in django urls.py

The question is also inspired from documentation here.
I am using generic view (ListView) in Django in order to list out all the questions, current logged in user has asked. I was curious to do it without creating a View in views.py. So in urls.py I added a path like:
urlpatterns += [
path('myqn/', login_required(views.ListView.as_view(model=models.Question, queryset=models.Question.objects.filter(user__id=request.user.id), template_name='testapp/question_list.html', context_object_name='questions')), name='myqn'),
]
Its giving me that:
NameError: name 'request' is not defined
I know it. Since, request object is passed by the URLConf to the View class/function. So, is there a way, I can access the user.id in this scope.
PS: The code works if I replace user__id=9. It lists out all the questions asked by user-9. :)
You normally do this by overriding the get_queryset method in a subclass of the ListView. So you can create a view:
# app/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.list import ListView
from app.models import Question
class QuestionListView(LoginRequiredMixin, ListView):
model = Question
template_name='testapp/question_list.html'
context_object_name='questions'
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
user_id=self.request.user.id
)
In the urls.py you then use the QuestionListView
# app/urls.py
from django.urls import path
from app.views import QuestionListView
urlpatterns += [
path('myqn/', QuestionListView.as_view(), name='myqn'),
]
You can define a function or lambda expression with:
import inspect
def custom_queryset(*args, **kwargs):
self = inspect.currentframe().f_back.f_locals['self']
return Question.objects.filter(
user_id=self.request.user.id
)
urlpatterns += [
path('myqn/', QuestionListView.as_view(get_queryset=custom_queryset), name='myqn'),
]
This is however not a good idea. First of all, it inspects the call stack, and if later the ListView is altered, it might not work anymore. Furthermore here this listview will not check if the user has logged in for example. We can not make use of the method resolution order (MRO) to make a call to a super() method.
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].
No, You can't.
The as_view() accepts any class attributes of a view class. In your case, the request object will not accessible from the class
class Foo(ListView):
queryset = Question.objects.filter(user__id=request.user.id)
The above snippet you can't reference the request and hence also in your urls.py
In these kinds of complex situations, we should override the get_queryset(), as you know.

Control requests to view and template output in django

This is a view for get all the records in the EducationalRecord model:
def all_education_resume(request):
RESUME_INFO['view'] = 'education'
educations_resume = EducationalRecord.objects.all().order_by('-created_date')
template = 'resumes/all_resume.html'
context = {'educations_resume': educations_resume, 'resume_info': RESUME_INFO}
return render(request, template, context)
Now, if I want to write exactly this view for other models (like job resumes, research resumes , etc.),
I must another view one separately.
My question is:
How can I get a view for all these requests, so first check the URL of
the request and then do the relevant query? How can I control URL
requests in my views?
My other question is exactly the same as my first question,with this difference:
control view that must render in specific template.In other words,in
second question the ratio between the template and the view is instead
of the ratio of the view to the url or how to create a template for
multiple views (for example, for a variety of database resume
resumes, I have a template) and then, depending on which view render,
the template output is different.
I have implemented these two issues as follows:
I wrote a view for each of request!
In each view, I set the value of RESUME_INFO['view'], and then I've checked it in a template page and specified the corresponding template.
What is the best solution to these two questions?
How can I get a view for all these requests, so first check the URL of the request and then do the relevant query? How can I control URL requests in my views?
You can access request.path, or you can let the url(..)s pass a parameter with kwargs that holds a reference to the model for example, but this is usually bad design. Typically if you use different models, you will likely have to order these different as well, filter these differently, render these differently, etc. If not, then this typically indicates that something is wrong with the modeling.
You can however make use of class-based views [Django-doc], to remove as much boilerplate as posssible. Your view looks like a ListView [Django-doc], by using such view, and patching where necessary, we can omit most of the "boilerplate" code:
# app/views.py
from django.views.generic.list import ListView
class MyBaseListView(ListView):
resume_info = None
template = 'resumes/all_resume.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['resume_info'] = {'view': self.resume_info}
return context
In the individual listviews, you then only need to specify the resume_info and the model or queryset to render it with the 'all_resume.html' template, for example:
# app/views.py
# ...
class EducationalResumeView(MyBaseListView):
queryset = EducationalRecord.objects.order_by('-created_date')
resume_info = 'education'
class OtherModelView(MyBaseListView):
model = OtherModel
resume_info = 'other_info'
So we can here use inheritance to define common things only once, and use it in multiple views. In case we need to change something in a specific view, we can override it at that level.
In the urls.py, you define such view with the .as_view() method [Django-doc]. For example:
# app/urls.py
from django.urls import path
from app.views import EducationalResumeView, OtherModelView
urlpatterns = [
path('education/', EducationalResumeView.as_view()),
path('other/', OtherModelView.as_view()),
]

Django: DRY principle and UserPassesTestMixin

I have a model named Post and have a field there called owner (foreign key to User). Of course, only owners can update or delete their own posts.
That being said, I use login_required decorator in the views to make sure the user is logged in but then, I also need to make sure the user trying to update/delete the question is the owner.
As I'm using Django: Generic Editing Views the documentation says I need to use Django: UserPassesTestMixin.
This validation will be done for the update and delete views. DRY, what is the way to go about this? should I create a class named TestUserOwnerOfPost and create a test_func() and then make the update and delete views inherit from it?
Cause that's what I have tried and didn't work, code below:
from django.views.generic.edit import UpdateView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import UserPassesTestMixin
class TestUserOwnerOfPost(UserPassesTestMixin):
def test_func(self):
return self.request.user == self.post.owner
class EditPost(UpdateView, TestUserOwnerOfPost):
model = Post
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(EditPost, self).dispatch(*args, **kwargs)
With the code above, every logged-in user in the system can edit/delete any post. What am I doing wrong? am I missing something? thanks.
The first problem is that the order of the classes you inherit is incorrect, as #rafalmp says.
However, fixing that doesn't solve the problem, because the UserPassesTest mixin performs the test before running the view. This means that it's not really suitable to check the owner of self.object, because self.object has not been set yet. Note I'm using self.object instead of self.post -- I'm don't think that the view ever sets self.post but I might be wrong about that.
One option is to call self.get_object() inside the test function. This is a bit inefficient because your view will fetch the object twice, but in practice it probably doesn't matter.
def test_func(self):
self.object = self.get_object()
return self.request.user == self.object.owner
Another approach is to override get_queryset, to restrict it to objects owned by the user. This means the user will get a 404 error if they do not own the object. This behaviour is not exactly the same as the UserPassesTestMixin, which will redirect to a login page, but it might be ok for you.
class OwnerQuerysetMixin(object):
def get_queryset(self):
queryset = super(OwnerQuerysetMixin, self).get_queryset()
# perhaps handle the case where user is not authenticated
queryset = queryset.filter(owner=self.request.user)
return queryset
The order of the classes you inherit from matters. For your access control to work, it must be enforced before UpdateView is executed:
class EditPost(TestUserOwnerOfPost, UpdateView):

decorate as_view() with extra context

I want to call class-based generic view with extra context from my method (view). Error that I get is as_view() takes exactly 1 argument (4 given) . I'm using django-userena.
Code that executes this is:
return userena_views.ProfileListView.as_view(request,template_name='userena/profil.html', extra_context=projekti)
In urls.py I have this line:
url(r'^accounts/(?P<username>[\.\w-]+)', userena_views.ProfileListView.as_view(template_name='userena/profil.html', extra_context=Projekat.objects.all), name='userena_profile_list'),
Why are these two different? What am I doing wrong?
this is due to how url functions. you can use kwargs to pass the parameters, and define a url pattern as follows:
url(r'^accounts/(?P<username>[\.\w-]+)', userena_views.ProfileListView.as_view(), name='userena_profile_list', kwargs={'template_name':'userena/profil.html', 'extra_context':Projekat.objects.all}),
EDIT
I misunderstood your question, sorry.
Then, trying to answer your question correctly... your code should be like this:
your_callable_view = userena_views.ProfileListView.as_view()
return your_callable_view(request, template_name='userena/profil.html', extra_context=projekti)
the reason is ProfileListView.as_view() returns a function that have to be called with parameters. url() do this for you, this is why it works in your ulrpatterns and not in your code. The only parameter as_view() is requiring is self.
Class based views can provide extra context data via a get_context_data method.
In the example below from the Django documentation, book_list is added to the DetailView context. The same method works for FormViews as well.
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetailView(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context

How to use current logged in user as PK for Django DetailView?

When defining URL patterns, I am supposed to use a regular expression to acquire a PK from the URL.
What if I want a URL that has no PK, and if it's not provided, it will use the currently logged in user? Examples:
visiting /user will get a DetailView of the currently logged in user
/user/edit will show an UpdateView for the currently logged in user
I tried hard-coding the pk= in the Detail.as_view() call but it reports invalid keyword.
How do I specify that in the URL conf?
My sample code that shows PK required error when visiting /user URL:
urlpatterns = patterns('',
url(r'user/$',
DetailView.as_view(
model=Account,
template_name='user/detail.html')),
)`
An alternative approach would be overriding the get_object method of the DetailView subclass, something along the line of:
class CurrentUserDetailView(UserDetailView):
def get_object(self):
return self.request.user
Much cleaner, simpler and more in the spirit of the class-based views than the mixin approach.
EDIT: To clarify, I believe that two different URL patterns (i.e. one with a pk and the other without) should be defined separately in the urlconf. Therefore they could be served by two different views as well, especially as this makes the code cleaner. In this case the urlconf might look something like:
urlpatterns = patterns('',
url(r"^users/(?P<pk>\d+)/$", UserDetailView.as_view(), name="user_detail"),
url(r"^users/current/$", CurrentUserDetailView.as_view(), name="current_user_detail"),
url(r"^users/$", UserListView.as_view(), name="user_list"),
)
And I've updated my example above to note that it inherits the UserDetailView, which makes it even cleaner, and makes it clear what it really is: a special case of the parent view.
As far as I know, you can't define that on the URL definition, since you don't have access to that information.
However, what you can do is create your own mixin and use it to build views that behave like you want.
Your mixin would look something like this:
class CurrentUserMixin(object):
model = Account
def get_object(self, *args, **kwargs):
try:
obj = super(CurrentUserMixin, self).get_object(*args, **kwargs)
except AttributeError:
# SingleObjectMixin throws an AttributeError when no pk or slug
# is present on the url. In those cases, we use the current user
obj = self.request.user.account
return obj
and then, make your custom views:
class UserDetailView(CurrentUserMixin, DetailView):
pass
class UserUpdateView(CurrentUserMixin, UpdateView):
pass
Generic views uses always RequestContext. And this paragraph in the Django Documentation says that when using RequestContext with auth app, the template gets passed an user variable that represents current user logged in. So, go ahead, and feel free to reference user in your templates.
You can get the details of the current user from the request object. If you'd like to see a different user's details, you can pass the url as parameter. The url would be encoded like:
url(r'user/(?P<user_id>.*)$', 'views.user_details', name='user-details'),
views.user_details 2nd parameter would be user_id which is a string (you can change the regex in the url to restrict integer values, but the parameter would still of type string). Here's a list of other examples for url patterns from the Django documentation.

Categories

Resources