Django CBV UserPassesTestMixin test_func not running - python

The test_func doesn't appear to run at all. Right now I'm just checking if the user is staff, as an easy test. Here's the code:
class MyModelCreate(CreateView, UserPassesTestMixin):
def test_func(self):
print("checking if user passes test....")
return self.request.user.is_staff
model = MyModel
....
However, when I go to the create page for this model, anyone can edit it, and there is no output to the python console showing that the test_func runs.
The view is called:
urlpatterns = [
...
url(r'^create/$', views.MyModelCreate.as_view(), name='my_model_create'),
]

In python the order of the classes matters because their methods override each other
Your class based view should look like this:
class MyModelCreate(UserPassesTestMixin, CreateView):
def test_func(self):
print("checking if user passes test....")
return self.request.user.is_staff
model = MyModel
....

Related

How do I apply Mixins to all CBVs in a django app?

Let's say I want to use the LoginRequiredMixin and a UserPermissionMixin created by myself and apply them to all the views in an app. This is just an example, I might also have mixins that add some context or do other stuff.
I could do it manually, for example this view:
class MyCreateView(LoginRequiredMixin, UserPermissionMixin, CreateView)
But, since I have many views and I might have other specific mixins for some views, this gets messy and hard to manage.
One solution that came to mind would be to create new classes for the generic views:
class DecoratedCreateView(LoginRequiredMixin, UserPermissionMixin, CreateView):
pass
class DecoratedDetailView(LoginRequiredMixin, UserPermissionMixin, DetailView):
pass
class DecoratedUpdateView(LoginRequiredMixin, UserPermissionMixin, UpdateView):
pass
class DecoratedDeleteView(LoginRequiredMixin, UserPermissionMixin, DeleteView):
pass
and then, use these as my generic views:
class MyCreateView(DecoratedCreateView)
Is this a good approach? Do I have to add any methods in the classes above or do I just leave them blank and it'll work as expected?
Is there any other way to achieve this, maybe in urls.py ?
Your approach is good. I've been doing so for some projects with a slight difference:
myapp/views/generic.py
from django.views.generic import (
CreateView as BaseCreateView,
DetailView as BaseDetailView,
UpdateView as BaseUpdateView,
DeleteView as BaseDeleteView,
)
__all__ = ['MyappMixin', 'CreateView', 'DetailView', 'UpdateView', 'DeleteView']
class MyappMixin(LoginRequiredMixin, UserpermissionMixin):
pass
class CreateView(MyappMixin, BaseCreateView):
pass
class DetailView(MyappMixin, BaseDetailView):
pass
class UpdateView(MyappMixin, BaseUpdateView):
pass
class DeleteView(MyappMixin, BaseDeleteView):
pass
myapp/views/base.py
from .generic import CreateView
class MyCreateView(CreateView):
pass
It works fine, without much hassle, and allows you to easily skip the mixin exceptionally if needed.
According to the usecase, another solution might be to use middlewares or context processors.
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
is_in_myapp = request.resolver_match.app_name == 'myapp'
if is_in_myapp and not request.user.is_authenticated:
response = HttpResponse("Permission denied", status=403)
else:
response = self.get_response(request)
return response

__init__ .. get_form_kwargs(self) which function called the other

I'm little confused about which of the following functions called the other one
is
___init____(self,*args,**kwargs) called get_forms_kwargs(self)
or
get_forms_kwargs(self) called ___init____()
as I made the following mixin:
class RequestformattachMixin(object):
def get_form_kwargs(self):
kwargs=super().get_form_kwargs()
kwargs['request']=self.request
print(kwargs)
return kwargs
and removed the get_forms_kwargs(self) from the CBV in view.py and instead updated the CBV to let it inherit from the mixin
class LoginView(NextUrlMixin,RequestformattachMixin,FormView):
form_class = login_page
template_name = 'login.html'
success_url = '/'
and didn't call the get_forms_kwargs(self) in CBV and it's working with no errors.
please need an explanation for that.

How can I add a non-model/queryset returning view with django-restframework when using DjangoObjectPermissions?

