South ignores ManyToManyField in my model - django

My model:
class Article(models.Model):
user = models.ForeignKey(User)
categories = models.ManyToManyField(AuditGroup)
topic = models.ManyToManyField(Topic)
title = models.CharField(max_length=255)
short_desc = models.TextField(blank=True)
The migration created:
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Article'
db.create_table('certification_article', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
('title', self.gf('django.db.models.fields.CharField')(max_length=255)),
('short_desc', self.gf('django.db.models.fields.TextField')(blank=True)),
))
None of the two many-to-many relationships are being produced! What am I missing?
Note: there's a strange thing in my model (took over the project): There is a class ProgramOverview in my model.py. But all code in this class is lowercase! In fact running --auto on schemamigration produces errors about ProgramOverview. Removing it, south wants to delete this class (turns out this is a view in the DB which is needed!) --> This seems to have been put there for some "hacky-ish" reason...So I produced the migration with:
./manage.py schemamigration certification --add-model Article
EDIT: This is the real problem. Somehow my editor messed up the ProgramOverview code. After restoring the code, I was able to run ./manage.py schemamigration certification --auto which produced all the needed tables!
END EDIT
I need the many-to-many though.

You wouldn't see an M2M in that declaration. What you see is correct.
An M2M "field" is an abstraction for a new table. There is no database level field in the 'Article' model.
Scan down the page to see the relevant M2M table creation code.

Related

How to change ForeignKey to Many-to-Many without any conflicts [duplicate]

