I'm trying to restrict access of CRUD pages to the owners, but I can't find the class-based view equivalent of "if request.user != post.author raise Http404". Thx for your time.
models.py
class Article(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('article_detail', args=[str(self.id)])
views.py
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
model = Article
fields = ['title', 'body']
template_name = 'article_edit.html'
login_url = 'login'
I tried the following (and many other combination arround those lines), but it isn't working.
def get(self, request, *args, **kwargs):
if self.request.user == self.obj.author:
raise Http404()
Youn can do something like this:-
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
model = Article
fields = ['title', 'body']
template_name = 'article_edit.html'
login_url = 'login'
def get(self, request, *args, **kwargs):
self.obj = self.get_object()
if self.request.user != self.obj.author:
raise Http404()
return super(ArticleUpdateView, self).get(request, *args, **kwargs)
I think you can override the get_queryset method to achieve this. For example:
class ArticleUpdateView(...):
def get_queryset(self):
queryset = super(ArticleUpdateView, self).get_queryset()
return queryset.filter(author = self.request.user)
So, when a user tries to update an post which is not created by him, then he will not be able to get it because will not be able find the post object in Queryset provided by get_queryset method. For details, please SingleObjectMixin which is later sub-classed by UpdateView. FYI you don't need to override the get method for this implementation.
Related
I'm a django newbie and i'm making a form where a User can make a Post and pick one of his Vehicles for the Post. The Vehicle and the Post models are created like so:
*blog/models.py*
class Post(models.Model):
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
vehicle = models.ForeignKey(Vehicle, on_delete=models.CASCADE, null=True)
def get_absolute_url(self):
return reverse('post-detail', kwargs ={'pk': self.pk} )
*vehicles/models.py*
class Vehicle(models.Model)*:
TESLA = 'TESLA'
MAZDA = 'MAZDA'
VOLVO = 'VOLVO'
VEHICLE_CHOICES = (
(TESLA, "Tesla"),
(MAZDA, "Mazda"),
(VOLVO, "Volvo"),
)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
model = models.CharField(max_length=9,
choices=VEHICLE_CHOICES,
default=TESLA)
def __str__(self):
return self.model
My blog views:
*blog/views.py*
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = [ 'vehicle']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
I would like to filter the vehicles so that only the current logged in User's vehicles
show up in the form, i've tried a variety of different solutions but I seem to be going around in circles, if you could help me out that would be awesome. Thanks!
Since you are using createview, you can create a form in forms.py. First you have to send the logged in user to the form, then in the form, pop the user from kwargs and use it to filter the vehicles.
views.py
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['vehicle']
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
self.fields['vehicle'].queryset = Vehicle.objects.filter(owner=user)
How to allow only the author of the article in the Django UpdateView to access the article update page?
#views.py
class ArticleUpdate(LoginRequiredMixin, UpdateView):
model = Article
template_name = 'articles/update_view.html'
context_object_name = 'article_update'
form_class = ArticleForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['securities_types_list'] = StocksETFsBonds.objects.all()
context['tags_list'] = Tag.objects.annotate(articles_quantiy=Count('taggit_taggeditem_items')).order_by(
'-articles_quantiy')[:10]
return context
Implement get_object and check if the requesting user is the author of the article (you did not provide the details of your models, so i will presume that your Article model has a author field):
class ArticleUpdate(LoginRequiredMixin, UpdateView):
model = Article
template_name = 'articles/update_view.html'
context_object_name = 'article_update'
form_class = ArticleForm
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
if obj.author != self.request.user:
raise PermissionDenied()
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['securities_types_list'] = StocksETFsBonds.objects.all()
context['tags_list'] = Tag.objects.annotate(articles_quantiy=Count('taggit_taggeditem_items')).order_by(
'-articles_quantiy')[:10]
return context
You can also implement ArticleUpdate.get_queryset (if the requesting user is not the author of the article they receive a 404 error):
def get_queryset(self, *args, **kwargs):
return Article.objects.filter(author=self.request.user)
I created a FormView and it works fine if the user executed the process the first time. However when it is executed the second time I get an error that the record already exist. This is expected as the user in the model is unique. How can I overcome this problem so that the current record is overwritten by the form.save if the record already exist.
models.py
class ttemp_selection(models.Model):
select_account = models.ForeignKey(tledger_account, on_delete=models.CASCADE)
date_from = models.DateField(default=datetime.today)
date_to = models.DateField(default=datetime.today)
user = models.ForeignKey(custom_user, on_delete=models.CASCADE, unique=True)
def __str__(self):
return self.select_account
forms.py
class Meta:
model = ttemp_selection
fields = ['select_account', 'date_from', 'date_to', 'user']
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(SelectAccountForm, self).__init__(*args, **kwargs)
user = self.request.user
current_company = user.current_company
self.fields['select_account'].queryset = tledger_account.objects.filter(
company=current_company, gl_category='Cash and Bank')
view.py
class sasView(FormView):
template_name = 'cashflow/select_account.html'
form_class = SelectAccountForm
success_url = 'home'
def form_valid(self, form):
form.save()
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super(sasView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
I can determine the record by using ttemp_selection.objects.get(user=request.user)
I know I can make use of the UpdateView class but that will create a problem when the record does not exist. It will also add an extra step that is unnecessary.
Assistance will be appreciated.
You can work with a CreateView, and slightly alter the behavior to specify a self.object if that exists:
from django.contrib.auth.mixins import LoginRequiredMixin
class sasView(LoginRequiredMixin, CreateView):
template_name = 'cashflow/select_account.html'
form_class = SelectAccountForm
success_url = 'home'
def get_form(self, *args, **kwargs):
self.object = ttemp_selection.objects.filter(
user=self.request.user
).first()
return super().get_form(*args, **kwargs)
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super(sasView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
It however makes no sense to include the user as field, since - if I understand it correctly - you use the logged in user. By including it, you make it possible that a person forges a POST request, and thus changes the account of a different user. You should omit this filed:
class SelectAccountForm(forms.ModelForm):
class Meta:
model = ttemp_selection
# no user ↓
fields = ['select_account', 'date_from', 'date_to']
# …
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].
I have DetaiView for my post and I want to use a form in this view so I decided to use DetailView with FormMixin. I need to set some initial to this form and I don't know how to do it. Here is my code:
models.py
class Comment(models.Model):
post = models.ForeignKey(Post, related_name="comments", on_delete=models.CASCADE)
name = models.CharField("Nick", max_length=80)
email = models.EmailField()
body = models.TextField("Body")
created = models.DateTimeField("created", auto_now_add=True)
updated = models.DateTimeField("Updated", auto_now=True)
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = (
"name",
"email",
"body"
)
views.py
class PostDetailView(FormMixin, DetailView):
model = Post
form_class = CommentForm
template_name = "newspaper/post-detail.html"
def get_success_url(self):
return reverse("post-detail", kwargs={"slug": self.object.slug})
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context["form"] = self.get_form()
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
return super().form_valid(form)
So I want to post in CommentForm to post of this DetailView. I hope you understand :D.
Thanks in advance for the help!
With FormMixin you can specify form's initial using initial attribute:
class PostDetailView(FormMixin, DetailView):
model = Post
form_class = CommentForm
template_name = "newspaper/post-detail.html"
initial={'name': 'John'}
Or get_initial method:
def get_initial(self):
return {"post": self.get_object() }
In a class-base UpdateView in Django, I exclude the user field as it is internal to the system and I won't ask for it. Now what is the proper Django way of passing the user into the form.
(How I do it now, is I pass the user into the init of the form and then override the form's save() method. But I bet that there is a proper way of doing this. Something like a hidden field or things of that nature.
# models.py
class Entry(models.Model):
user = models.ForeignKey(
User,
related_name="%(class)s",
null=False
)
name = models.CharField(
blank=False,
max_length=58,
)
is_active = models.BooleanField(default=False)
class Meta:
ordering = ['name',]
def __unicode__(self):
return u'%s' % self.name
# forms.py
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
exclude = ('user',)
# views.py
class UpdateEntry(UpdateView):
model = Entry
form_class = EntryForm
template_name = "entry/entry_update.html"
success_url = reverse_lazy('entry_update')
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(UpdateEntry, self).dispatch(*args, **kwargs)
# urls.py
url(r'^entry/edit/(?P<pk>\d+)/$',
UpdateEntry.as_view(),
name='entry_update'
),
Hacking around like passing a hidden field doesn't make sense as this truly has nothing to do with the client - this classic "associate with logged in user" problem should definitely be handled on the server side.
I'd put this behavior in the form_valid method.
class MyUpdateView(UpdateView):
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
super(MyUpdateView, self).save(form)
# the default implementation of form_valid is...
# def form_valid(self, form):
# self.object = form.save()
# return HttpResponseRedirect(self.get_success_url())
Must return an HttpResponse object. The code below works:
class MyUpdateView(UpdateView):
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
return super(MyUpdateView, self).form_valid(form)
We can also do like
class MyUpdateView(UpdateView):
form_class = SomeModelForm
def form_valid(self, form):
form.instance.user = self.request.user
return super(MyUpdateView, self).form_valid(form)