问题
I am new to django, I want to modify my models (Which is already in my sqlite3 database) without bugging my entire project. Last time I modified my models it took me hours to fix it cause I bugged the whole project. So I dont want to make the same error, could you guys help me witht the commands please? Thanks for helping
models.py (at the moment)
from django.db import models
THE_GENDER = [
("Monsieur", "Monsieur"),
("Madame", "Madame")
]
class Post(models.Model):
name = models.CharField(max_length=100)
email = models.CharField(max_length=100)
gender = models.CharField(max_length=8, choices=THE_GENDER)
number = models.CharField(max_length=100)
def __str__(self):
return self.name
The edit that I want to make to the models (I want to add an author user into the models. So i know whos making the form.)
from django.db import models
from django.contrib.auth.models import User
THE_GENDER = [
("Monsieur", "Monsieur"),
("Madame", "Madame")
]
class Post(models.Model):
name = models.CharField(max_length=100)
email = models.CharField(max_length=100)
gender = models.CharField(max_length=8, choices=THE_GENDER)
number = models.CharField(max_length=100)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.name
my admin.py files (not sure If ill need to leave it the same or modify it)
from django.contrib import admin
from .models import Post
admin.site.register(Post)
my forms.py files
from django import forms
from .models import Post
from crispy_forms.helper import FormHelper
class post_form(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(post_form, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
class Meta:
model = Post
fields = ["name", "email", "gender", "number"]
views.py
from django.shortcuts import render
from django.http import HttpResponse
from .forms import post_form
# Create your views here.
def home(request):
form = post_form(request.POST or None)
if form.is_valid():
form.save()
context = {
"form": form
}
return render(request, "sms/home.html", context)
回答1:
To elaborate on my comment above...
Adding a new non-nullable ForeignKey in Django is generally a three-step process.
- First, you add the new ForeignKey to your model definition with
null=True
, and runmakemigrations
. This will create a migration that will add the field, nothing special about it. Executing this migration will add a column with all rows havingNULL
as the value. - Second, you create a new empty migration for the same app (
makemigrations --empty
), then edit that migration to contain a data migration step. This is where you'll need to, according to your business logic, choose some value for the new foreign key. - Third, you modify the ForeignKey in your model definition to set
null=False
and create a third migration withmakemigrations
. Django will ask whether you've dealt withnull
s somehow – you need to say that "yep, I swear I have" (since you did, above in step 2).
In practice, for a simplified version of OP's question where we'll want to add an User foreign key:
Original state
class Post(models.Model):
name = models.CharField(max_length=100)
1a. Add nullable field.
class Post(models.Model):
name = models.CharField(max_length=100)
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE))
1b. Run makemigrations.
$ python manage.py makemigrations
Migrations for 'something':
something/migrations/0002_post_author.py
- Add field author to post
2a. Create a new empty migration.
$ python manage.py makemigrations something --empty -n assign_author
Migrations for 'something':
something/migrations/0003_assign_author.py
2b. Edit the migration file.
More information on data migrations can be found, as always, in the manual.
from django.db import migrations
def assign_author(apps, schema_editor):
User = apps.get_model('auth', 'User') # or whatever is your User model
Post = apps.get_model('something', 'Post') # or wherever your Post model is
user = User.objects.filter(is_superuser=True).first() # Choose some user...
assert user # ... and ensure it exists...
Post.objects.all().update(author=user) # and bulk update all posts.
class Migration(migrations.Migration):
dependencies = [...]
operations = [
migrations.RunPython(assign_author, migrations.RunPython.noop),
]
3a. Make the field non-nullable.
class Post(models.Model):
name = models.CharField(max_length=100)
author = models.ForeignKey(User, null=False, on_delete=models.CASCADE))
3b. Run Makemigrations.
Answer truthfully to the question – you've just added a RunPython operation.
$ python manage.py makemigrations something -n post_author_non_null
You are trying to change the nullable field 'author' on something. to non-nullable 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 with a null value for this column)
2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL values in a previous data migration)
3) Quit, and let me add a default in models.py
Select an option: 2
Migrations for 'something':
something/migrations/0004_post_author_non_null.py
- Alter field author on post
All done!
Running migrate
will now run these three migrations and your model will have author
without data loss.
回答2:
First you need to edit your models.py
# models.py
class Post(models.Model):
name = models.CharField(max_length=100)
email = models.CharField(max_length=100)
gender = models.CharField(max_length=8, choices=THE_GENDER)
number = models.CharField(max_length=100)
author = models.ForeignKey(User, on_delete=models.CASCADE) # add new field
def __str__(self):
return self.name
Since you added new field author
, you have to run makemigrations command and then the migrate command.
During migrations, you may be asked to provide a default value for author
field for existing Post
instances. In that time, you can "interactively" provide values via Django shell. Any valid PK
of User
instances will be a valid input during the time. FYI: This is a onetime process
After that, change your home(...)
view as,
from django.contrib.auth.decorators import login_required
@login_required
def home(request):
form = post_form(request.POST or None)
if form.is_valid():
instance = form.save(commit=False)
instance.auther = request.user
instance.save()
context = {
"form": form
}
return render(request, "sms/home.html", context)
Note: You should use @login_required(...) decorator to get the authenticated user in request.user
attribute.
回答3:
Your migration is tricky, because you want every Post related to a user. To workaround this you can do the following:
Add your new field with null=True
models.ForeignKey(User, on_delete=models.CASCADE, null=True)
then manage.py makemigrations
then you have to decide what to do. Either (1) add authors to your existing posts by hand, or (2) delete them all
manage.py shell
from x.models import Post
from django.contrib.auth.models import User
# 1
user = User.objects.get(id=1) # get your user
Post.objects.all().update(author=user)
# or 2
Post.objects.all().delete()
then you can finally set your authors like you want them:
models.ForeignKey(User, on_delete=models.CASCADE)
again manage.py makemigrations
See the comments for more information
回答4:
You can handle this issue in different ways.
If there is no data, you can easily add the new field and run your migrations. You should be fine
If you already have posts, you can allow the field to be nullable, then run your migrations before going to populate it.
A more intuitive way of doing this especially if you already have data is to assign a user to the post while running the migrations.
First create a migration with the author field allowed to be null, then
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
manage.py makemigrations manage.py migrate
after that, remove the null
attribute.
author = models.ForeignKey(User, on_delete=models.CASCADE)
manage.py makemigrations
open the migration file now generated which will be which you will modify with a custom method like below and run migrate after that.
from django.db import migrations, models
# create custom method
def add_author(apps, schema_editor):
User = apps.get_model('auth', 'User')
Post = apps.get_model('something', 'Post')
posts = Post.objects.all()
author = User.object.get(id=1) # whichever user you want to use as the author
for post in posts:
post.author = author
post.save()
class Migration(migrations.Migration):
dependencies = [
('posts', '00000000'),
]
operations = [
migrations.RunPython(add_author), # this will run the method above
migrations.AlterField(
model_name='post',
name='author',
field=models.ForeignKeyField...,
),
]
check this link for a more detailed explanation of how this works
来源:https://stackoverflow.com/questions/60191615/how-to-modify-a-models-whos-already-migrated-in-database