Правила Django с абстрактным базовым классом выдают ошибку после добавления

Пытаясь добавить Django-правила в свой проект Django, я столкнулся со следующей проблемой: У меня есть абстрактный базовый класс, который я использую для добавления пары общих ключей. Теперь я хочу добавить разрешения по умолчанию в абстрактный класс и при необходимости перезаписать их. Ниже приведены мой абстрактный базовый класс и пример подкласса.

class BaseModel(RulesModelBaseMixin):
    company_id = models.ForeignKey('company.Company', on_delete=models.CASCADE)

    created_at = models.DateTimeField(auto_now_add=True)
    created_by = models.ForeignKey(
        'user.User', on_delete=models.SET_NULL, null=True, blank=True)

    class Meta:
        abstract = True
        rules_permissions = {
            "can_create": can_create_in_company | is_superuser,
            "can_view": can_view_in_company | is_author | is_superuser,
            "can_edit": can_change_obj | is_author | is_superuser,
            "can_delete": can_delete_obj | is_author | is_superuser,
        }

class Ticket(RulesModelMixin, BaseModel, metaclass=RulesModelBase):
    name = models.CharField(max_length=512, null=True, blank=True)
    description = models.TextField(blank=True, null=True)

После добавления этой абстрактной базы я получаю сообщение об ошибке от Django:

Traceback (most recent call last):
      File "REPO_PATH/.venv/lib/python3.10/site-packages/django/utils/module_loading.py", line 30, in import_string
        return cached_import(module_path, class_name)
      File "REPO_PATH/.venv/lib/python3.10/site-packages/django/utils/module_loading.py", line 15, in cached_import
        import_module(module_path)
      File "/opt/homebrew/Cellar/python@3.10/3.10.4/Frameworks/Python.framework/Versions/3.10/lib/python3.10/importlib/__init__.py", line 126, in import_module
        return _bootstrap._gcd_import(name[level:], package, level)
      File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
      File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
      File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
      File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
      File "<frozen importlib._bootstrap_external>", line 883, in exec_module
      File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
      File "REPO_PATH/app/api/pagination.py", line 10, in <module>
        from ticket.models import Ticket, TicketStatus, TicketType
      File "REPO_PATH/app/ticket/models.py", line 106, in <module>
        class Ticket(RulesModelMixin, BaseModel, metaclass=RulesModelBase):
      File "REPO_PATH/.venv/lib/python3.10/site-packages/rules/contrib/models.py", line 36, in __new__
        new_class._meta.rules_permissions = perms
    AttributeError: type object 'Ticket' has no attribute '_meta'. Did you mean: 'Meta'?

Удаление нового кода подтвердило, что дело действительно в этом фрагменте. Похоже, что это как-то связано с пользовательской пагинацией, но я не думаю, что это причина проблемы, так как раньше все работало.

  1. BaseModel не должен подклассифицировать RulesModelBaseMixin, который используется как metaclass. Однако BaseModel не нуждается в metaclass=RulesModelBaseMixin, поскольку Ticket имеет metaclass=RulesModelBase, а RulesModelBase подклассы RulesModelBaseMixin.
  2. BaseModel должен быть подклассом Model, поскольку он определяет поля модели, предназначенные для наследования. Model также предоставляет много функциональных возможностей, которые вы могли бы ожидать.
  3. Ticket должен подклассифицировать класс, имеющий metaclass=ModelBase (например, Model), чтобы получить установленный _meta (ошибка, показанная в вопросе). Поскольку Ticket подклассы BaseModel, которые мы изменим на подклассы Model, ему не нужно явно подклассировать Model снова.
  4. django-rules не поддерживает определение rules_permissions в абстрактной модели Meta. Вы можете реализовать метод класса preprocess_rules_permissions для динамического определения этого в подклассах. Вам также нужно поставить BaseModel перед RulesModelMixin в Ticket, чтобы переопределить этот метод класса.
# class BaseModel(RulesModelBaseMixin):
class BaseModel(Model):
    ...

    class Meta:
        abstract = True
        # rules_permissions = {
        #     "can_create": can_create_in_company | is_superuser,
        #     "can_view": can_view_in_company | is_author | is_superuser
        # }

    @classmethod
    def preprocess_rules_permissions(cls, perms):
        perms.update({
            "can_create": can_create_in_company | is_superuser,
            "can_view": can_view_in_company | is_author | is_superuser,
        })


# class Ticket(RulesModelMixin, BaseModel, metaclass=RulesModelBase):
class Ticket(BaseModel, RulesModelMixin, metaclass=RulesModelBase):
    ...

Вы можете упростить все это, сделав BaseModel подклассом RulesModel:

class BaseModel(RulesModel):
    ...

    class Meta:
        abstract = True

    @classmethod
    def preprocess_rules_permissions(cls, perms):
        perms.update({
            "can_create": can_create_in_company | is_superuser,
            "can_view": can_view_in_company | is_author | is_superuser,
        })


class Ticket(BaseModel):
    ...
Вернуться на верх