Django UpdateView user validation not working - django

Hello guys I have this update view where I am not able to validate the user(owner). How to tweak this to add that bit too.? Please have a look at the code.
class StoreInfoView(UpdateView, LoginRequiredMixin):
model = Store
template_name = 'store/store_information.html'
form_class = StoreInfoForm
success_message = 'Updated'
success_url = reverse_lazy('store:store_home')
def get_object(self, queryset=None):
obj = Store.objects.get(id=self.kwargs['id'])
if obj.user != self.request.user:
raise PermissionDenied('You Don\'t have permission to edit!')
return obj
def get(self, *args, **kwargs):
self.object = Store.objects.get(id=self.kwargs['id'])
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(object=self.object, form=form)
return self.render_to_response(context)
Thanks

Instead of overriding like this, you can simply override get_queryset() method. Like this:
class StoreInfoView(UpdateView, LoginRequiredMixin):
model = Store
template_name = 'store/store_information.html'
form_class = StoreInfoForm
success_message = 'Updated'
success_url = reverse_lazy('store:store_home')
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
return queryset.filter(user=self.request.user)
In this way, non-owner users will get 404 error when they try to update.
Also, you do not need to override any other methods like get() and get_object() method.

The issue to your problem is the order of inheritance. When you will go through the official docs for LoginRequiredMixin, you will find this
This mixin should be at the leftmost position in the inheritance list.
Please update your code to this
class StoreInfoView(LoginRequiredMixin, UpdateView):
model = Store
template_name = 'store/store_information.html'
form_class = StoreInfoForm
success_message = 'Updated'
success_url = reverse_lazy('store:store_home')
...
Notice, now LoginRequiredMixin is put before the UpdateView. This should resolve your query.
I hope it helps. :)

Related

How Do I Access Data From a Multi-Table Inheritance Definition?

Straight from the Django Docs....
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
I can see the data in my database so I know it's working...but I can't figure out how to access the data. In the past I have used Class Based Views...this is my first go at using a multi-table inheritance structure beyond class based views.
I have tried to do...
def get_absolute_url(self):
return reverse("App:create_restaurant_detail",kwargs={'pk':self.object.place_ptr_id})
Here's my urls...
path("create_restaurant",views.CreateRestaurantView.as_view(), name='create_restaurant'),
path("create_restaurant_detail/<pk>/",views.CreateRestaurantDetailView.as_view(), name='create_restaurant_detail'),
And my Views....
class CreateRestaurantView(LoginRequiredMixin,CreateView):
model = Restaurant
form_class = CreateRestaurantForm
template_name = 'create_restaurant.html'
def get_success_url(self):
return redirect('App:create_restaurant_detail', kwargs={'pk':self.object.place_ptr_id})
class CreateRestaurantDetailView(LoginRequiredMixin,DetailView):
model = Restaurant
context_object_name = 'restaurant_detail'
template_name = 'create_restaurant_detail.html'
But the url lookup keeps saying not found.
In the log...I see....
django.urls.exceptions.NoReverseMatch: Reverse for 'create_restaurant_detail' with keyword arguments '{'kwargs': {'pk': 12}}' not found. 1 pattern(s
) tried: ['LevelSet/App/create_restaurant_detail/1bad5cba\\-a087\\-4f0a\\-9c3b\\-65ac096c3e42/(?P<pk>[^/]+)/$']
I'm trying to figure out how to access the data in the table. Thanks for any thoughts and help in advance.
class CreateRestaurantView(LoginRequiredMixin,CreateView):
model = Restaurant
form_class = CreateRestaurantForm
template_name = 'create_restaurant.html'
def form_valid(self, form):
instance = form.save()
return JsonResponse({ 'id': instance.pk, 'success_url': self.get_success_url() })
def post(self, request, *args, **kwargs):
if "cancel" in request.POST:
return HttpResponseRedirect(reverse('Main:main'))
else:
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
restaurant_instance = form.save()
self.object = restaurant_instance
return self.form_valid(form)
def get_success_url(self):
return reverse('Newapp:create_restaurant_detail', kwargs={ 'pk' : self.object.place_ptr_id })
My URL...
path("create_restaurant_detail/<int:pk>/",views.CreateRestaurantDetailView.as_view(), name='create_restaurant_detail'),
I misunderstood CreateView. This code is working at the moment, but I'm not sure JsonResponse is the best answer for form_valid. I couldn't figure out the proper alternative but my original problem has been solved. Thanks to Iain for helping me work through it.