I have a Django application in which I want to change a field from a ForeignKey to a ManyToManyField. I want to preserve my old data. What is the simplest/best process to follow for this? If it matters, I use sqlite3 as my database back-end.
If my summary of the problem isn't clear, here is an example. Say I have two models:
class Author(models.Model):
author = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
Say I have a lot of data in my database. Now, I want to change the Book model as follows:
class Book(models.Model):
author = models.ManyToManyField(Author)
title = models.CharField(max_length=100)
I don't want to "lose" all my prior data.
What is the best/simplest way to accomplish this?
Ken
I realize this question is old and at the time the best option for Data Migrations was using South. Now Django has its own migrate command, and the process is slightly different.
I've added these models to an app called books -- adjust accordingly if that's not your case.
First, add the field to Book and a related_name to at least one, or both of them (or they'll clash):
class Book(models.Model):
author = models.ForeignKey(Author, related_name='book')
authors = models.ManyToManyField(Author, related_name='books')
title = models.CharField(max_length=100)
Generate the migration:
$ ./manage.py makemigrations
Migrations for 'books':
0002_auto_20151222_1457.py:
- Add field authors to book
- Alter field author on book
Now, create an empty migration to hold the migration of the data itself:
./manage.py makemigrations books --empty
Migrations for 'books':
0003_auto_20151222_1459.py:
And add the following content to it. To understand exactly how this works, check the documentation on Data Migrations. Be careful not to overwrite the migration dependency.
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
def make_many_authors(apps, schema_editor):
"""
Adds the Author object in Book.author to the
many-to-many relationship in Book.authors
"""
Book = apps.get_model('books', 'Book')
for book in Book.objects.all():
book.authors.add(book.author)
class Migration(migrations.Migration):
dependencies = [
('books', '0002_auto_20151222_1457'),
]
operations = [
migrations.RunPython(make_many_authors),
]
Now remove the author field from the Model -- it should look like this:
class Book(models.Model):
authors = models.ManyToManyField(Author, related_name='books')
title = models.CharField(max_length=100)
Create a new migration for that, and run them all:
$ ./manage.py makemigrations
Migrations for 'books':
0004_remove_book_author.py:
- Remove field author from book
$ ./manage.py migrate
Operations to perform:
Synchronize unmigrated apps: messages, staticfiles
Apply all migrations: admin, auth, sessions, books, contenttypes
Synchronizing apps without migrations:
Creating tables...
Running deferred SQL...
Installing custom SQL...
Running migrations:
Rendering model states... DONE
Applying books.0002_auto_20151222_1457... OK
Applying books.0003_auto_20151222_1459... OK
Applying books.0004_remove_book_author... OK
And that's it. The authors previously available at book.author now should be in the queryset you get from book.authors.all().
Probably the best and easiest thing you should do would be:
Create the Many to many field with a different name say
authors = models.ManyToManyField(Author)
write a small function to convert foreignkey values to M2M values:
def convert():
books = Book.objects.all()
for book in books:
if book.author:
li = [book.author.id]
book.authors.append(li)
book.save()
Once it is run, you can delete the author field from the table and run migration again.

Django default entries on model creation

I'm sure this has been asked before, but I cannot find the answer. In django, if I have this model
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
How would one populate this model with a default Person entry when the first migrating the field?
I'm not talking about default values for the fields, but for default entries in the database.
thanks
You can make a data migration, that follows on the migration where you create the Person object. You can first let Django write the "skeleton" of the migration, this can be done with:
python manage.py makemigrations --empty appname
Next Django will make a file. In that file you can add RunPython item to the operations list. This then obtain the historical model (the model at that moment of the migration), where you then create a Person object in the database. For example with:
from django.db import migrations
class Migration(migrations.Migration):
def create_person(apps, schema_editor):
Person = apps.get_model('appname', 'Person')
Person.objects.create(first_name='will', last_name='mendil')
dependencies = [
('appname', 'migrationname'),
]
operations = [
migrations.RunPython(create_person)
]

django_comments adding new field in the model

I am trying to add a new field in django_comment model. According to the documentation, most custom comment models will subclass the CommentAbstractModel model:
from django.db import models
from django_comments.models import CommentAbstractModel
class CommentWithTitle(CommentAbstractModel):
title = models.CharField(max_length=300)
If I generate a migration, then it adds all the fields into migrations (all fields from comment model plus title field).
And after running migrations, CommentWithTitle table and django_comments table are created. But django_comments would be useless (not in use).
Another approach is to generate the table this way:
from django_comments.models import Comment
class CommentWithTitle(Comment):
title = models.CharField(max_length=300)
And it generates the migration with one field only with the reference of comment_ptr.
My Question is: which approach is better? I think the first model is good as it has all fields in one table. But that generates the django_model which is not in use at all.
I would follow the documentation.
Looking at the implementation, Comment is basically just extending CommentAbstractModel with db_table specified.
class Comment(CommentAbstractModel):
class Meta(CommentAbstractModel.Meta):
db_table = "django_comments"
I'm suspecting that if you do the second option you mentioned, the migration would throw an error because the db_table will be created twice.

Django database error on one to many relationship

I created a data model in Django which has many to one relation (N topics to 1 user) like this:
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Topic(models.Model):
content = models.CharField(max_length=2000)
pub_date = models.DateTimeField('date published')
author = models.ForeignKey(User)
When I try to load the data model in the admin page, I get this error:
Exception Value:
no such column: talk_comment.author_id
Did I miss something in the data model?
Thanks.
You forgot to actually modify/create the tables in database (manually, with South or manage.py syncdb).
You can't modify the table with syncdb . you need to use South Migrations
Its really very good and you can even revert back to previous migration in case of some problem

Adding a "through" table to django field and migrating with South?

Seems like this should be "easy" or at least documented somewhere, I just cant find it.
Lets say I have a model:
class A(models.Model):
users = models.ManyToMany('auth.User', blank=True)
Now I want to migrate to have a through table to add fields to the ManyToMany relation...
class AUsers(models.Model):
user = models.ForeignKey('auth.User')
a = models.ForeignKey('A')
new_field = models.BooleanField()
class A(models.Model):
users = models.ManyToMany('auth.User', blank=True, through='AUsers')
Then I do:
% ./manage.py schemamigration app --auto
Not totally surprising, it tells me it is going to drop the original auto-created through table and create a new one for AUsers. What's the best practice at this point? Is there a decent way to migrate to the new through table? Do I use db_table in Meta? Do I just not use the through=... right away... then do a schemamigration --auto, then a datamigration to copy the current table (somehow, not sure...) and then add the through relation and let it kill the table?
What's the trick here? Is this really that hard?
You should be able to do this pretty easily.
First of all, make sure that the manual through table that you are creating has the same table name in the database as the one Django originally created automatically.
So, first, let's consider a manual through model before your change:
class AUsers(models.Model):
user = models.ForeignKey('auth.User')
a = models.ForeignKey('A')
class Meta:
db_table = 'appname_a_user'
That should be functionally (almost) identical to the ManyToManyField you used to have. Actually, you could make an empty migration and apply it, and then use --auto for your changes (but don't).
Now, add your field like you did in your sample code above, and then run ./manage.py schemamigration appname manual_through_table --empty. That will give you an empty migration named ####_manual_through_table.py.
In the migration itself, there will be a forwards and backwards method. Each one needs to be one line each:
def forwards(self, orm):
db.add_column('appname_a_user', 'new_field', self.gf('django.db.models.fields.BooleanField')(default=False))
def backwards(self, orm):
db.delete_column('appname_a_user', 'new_field')
That should get you what you are after.
If anyone comes across this question when trying to do the same thing with the moderns migration framework, here are the steps:
Create a new model class that exactly matches the built-in through table
Use the Meta class to set the table name to match the existing table
Generate a migration, which will create the new table and set it as the through for the field.
Without running that migration, edit it to wrap it in a migrations. SeparateDatabaseAndState migration, where the auto-generated steps are in the state_operations field and the database operations are empty.
Modify your through table, as required, making sure to generate new migrations as normal.
As mentioned in a comment, the first step may be simplified using db.rename_table as described here, which gives this through model:
class AUsers(models.Model):
user = models.ForeignKey('auth.User')
a = models.ForeignKey('A')
class Meta:
unique_together = (('user', 'a'),)
Then, create a migration with --auto (this way you'll have the names of the DB tables visible), and replace the content with:
class Migration(SchemaMigration):
def forwards(self, orm):
db.rename_table('appname_a_user', 'appname_auser')
def backwards(self, orm):
db.rename_table('appname_auser','appname_a_user')
I just applied it in my project without issues.