6
votes

Champs de modèle Django avec des noms dynamiques

Je voudrais ajouter aux modèles existants de nouveaux CharFields via un mixin commun ou un modèle abstrait mais les noms de ces champs dépendent de la configuration. donc un modèle aura un champ someprefix1_title et un autre modèle - someprefix2_title.

Est-il possible de faire fonctionner cette approche:

class AbstractModel(models.Model):
    self.fields_prefix + '_title' = models.CharField(max_length=255, blank=True, default='')

    class Meta:
        abstract = True

class ModelOne(AbstractModel):
    fields_prefix = 'someprefix1'
    id = models.AutoField(primary_key=True)

class ModelTwo(AbstractModel):
    fields_prefix = 'someprefix2'
    id = models.AutoField(primary_key=True)

afin que ModelOne puisse avoir les champs id et someprefix1_title.

upd: qu'en est-il du monkey-patching avec add_to_class () est-ce que ça fonctionnera ou c'est un anti-modèle et ne devrait pas être utilisé?


4 commentaires

Peut-être pouvez-vous clarifier ce que vous voulez réaliser? Vous ne faites que générer des champs de modèle normaux avec un nom dynamique ou vraiment générer de nouveaux champs au moment de l'exécution?


Je voudrais ajouter aux modèles existants de nouveaux CharFields via 1 mixin commun ou modèle abstrait, mais les noms de ces champs dépendent de la configuration. donc un modèle aura un champ someprefix1_title et un autre modèle - someprefix2_title. Merci, j'ai mis à jour la question.


Si la configuration est vraiment dynamique, c'est-à-dire runtime ou settings.py , cela ne fonctionnera pas avec les migrations.


Une approche de type EAV ne fonctionnerait-elle pas mieux ici?


4 Réponses :


6
votes

Les modèles Django peuvent être créés avec des noms de champs dynamiques. Voici un modèle Django simple:

from south.db import db
model_class = generate_my_model_class()
fields = [(f.name, f) for f in model_class._meta.local_fields]
table_name = model_class._meta.db_table
db.create_table(table_name, fields)
# some fields (eg GeoDjango) require additional SQL to be executed
db.execute_deferred_sql()

Et voici la classe équivalente construite en utilisant type():

attrs = {
    'name': models.CharField(max_length=32),
    '__module__': 'myapp.models'
}
Animal = type("Animal", (models.Model,), attrs)

Tout modèle Django qui peut être défini de manière normale peut être créé en utilisant type().

Pour exécuter des migrations: South dispose d'un ensemble de fonctions fiables pour gérer le schéma et migrations de bases de données pour les projets Django. Quand utilisé en développement, South peut suggérer des migrations mais n'essaye pas de les appliquer automatiquement

class Animal(models.Model):
    name = models.CharField(max_length=32)


5 commentaires

Qu'en est-il des migrations?


@ElmoVanKielmo c'est faux. La fonction type résout automatiquement la métaclasse à partir des bases et déclenche une TypeError en cas d'échec. pour créer automatiquement des relations intermédiaires ManyToManyField lorsqu'aucun à explicite n'est spécifié .


@ElmoVanKielmo désolé si mon premier commentaire est sorti comme sournois mais vous vous trompez encore. Je ne sais pas pourquoi vous avez évoqué Model.Meta qui est effectivement sans rapport, mais vous pouvez tester localement que type résoudra efficacement metaclass à partir de < code> bases avec le code suivant. assert isinstance (type ('Animal', (models.Model,), attrs), models.base.ModelBase) en supposant que vous passez le bon attrs (par exemple __module__ , ...).


En résumé, les classes sont des instances de type et l'appel du type avec trois arguments résoudra la métaclasse à partir des bases . La forme nom de classe (bases): attrs` est juste un sucre syntaxique sur type (nom, bases, attrs) et l'argument optionnel metaclass = MetaClass peut être répliqué en l'appelant directement. par exemple. MetaClass (nom, bases, attrs) .


@SimonCharette désolé, ce n'était pas ma meilleure journée. Vous avez bien sûr raison.



4
votes

Le moyen le plus propre serait probablement d'utiliser add_to_class():

ModelOne.add_to_class(
    '%s_title' % field_prefix, 
    models.CharField(max_length=255, blank=True, default='')
)

Cela peut quand même être considéré comme un "monkey-patching" avec tous ses inconvénients comme créer l'application plus difficile à maintenir, avoir du code plus difficile à comprendre etc ... Mais si votre cas d'utilisation rend vraiment nécessaire de faire quelque chose comme ça, ce serait probablement la meilleure solution comme add_to_class () est certaines fonctionnalités fournies par Django lui-même et sont stables depuis un certain temps.


0 commentaires

0
votes

Techniquement, c'est une mauvaise pratique de créer des champs de modèle de manière dynamique car cela enfreint la règle standard de conserver l'historique du schéma de base de données en utilisant le processus de migration de django.

Tout ce dont vous avez besoin est de stocker certains champs sous un modèle de django qui a la flexibilité de stocker des champs dynamiques. Je vous suggère donc d'utiliser le HStoreField .

En utilisant HStoreField , vous pouvez stocker des données dans un format de paire clé-valeur ou json. Cela résoudra donc le problème de stockage des champs dynamiques.

Voici un exemple donné par la documentation Django.

>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
>>> Dog.objects.create(name='Meg', data={'breed': 'collie'})

>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>

Voici comment vous HStoreFields.

from django.contrib.postgres.fields import HStoreField
from django.db import models

class Dog(models.Model):
    name = models.CharField(max_length=200)
    data = HStoreField()

    def __str__(self):
        return self.name

J'espère que cela résoudra votre problème. Faites-moi savoir si vous avez des questions.

Merci


0 commentaires

3
votes

Essayez d'utiliser un modèle d'usine pour configurer vos différentes versions de AbstractModel .

Avec cette approche, vous pouvez contrôler plus strictement la façon dont AbstractModel est modifié de manière de la fonction d'usine dynamic_fieldname_model_factory.

Nous ne modifions pas non plus ModelOne ou ModelTwo après leurs définitions - autres solutions ont souligné que cela permet d'éviter les problèmes de maintenabilité.

models.py:

# Generated by Django 2.1.7 on 2019-03-07 19:53

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='ModelOne',
            fields=[
                ('someprefix1_title', models.CharField(blank=True, default='', max_length=255)),
                ('id', models.AutoField(primary_key=True, serialize=False)),
            ],
            options={
                'abstract': False,
            },
        ),
        migrations.CreateModel(
            name='ModelTwo',
            fields=[
                ('someprefix2_title', models.CharField(blank=True, default='', max_length=255)),
                ('id', models.AutoField(primary_key=True, serialize=False)),
            ],
            options={
                'abstract': False,
            },
        ),
    ]

Voici la migration générée par ce code:

XXX


0 commentaires