Django Class Based View UpdateView Restricted User - python

I am trying to use a Django UpdateView to display an update form for the user. https://docs.djangoproject.com/en/1.8/ref/class-based-views/generic-editing/
I only want the user to be able to edit their own form.
How can I filter or restrict the the objects in the model to only show objects belonging to the authenticated user?
When the user only has one object I can use this:
def get_object(self, queryset=None):
return self.request.user.profile.researcher
However, I now need the user to be able to edit multiple objects.
UPDATE:
class ExperimentList(ListView):
model = Experiment
template_name = 'part_finder/experiment_list.html'
def get_queryset(self):
self.researcher = get_object_or_404(Researcher, id=self.args[0])
return Experiment.objects.filter(researcher=self.researcher)
class ExperimentUpdate(UpdateView):
model = Experiment
template_name = 'part_finder/experiment_update.html'
success_url='/part_finder/'
fields = ['name','short_description','long_description','duration', 'city','address', 'url']
def get_queryset(self):
qs = super(ExperimentUpdate, self).get_queryset()
return qs.filter(researcher=self.request.user.profile.researcher)
URL:
url(r'^experiment/update/(?P<pk>[\w\-]+)/$', login_required(ExperimentUpdate.as_view()), name='update_experiment'),

UpdateView is only for one object; you'd need to implement a ListView that is filtered for objects belonging to that user, and then provide edit links appropriately.
To prevent someone from simply putting the URL for an edit view explicitly, you can override get_object (as you are doing in your question) and return an appropriate response.
I have successfully been able to generate the list view and can get
the update view to work by passing a PK. However, when trying to
override the UpdateView get_object, I'm still running into problems.
Simply override the get_queryset method:
def get_queryset(self):
qs = super(ExperimentUpdate, self).get_queryset()
# replace this with whatever makes sense for your application
return qs.filter(user=self.request.user)
If you do the above, then you don't need to override get_object.
The other (more complicated) option is to use custom form classes in your UpdateView; one for each of the objects - or simply use a normal method-based-view with multiple objects.

As the previous answer has indicated, act on the list to show only the elements belonging to the user.
Then in the update view you can limit the queryset which is used to pick the object by overriding
def get_queryset(self):
qs = super(YourUpdateView, self).get_queryset()
return qs.filter(user=self.request.user)

Related

Render fields conditionally with Django-Filters

I'm working on my Django SAAS app in which I want to allow the user to have some custom settings, like disable or enable certain filters. For that I'm using django-user-setttings combined with django-filters and simple forms with boolean fields:
class PropertyFilterSetting(forms.Form):
filter_by_loans = forms.BooleanField(required=False)
filter_by_tenants = forms.BooleanField(required=False)
The issue is that when trying to apply those settings, I keep running into serious spaghetti code:
views.py
class PropertyListView(LoginRequiredMixin, FilterView):
template_name = 'app/property_list.html'
context_object_name = 'properties'
def get_filterset_class(self):
print(get_user_setting('filter_by_tenants', request=self.request))
return PropertyFilterWithoutTenant if not get_user_setting('filter_by_tenants', request=self.request)['value'] else PropertyFilter
filter.py
class PropertyFilter(django_filter.FilterSet):
...
class PropertyFilterWithoutTenant(PropertyFilter):
...
and I'd have to do the same thing with the rest of the features. Is there any better way to implement this?
You can create methods in your User model, or a new class which acts as a store for all the methods. Each method will give you the relevant filterset class based on the value of corresponding user setting.
Something like:
class UserFilterset:
def __init__(self, request):
self.request = request
def get_property_filterset(self):
if not get_user_setting('filter_by_tenants', request=self.request)['value']:
return PropertyFilterWithoutTenant
return PropertyFilter
... # add more such methods for each user setting
Now you can use this method to get the relevant filterset class
class PropertyListView(LoginRequiredMixin, FilterView):
template_name = 'app/property_list.html'
context_object_name = 'properties'
def get_filterset_class(self):
return UserFilterset(self.request).get_property_filterset()
So even if in future you want to add some more logic, you can just update the relevant method, it would be cleaner and manageable.
I'm not sure how MVT stucture will exactly respond to this one but i use a custom generic class in REST structure to add custom filter fields in any viewset that i want
class ListAPIViewWithFilter(ListAPIView):
def get_kwargs_for_filtering(self):
filtering_kwargs = {}
if self.my_filter_fields is not []:
# iterate over the filter fields
for field in self.my_filter_fields:
# get the value of a field from request query parameter
field_value = self.request.query_params.get(field)
if field_value:
filtering_kwargs[field] = field_value
return filtering_kwargs
def get_queryset(self):
queryset = super(ListAPIViewWithFilter, self).get_queryset()
filtering_kwargs = self.get_kwargs_for_filtering()
if filtering_kwargs != {}:
# filter the queryset based on 'filtering_kwargs'
queryset = queryset.filter(**filtering_kwargs)
self.pagination_class = None
else:
return queryset
return queryset[:self.filter_results_number_limit]
changing origional get_queryset function in views.py should be the key to solving your problem (it works in django rest).
try checking what function gets the data then just identify the field wanted from it.

