You are trying to add a non-nullable field

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour !

Je m'initie à Django, et je commence déjà à m'arracher les cheveux. En effet, j'ai commencé par créer un modèle simple :

1
2
3
4
5
6
7
8
from django.db import models
from . import settings as stg

class Project(models.Model):
    name = models.CharField(
        max_length=stg.PROJECT_NAME_MAX_LEN,
        verbose_name='Nom'
    )

J'ai effectué les migrations :

1
2
3
$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py runserver

Tout fonctionnait, j'avais mon mondèle dans le panneau d'administration, à partir duquel j'ai créé un projet (une instance de mon modèle).

Puis j'ai mis à jour mon modèle (en plusieurs fois pour être précis) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils.timezone import now

from . import settings as stg

class Project(models.Model):
    correspondent = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(
        max_length=stg.PROJECT_NAME_MAX_LEN,
        verbose_name='Nom'
    )
    creation_date = models.DateField(default=now, verbose_name='Date de création')
    background_image = models.ImageField(verbose_name="Image d'arrière-plan")
    theoretical_duration = models.DurationField(
        verbose_name='Durée théorique'
    )
    latitude = models.FloatField(
        validators=[MinValueValidator(stg.MIN_LAT), MaxValueValidator(stg.MAX_LAT)]
    )
    longitude = models.FloatField(
        validators=[MinValueValidator(stg.MIN_LNG), MaxValueValidator(stg.MAX_LNG)]
    )
    description = models.TextField(default='')
    progression = models.PositiveSmallIntegerField(
        default=0,
        validators=[MaxValueValidator(100)]
    )
    validated = models.BooleanField(default=False, verbose_name='Validé')
    archived = models.BooleanField(default=False, verbose_name='Archivé')

    def __str__(self):
        return self.name

Sauf qu'au moment de faire ma migration, il m'insulte :

1
2
3
4
5
6
$  python manage.py makemigrations
You are trying to add a non-nullable field 'correspondent' to project without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows)
 2) Quit, and let me add a default in models.py
Select an option: ^CTraceback (most recent call last):

Je me suis dit que c'était parce que j'avais des champs vides (de mon objet créé tout à l'heure). J'ai donc nettoyé la base :

1
python manage.py flush

Mais rien n'y fait. Auriez-vous une piste ?

Merci.

+0 -0

Cette réponse a aidé l'auteur du sujet

Salut,

Alors, déjà, n'importe pas ton fichier de settings, passe par django.conf, c'est préférable.

1
from django.conf import settings

Ensuite, ton problème est très simple. Django ne fait pas de migrations sur un modèle existant (sous entendu qui peut déjà contenir des tuples) si les nouveaux champs ajoutés n'ont pas tous une valeur par défaut. En effet, Django ne peut pas savoir quelle valeur par défaut tu souhaites avoir pour tes nouveaux champs s'il y a déjà des entrées dans ta bdd sur cette table… L'idée de reconstruire la base est bonne, mais Django garde en mémoire les précedentes migrations que tu as faites, elle se trouve dans le dossier "migrations" de chacune de tes apps, la suppression de la base n'est donc pas suffisante, il faut virer aussi les migrations correspondantes. Sachant que tu peux aussi regarder la table migrations que Django a créée à l'initialisation qui contient un historique des migrations effectuées en correspondance avec les fichiers dans le dossier migrations.

Dans ton cas, il s'agit de ta clé étrangère "correspondent" (par défaut le champs ForeignKey n'accepte que des valeurs non-null, et dans ton cas, tu ne lui as fourni aucune valeur, il ne sait donc pas ce qu'il doit faire). D'ailleurs, lorsque tu veux faire référence au model User de Django, utilise plutot la variable AUTH_USER_MODEL, cela te facilitera la tache si tu dois un jour remplacer le modèle user de Django par un perso.

1
2
from django.conf import settings
settings.AUTH_USER_MODEL

Voila, n'hesite pas revenir si tu as d'autre soucis

Édité par grugru

+0 -0
Staff

Bah en gros tu lui dis "ta valeur doit absolument être non null". Mais lorsque tu fais la migration tu ne lui dis pas de donner une valeur dans la nouvelle colonne. Du coup lui il te dit que ta bdd sera sûrement instable (je rappelle qu'une migration est sensée pouvoir s'appliquer sur une bdd déjà en prod). Du coup forcément ça foire.

Donc soit il faut que tu ajoute un null=True à ton champ correspondent soit que tu lui donnes une valeur par défaut. Soit que tu utilises la commande makemigrations de manière à ce qu'elle fusionne cette migration avec la dernière (si tu es sur du pur dev).

+0 -0
Auteur du sujet

Alors, déjà, n'importe pas ton fichier de settings, passe par django.conf, c'est préférable.

Mon projet est de cette forme et il ne me semble pas judicieux de stocker les constantes propres à une application (ici polls) dans mysite/settings.py, donc j'ai créé un polls/settings.py. Y a-t-il une meilleure façon de faire ?

Soit que tu utilises la commande makemigrations de manière à ce qu'elle fusionne cette migration avec la dernière (si tu es sur du pur dev).

Je suis effectivement sur du dev pur, ma base de données sert uniquement au test. Si je supprime le dossier polls/migrations, c'est problématique ?

Merci.

+0 -0

Alors, déjà, n'importe pas ton fichier de settings, passe par django.conf, c'est préférable.

Mon projet est de cette forme et il ne me semble pas judicieux de stocker les constantes propres à une application (ici polls) dans mysite/settings.py, donc j'ai créé un polls/settings.py. Y a-t-il une meilleure façon de faire ?

Alors si tu as des options spécifiques à chaque application, là, y a plusieurs écoles… Si tu n'es pas dans une optique de séparation complète de chaque application, le plus généralement, on prefix les options de chaque avec le nom de l'app en mai et on met tout dans le dossier mysite. Après dans ce dossier tu peux choisir de séparer comme tu l'entends (c'est du python donc ca te laisse pas mal de liberté).

Si tu prévois de réparer ton application (pour publication ou autre), là, je te renvoie à la documentation officiel de Django qui explique en détail l'architecture souhaitée : https://docs.djangoproject.com/fr/1.9/ref/applications/

Mais dans tous les cas, passe toujours par django.conf.settings, c'est la convention. ;)

Soit que tu utilises la commande makemigrations de manière à ce qu'elle fusionne cette migration avec la dernière (si tu es sur du pur dev).

Je suis effectivement sur du dev pur, ma base de données sert uniquement au test. Si je supprime le dossier polls/migrations, c'est problématique ?

Tu peux toujours choisir de tout supprimer et de tout reconstruire, mais cela est un peu contre productif. Si tu détruis le dossier migrations, tu dois supprimer aussi les migrations dans ta bdd (les lignes correspondantes de la table vdd ainsi que les modifications qu'elles ont provoqués). Mais il est bien plus simple de faire tes modifications comme bon te semble et de générer à chaque fois des migrations, cela ne pose pas de problème pour Django, et lorsque tu as fini une version de ton projet, il te suffit de fusionner les migrations ensembles avec l'option merge comme l'a dit Artragis : https://docs.djangoproject.com/fr/1.9/topics/migrations/#squashing-migrations

Je te conseille d'aller lire les articles du blog de sametmax.com[NSFW] qui est en frais et contient pas mal d'article intéressant sur python et Django, et un en particulier sur ce qui se fait ou pas au niveau de l'architecture d'un projet Django.

Édité par grugru

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte