بنقرة واحدة
permissions
Django Spire permission patterns, cascading structure, and conventions
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Django Spire permission patterns, cascading structure, and conventions
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Best practices for writing Django tests (updated template)
GlueFetchHelper patterns for making AJAX calls in templates
Advanced seeding patterns for linking specific foreign keys
Apply DjangoSpire app CSS variables in templates instead of inline styles
Django URL configuration patterns, namespaces, and nested structure conventions
Best practices for AI bots, prompts, and intel in scope_of_work
| name | permissions |
| description | Django Spire permission patterns, cascading structure, and conventions |
Django Spire uses standard Django permission decorators on function-based views (FBVs) with a cascading permission structure where child apps inherit parent app permissions. Understanding app labels and the cascading system is critical for correct permission implementation.
Permissions are formatted as {app_label}.{action}_{model_name}. The app label comes from the label field in AppConfig, not the directory name. Always verify the actual app label in apps.py or admin.py before writing permissions.
# app/inventory/apps.py
class InventoryConfig(AppConfig):
label = 'inventory' # Permission prefix = 'inventory.*'
name = 'app.inventory'
Child apps inherit parent app permissions through MODEL_PERMISSIONS in their app config. This means sub-apps do not need their own permissions—a user with inventory.change_inventory can manage inventory records, events, and batches without explicit inventory_record.change_inventory_record permissions.
# app/inventory/record/apps.py
class InventoryRecordConfig(AppConfig):
label = 'inventory_record'
name = 'app.inventory.record'
MODEL_PERMISSIONS = (
{
'name': 'inventory_record',
'model_class_path': 'app.inventory.models.Inventory',
'is_proxy_model': False,
},
)
With this configuration, inventory.record views check inventory.* permissions instead of inventory_record.* permissions.
| Action | Usage |
|---|---|
add_{model} | Create operations |
change_{model} | Update operations and edit buttons |
delete_{model} | Delete operations |
view_{model} | Read operations and list/detail views |
Special permissions follow the can_{action} pattern and represent non-standard operations like workflow actions, locking, or status advancement. Define them in the model's Meta.permissions tuple.
# app/sales/purchase_order/models.py
class SalesPurchaseOrder(models.Model):
name = models.CharField(max_length=255)
class Meta:
verbose_name = 'Sales Purchase Order'
permissions = [
('can_lock_sales_orders', 'Can Lock Sales Orders'),
('can_advance_sales_order_status', 'Can Advance Sales Order Status'),
]
In views, use request.user.has_perm() for programmatic checks:
if not request.user.has_perm('sales_purchase_order.can_advance_sales_order_status'):
return JsonResponse({'type': 'error', 'message': 'Permission denied'})
In templates, use {% if perms.app.can_action %}:
{% if perms.sales_purchase_order.can_lock_sales_orders %}
<button hx-post="{% url 'sales:purchase_order:json:lock' pk=order.pk %}">
{% if order.is_locked %}Unlock{% else %}Lock{% endif %}
</button>
{% endif %}
Apply @permission_required to FBVs. Use the parent app's permission for sub-apps.
# Standard app
@permission_required('inventory.add_inventory')
def create_view(request):
...
# Sub-app using parent permissions
@permission_required('inventory.change_inventory')
def update_view(request, pk):
...
Use {% if perms.app_label.action_model %} to conditionally render UI elements.
Define in Model.Meta.permissions and use with request.user.has_perm() or {% if perms.app.can_custom_permission %}.
The inventory.record module demonstrates cascading permissions. All CRUD operations check inventory.* permissions despite the app label being inventory_record.
# app/inventory/record/views/form_views.py
@permission_required('inventory.add_inventory')
def create_view(request: WSGIRequest) -> TemplateResponse:
form = self.form_class(request.POST or None)
if form.is_valid():
form.save()
return redirect(self.success_url)
return render(request, self.template_name, {'form': form})
@permission_required('inventory.change_inventory')
def update_view(request: WSGIRequest, pk: int) -> TemplateResponse:
record = get_object_or_404(models.InventoryRecord, pk=pk)
form = self.form_class(request.POST or None, instance=record)
if form.is_valid():
form.save()
return redirect(self.success_url)
return render(request, self.template_name, {'form': form})
@permission_required('inventory.delete_inventory')
def delete_view(request: WSGIRequest, pk: int) -> TemplateResponse:
record = get_object_or_404(models.InventoryRecord, pk=pk)
if request.method == 'POST':
record.delete()
return redirect(self.success_url)
return render(request, self.template_name, {'record': record})
<!-- templates/inventory/record/list_page.html -->
{% if perms.inventory.add_inventory %}
<a href="{% url 'inventory:record:form:create' %}" class="btn btn-primary">
Add Record
</a>
{% endif %}
{% if perms.inventory.view_inventory %}
<ul>
{% for record in records %}
<li>
{{ record.name }}
{% if perms.inventory.change_inventory %}
<a href="{% url 'inventory:record:form:update' pk=record.pk %}">Edit</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
# app/company/apps.py
class CompanyConfig(AppConfig):
label = 'company'
name = 'app.company'
MODEL_PERMISSIONS = (
{'name': 'company', 'model_class_path': 'app.company.models.Company', ...},
)
# app/company/location/apps.py
class CompanyLocationConfig(AppConfig):
label = 'company_location'
name = 'app.company.location'
MODEL_PERMISSIONS = (
{'name': 'location', 'model_class_path': 'app.company.models.Company', ...},
)
Both company and company.location apps use company.* permissions. A user needs company.view_company to view both companies and company locations.
Do not create separate permissions for every nested app. Leverage cascading—grant parent app permissions to access all sub-app functionality.
Do not assume the app label matches the directory name. Always verify in apps.py or admin.py.
Do not skip permission checks in templates when views are decorated. Template-level checks provide UI filtering that decorator-level checks cannot.
When reviewing permission code, verify the following:
AppConfig.label, not the directory or module nameMODEL_PERMISSIONS@permission_required decoratorsadd, change, delete, view)can_* permissions are defined in Meta.permissions with descriptive namesMeta.permissions and usage in views/templatesor for multiple permission checks, confirm at least one valid app label is usedMeta.permissions