|
3 | 3 | import logging |
4 | 4 |
|
5 | 5 | from django.apps import apps |
| 6 | +from django.core.checks import Error |
6 | 7 | from django.core.exceptions import ValidationError |
7 | 8 | from django.db import models |
| 9 | +from django.db.models import Manager |
8 | 10 | from django.db.models import Q |
9 | 11 | from django.db.models.base import ModelBase |
10 | 12 | from django.db.models.constraints import CheckConstraint |
|
27 | 29 | from django_tenant_options.exceptions import IncorrectSubclassError |
28 | 30 | from django_tenant_options.exceptions import InvalidDefaultOptionError |
29 | 31 |
|
| 32 | +from .checks import check_manager_compliance |
| 33 | + |
30 | 34 |
|
31 | 35 | logger = logging.getLogger("django_tenant_options") |
32 | 36 |
|
33 | 37 |
|
| 38 | +def get_all_managers(model): |
| 39 | + """Get all managers from a model using multiple approaches.""" |
| 40 | + managers = set() |
| 41 | + |
| 42 | + # Check default manager |
| 43 | + if hasattr(model._meta, "default_manager"): |
| 44 | + managers.add(model._meta.default_manager) |
| 45 | + |
| 46 | + # Check _managers list |
| 47 | + if hasattr(model._meta, "_managers"): |
| 48 | + managers.update(model._meta._managers) |
| 49 | + |
| 50 | + # Check direct manager attributes |
| 51 | + for attr_name in dir(model): |
| 52 | + attr = getattr(model, attr_name) |
| 53 | + if isinstance(attr, Manager) and attr.__class__ != Manager: |
| 54 | + managers.add(attr) |
| 55 | + |
| 56 | + return managers |
| 57 | + |
| 58 | + |
34 | 59 | def validate_model_is_concrete(model): |
35 | 60 | """Raises an error if the provided model class is abstract.""" |
36 | 61 | if model._meta.abstract: # pylint: disable=W0212 |
@@ -471,6 +496,37 @@ def get_concrete_subclasses(cls) -> list: |
471 | 496 | result.append(model) |
472 | 497 | return result |
473 | 498 |
|
| 499 | + @classmethod |
| 500 | + def check(cls, **kwargs): |
| 501 | + """Check that the model has at least one manager that inherits from OptionManager and uses OptionQuerySet.""" |
| 502 | + errors = super().check(**kwargs) |
| 503 | + |
| 504 | + if cls._meta.abstract: |
| 505 | + return errors |
| 506 | + |
| 507 | + managers = get_all_managers(cls) |
| 508 | + has_valid_manager = False |
| 509 | + |
| 510 | + for manager in managers: |
| 511 | + results = check_manager_compliance(cls, manager, OptionManager, OptionQuerySet, ("001", "002")) |
| 512 | + errors.extend(results) |
| 513 | + |
| 514 | + # Check if this manager is fully compliant |
| 515 | + if not any(isinstance(r, Error) for r in results): |
| 516 | + has_valid_manager = True |
| 517 | + |
| 518 | + if not has_valid_manager: |
| 519 | + errors.append( |
| 520 | + Error( |
| 521 | + f"Model {cls.__name__} must have at least one manager that inherits from OptionManager " |
| 522 | + "and uses OptionQuerySet", |
| 523 | + obj=cls, |
| 524 | + id="django_tenant_options.E003", |
| 525 | + ) |
| 526 | + ) |
| 527 | + |
| 528 | + return errors |
| 529 | + |
474 | 530 | def clean(self): |
475 | 531 | """Ensure no tenant can have the same name as a MANDATORY or OPTIONAL option.""" |
476 | 532 | if self.option_type == OptionType.CUSTOM: |
@@ -648,3 +704,34 @@ def get_concrete_subclasses(cls) -> list: |
648 | 704 | if issubclass(model, cls) and model is not cls and not model._meta.abstract: # pylint: disable=W0212 |
649 | 705 | result.append(model) |
650 | 706 | return result |
| 707 | + |
| 708 | + @classmethod |
| 709 | + def check(cls, **kwargs): |
| 710 | + """Check that model has at least one manager that inherits from SelectionManager and uses SelectionQuerySet.""" |
| 711 | + errors = super().check(**kwargs) |
| 712 | + |
| 713 | + if cls._meta.abstract: |
| 714 | + return errors |
| 715 | + |
| 716 | + managers = get_all_managers(cls) |
| 717 | + has_valid_manager = False |
| 718 | + |
| 719 | + for manager in managers: |
| 720 | + results = check_manager_compliance(cls, manager, SelectionManager, SelectionQuerySet, ("004", "005")) |
| 721 | + errors.extend(results) |
| 722 | + |
| 723 | + # Check if this manager is fully compliant |
| 724 | + if not any(isinstance(r, Error) for r in results): |
| 725 | + has_valid_manager = True |
| 726 | + |
| 727 | + if not has_valid_manager: |
| 728 | + errors.append( |
| 729 | + Error( |
| 730 | + f"Model {cls.__name__} must have at least one manager that inherits from SelectionManager " |
| 731 | + "and uses SelectionQuerySet", |
| 732 | + obj=cls, |
| 733 | + id="django_tenant_options.E006", |
| 734 | + ) |
| 735 | + ) |
| 736 | + |
| 737 | + return errors |
0 commit comments