Custom Permissions
Volunteers shouldn't change an animal's status or an adopted animal's record. Employees need to change both. Django's built-in permissions can't tell those apart, so we'll add a custom permission and check it in the admin.
Adding a Custom Permission
We can add custom permissions to a model. Open animals/models.py and add a Meta class to Animal, below its fields:
intake_date = models.DateField(default=date.today)
class Meta:
permissions = [
(
"manage_animal",
"Can change an animal's status, and change adopted animals",
),
]
Each permission is a row in the auth_permission table. The migration records our new Meta option, and migrate then creates a row for each permission it finds:
uv run manage.py makemigrations
uv run manage.py migrate
Employees should have it:
- As
admin, open Groups, then Employees. - Add Animals | animal | Can change an animal's status, and change adopted animals, and click Save.
A permission does nothing until code checks it. The admin checks the built-in ones, and our code checks ours.
Checking a Permission
The admin's methods are called with the request, and request.user is the logged-in user.
request.user.has_perm("animals.manage_animal") checks our permission, named by the app's label and the codename. It returns True for a superuser, and for a user who has the permission directly or through a group.
A Read-Only Status
Most admin options have a get_ method that's called with the request, such as get_readonly_fields(), so a setting can depend on who's asking. Add one to AnimalAdmin:
@admin.register(Animal)
class AnimalAdmin(admin.ModelAdmin):
list_display = ["name", "species", "breed", "status", "intake_date"]
list_filter = ["species", "status"]
search_fields = ["name", "breed"]
inlines = [AdoptionApplicationInline]
def get_readonly_fields(self, request, obj=None):
fields = list(super().get_readonly_fields(request, obj))
if not request.user.has_perm("animals.manage_animal"):
fields.append("status")
return fields
super() returns readonly_fields as the class sets them. Without the permission, we add status, and the admin shows it as text. A read-only field isn't in the form, so an animal a volunteer adds gets the model's default status, Available.
An adopted animal should be closed to volunteers too. That rule goes in has_change_permission(), and we'll write it in the exercise.
Volunteers and Employees in the Admin
- Log out, and log in as
volunteer/rescue-volunteer. The admin shows only Animals. - Open Pepper. The page has no Delete button, and no table of applications, since the group can't view them. The status is text.
- Go back to the list of animals, and click Add animal. The status is text, Available, and a new animal is saved with it.
- Log out, and log in as
employee/rescue-employee. The status is a menu again, and Pepper's page has the table of applications.