Files
2026-10-08 16:37:45 +03:30

110 lines
5.3 KiB
Python

"""Common admin ergonomics without changing application API contracts."""
from django import forms
from django.contrib import admin
from django.db import models
from django.core.exceptions import ValidationError
from utils.admin_excel import excel_response
def workspace_id(obj):
if obj is None:
return None
if hasattr(obj, 'sys_ws_id'):
return obj.sys_ws_id
for relation in ('order', 'form', 'ticket_comment'):
if hasattr(obj, relation):
return workspace_id(getattr(obj, relation))
return None
class RelatedWorkspaceForm(forms.ModelForm):
def clean(self):
cleaned = super().clean()
workspace = cleaned.get('sys_ws')
scope = workspace.pk if workspace else None
if scope is None:
for key in ('order', 'form', 'ticket_comment'):
scope = workspace_id(cleaned.get(key))
if scope is not None:
break
if scope is not None:
for field in self._meta.model._meta.fields:
if field.is_relation and field.name != 'sys_ws':
value = cleaned.get(field.name)
related_scope = workspace_id(value)
if related_scope is not None and related_scope != scope:
self.add_error(field.name, 'این رابطه متعلق به فضای کاری دیگری است.')
self.check_category_parent(cleaned)
self.check_domain_values(cleaned)
return cleaned
def check_domain_values(self, cleaned):
label = self._meta.model._meta.label_lower
if label == 'cuser.cuserrole' and cleaned.get('mobile') is not None:
from utils.validations.mobile import normalize_mobile
try:
cleaned['mobile'] = normalize_mobile(cleaned['mobile'])
except ValidationError as error:
self.add_error('mobile', error.messages)
user = cleaned.get('cuser')
if user and user.mobile != cleaned.get('mobile'):
self.add_error('mobile', 'شماره نقش باید با شماره کاربر یکسان باشد.')
if label in ('shop.cart', 'shop.orderitem'):
key = 'quantity' if label == 'shop.cart' else 'amount'
if cleaned.get(key) is not None and cleaned[key] < 1:
self.add_error(key, 'تعداد باید حداقل یک باشد.')
if label == 'shop.orderitem' and cleaned.get('price') is not None and cleaned['price'] < 0:
self.add_error('price', 'قیمت نباید منفی باشد.')
def check_category_parent(self, cleaned):
if self._meta.model._meta.label_lower != 'shop.productcategory':
return
parent = cleaned.get('parent')
visited = set()
while parent is not None:
if parent.pk == self.instance.pk or parent.pk in visited:
self.add_error('parent', 'دسته والد نباید یک چرخه ایجاد کند.')
return
visited.add(parent.pk)
parent = parent.parent
class CRMModelAdmin(admin.ModelAdmin):
form = RelatedWorkspaceForm
list_per_page = 50
show_full_result_count = False
save_on_top = True
actions = ('export_selected',)
sensitive_fields = ()
def __init__(self, model, admin_site):
super().__init__(model, admin_site)
fields = model._meta.fields
names = {field.name for field in fields}
if self.list_display == ('__str__',):
candidates = ('id', 'name', 'name_family', 'title', 'mobile', 'customer', 'lead', 'form',
'product', 'order', 'cuser_role', 'role_type', 'sys_ws', 'status', 'type',
'is_active', 'expire', 'datetime', 'created_at')
self.list_display = tuple(name for name in candidates if name in names)[:9]
if not self.search_fields:
self.search_fields = tuple(field.name for field in fields if isinstance(field, (models.CharField, models.TextField))
and field.name not in self.sensitive_fields)
if not self.list_filter:
self.list_filter = tuple(field.name for field in fields if field.name == 'sys_ws' or field.choices
or isinstance(field, models.BooleanField))
self.readonly_fields = tuple(dict.fromkeys((*self.readonly_fields,
*(name for name in ('created_at', 'updated_at', 'uuid') if name in names))))
self.raw_id_fields = tuple(field.name for field in fields if field.many_to_one)
self.list_select_related = tuple(field.name for field in fields if field.many_to_one)
if 'created_at' in names:
self.date_hierarchy = 'created_at'
if not self.ordering and not model._meta.ordering:
self.ordering = ('-pk',)
@admin.action(description='خروجی اکسل ردیف‌های انتخاب‌شده', permissions=['view'])
def export_selected(self, request, queryset):
fields = [field for field in self.model._meta.fields if field.name not in self.sensitive_fields]
rows = queryset.values_list(*(field.attname for field in fields)).iterator()
return excel_response([str(field.verbose_name) for field in fields], rows, f'{self.model._meta.model_name}.xlsx')