I have a view that I want to add to my django-restframework api that does not relate to any model. Though I'm using 'rest_framework.permissions.DjangoObjectPermissions' in DEFAULT_PERMISSION_CLASSES.
class EnumChoices(views.APIView):
def get(self, request):
enums = {}
return Response(enums)
Now Django complains about my view:
AssertionError at /api/enums/
Cannot apply DjangoModelPermissions on a view that does not have `.queryset` property or overrides the `.get_queryset()` method.
I need the permission class for almost all other views and do not want to get rid of it. How can I get around the mandatory attributes for the one view?
You can add a view-specific permission logic to overwrite the model permission check. Create a BasePermission class object and add it to your views permission_classes attribute. Don't forget IsAuthenticated unless you want to allow anonymous users too.
class EnumChoices(views.APIView):
class EnumPermission(permissions.BasePermission):
def has_permission(self, request, view):
# whatever permission logic you need, e.g.
return request.user.has_perm("planning.view_enums")
permission_classes = (permissions.IsAuthenticated, EnumPermission)
def get(self, request):
enums = {}
return Response(enums)
Now the view will ensure the user is authenticated and has the view_enums permission.
More info here: http://www.django-rest-framework.org/api-guide/permissions/#custom-permissions

Creating a display-only (non-editable) Django admin field

