How to use value from URL and request in Django DetailView? - django

I have a DetailView in django views.py where I want to be able to compare the pk value from the url ex:localhost:8000/myapp/details/3/ and the request.user.id with an if statement.
There is nothing more than the following few lines of code in the view:
class UserDetail(DetailView):
model = Profile
template_name = 'details.html'
Any help would be much appreciated.

Inside a DetailView you have access to self.request, self.args and self.kwargs!
ref.: https://docs.djangoproject.com/en/dev/topics/class-based-views/generic-display/#dynamic-filtering
In your urls.py add something like this:
urlpatterns = [
#...
url(r'^details/(?P<pk>[0-9]+)/$', UserDetail.as_view()),
]
and your UserDetail can now access request.user.id and pk by self.kwargs['pk'] (see reference above: kwargs is name-based, so that you can access it by self.kwargs['name'] and self.args is position-based, so you would access it by self.args[0]).
If I understand your problem correctly, you are trying to manipulate the queryset of the DetailView, to only return the data if the current logged in user is trying to access his page.
If this is true, then you should override get_queryset in your class, like that:
def get_queryset(self):
if self.kwargs['pk'] == self.request.user.id:
return Profile.objects.filter(id=self.request.user.id)
else:
return Profile.objects.none()

Related

Why does FormView (CBV) based view not have a URL parameter in context?

I have a class based view which needs to accept a Form submission. I am trying to auto-populate some of the form fields using the primary key within the URL path (e.g. /main/video/play/135). The class based view is based on FormView, the code I have makes the pk available in context if I use TemplateView, but that is not particularly good for handling forms.
urls.py
app_name = 'main'
urlpatterns = [
#path('', views.index, name='index'),
path('video/<int:pk>', views.VideoDetailView.as_view(), name='detail'),
path('video/preview/<int:pk>', views.VideoPreview.as_view(), name='preview'),
path('player', views.PlayerListView.as_view(), name='player_list'),
path('video/play/<int:pk>/', views.VideoPlayView.as_view(), name='play'),
path('', views.VideoListView.as_view(), name="video_list")
]
Relevant class from views.py:
class VideoPlayView(FormView):
template_name = "main/video_play.html"
form_class = VideoPlayForm
initial = {}
http_method_names = ['get', 'post']
def get_initial(self, **kwargs):
initial = super().get_initial()
#initial['video'] = pk
initial['watch_date'] = datetime.date.today()
return initial
def get_context_data(self, **kwargs):
kc = kwargs.copy()
context = super().get_context_data(**kwargs)
video = Video.objects.get(context['pk'])
context['video'] = video
context['test'] = kc
self.initial['video'] = video.pk
context['viewers'] = Viewer.objects.all()
context['players'] = Player.objects.filter(ready=True)
return context
def form_valid(self, form):
return HttpResponse("Done")
I get a key error at the line:
video = Video.objects.get(context['pk'])
Viewing the debug info on the error page indicates that the context does not have the pk value stored within it.
If I change the base class to TemplateView with a FormMixin I don't get this key error (but I do have problems POSTing the form data), so I know that the code is basically okay. My understanding is that the FormView class should populate context in the same way as the TemplateView class.
Any idea why FormView behaves this way, and how can I get this working?
If you want pk from the URL, self.kwargs['pk'] will work in all Django generic class-based-views.
In TemplateView, the get() method passes kwargs to the get_context_data method, so you can use context['pk']. The FormView get() method calls get_context_data() without passing any kwargs, so that won't work.

Having trouble configuring a URL in Django to work with username instead of id (could not resolve URL for hyperlinked relationship)

In my Django code, I have a Profile class which basically extends AbstractUser. I wanted the detail view for this user to use the username instead of the userid, and that is actually working perfectly.
In my urls.py, I have url(r'^profile/view/(?P<username>[\w.#+-]+)/$', ProfileDetail.as_view(), name="profile-detail") and my ProfileDetail class looks like this:
class ProfileDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = ProfileSerializer
queryset = Profile.objects.all()
lookup_field = 'username'
accompanied by this line in my Profile serializer: url = serializers.HyperlinkedIdentityField(view_name='profile-detail', lookup_field='username').
This all works great. The problem is when I attempt to view all of my user's "Swipes". My Swipe class has a foreign key relationship with my Profile class (and related_name='swipes' in this foreign key).
I added this to urls.py: url(r'^profile/view/(?P<username>[\w.#+-]+)/swipes/$', ProfileSwipesList.as_view(), name="swipe-detail") where my ProfileSwipesList class looks like:
class ProfileSwipesList(generics.ListAPIView):
serializer_class = SwipeSerializer
def get_queryset(self, *args, **kwargs):
return Swipe.objects.filter(user = Profile.objects.get(username=self.kwargs['username']))
I verified that the queryset is returning the correct thing, a queryset of Swipe objects. In my Swipe serializer, I tried adding a similar line as the one I added in my Profile serializer: url = serializers.HyperlinkedIdentityField(view_name='swipe-detail').
However, when I try accessing this URL, I get this error:
Could not resolve URL for hyperlinked relationship using view name "swipe-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
I'm new to Django, so forgive me if the solution to this is obvious, but I'd appreciate any help with how to get this view to correctly show the queryset I'm returning from my list view.
Try setting lookup_field on ProfileSwipesList and add the lookup_field to the respective HyperlinkedIdentityField.
Edit:
Based on your comment, you need to use a custom hyperlinked identify field.
class SwipeIdentityField(serializers.HyperlinkedIdentityField):
def get_url(self, obj, view_name, request, format):
# Unsaved objects will not yet have a valid URL.
if hasattr(obj, 'pk') and obj.pk in (None, ''):
return None
url_kwargs = {
'username': obj.user.username,
}
return reverse(
view_name, kwargs=url_kwargs, request=request, format=format)
class SwipeSerializer:
url = serializers.HyperlinkedIdentityField(view_name='swipe-detail')