Django custom manager return everything when user is on admin page

I'm creating a Django app. It's an article app. I have a field named hidden and I want to return a queryset without articles when hidden is true and the user isn't in the admin panel.
Admin-page -> Show everything
Normal search -> Show only with hidden = False
My "Normal Search" is a custom search I made. I'm filtering results with django-filter and I want to automatically filter out hidden articles.
I'm creating this with a custom manager:
class ArticleManager(models.Manager):
def get_queryset(self, request):
if request.user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(hidden=False)
but I'm just getting this error:
TypeError: get_queryset() missing 1 required positional argument: 'request'
Based on the updated question: You shouldn't redefine the model manager's get_queryset function signature to take a request parameter. Instead, you need to create a new manager function with a user parameter which will return only the items you want. You will then use that as the queryset to your filter.
For example:
class ArticleManager(models.Manager):
def get_visible_items(self, user):
if user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(hidden=False)
# In your view:
user = request.user
artice_filter = ArticleFilter(queryset=Article.objects.get_visible_items(user))
Usually no request instance would get passed to manager methods.
But you can customize the queryset used inside an admin using its get_queryset() method:
class ArticleAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(hidden=False)
Note that this queryset will also get for editing instances, so you can really restrict which objects are accessible for certain users.

Django get custom queryset into ListView

I have a listview that I access in a pretty bog standard way to return all metaobjects.
#url
url(r'^metaobject/$', MetaObjectList.as_view(),name='metaobject_list'),
#ListView
class MetaObjectList(ListView):
model = MetaObject
I've recently added a search form that I want to scan my objects (I've got about 5 fields but I've simplified the example). What I'd like to do is re-use my MetaObjectList class view with my specific subset. I am guessing I need to override the get_queryset method but I'm not clear in how I get the queryset from my FormView into the listview. I mucked around a bit with calling the as_view() in the formveiw's form_valid function with additional parameters but couldn't get it to work and it seemed hacky anyway.
class SearchView(FormView):
template_name = 'heavy/search.html'
form_class = SearchForm
#success_url = '/thanks/'
def form_valid(self, form):
#build a queryset based on form
searchval=form.cleaned_data['search']
list = MetaObject.objects.filter(val=search)
#where to from here?
I also looked at trying to post the data from the form view over to the listview but that seemed like I'd need to re-write the form logic into the listview.
I'm on python 3.x and django 1.11.
I found what I feel is more elegant than the comment on the question:
My form valid now points to the list object's as_view method and passes the request and the queryset I want
def form_valid(self, form):
#build a queryset based on form
searchval=form.cleaned_data['search']
list = MetaObject.objects.filter(val=search)
return MetaObjectList.as_view()(self.request,list)
This hits the ListView as a post which I use to alter the queryset
class MetaObjectList(ListView):
model = MetaObject
queryset = MetaObject.objects.prefetch_related('object_type','domain')
def post(self, request, *args, **kwargs):
self.queryset = args[0]
return self.get(request, *args, **kwargs)
The only obvious change is using kwargs to make it a bit clearer. Otherwise this seems to work well.

Django Rest Framework. How Retrieve works