Unable to restrict access to the post edit screen with django

I am restricting access to the posted edit screen.
I want to make sure that only the user who posted or the super user can edit the post.
For that purpose, "UserPassesTestMixin" is used.
However, there is no limit.
And I think that my own "OnlyRecordEditMixin" is not responding. If you understand, thank you.
#mixin
class OnlyRecordEditMixin(UserPassesTestMixin):
raise_exception = True
def test_func(self):
user = self.request.user
id = self.kwargs['id']
print(id)
return user.pk == URC.objects.get(id=id).user or user.is_superuser
#view
class RecordDetailEdit(UpdateView,OnlyRecordEditMixin):
template_name = 'records/detail_edit.html'
model = URC
pk_url_kwarg = 'id'
form_class = RecordDetailEditForm
success_url = reverse_lazy('person:home')
def get_object(self, queryset=None):
obj = URC.objects.get(id=self.kwargs['id'])
return obj
#url
path('<id>/edit/', views.RecordDetailEdit.as_view(), name='record_detail_edit'),
#model
class URC(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
A mixin should be put before the base view class, to construct a good MRO here.
class RecordDetailEdit(OnlyRecordEditMixin, UpdateView):
template_name = 'records/detail_edit.html'
model = URC
pk_url_kwarg = 'id'
form_class = RecordDetailEditForm
success_url = reverse_lazy('person:home')
You should not override the get_object(..) method here. The get_object(..) method is defined in terms of get_queryset(..): it filters the get_queryset(..) with the given primary key (and/or slug).
You can however easily restrict access by restricting the get_queryset:
class OnlyRecordEditMixin(LoginRequiredMixin):
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
if not self.user.is_superuser:
return qs.filter(user=self.request.user)
return qs
or even more generic:
class OnlyRecordEditMixin(LoginRequiredMixin):
user_field = 'user'
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
if not self.user.is_superuser:
return qs.filter(**{self.user_field: self.request.user})
return qs
Here we can thus change the user_field to point to the name that is the owner of the object.
This will return a HTTP 404 response if a user that is not a superuser, nor the "owner" of the object aims to update that object.
This is cheaper, since you prevent fetching the object multiple times.

Show a paginated ListView and an UpdateView on the same template page

I am trying to create a Django page where something can be updated and something can be viewed in a paginated table. The model looks like this:
class CostGroup(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse(
'costgroup_detail',
kwargs={
'costgroup_pk': self.pk,
}
)
class Cost(models.Model):
cost_group = models.ForeignKey(CostGroup)
amount = models.DecimalField(max_digits=50, decimal_places=2)
def get_absolute_url(self):
return reverse(
'cost_detail',
kwargs={
'cost_pk': self.pk,
}
)
So the edit form is for the name and description fields of the CostGroup model and the table should show a list of the 'amounts`
I previously had it working by just having an UpdateView for the form and the table included in the form template. Now though, as I want to include pagination on the table, I need to use two views on the same page. The page I have designed should look something like this in the end:
I am not worried about the styling at the moment my main focus at the moment is getting the form and the table on the same page. In its current state the only thing that I don't have is the pagination for the table:
The view currently looks like this:
class CostDetail(UpdateView):
model = models.Cost
pk_url_kwarg = 'cost_pk'
template_name = 'main/cost_detail.html'
form_class = forms.CostDetailEditForm
success_url = reverse_lazy('cost_list')
I have a feeling that leveraging the underlying mixins that the Django CBVs use is probably the way to go but I am not sure how to begin with this.
Any help would be much appreciated
Thanks for your time
(This clarification seemed to work better as a new answer)
It looks like you're dealing with both of the tables. The object level is using CostGroup, while the List view is showing the child records from Cost linked to a CostGroup. Assuming that is true, here's how I would proceed:
class CostDetail(ModelFormMixin, ListView):
model = CostGroup # Using the model of the record to be updated
form_class = YourFormName # If this isn't declared, get_form_class() will
# generate a model form
ordering = ['id']
paginate_by = 10
template_name = 'main/cost_detail.html' # Must be declared
def get_queryset(self):
# Set the queryset to use the Cost objects that match the selected CostGroup
self.queryset = Cost.objects.filter(cost_group = get_object())
# Use super to add the ordering needed for pagination
return super(CostDetail,self).get_queryset()
# We want to override get_object to avoid using the redefined get_queryset above
def get_object(self,queryset=None):
queryset = CostGroup.objects.all()
return super(CostDetail,self).get_object(queryset))
# Include the setting of self.object in get()
def get(self, request, *args, **kwargs):
# from BaseUpdateView
self.object = self.get_object()
return super(CostDetail,self).get(request, *args, **kwargs)
# Include the contexts from both
def get_context_data(self, **kwargs):
context = ModelFormMixin.get_context_data(**kwargs)
context = ListView.get_context_data(**context)
return context
# This is the post method found in the Update View
def post(self, request, *args, **kwargs):
# From BaseUpdateView
self.object = self.get_object()
# From ProcessFormView
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
I haven't tried to run this, so there may be errors. Good luck!
(Remember ccbv.co.uk is your friend when digging into Class-based Views)
An app I'm working on now uses a similar approach. I start with the ListView, bring in the FormMixin, and then bring in post() from the FormView.
class LinkListView(FormMixin, ListView):
model = Link
ordering = ['-created_on']
paginate_by = 10
template_name = 'links/link_list.html'
form_class = OtherUserInputForm
#=============================================================================#
#
# Handle form input
#
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
def get_success_url(self):
return reverse('links')
You may also wish to override get_object(), get_queryset(), and get_context().

Filtering a model in a CreateView with get_queryset

I'm trying to filter a model with get_queryset() and it seems to work in the view but not in the template.
My view :
class FolderCreate(CreateView):
fields = ['name', 'parent']
template_name = 'Form/folder_create.html'
def get_queryset(self):
folders = Folder.objects.filter(owner=self.request.user)
print folders # ==> return [<Folder: Folder>, <Folder: Another folder>]
return folders
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.owner = self.request.user
return super(FolderCreate, self).form_valid(form)
def get_initial(self):
if self.request.method == 'GET':
foldersUrl = self.request.META['HTTP_REFERER'].split('/')
foldersUrl.pop()
folder = urllib2.unquote(foldersUrl[-1])
try:
return {'parent' : Folder.objects.get(name=folder, owner=self.request.user)}
except Folder.DoesNotExist:
pass
As you can see, folders return two objects related to the session user in get_queryset() : 'Folder' and 'Another folder
Infortunately, the combobox of my template get all the folders, without any filtering.
Any idea ?
The issue here is that get_queryset is not used in a CreateView, as it's meant for filtering the models returned for display in a list or detail view. You want something completely different: you want to filter the choices available in a form field.
To do that you will need to create a custom ModelForm that accepts a user kwarg and filters the queryset accordingly:
class FolderForm(forms.ModelForm):
class Meta:
model = Folder
fields = ['name', 'parent']
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(FolderForm, self).__init__(*args, **kwargs)
self.fields['parent'].queryset = Folder.objects.filter(user=user)
and then change your view to use that form and pass in the user parameter:
class FolderCreate(CreateView):
template_name = 'Form/folder_create.html'
form_class = FolderForm
def get_form_kwargs(self):
kwargs = super(FolderCreate, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs

FormSetView save with Django Extra Views

as shown in the following code i am trying to save the formset once it is validated. but i always get object has no attribute 'save'error. its great if someone can help me to use FormSetView to save and update data using forms.
from extra_views import FormSetView
from foo.forms import MyForm
class MyFormSetView(FormSetView):
template_name = 'myformset.html'
form_class = MyForm
success_url = 'success/'
def formset_valid(self, formset):
formset.save()
return super(MyFormSetView, self).formset_valid(formset)
You should use ModelFormSetView and then override your widget:
class MyForm(forms.ModelForm):
model = customer
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['birthday'].widget = birthdaySelector()
class MyFormSetView(ModelFormSetView):
template_name = 'myformset.html'
model = customer
success_url = 'success/'
form_class = MyForm
No need to define formset_valid, ModelFormSetView saves formset automatically.