Django writing generic update view restricted to certain user - python

I am building a small blog using django.I want to build a function that allow post author to delete and update their own posts.
Then I find django has LoginMixin for generic view,but it only block those who don't login.
My article Model is like below
class Article(models.Model):
author = models.ForeignKey(User)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=50)
context = models.TextField()
genre_choice = (('O','Others'),('P','Programming'),('L','Learning'),('E','Entertainment'))
genre = models.CharField(max_length=2,choices=genre_choice,default='O')
def __str__(self):
return "{} - {}".format(self.title,self.author)
def get_absolute_url(self):
return reverse("article-detail",args=str(self.id))
This is the generic article detail view.
class ArticleDetail(DetailView):
model = Article
I firstly want to add something like this in the detail template:
{% if article.author == user.username%}
<!-- Some a tag that directs to the update view -->
{% endif %}
Then I realize that this just hides the a tag ,it can't stop other users to touch the update url simply change the url.
Is there anyway in django can restricted the update and delete permissions to the original user by simply using generic view?Even if they directly enter the update url,they will be rejected.

Override get_queryset in your UpdateView, so that the user can only access items that they authored. Use the LoginRequiredMixin to ensure that only logged-in users can access the view.
from django.contrib.auth.mixins import LoginRequiredMixin
class UpdateArticle(LoginRequiredMixin, UpdateView):
model = Article
def get_queryset(self):
queryset = super(UpdateArticle, self).get_queryset()
queryset = queryset.filter(author=self.request.user)
return queryset
In the template, I would compare the author_id with the user's primary key to decide whether to show the link or not.
{% if article.author_id == user.pk %}

One option is to create your own mixin/decorator to check if the logged user is the author, if not then reload/show a message etc.

I believe a safer way now would be to use built-in mixin UserPassesTestMixin. In particular, you can inherit it in your class and change its test_func() to check for the author. Don't forget to also inherit LoginRequiredMixin to make sure the user is logged in:
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class UpdateArticle(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Article
def test_func(self):
thisArticle = self.get_object()
if self.request.user == thisArticle.author:
return True
return False
If a user who is not the author attempts to update the article, '403 Forbidden' Error is returned which is just what you want in such a situation.

Related

Can't display foreign key related object in Django DetailView

I've got a booking class which is related to Customer and Barber models. I can display all bookings using the detail view, however, can't figure out how to display a booking/bookings that a specific barber has. Basically, I want to get a booking or multiple bookings of a barber based on the ID given to the url.
Here is my model:
customer_id = models.ForeignKey(User, on_delete = models.CASCADE,)
barber_id = models.ForeignKey(Barber, on_delete = models.CASCADE)
timeslot = models.DateTimeField('appointment')
def __str__(self):
return f"{self.customer_id} {self.barber_id} {self.timeslot}"
def get_absolute_url(self):
return reverse("model_detail", kwargs={"pk": self.pk})
My view:
class GetBarberBooking(DetailView):
model = Booking
template_name = "barber_booking.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['barber'] = Booking.objects.filter(
id=self.kwargs.get('<str:pk'))
return context
My url path:
path('barber-booking/<str:pk>/', views.GetBarberBooking.as_view(), name='barber-booking'),
You can remove this piece of code:
context['barber'] = Booking.objects.filter(
id=self.kwargs.get('<str:pk'))
And in template just use:
{{ object.barber_id }}
And show all the booking for barber:
{{ object.barber_id.booking_set.all }}
It will show all the the barber. This works because of FK relation between Booking and Barber model. More information can be found in Django documentation. For reverse relation (Many to one), please check this documentation.
FYI, you do not need to create a field name suffix with _id, because Django creates that automatically for you.
Also, if you want to query a Barber, then you should use Barber as the model in the DetailView. Then you can use a similar query mentioned above:
# view
class GetBarberBooking(DetailView):
model = Barber
# template
{{ object.booking_set.all }}

Django: How to retrieve the logged in user's details

I am in the process of learning Django. I am trying to create a simple directory web app. I am able to print out all the users details for the main directory page. However, I want to add a feature that when a person logs into the directory app they are brought to their 'profile' page where they will be able to see all their own details e.g. business name, profile pic.
I know how to retrieve the default fields e.g. username and email. But cannot seem to retrieve the custom fields that I declared myself. Here is my attempts so far...
Models.py:
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class UserProfileInfo(models.Model):
user = models.OneToOneField(User,on_delete=models.DO_NOTHING)
#additional classes
business_name = models.CharField(max_length=191,blank=True)
trade = models.CharField(max_length=191,blank=True)
portfolio_site = models.URLField(blank=True)
profile_pic = models.ImageField(upload_to='profile_pics',blank=True)
def __str__(self):
return self.user.username
Views.py:
#login_required
def profile(request):
profile = UserProfileInfo.objects.filter(user=request.user)
context = { 'profile': profile }
return render(request, 'dir_app/profile.html',context)
Profile.html:
<div class="container-fluid text-center">
{% for p in profile %}
<h3>{{p.business_name}}</h3>
{% endfor %}
</div>
Since UserProfileInfo is related to User via OneToOneField, you can have one UserProfileInfo per User. So, instead of Filter, you can simply get your desired UserProfileInfo object through your current (logged in) User as follows.
views.py,
profile = UserProfileInfo.objects.get(user=request.user)
Also, before you can get a request.user object, you have to make sure that your user is authenticated and logged in. Otherwise, you might get None in place of a User object and therefore, no associated UserProfileInfo.
Since it is a OneToOneField there is only one Profile object for a User, you thus can obtain this with:
#login_required
def profile(request):
profile = request.user.userprofileinfo
return render(request, 'my_template.html',{'profile': profile})
Then in the template, you render it with:
{{ profile.business_name }}
you can use it directly on template without sending it f:
{{request.user.userprofile}}