Subclass a view in django-registration-redux

I'm using Django-registration-redux and I want give more data to a view to render my base template. I read the example in doc.
My url.py:
class MyPasswordChangeView(PasswordChangeView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# context['book_list'] = Book.objects.all() # example in doc
context_dict = services.get_base_data_for_views(request)
return context_dict
urlpatterns = [
...
path('accounts/password/change/', MyPasswordChangeView.as_view(
success_url=reverse_lazy('auth_password_change_done')), name='auth_password_change'),
...
]
I have the extra data in services.py but this code gives error:
name 'request' is not defined
So context_dict isn't defined. Where can I take my request from? Mainly I need the user (but print(user)= 'user' is not defined). Or should I write another function?
In methods of Django class based views, you can access the request with self.request.
class MyPasswordChangeView(PasswordChangeView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context_dict = services.get_base_data_for_views(self.request)
return context_dict
Therefore you can access the user with self.request.user. Normally you would want to use login_required or LoginRequiredMixin so that only logged-in users can access the view, but in your case PasswordChangeView takes care of that for you.

Django User model UpdateView "No URL to redirect to"

I want to be able to edit existing users info (like first name etc.) from users panel. I have a list of all users displayed and when I press "edit" button I get "No URL to redirect to. Either provide a url or define a get_absolute_url method on the Model." error.
Here is some of my code:
class UserUpdate(UpdateView):
model = User
fields = ['first_name', 'last_name', 'email']
pk_url_kwarg = 'user_id'
template_name = 'companies/user_update.html'
urls.py
url(r'^users/$', views.user_index, name='user_index'),
# /company/user/<id>
url(r'^users/(?P<user_id>[0-9]+)/$', views.user_detail,
name='user_detail'),
# /company/users/<id>/update/
url(r'^users/(?P<user_id>[0-9]+)/update/$', views.UserUpdate.as_view(),
name='user_update'),
I tried fixing it by adding this code to models.py, but I get same error
class UserMethods(User):
def get_absolute_url(self):
return reverse('companies:user_detail', kwargs={'pk': self.pk})
class Meta:
proxy = True
I also tried adding this method to UpdateView in views.py, but with this I get
"'UserUpdate' object has no attribute 'user_id'"
def get_success_url(self):
return reverse('companies:user_detail', kwargs={
'user_id': self.user_id})
Why did you put the get_absolute_url method on a proxy, rather than the User model itself? If you wanted to do this, you would have to use UserMethods as the model in your view.
But the fix for the other approach is to get the user_id from self.object:
return reverse('companies:user_detail', kwargs={'user_id': self.object.id})
Try this on your model.
class UserMethods(User):
def get_absolute_url(self):
return reverse('companies:user_detail', args=(self.id,))
Whenever you want to provide this url in the template just type {{ object.get_absolute_url }}.

Django class based views, is this right approach?

I am using django with django-braces. This is to ask for an opinion if this is the right approach.
For this example I am just trying to return Users and a particular user in json format using CBV.
Views
class ListAllUsers(JSONResponseMixin, DetailView):
model = User
json_dumps_kwargs = {u"indent": 2}
def get(self, request, *args, **kwargs):
object = self.get_object()
context_dict = collections.defaultdict(list)
if not self.kwargs.get("pk"):
for obj in object:
context_dict[obj.id].append ({
u"name": obj.username,
u"email": obj.email
})
else:
context_dict[object.id].append ({
u"name": object.username,
u"email": object.email
})
return self.render_json_response(context_dict)
def get_object(self):
if not self.kwargs.get("pk"):
return User.objects.all()
else:
return get_object_or_404(User, pk=self.kwargs.get("pk"))
urls
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^users/$', ListAllUsers.as_view(), name="users-list"),
url(r'^users/(?P<pk>[0-9]+)/$', ListAllUsers.as_view(), name="users-detail")
]
I know it's a subjective question but I want your opinions please be supportive because I am having a hard time figuring out how to use CBVs optimally.
You're actually using a function-based view within a DetailView class based view (the if statement is the function determining whether to return one object or all the objects). DetailView is a Django class based view for showing detail on one object. You should make this view do that one thing only.
To show a list of objects, you should use ListView.
One solution is to write a second ListView and call it from your urls.py rather than calling your DetailView for both of your url endpoints.