Is it possible to build a custom model field/widget combination which displays a value but never writes anything back to the database? I would use this widget exclusively in the admin's forms.
I wrote my own field, which overwrites the formfield() method to declare its own widget class. It displays just fine, but as soon as the 'Save' button is clicked in the admin, I'm getting a validation error:
This field is required.
That makes sense, considering that my widget didn't render out a form field. However, what I'd like to do is basically remove this field from the update process: whenever used in the admin, it just shouldn't be mentioned in the SQL UPDATE at all.
Is that possible?
Here's a sketch of the code I have so far:
class MyWidget(Widget):
def render(self, name, value, attrs=None):
if value is None:
value = ""
else:
# pretty print the contents of value here
return '<table>' + ''.join(rows) + '</table>'
class MyField(JSONField):
def __init__(self, *args, **kwargs):
kwargs['null'] = False
kwargs['default'] = list
super(MyField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {
'form_class': JSONFormField,
'widget': MyWidget,
}
defaults.update(**kwargs)
return super(MyField, self).formfield(**defaults)
UPDATE 1: The use case is that the field represents an audit log. Internally, it will be written to regularly. The admin however never needs to write to it, it only has to render it out in a very readable format.
I'm not using any other ModelForms in the application, so the admin is the only form-user. I don't want to implement the behavior on the admin classes themselves, because this field will be reused across various models and is always supposed to behave the same way.
There are multiple ways to create a read-only field in the admin pages. Your requirements on the database storage are a bit fuzzy so I go through the options.
You have to register an AdminModel first in admin.py:
from django.contrib import admin
from yourapp.models import YourModel
class YourAdmin(admin.ModelAdmin):
pass
admin.site.register(YourModel, YourAdmin)
Now you can add different behavior to it. For example you can add the list of fields shown in the edit/add page:
class YourAdmin(admin.ModelAdmin):
fields = ['field1', 'field2']
This can be names of the model fields, model properties or model methods. Methods are displayed read-only.
If you want to have one field read-only explicitly add this:
class YourAdmin(admin.ModelAdmin):
fields = ['field1', 'field2']
readonly_fields = ['field2']
Then you have the option to overwrite the display of the field completely by adding a method with the same name. You will not even need a model field/method with that name, then:
class YourAdmin(admin.ModelAdmin):
fields = ['field1', 'field2']
readonly_fields = ['field2']
def field2(self, obj):
return '*** CLASSIFIED *** {}'.format(obj.field2)
With django.utils.safestring.mark_safe you can return HTML code as well.
All other options of the Admin are available, except the widget configuration as it applies to the writable fields only.
I might be a little confused as to what you want but you might want to look into model properties. Here is an example for my current project.
Code inside your model:
class Textbook(models.Model):
#other fields
#property
def NumWishes(self):
return self.wishlist_set.count()
Then you can just display it on the admin page.
class Textbook_table(admin.ModelAdmin):
fields = ["""attributes that are saved in the model"""]
list_display = ("""attributes that are saved in the model""", 'NumWishes'')
So now I can display NumWishes in the admin page but it doesn't need to be created with the model.
Hello in the class admin modify the permission method
#admin.register(my_model)
class My_modelAdmin(admin.ModelAdmin):
def has_delete_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False

What is the Django-way of namespacing ModelAdmin.get_urls when using model(admin) inheritance?

TL;DR
Is there a way to have (namespaced,) well-named views defined when using ModelAdmin.get_urls and ModelAdmins extended by inheritance?
Preferably without resorting to ModelAdmin.model._meta or some other solution of slightly questionable nature.
Pretext
View names added through get_urls get overridden when using and inheriting from custom ModelAdmins.
That is, the view name admin:tighten gets overriden in the following example:
class Screw(models.Model):
"A screw"
class HexCapScrew(Screw):
"A hex cap screw"
class ScrewAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(ScrewAdmin, self).get_urls()
extra_urls = patterns('',
url(r'^tighten/$', self.tighten, name='tighten'),
)
return extra_urls + urls
def tighten(self, request):
pass
class HexCapScrewAdmin(ScrewAdmin):
pass
admin.site.register(Screw, ScrewAdmin)
admin.site.register(HexCapScrew, HexCapScrewAdmin)
On shell the following happens:
In [1]: reverse('admin:tighten')
Out[1]: u'/admin/parts/hexscrew/tighten/'
This is of course understandable since the registration of HexCapScrewAdmin overides the tighten in ScrewAdmin however now it's impossible to reverse ScrewAdmin.tighten.
A preferred solution
However I would like to be able to
reference both views separatedly and
preferably have views in their own instance namespaces.
Progress so far
The best I've come up with is the following setup (can be copy&pasted directly to some app for testing):
from django.contrib import admin
from django.db import models
class Screw(models.Model):
"A screw"
class Meta:
app_label = 'parts'
class HexCapScrew(Screw):
"A hex cap screw"
class Meta:
app_label = 'parts'
proxy = True
class ScrewAdmin(admin.ModelAdmin):
def tighten(self, request):
pass
def get_urls(self):
urls = super(ScrewAdmin, self).get_urls()
extra_urls = patterns('',
url(r'^tighten/$', self.tighten, name='tighten'),
)
# Find out the slugified name of the model this admin is bound to
# TODO: Feels dirty
model_name = self.model._meta.model_name
# Add the to `extra_urls` to their own namespace
namespaced_extra_urls = patterns('',
url(r'^', include(extra_urls, namespace=model_name, app_name='screw')),
)
return namespaced_extra_urls + urls
class HexCapScrewAdmin(ScrewAdmin):
pass
admin.site.register(Screw, ScrewAdmin)
admin.site.register(HexCapScrew, HexCapScrewAdmin)
Now I have the following:
In [1]: reverse('admin:screw:tighten')
Out[1]: u'/admin/parts/screw/tighten/'
In [2]: reverse('admin:hexscrew:tighten')
Out[2]: u'/admin/parts/hexscrew/tighten/'
In [3]: reverse('admin:screw:tighten', current_app='hexscrew')
Out[3]: u'/admin/parts/hexscrew/tighten/'
which is nice and works but includes a bit of hackery.
Is this the best that's available or am I just missing something? Any suggestions?
(At least one other way would be to do as Django's ModelAdmin.get_urls use ModelAdmin.model._meta to parametrize the view names but then I would use the namespaces.)
If you look at the way the admin does it here, you will see that in addition to defining the url, the model admin also prefixes the app_label and model_name to the url name, thus avoiding the subclassing issue to begin with. It also has the advantage of securing the view against unauthorised users (using the self.admin_site.admin_view decorator). Your get_urls() method would then become:
def get_urls(self):
from django.conf.urls import url
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
urlpatterns = super(ScrewAdmin, self).get_urls()
urlpatterns.append(
url(r'^tighten/$', wrap(self.tighten), name='%s_%s_tighten' % info))
return urlpatterns
Then, you'd look up your url like: reverse('admin:app_screw_tighten') or reverse('admin:app_hex_screw_tighten').

Categories