Django ManyToMany field filter

So I have this system where my Post object has a ManyToMany field and it's called Saves. So like for example on Reddit you can save a post. So I got it working and users can save posts, and it adds them to the ManyToMany field. However, I want to filter out these posts and only show the posts where said user is in the ManyToMany field.
Here is my models.py
class Post(models.Model):
author = models.ForeignKey(User,related_name='posts',on_delete=models.CASCADE)
saves = models.ManyToManyField(User,blank=True,related_name='post_saves')
I have the saves field connected to the User model Django provides.
And here is my views.py
class PostSaveRedirect(RedirectView):
def get_redirect_url(self,*args,**kwargs):
pk = self.kwargs.get("pk")
slug = self.kwargs.get("slug")
obj = get_object_or_404(Post,pk=pk,slug=slug)
url_ = obj.get_absolute_url()
user = self.request.user
if user.is_authenticated:
if user in obj.saves.all():
obj.saves.remove(user)
else:
obj.saves.add(user)
return url_
So this is all working fine, it adds the user to the ManyToMany field, but now I want to know how I can filter out posts and only display ones where the user is in the ManyToMany field.
Here is my saved posts view.
class PostSaveListView(ListView):
model = Post
template_name = 'mainapp/post_saved.html'
paginate_by = 10
queryset = models.Post.objects.all()
def get(self,request):
posts = Post.objects.all()
return render(request, self.template_name)
def get_queryset(self):
return Post.objects.filter().order_by('-published_date')
So with Post.objects.all(), how can I change it so it will filter to my needs? This is a similar queryset for a user post list view I have
I have been Googling and reading up the docs and other articles but have not found anything that has been able to show me how to filter a ManyToMany field. Any help would be much appreciated
edit your model like this:
class PostSaveListView(ListView):
model = Post
template_name = 'mainapp/post_saved.html'
paginate_by = 10
-
def get(self,request):
posts = Post.objects.all()
return render(request, self.template_name)
def get_queryset(self):
object_list = Post.objects.filter(saves__in=[self.request.user]).order_by('-published_date').distinct()
return object_list
The easiest and quickest option for you would be to use the filtering option in like this:
def get_queryset(self):
return Post.objects.filter(saves__in=[self.request.user]).order_by('-published_date')
Please notice the list inclusion for the user object, as that option only filters from lists.
You may consider also adding .distinct() call to the filter also to avoid repetition of objects.

I would like to add condition to Django DetailView

I am using Django Generic view, DetailView.
But I'd like to block users to access to detail post who did not email_confirmed yet.
I have a email_confirmed field in User model.
My code is :
#method_decorator(login_required(login_url='/login/'), name='dispatch')
class RecruitView(generic.DetailView):
model = Recruit
template_name = 'recruit.html'
and I want to add :
if not request.user.email_confirmed:
error_message = "you did not confirmed yet. please check your email."
return render(request, 'info.html', {'error_message': error_message})
else: pass
How can I add this condition to DetailView?
(I tried to override 'as_view' but I don't know how to do it)
I would use the PermissionRequiredMixin. With this you can specify specific permissions users need to have or override the has_permission method.
from django.contrib.auth.mixins import PermissionRequiredMixin
class RecruitView(PermissionRequiredMixin, generic.DetailView):
...
login_url = '/login/'
permission_denied_message = 'you did not confirmed yet. please check your email.'
def has_permission(self):
return self.request.user.email_confirmed
This will redirect users without the email_confirmed to the login_url where you can display the error message. In order to use the index.html template instead you might need to override the handle_no_permission method.

Foreign key to User table in django

I'm using django's built-in contrib.auth module and have setup a foreign key relationship to a User for when a 'post' is added:
class Post(models.Model):
owner = models.ForeignKey('User')
# ... etc.
Now when it comes to actually adding the Post, I'm not sure what to supply in the owner field before calling save(). I expected something like an id in user's session but I noticed User does not have a user_id or id attribute. What data is it that I should be pulling from the user's authenticated session to populate the owner field with? I've tried to see what's going on in the database table but not too clued up on the sqlite setup yet.
Thanks for any help...
You want to provide a "User" object. I.e. the same kind of thing you'd get from User.objects.get(pk=13).
If you're using the authentication components of Django, the user is also attached to the request object, and you can use it directly from within your view code:
request.user
If the user isn't authenticated, then Django will return an instance of django.contrib.auth.models.AnonymousUser. (per http://docs.djangoproject.com/en/dev/ref/request-response/#attributes)
Requirements --> Django 3, python 3
1) For add username to owner = models.ForeignKey('User') for save that, in the first step you must add from django.conf import settings above models.py and edit owner = models.ForeignKey('User') to this sample:
class Post(models.Model):
slug = models.SlugField(max_length=100, unique=True, null=True, allow_unicode=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, default=1, on_delete=models.CASCADE)
2) And for show detail Post, special owner name or family or username under the post, you must add the following code in the second step in views.py:
from django.shortcuts import get_object_or_404
.
.
.
def detailPost(request,slug=None):
instance = get_object_or_404(Post, slug=slug)
context = {
'instance': instance,
}
return render(request, template_name='detail_post.html', context=context)
3) And in the third step, you must add the following code for show user information like user full name that creates a post:
<p class="font-small grey-text">Auther: {{ instance.owner.get_full_name }} </p>
now if you want to use user name, you can use {{ instance.owner.get_username }}
or if you want to access short name, you can use {{ instance.owner.get_short_name }}.
See this link for more information.

Categories