L'application mayan/apps/databases/ fournit l'infrastructure commune liée à la base de données de Mayan EDMS : registre d'attributs de modèles, optimisation des querysets, sérialisation de querysets pour Celery, mixins de modèles et utilitaires de tests.
ModelAttribute)ModelAttribute est un système de registre qui permet d'associer à un modèle Django une liste d'attributs introspectables (champs, propriétés, champs liés). Utilisé notamment par le moteur de templates Mayan et les colonnes de navigation.
| Classe | class_name |
Description |
|---|---|---|
ModelField |
'field' |
Champ direct du modèle (avec résolution du label depuis verbose_name) |
ModelFieldRelated |
'related_field' |
Champ d'un modèle lié (chemin traversant des FK) |
ModelProperty |
'property' |
Propriété ou méthode Python du modèle |
ModelReverseField |
'reverse_field' |
Relation inverse (reverse FK / M2M) |
# Dans apps.py d'une app, après le ready() de base
from mayan.apps.databases.classes import ModelField, ModelProperty
ModelField(
model=Document,
name='label',
)
ModelField(
model=Document,
name='document_type__label', # chemin FK avec __
description='Type label'
)
ModelProperty(
model=Document,
name='file_latest',
label=_('Latest file'),
description=_('The most recent file uploaded for this document')
)
ModelField résout automatiquement le label depuis verbose_name en parcourant le chemin FK. Il résout aussi le description (help_text) via help_text_for_field_recursive().
Pour un chemin FK document_type__label, le label affiché sera Type de document > Label.
ModelField.get_for(model=Document) # liste des ModelField de Document
ModelAttribute.get_all_choices_for(model=Document) # toutes classes confondues, formaté pour <select>
ModelWrapperVue résumée d'un modèle (app, label, nom complet). ModelWrapper.all() retourne tous les modèles ayant des ModelProperty enregistrées, triés par nom complet.
ModelQueryFields — optimisation des querysetsRegistre centralisé pour déclarer les select_related et prefetch_related d'un modèle. Évite de dupliquer ces optimisations dans chaque vue.
# Dans apps.py, après le démarrage des apps
from mayan.apps.databases.classes import ModelQueryFields
ModelQueryFields.get(model=Document).add_select_related_field('document_type')
ModelQueryFields.get(model=Document).add_prefetch_related_field('files')
# Dans une vue ou un manager
queryset = ModelQueryFields.get(model=Document).get_queryset()
# équivaut à : Document.objects.select_related('document_type').prefetch_related('files')
get_queryset() accepte un manager_name optionnel pour utiliser un manager spécifique.
Si les champs ne sont pas déclarés, ModelQueryFields les crée automatiquement à la demande via get_or_create interne.
QuerysetParametersSerializer — sérialisation pour CeleryPermet de sérialiser un queryset (modèle + méthode + arguments) sous forme d'un dictionnaire JSON-compatible pour le passer à une tâche Celery, puis de le reconstruire dans la tâche.
# Avant de passer à Celery
decomposed = QuerysetParametersSerializer.decompose(
_model=Action,
_method_name='filter',
target=document # instance de modèle → sérialisée via ContentType
)
task_event_queryset_clear.apply_async(kwargs={'decomposed_queryset': decomposed})
# Dans la tâche Celery
queryset = QuerysetParametersSerializer.rebuild(decomposed_queryset=decomposed)
# → Action.objects.filter(target=document)
Les instances de modèle sont sérialisées comme {content_type_id, object_id} et restaurées via ContentType.get_object_for_this_type(). Les valeurs scalaires sont passées telles quelles.
ExtraDataModelMixinPermet de passer des données arbitraires lors de l'instanciation d'un modèle, qui seront définies comme attributs d'instance (utile pour passer du contexte sans champs DB) :
instance = MyModel(_instance_extra_data={'_event_actor': request.user})
# → instance._event_actor == request.user
ValueChangeModelMixinMémorise les valeurs précédentes des champs lors du chargement depuis la DB. Permet de détecter si un champ a changé avant un save() :
class MyModel(ValueChangeModelMixin, models.Model):
status = CharField(...)
instance = MyModel.objects.get(pk=1)
instance.status = 'new_value'
instance._has_field_changed('status') # True si valeur modifiée
instance._get_field_previous_value('status') # ancienne valeur
ModelMixinConditionField + MixinConditionTemplateAjoute un champ condition (template Django) à un modèle. Évalue la condition au moment de l'exécution pour déterminer si l'objet doit s'exécuter (ex. : actions de workflow conditionnelles).
class WorkflowStateAction(ModelMixinConditionField, ...):
# hérite de : condition = TextField(blank=True)
pass
action.evaluate_condition(context={'document': document})
# → '' ou None → False (ne pas exécuter)
# → n'importe quelle autre valeur → True (exécuter)
La condition est rendue via le moteur de templates Mayan (mayan.apps.templating). has_condition() retourne True si le champ condition n'est pas vide.
ManagerMinixCreateBulkManager avec un générateur coroutine pour l'insertion en lot par batches :
class Notification(models.Model):
objects = ManagerMinixCreateBulk()
generator = Notification.objects.create_bulk()
next(generator) # initialise le générateur
generator.send({'user': user1, 'action': action1})
generator.send({'user': user2, 'action': action2})
generator.close() # flush le dernier batch
Insère par batches de create_bulk_batch_size (défaut : 100) via bulk_create(). Le GeneratorExit déclenche le flush du dernier batch incomplet.
utils.py)| Fonction | Description |
|---|---|
check_for_sqlite() |
Retourne True si SQLite est utilisé en production (non-DEBUG) |
check_queryset(view, queryset) |
Valide qu'un queryset est utilisable (a .query ou est itérable) |
instance_list_to_queryset(list) |
Convertit une liste d'instances en queryset filtré par PKs |
get_model_attribute_recursive(attribute, model) |
Résout un chemin __ jusqu'au dernier champ/modèle (mis en cache) |
label_for_field_recursive(model, name) |
Retourne le label d'un champ via son chemin __ (mis en cache) |
help_text_for_field_recursive(model, name) |
Retourne le help_text d'un champ via son chemin __ (mis en cache) |
get_model_ordering_fields(model) |
Retourne tous les champs d'ordonnancement valides d'un modèle et ses relations |
patch_MigrationMonkey-patch de Migration.apply et Migration.unapply pour afficher le delta de temps de chaque migration en mode DEBUG :
Applying documents.0001_initial... (Time delta: 0:00:00.123456)
Activé automatiquement au démarrage de l'app.
Si l'application détecte SQLite en production (DEBUG=False), elle émet un DatabaseWarning :
Your database backend is set to use SQLite. SQLite should only be used
for development and testing, not for production.
ConnectionsCheckTestCaseMixinVérifie qu'aucune connexion DB n'est ouverte à la fin du test (détecte les fuites de connexions).
RandomPrimaryKeyModelMonkeyPatchMixinMonkey-patche models.Model.save() pendant les tests pour assigner des PKs aléatoires (entre 1 et 32767) au lieu de séquentiels. Détecte les bugs liés à l'hypothèse que les PKs sont consécutifs ou croissants.
Gère les collisions de PK via retry avec transaction.atomic(). Envoie manuellement pre_save et post_save pour contourner la désactivation des signaux lors du force_insert.
TestModelTestCaseMixinPermet de créer des modèles Django temporaires dans les tests (table créée et supprimée à chaque test) :
class MyTest(GenericViewTestCase):
def setUp(self):
super().setUp()
self._create_test_model(
fields={
'name': models.CharField(max_length=32)
}
)
self._create_test_object()
# self._TestModel → le modèle créé
# self._test_object → l'instance créée
Gère la création/suppression de la table via SchemaEditor, enregistre le ContentType Django, et nettoie le cache des apps et ContentTypes dans tearDown().
auto_create_test_object = True sur la classe de test pour création automatique dans setUp().
ModelTestCaseMixinFournit _model_instance_to_dictionary(instance) → retourne les valeurs de tous les champs d'une instance sous forme de dict (utile pour les assertions).
ContentTypeTestCaseMixinInjecte _test_object_content_type et _test_object_view_kwargs (avec app_label, model_name, object_id) pour les tests de vues génériques basées sur des ContentType.