Django CBV Detailview - python

Hello Everybody excuse my english....
I am facing a problem with django.
I need to restrict object so only their owners can print it.
Model.py
class Post(models.Model):
title = models.CharField(max_length=50, blank=False)
prenom = models.CharField(max_length=255, blank=False)
user = models.ForeignKey(User, null=False)
View.py
class detailpost(DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
url.py
url(r'detail-post/(?P<pk>[-\d]+)$', views.detailpost.as_view(), name='detailpost'),
This works properly but the problem is that every users can access to the post of another user (http://localhost:8000/detail-post/1). So my question is how can i do some stuff befor rendering the page and see if the post belongs to the current user if yes we print it else we redirect the user to another page.

You can use the LoginRequiredMixin (new in Django 1.9) to make sure that only logged in users can access the view.
Then override the get_queryset method, and filter the queryset so that it only includes posts by the logged-in user.
from django.contrib.auth.mixins import LoginRequiredMixin
class DetailPost(LoginRequiredMixin, DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
def get_queryset(self):
queryset = super(DetailPost, self).get_queryset()
return queryset.filter(owner=self.request.user)
If the user views a post that does not belong to them, they will see a 404 page. If you must redirect the user instead of showing a 404, then you'll have to take a different approach.
Note that I have renamed your class DetailPost (CamelCase is recommended for classes in Django. You'll have to update your urls.py as well.

You can override get() or post() method in your view class.
from django.shortcuts import redirect
class detailpost(DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
def get(self, request, *args, **kwargs):
self.post = Post.objects.get(pk=self.kwargs['pk'])
if self.post.user != request.user or not request.user.is_superuser:
return redirect('login')
else:
return super(detailpost, self).get(request, *args, **kwargs)

You should override 'get()' method in your 'detailpost' class, so that it would be something like below:
def get(self, request, *args, **kwargs):
queryset = self.model._default_manager.filter(user=request.user)
self.object = self.get_object(queryset)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
It seems like it is the only way to pass User from Request instance to filter queryset.
I did not find that DetailView uses self.request

Related

Understanding Django Rest Framework's ModelViewSet Router

I have my Comment model:
class Comment(models.Model):
"""A comment is a content shared by a user in some post."""
user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=False)
post = models.ForeignKey('posts.Post', on_delete=models.CASCADE, null=False)
content = models.TextField(max_length=1000, null=False, blank=False)
def __str__(self):
"""Return the comment str."""
return "'{}'".format(self.content)
Its serializer:
class CommentSerializer(serializers.ModelSerializer):
"""Comment model serializer."""
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Comment
fields = '__all__'
def create(self, validated_data):
"""Create a new comment in some post, by request.user."""
validated_data['user'] = self.context['request'].user
return super().create(validated_data)
def list(self, request):
"""List all the comments from some post."""
if 'post' not in request.query_params:
raise ValidationError('Post id must be provided.')
q = self.queryset.filter(post=request.query_params['post'])
serializer = CommentSerializer(q, many=True)
return Response(serializer.data)
The viewset:
class CommentViewSet(viewsets.ModelViewSet):
serializer_class = CommentSerializer
queryset = Comment.objects.all()
def get_permissions(self):
permissions = []
if self.action == 'create' or self.action == 'destroy':
permissions.append(IsAuthenticated)
return [p() for p in permissions]
def get_object(self):
"""Return comment by primary key."""
return get_object_or_404(Comment, id=self.kwargs['pk']) # this is the drf's get_object_or_404 function
def destroy(self, request, *args, **kwargs):
"""Delete a comment created by request.user from a post."""
pdb.set_trace()
instance = self.get_object()
if instance.user != request.user:
raise ValidationError('Comment does not belong to the authenticated user.')
self.perform_destroy(instance)
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
So far so good when it comes to list, create and retrieve. But when it comes to delete/destroy (Idk the difference) I don't know how to get the URL for the DELETE request.
I'm using Postman to do so.
Urls.py:
router = routers.SimpleRouter()
router.register(r'comments', CommentViewSet, basename='comments')
pdb.set_trace() # Put a pdb here to see what does router.urls have.
urlpatterns = [
# Non api/simple django views
path('create_comment/', create_comment, name='create_comment'),
path('delete_comment/', delete_comment, name='delete_comment'),
# rest api views
path('rest/', include(router.urls))
]
When I debug the router.urls, the terminal shows this:
[<URLPattern '^comments/$' [name='comments-list']>, <URLPattern '^comments/(?P<pk>[^/.]+)/$' [name='comments-detail']>]
Why there is no url for create and destroy?
I could create some Comments from the api, and I didn't even know how did I get the POST request url for the create function, I just guessed lol, but that's not what you want when you're programming right?
I have checked the Routers documentation but I don't get it. Please give some help! Many thanks.
The answer is simple, Django uses the comments-list list for create and list operation and comments-detail for update and delete operations.
That is, there are only two URL end-points, but it supports several actions, which can be performed by changing the HTTP methods
You can use HTTP GET foo-bar/comments/ to retrieve all comments where as HTTP POST foo-bar/comments/ can be used to create a new comment.

Django set bookmark for url querystring

I'm querying through some filters (django-filters) a list of Book objects, what I want to do is save my research (aka my url with the GET params), creating a Bookmark object for the user.
models.py
class Bookmark(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
url = models.URLField(max_length=200)
forms.py
class BookmarkForm(BootstrapForm, forms.ModelForm):
class Meta:
model = Bookmark
fields = ['nome', 'url']
widgets = {
'url': forms.HiddenInput()
}
To do so I'm extending a CreateView and trying to pass current url to the initial valued of bookmark.url.
class AddBookmarkView(PermissionRequiredMixin, LoginRequiredMixin, CreateView):
permission_required = 'core.add_bookmark'
template_name = 'core/bookmark_form.html'
model = Bookmark
form_class = BookmarkForm
def get_initial(self):
initial = super().get_initial()
initial['url'] = request.GET.urlencode()
return initial
def get_success_url(self):
return reverse('elenco_libri')
def form_valid(self, form):
bookmark = form.save()
bookmark.user = self.request.user
bookmark.save()
return HttpResponseRedirect(self.get_success_url())
This doesn't get anywhere because I don't have the GET request in get_initial() function. Where could I store the url? I thought about the get() function but I don't know how. Can anyone help? Thanks in advance.
This doesn't get anywhere because I don't have the GET request in get_initial() function.
Actually you have, the request object in a class-based view is an attribute: self.request.
So you can generate a URL with:
class AddBookmarkView(PermissionRequiredMixin, LoginRequiredMixin, CreateView):
permission_required = 'core.add_bookmark'
template_name = 'core/bookmark_form.html'
model = Bookmark
form_class = BookmarkForm
def get_initial(self):
initial = super().get_initial()
initial['url'] = self.request.GET.urlencode()
return initial
def form_valid(self, form):
bookmark = form.save(commit=False)
bookmark.user = self.request.user
bookmark.save()
return HttpResponseRedirect(self.get_success_url())
The above will however, only give you the querystring. If you are interested in the path as well, you might want to use request.get_full_path() [Django-doc].
You probably also do not want to save the object to the database in the form_valid function, so first obtain the bookmark, then set the user, and then save that object to the database.
Note that a HiddenInput() is rendered at the client side, and thus can be tampered with. Furthermore I'm not convinced that setting a bookmark through a form is per se necessary. You could construct a view that for example sets a bookmark with an AJAX call, and render a form with JavaScript to set the name and submit the bookmark.

Allow only the author of the post to edit in class based views- Django?

views.py
class EditPost(UserPassesTestMixin, LoginRequiredMixin, UpdateView):
model = Posts
form_class = PostForm
template_name="posts/add_post.html"
def test_func(self):
x = self.request.user.pk
print (x)
y = Posts.objects.get(user='user')
print (y)
if x == y:
return True
else:
if self.request.user.is_authenticated():
raise Http404("You are not allowed to edit this Post")
models.py
class Posts(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
post = models.CharField(max_length=1200, blank=False)
How do i match the loggedin user and the user object of the Post
i could not find any solution since i am using class based views.
try this:
add dispatch to EditPost class
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if obj.user != self.request.user:
raise Http404("You are not allowed to edit this Post")
return super(EditPost, self).dispatch(request, *args, **kwargs)
Doing the check in test_func is tricky. You need to fetch the object once in test_func to check whether the user is allowed to use it, and then the object is fetched again by the same fiew.
An easier approach is to override the get_queryset method. If the user is not the author of the post, they will get a 404.
class EditPost(LoginRequiredMixin, UpdateView):
def get_queryset(self):
return super(EditPost, self).filter(user=self.request.user)
To add to the previous posts, try this:
Add to your class EditPost:
login_url = 'your url name or path to login page'
def test_func(self):
obj = self.get_object()
return obj.author == self.request.user
The test_func method is used by UserPassesTestMixin for this particular logic. To override it we set variable obj to the current object returned by the view using get.object().
After that, if the author on the current object (obj.author) matches the current user (self.request.user) we allow editing. If not (i.e. false) we throw an error.
login_url is from LoginRequiredMixin and the default location is /accounts/login. To override it, set your own path or name of the login template. This will take those people who are not logged in to the login page.

Django Login Required to view

I am building a small application which needs user profiles, I've used the build in user system from Django. But I have a problem regarding that even if you are not logged in you can still view the profile also another thing is that each user should only see his profile not others I need some tips on this
views.py
class UserProfileDetailView(DetailView):
model = get_user_model()
slug_field = "username"
template_name = "user_detail.html"
def get_object(self, queryset=None):
user = super(UserProfileDetailView, self).get_object(queryset)
UserProfile.objects.get_or_create(user=user)
return user
class UserProfileEditView(UpdateView):
model = UserProfile
form_class = UserProfileForm
template_name = "edit_profile.html"
def get_object(self, queryset=None):
return UserProfile.objects.get_or_create(user=self.request.user)[0]
def get_success_url(self):
return reverse("profile", kwargs={"slug": self.request.user})
Since you are using the Class Based Generic View, you need to add decorator #login_required in your urls.py
#urls.py
from django.contrib.auth.decorators import login_required
from app_name import views
url(r'^test/$', login_required(views.UserProfileDetailView.as_view()), name='test'),
Have you checked out the login_required decorator? Docs are here.
Since it seems you are using Class Based Views, you need to decorate in the urlconf, see here for more info.
At this moment you can add LoginRequiredMixin for your custom view.
Example:
class MyListView(LoginRequiredMixin, ListView): # LoginRequiredMixin MUST BE FIRST
pass
Doc: https://docs.djangoproject.com/en/4.1/topics/auth/default/#the-loginrequiredmixin-mixin
The below is what you should typically do
#login_required
def my_view(request, uid):
# uid = user id taken from profile url
me = User.objects.get(pk=uid)
if me != request.user:
raise Http404

django form exclude a user instance from a queryset

I have the following model:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name="user")
people_interested = models.ManyToManyField(User, related_name="interested")
Now I want a form where I want to offer users a form where they can choose people_interested, so I add the following forms.py
class ChooseForm(forms.Form):
q_set = User.objects.all()
peers = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset = q_set)
and then in views:
form = ChooseForm(data = request.POST or None)
if request.POST and form.is_valid():
uprofile, created = UserProfile.objects.get_or_create(user=request.user)
uprofile.people_interested = form.cleaned_data['peers']
uprofile.save()
return HttpResponseRedirect("/")
else:
return render(request, "form_polls.html", {'form':form})
But the trouble with this is, the current user instance also gets displayed. So I tried the following in views.py:
form = ChooseForm(request.user.id, data = request.POST or None)
and then in forms.py
class ChooseForm(forms.Form):
def __init__(self, uid, *args, **kwargs):
super(ChooseForm, self).__init__(*args, **kwargs)
self.fields['peers'].queryset = User.objects.exclude(id=uid)
q_set = User.objects.all()
peers = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset = q_set)
But the above is not a clean implementation, is there a better method of doing it??
What makes you say this is not a clean implementation? Overwriting queryset on __init__ is perfectly acceptable.
The only things I'd do to improve your code is using a post_save signal on User to create it's UserProfile, then just do user.get_profile() on your view. See this question
You could also use a ModelForm for UserProfile instead of a regular form, and limit the fields to people_interested.

Categories