Is there a way to apply a Meta permission to every model in a Django app?
Question:
I would like to add a can_view
Meta permission to every model in my Django app.
Pretty much I want to add this to every class in models.py
class Meta:
permissions = [ ( "can_view", "Can view {something}".format( something = self.verbose_name ) ]
I’m not even sure if self.verbose_name
would even work like that in this case….
Is this possible?
Bonus question: Once I add a permission inside the model’s Meta
then I can call it with has_perm
right? Like
if request.user.has_perm( 'polls.can_view' ) :
# Show a list of polls.
else :
# Say "Insufficient permissions" or something.
Answers:
The most simplest and obvious approach would be a custom base model that is a parent of all your models. This way you (or another programmer) will ever wonder where the heck can_view
is coming from.
class CustomBaseModel(models.Model):
class Meta:
abstract = True
permissions = [ ( "can_view", "Can view {something}".format( something = self.verbose_name ) ]
class SomeModel(CustomBaseModel):
# ...
However, this requires you to change all your models (this is easily done with a little search & replace) and it won’t change Djangos builtin models (like User
).
Permission is also a just normal Django model instance. You can create it like any other model.
So you need something like
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
for content_type in ContentType.objects.all():
Permission.objects.create(content_type=content_type, codename='view_%s' % content_type.model, name='Can view %s' % content_type.name)
You need to do it once, so post_syncdb signal looks like a good place for that.
As someone else suggested in another answer, an abstract class is an obvious approach. However, the code given in that answer throws errors.
To be frank, doing this with an abstract class (as of summer 2023) is clunky and needs work arounds. Testing this is also hard, because I don’t think Django removes any permissions from the auth_permission table when undoing migrations (migrate my_app 0022
where 0022 was the prefix of a previous migration)
What does NOT work:
# BROKEN, DOES NOT WORK
class MyAppBaseModel(models.Model):
class Meta:
abstract = True
permissions = (("see_details_%(class)s", "Can see details of %(class)s"),)
class ChildModel(MyAppBaseModel):
class Meta:
# Ideally would result in model getting default permissions plus "see_details_childmodel" and "foobar_childmodel"
permissions = (("foobar_childmodel", "Can foobar childmodel"),)
What DOES work (source):
- fairly short
- takes advantage of how Django does meta-class inheritance (needs explanation with comments)
- Adds to the default permissions (from what I could see)
- ALL OBJECTS GET THE SAME PERMISSION NAME
Code:
class AbstractBaseModel(models.Model):
class Meta:
abstract = True
permissions = (("see_details_generic","See details (Generic)"),) # <-- Trailing comma
class SomeClass(AbstractBaseModel):
name = models.CharField(max_length=255,verbose_name="Name")
class Meta(AbstractBaseModel.Meta): # <--- Meta Inheritance
# Results in child getting default permissions plus "see_details_generic" (NOT view_childmodel)
pass
# Still trying to figure out how to get this to work
# As written, if use the below instead of pass, it removes see_details_generic
# permissions = (("foobar_childmodel", "Can foobar childmodel"),)
P.S. An improvement to Django to help make the broken example work has been kicked around for 14 years. Hoping someone could finish it up on Github.
I would like to add a can_view
Meta permission to every model in my Django app.
Pretty much I want to add this to every class in models.py
class Meta:
permissions = [ ( "can_view", "Can view {something}".format( something = self.verbose_name ) ]
I’m not even sure if self.verbose_name
would even work like that in this case….
Is this possible?
Bonus question: Once I add a permission inside the model’s Meta
then I can call it with has_perm
right? Like
if request.user.has_perm( 'polls.can_view' ) :
# Show a list of polls.
else :
# Say "Insufficient permissions" or something.
The most simplest and obvious approach would be a custom base model that is a parent of all your models. This way you (or another programmer) will ever wonder where the heck can_view
is coming from.
class CustomBaseModel(models.Model):
class Meta:
abstract = True
permissions = [ ( "can_view", "Can view {something}".format( something = self.verbose_name ) ]
class SomeModel(CustomBaseModel):
# ...
However, this requires you to change all your models (this is easily done with a little search & replace) and it won’t change Djangos builtin models (like User
).
Permission is also a just normal Django model instance. You can create it like any other model.
So you need something like
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
for content_type in ContentType.objects.all():
Permission.objects.create(content_type=content_type, codename='view_%s' % content_type.model, name='Can view %s' % content_type.name)
You need to do it once, so post_syncdb signal looks like a good place for that.
As someone else suggested in another answer, an abstract class is an obvious approach. However, the code given in that answer throws errors.
To be frank, doing this with an abstract class (as of summer 2023) is clunky and needs work arounds. Testing this is also hard, because I don’t think Django removes any permissions from the auth_permission table when undoing migrations (migrate my_app 0022
where 0022 was the prefix of a previous migration)
What does NOT work:
# BROKEN, DOES NOT WORK
class MyAppBaseModel(models.Model):
class Meta:
abstract = True
permissions = (("see_details_%(class)s", "Can see details of %(class)s"),)
class ChildModel(MyAppBaseModel):
class Meta:
# Ideally would result in model getting default permissions plus "see_details_childmodel" and "foobar_childmodel"
permissions = (("foobar_childmodel", "Can foobar childmodel"),)
What DOES work (source):
- fairly short
- takes advantage of how Django does meta-class inheritance (needs explanation with comments)
- Adds to the default permissions (from what I could see)
- ALL OBJECTS GET THE SAME PERMISSION NAME
Code:
class AbstractBaseModel(models.Model):
class Meta:
abstract = True
permissions = (("see_details_generic","See details (Generic)"),) # <-- Trailing comma
class SomeClass(AbstractBaseModel):
name = models.CharField(max_length=255,verbose_name="Name")
class Meta(AbstractBaseModel.Meta): # <--- Meta Inheritance
# Results in child getting default permissions plus "see_details_generic" (NOT view_childmodel)
pass
# Still trying to figure out how to get this to work
# As written, if use the below instead of pass, it removes see_details_generic
# permissions = (("foobar_childmodel", "Can foobar childmodel"),)
P.S. An improvement to Django to help make the broken example work has been kicked around for 14 years. Hoping someone could finish it up on Github.