Unable to restrict access to the post edit screen with django - 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.

Related

Django UpdateView user validation not working

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. :)

How to override queryset used by Django admin/form for a Foreign Key

I've tried everything I can find on the internet here, and nothing seems to work, so wondering if lots of the previous answers are for old versions. I'm on Django 2.2.9.
#models.py
class ParentModel(models.Model):
title = models.CharField()
class ChildModel(models.Model):
parent = models.ForeignKey(
ParentModel,
on_delete=models.CASCADE,
related_name='parent'
)
# admin.py
#admin.register(ParentModel)
class ParentModelAdmin(admin.ModelAdmin):
model = ParentModel
def get_queryset(self, request):
return ParentModel.objects.get_complete_queryset()
class ChildModelForm(forms.Form):
def __init__(self, u, *args, **kwargs):
super(ChildModelForm, self).__init__(*args, **kwargs)
self.fields['parent'].queryset = ParentModel.objects.get_complete_queryset()
class Meta:
model = ChildModel
fields = '__all__'
#admin.register(ChildModel)
class ChildModelAdmin(admin.ModelAdmin):
model = ChildModel
form = ChildModelForm
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "parent":
kwargs["queryset"] = ParentModel.objects.get_complete_queryset()
return super().formfield_for_foreignkey(db_field, request, **kwargs)
I have a manager query called get_complete_queryset on ParentModel that returns a broader set of Parents than the default queryset.
The setup above allows me to go to my ChildModelAdmin and select the 'hidden' Parents from the dropdown, but when I try and save it gives me this error:
parent instance with id 2 does not exist.
There must be some queryset the form is using to save the model that isn't overridden, but I can't find what it is.
You can override get_form method like this:
def get_form(self, request, obj, **kwargs):
form = super(<YourModelAdmin>,self).get_form(request, obj, **kwargs)
form.base_fields['<you_field>'] = forms.ModelChoiceField(queryset=<your_queryset>)
return form

How to perform queries in django modelform?

I tried this in my modelform:
class Ledgerform(forms.ModelForm):
class Meta:
model = ledger1
fields = ('name', 'group1_Name')
def __init__(self, User, Company, *args, **kwargs):
self.User = kwargs.pop('User', None)
self.Company = kwargs.pop('Company', None)
super(Ledgerform, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs = {'class': 'form-control',}
self.fields['group1_Name'].queryset = group1.objects.filter(User= self.User,Company = self.Company)
In my views.py I have done something like this:
class ledger1ListView(LoginRequiredMixin,ListView):
model = ledger1
paginate_by = 15
def get_queryset(self):
return ledger1.objects.filter(User=self.request.user, Company=self.kwargs['pk'])
class ledger1CreateView(LoginRequiredMixin,CreateView):
form_class = Ledgerform
def form_valid(self, form):
form.instance.User = self.request.user
c = company.objects.get(pk=self.kwargs['pk'])
form.instance.Company = c
return super(ledger1CreateView, self).form_valid(form)
I want to perform the the same query that I have passed in my ledger1ListView by using queryset in my modelform but my kwargs.pop is not returning the current user or the company...
This is my models.py:
class ledger1(models.Model):
User = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=True,blank=True)
Company = models.ForeignKey(company,on_delete=models.CASCADE,null=True,blank=True,related_name='Companys')
name = models.CharField(max_length=32)
group1_Name = models.ForeignKey(group1,on_delete=models.CASCADE,blank=True,null=True)
Do any one know what I am doing wrong in my code?
Thank you in advance
You can override the FormMixin.get_form_kwargs [Django-doc] in your view, that it constructs a dictionary with the parameters necessary to initialize the form, like:
class ledger1CreateView(LoginRequiredMixin,CreateView):
form_class = Ledgerform
def get_form_kwargs(self):
data = super(ledger1CreateView, self).get_form_kwargs()
data.update(
User=self.request.User,
Company=company.objects.get(pk=self.kwargs['pk'])
)
return data
The form_valid function is called after the form is constructed, validated and appears to be valid. Typically it is used to redirect the user to the "success page".

Django DetailView + show related record of another Model

I would like to return to the User related record.
Somebody can help me?
part of my view
class UserProfileDetailView(DetailView):
model = get_user_model()
slug_field = "username"
template_name = "perfil.html"
def get_object(self, queryset=None):
user = super(UserProfileDetailView, self).get_object(queryset)
UserProfile.objects.get_or_create(user=user)
return user
Something like a old way>
def my_view(request, slug):
var = get_object_or_404(Model, slug=slug)
xxx = AnotherModel.objects.filter(var=var)
...
how can i perfome this in the first view UserProfileDetailView,
show related data?
What I do in this case is add the related object into the context data. It would be something like this:
class UserProfileDetailView(DetailView):
model = get_user_model()
slug_field = "username"
template_name = "perfil.html"
def get_context_data(self, **kwargs):
# xxx will be available in the template as the related objects
context = super(UserProfileDetailView, self).get_context_data(**kwargs)
context['xxx'] = AnotherModel.objects.filter(var=self.get_object())
return context
Another approach is to extend DetailView with MultipleObjectMixin, as in this example:
from django.views.generic import DetailView
from django.views.generic.list import MultipleObjectMixin
from django.contrib.auth import get_user_model
class DetailListView(MultipleObjectMixin, DetailView):
related_model_name = None
def get_queryset(self):
# a bit of safety checks
if not hasattr(self, "related_model_name"):
raise AttributeError(
"%s.related_model_name is missing." % (
self.__class__.__name,))
if not self.related_object_name:
raise NotImplementedError(
"%s.related_model_name must not be None." % (
self.__class__.__name,))
# get the object
obj = self.get_object()
# get the related model attached to the object
related_model = getattr(obj, "%s_set" % self.related_model_name, None)
# safety check if related model doesn't exist
if not related_model:
raise AttributeError(
"%s instance has no attribute \"%s_set\"" % (
obj.__class__.__name__, self.related_model_name)
# return the related model queryset
return related_model.all()
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
return super(DetailListView, self).get(request, *args, **kwargs)
class UserProfileDetailView(DetailListView):
template_name = "perfil.html"
model = get_user_model()
slug_field = "username"
related_model_name = "anothermodel"
def get_object(self, queryset=None):
user = super(UserProfileDetailView, self).get_object(queryset)
UserProfile.objects.get_or_create(user=user)
return user
In my opinion this approach is a little less cleaner and understandable, but it has a huge advantage in reusability. It definitely has one downside: if you are using the class variable context_object_name, it will refer to the related objects list and not to the object itself (this has to do with how the inheritance chain is set up when constructing the class).

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