I am fairly new to Django rest framework and I had a couple questions that would really clear up a lot of stuff for me.
I was looking at docs for simple CRUD generic views like ListAPIView, Retrieve... etc.
For my list view I created it like this:
class CourseListApiView(ListAPIView):
queryset = Course.objects.all()
serializer_class = CourseListSerializer
Which makes sense because of the queryset returns Course.objects.all() so all the courses appear.
What I am not clear about is how the queryset in RetrieveApi works
class CourseRetrieveAPIView(RetrieveAPIView):
queryset = Course.objects.all()
serializer_class = CourseRetrieveSerializer
This is my retrieve view, it takes pk from my link and returns a corresponding course. What is unclear to me is why the queryset is Course.objects.all(), not a filtered query that gets the kwargs from the URL and filters my Courses. I tried it my way and got the same results, my view was:
class CourseRetrieveAPIView(RetrieveAPIView):
serializer_class = CourseRetrieveSerializer
def get_queryset(self):
queryset = Course.objects.filter(pk=self.kwargs.get('pk'))
return queryset
This makes more sense since the queryset is Course.objects.filter(pk=self.kwargs.get('pk')) instead of Course.objects.all() which to me doesn't make sense since I am filtering my courses by the pk in the URL
Hope my question made sense. Leave a comment if you need any clarification. I know the answer will be pretty obvious but I am very new to the framework
You will have to go through the codebase of rest_framework. A function named get_object uses two class variables named lookup_field and lookup_url_kwarg which have a default value of pk and None respectively.
Excerpt from the GenericAPIView in rest_framework/generics.py
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
As you can see the lookup_url_kwarg is set to be equal to lookup_field if nothing is specified. If you change this value to a field of your requirement then the filter in get_object_or_404 changes.
Now coming back to your issue, when you are specifying the filter manually with url kwargs you are not using the functionality provided by the RetrieveAPIView. Instead what you are doing is filtering out your result with pk from url kwargs in get_queryset and then sending that QuerySet result to get_object which will again do the same thing for you.

Django multi model best practice

Basically what I'm trying to achieve is a multi-model django app where different models take advantage of the same views. For example I've got the models 'Car' 'Make' 'Model' etc and I want to build a single view to perform the same task for each, such as add, delete and edit, so I don't have to create a seperate view for add car, ass make etc. I've built a ModelForm and Model object for each and would want to create a blank object when adding and a pre-populated form object when editing (through the form instance arg), with objects being determined via url parameters.
Where I'm stuck is that I'm not sure what the best way to so this is. At the moment I'm using a load of if statements to return the desired object or form based on parameters I'm giving it, which get's a bit tricky when different forms need specifying and whether they need an instance or not. Although this seems to be far from the most efficient way of achieving this.
Django seems to have functions to cover most repetitive tasks, is there some magic I'm missing here?
edit - Here's an example of what I'm doing with the arguments I'm passing into the url:
def edit_object(request, object, id):
if(object==car):
form = carForm(instance = Car.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
What about using Class Based Views? Using CBVs is the best way in Django to make reusable code. For this example maybe it can be a little longer than function based views, but when the project grows up it makes the difference. Also remember "Explicit is better than implicit".
urls.py
# Edit
url(r'^car/edit/(?P<pk>\d+)/$', EditCar.as_view(), name='edit-car'),
url(r'^make/edit/(?P<pk>\d+)/$', EditMake.as_view(), name='edit-make'),
# Delete
url(r'^car/delete/(?P<pk>\d+)/$', DeleteCar.as_view(), name='delete-car'),
url(r'^make/delete/(?P<pk>\d+)/$', DeleteMake.as_view(), name='delete-make'),
views.py
class EditSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-edit.html'
class EditCar(EditSomethingMixin, UpdateView):
model = Car
form_class = CarForm
class EditMake(EditSomethingMixin, UpdateView):
model = Make
form_class = MakeForm
class DeleteSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-delete.html'
class DeleteCar(DeleteSomethingMixin, DeleteView):
model = Car
class DeleteMake(DeleteSomethingMixin, DeleteView):
model = Make
Just pass your class and form as args to the method then call them in the code.
def edit_object(request, model_cls, model_form, id):
form = model_form(instance = model_cls.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
then just pass in the correct classes and forms in your view methods
def edit_car(request,id):
return edit_object(request, Car, CarForm, id)
each method knows what classes to pass, so you eliminate the if statements.
urls.py
url(r'^car/delete/(?<pk>\d+)/', edit, {'model': Car})
url(r'^make/delete/(?<pk>\d+)/', edit, {'model': Make})
views.py
def edit(request, id, model):
model.objects.get(id=id).delete()

Categories

Resources