Django rules with abstract base class throw error after addition

280 Views Asked by At

Adding django-rules to my Django project I am encountering the following issue: I want to add default permissions to my abstract base class and overwrite them, if needed. Below an example of a base class and the inheriting class:

class BaseModel(RulesModelBaseMixin):
    company_id = models.ForeignKey('company.Company', on_delete=models.CASCADE)
    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
        }

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

After adding this abstract base class, there is this seemingly unrelated error message:

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/[email protected]/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'?

It seems to have something to do with the custom pagination later on, but I don't think that this is the cause of the problem, as this worked before. The base class case is even mentioned in the docs, but it doesn't work.

1

There are 1 best solutions below

5
aaron On BEST ANSWER
  1. BaseModel should not subclass RulesModelBaseMixin, which is used as a metaclass. BaseModel doesn't need metaclass=RulesModelBaseMixin though, since Ticket has metaclass=RulesModelBase, and RulesModelBase subclasses RulesModelBaseMixin.
  2. BaseModel should subclass Model since it defines model fields meant to be inherited. Model also provides a lot of functionality that you might expect.
  3. Ticket needs to subclass a class that has metaclass=ModelBase (e.g. Model) to get _meta set (the error shown in the question). Since Ticket subclasses BaseModel, which we will change to subclass Model, it does not need to explicitly subclass Model again.
  4. django-rules does not support defining rules_permissions in abstract model Meta. You can implement preprocess_rules_permissions class method to dynamically define that on subclasses. You also need to put BaseModel before RulesModelMixin in Ticket to override that class method.
# 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):
    ...

You can simplify all that by making BaseModel subclass 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):
    ...