Django model with historycal change - django

How can i show model historical changes and show them and can modify?
for example
class MM(models.Model):
chnge1=model.ForeignKey('self')
changeText = models.TextField(max_length=200)

If you are using Django to manage your models you can always look through the migration files to see what has changed. The migration files are designed to keep track of changes to your models so that Django can apply those changes to the database.
See more here: Django Migrations
A typical migration file might look like this:
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [('migrations', '0001_initial')]
operations = [
migrations.DeleteModel('Tribble'),
migrations.AddField('Author', 'rating', models.IntegerField(default=0)),
]
You can see the 'Tribble' model was delete and a 'rating' field was added to the 'Author' model.
If you have many migrations for a single app and don't want to go through each migration file to see the changes try "squashing" your migration files into (usually) a single file and you can go through all the changes from there.
Squashing Migrations: Squashing Migrations

Related

How do you migrate a custom User model to a different app in Django?

In Django, I am trying to move a custom User model from one app to another. When I follow instructions such as found here, and try to apply the migration, I get errors with ValueError: Related model 'newapp.user' cannot be resolved that originate from django.contrib.admin.models.LogEntry.user, which is defined as models.ForeignKey(settings.AUTH_USER_MODEL, ...), so that is a model from the Django admin (which I also use) that has a foreign key to the User model. How can I do this migration?
For swappable models (models that can be swapped out through a value in settings) such a move is non-trivial. The root cause of the problem here is that LogEntry has a foreign key to settings.AUTH_USER_MODEL, but the history of settings.AUTH_USER_MODEL itself is not managed or known to Django migrations. When you change AUTH_USER_MODEL to point to the new User model, that retroactively changes the migration history as Django sees it. To Django it now looks like LogEntry's foreign key has always referenced the new User model in destapp. When you run the migration that creates the table for LogEntry (e.g. when re-initializing the database or running tests), Django cannot resolve the model and fails.
See also this issue and the comments there.
To work around this problem, AUTH_USER_MODEL needs to point to a model that exists in the initial migration for the new app. There are a few approaches to get this working. I'll assume the User model is moved from sourceapp to destapp.
Move the migration definition for the new User model from the last migration (where Django makemigrations would automatically put it) to the initial migration of destapp. Leave it wrapped in a SeparateDatabaseAndState state operation, because the database table is already created by the migrations in the sourceapp. Then, you'll need to add a dependency from the initial migration of destapp to the last migration of sourceapp. The problem is that if you try to apply the migrations as they are now, it will fail because destapp's initial migration has already been applied while it's dependency (sourceapp's last migration) has not been. So you will need to apply the migrations in sourceapp before adding the above migrations in destapp. In the gap between applying sourceapp and destapp's migrations the User model won't exist so your application will be temporarily broken.
Apart from temporarily breaking the application, this has the other problem that now destapp will depend on sourceapp's migrations. If you can do that, that's fine, but if there already exists a dependency from a sourceapp migration to a destapp migration this won't work and you've now created a circular dependency. If that's the case, look at the next options.
Forget about the User migration history. Just define the User class in destapp's initial migration, without the SeparateDatabaseAndState wrapper. Make sure you have CreateModel(..., options={'db_table': 'sourceapp_user'}, ...), so the database table will be created the same as it would when User lived in sourceapp. Then edit sourceapp's migration(s) where User is defined, and remove those definitions. After that, you can create a regular migration where you remove User's db_table setting so the database table gets renamed to what it should be for destapp.
This only works if there are no or minimal migrations in the migration history of sourceapp.User. Django now thinks User always lived in destapp, but its table was named as sourceapp_user. Django cannot track any database-level changes to sourceapp_user anymore since that information was removed.
If this works for you, you can either forego any dependencies between sourceapp and destapp, if sourceapp's migrations don't need User to be there, or have sourceapp's initial migration depend on destapp's initial migration so that the table for User is created before sourceapp's migrations are run.
If both don't work in your situation, another option is to add the definition for User to sourceapp's initial migration (without SeparateDatabaseAndState wrapper), but have it use a dummy table name (options={'db_table': 'destapp_dummy_user'}). Then, in the newest migration where you actually want to move User from sourceapp to destapp, do
migrations.SeparateDatabaseAndState(database_operations=[
migrations.DeleteModel(
name='User',
),
], state_operations=[
migrations.AlterModelTable('User', 'destapp_user'),
])
This will delete the dummy table in the database, and point the User model to the new table. The new migration in sourceapp should then contain
migrations.SeparateDatabaseAndState(state_operations=[
migrations.DeleteModel(
name='User',
),
], database_operations=[
migrations.AlterModelTable('User', 'destapp_user'),
])
so it is effectively the mirror image of the operation in the last destapp migration. Now only the last migration in destapp needs to depend on the last migration in sourceapp.
This approach appears to work, but it has one big disadvantage. The deletion of destapp.User's dummy database table also deletes all foreign key constraints to that table (at least on Postgres). So LogEntry now no longer has a foreign key constraint to User. The new table for User doesn't recreate those. You will have to add the missing constraints back in again manually. Either by manually updating the database or by writing a raw sql migration.
Update content type
After applying one of the three above options there's still one loose end. Django registers every model in the django_content_type table. That table contains a line for sourceapp.User. Without intervention that line will stay there as a stale row. That isn't a big problem as Django will automatically register the new destapp.User model. But it can be cleaned up by adding the following migration to rename the existing content type registration to destapp:
from django.db import migrations
# If User previously existed in sourceapp, we want to switch the content type object. If not, this will do nothing.
def change_user_type(apps, schema_editor):
ContentType = apps.get_model("contenttypes", "ContentType")
ContentType.objects.filter(app_label="sourceapp", model="user").update(
app_label="destapp"
)
class Migration(migrations.Migration):
dependencies = [
("destapp", "00xx_previous_migration_here"),
]
operations = [
# No need to do anything on reversal
migrations.RunPython(change_user_type, reverse_code=lambda a, s: None),
]
This function only works if there is no entry in django_content_type for destapp.User yet. If there is, you'll need a smarter function:
from django.db import migrations, IntegrityError
from django.db.transaction import atomic
def change_user_type(apps, schema_editor):
ContentType = apps.get_model("contenttypes", "ContentType")
ct = ContentType.objects.get(app_label="sourceapp", model="user")
with atomic():
try:
ct.app_label="destapp"
ct.save()
return
except IntegrityError:
pass
ct.delete()

django-oscar2.1 giving errors on is_public field. Its a model loading issue

I am migrating to django-oscar2.1
I have forked catalogue app, it has some existing migrations. Newly added field is_public is in migration #10. I have a custom migration #5 which is using create_from_breadcrumbs method. So when I tried to run migrate on fresh db migration#5 trigger error is_public field not exists in db.
create_from_breadcrumbs
Oscar loads a model with latest state which contains is_public in models but not in db.
In migration #5 I am loading model like this
Category = apps.get_model("catalogue", "Category")
You cannot use create_from_breadcrumbs in a migration like that. The reason for this is explained in Django's documentation on migrations - that the version of the model used in external code may be different from the one that this migration expects - which is why it fails.
If you want to create model objects as part of your migration you need to use the historic version of the model in the migration. This means defining your own version of create_from_breadcrumbs, and using the historic version of the model as suggested by the Django documentation:
def create_from_breadcrumbs_for_migration(apps, schema_editor):
Category = apps.get_model('catalogue', 'Category')
# Perform logic to create categories here.
class Migration(migrations.Migration):
dependencies = [
('catalogue', '0004_xxxx'),
]
operations = [
migrations.RunPython(create_from_breadcrumbs_for_migration),
]
That said, I would argue that migrations are not meant for this sort of data population in the first place. You would in my view be better off writing a separate management command for this, which you run after the migrations have been applied.

How to apply one specific change to database after 'makemigrations' command?

I added a field to one of my models, but in the 'models' folder I have two other python files which have only View models from which I query views in my database. When I run the makemigrations command, the new migrations file that is created includes also adding these view models to my database as tables (which I don't want to). How can I ignore these changes, and only commit the one addition of a field to an actual table on the database.
I think I maybe have to delete the migrations.CreateModel... in the new migrations file and only keep the migrations.addField... , then run the 'migrate' command. I didn't proceed with this because I'm not sure and maybe it will mess up my database in some way.
Thanks in advance to anyone who can help.
when you make a model for database view you must add meta class managed = false and db_table like this:
class MyViewModel(models.Model):
field: models.CharField(max_length=100)
class Meta:
managed = False
db_table = 'database_view_name'
when you write this and run makemigrations a migration generated contains this model but when you run migrate this migration doesnt change anything on database.
you also can create view using migrations in python. see migrations.RunPython for more details

Model not found in migration (apps.get_model raises LookupError)

I have the following migration (logic removed for simplicity):
def migrate_existing_discounts(apps, _):
ModelA = apps.get_model('myapp', 'ModelA')
ModelB = apps.get_model('myapp', 'ModelB')
class Migration(migrations.Migration):
dependencies = [
('myapp', '0071_auto_20160531_1342'),
]
operations = [
migrations.RunPython(migrate_existing_discounts)
]
When running it the following exception rises:
LookupError: App 'myapp' doesn't have a 'modelb' model.
ModelA inherits from models.Model and it's successfully loaded. On the other hand, ModelB inherits from TranslatableModel and so it breaks. I've read that (2 years ago) migrations used to have problems loading abstract classes(ticket#21786 and ticket#21519), and TranslatableModel is one of.
I've had this problem before and I ended up migrating with RunSQL instead, but I would like to know how to import the models properly, since there must be a way.
Note: The package django-hvad doesn't have migrations so there isn't any dependencies to add.
If all of your migrations are running from start to finish, the models you are referencing might not exist yet in your new database. Update your dependencies list inside the migration to reference the last migration file on the app where those models are defined.

Django 1.8 migration: any way to get data from database table that no longer has a model?

I'm trying to rename a model and I would like to write the migration in the way that it doesn't depend on the old name still present while it being applied. Can I somehow get data from a database table that no longer has a model in my migration code?
Details:
I have a Region model that I want to move into a more generic GeoObject model and remove from the models.py. If I write my migration code that creates GeoObjects from existing Regions with from models import Region I'll have to keep Region model until my main database will migrate. But I'd like to write a migration so that it doesn't depend on Region model being present, just check that the database table exists and use it. Is it possible to do it using Django instruments, without depending on a specific database type if possible?
Yes, you can.
But first of all, you really shouldn't import any model inside migration.
Take look at RunPython operation, that will allow you to run any python code inside your migration. RunPython will pass to your function 2 parameters: apps and schema_editor. First parameter contains structure of your models at stage of applying that migration, so if actual removing of model is later on that migration, you can still access that model using apps passed into function.
Let's say your model looked like this:
class SomeModel(models.Model):
some_field = models.CharField(max_length=32)
Now you're deleting that model, automatically created migration will contain:
class Migration(migrations.Migration):
dependencies = [
('yourapp', '0001_initial'), # or any other dependencies
]
operations = [
migrations.DeleteModel(
name='Main',
),
]
You can modify that migration by injecting RunPython just above DeleteModel operation:
operations = [
migrations.RunPython(
move_data_to_other_model,
move_data_back, # for backwards migration - if you won't ever want to undo this migration, just don't pass that function at all
),
migrations.DeleteModel(
name='SomeModel',
),
]
and creating 2 functions before Migration class:
def move_data_to_other_model(apps, schema_editor):
SomeModel = apps.get_model('yourapp', 'SomeModel')
for something in SomeModel.objects.all():
# do your data migration here
o = OtherModel.objects.get(condition=True)
o.other_field = something.some_field
def move_data_back(apps, schema_editor):
SomeModel = apps.get_model('yourapp', 'SomeModel')
for something in OtherModel.objects.all():
# move back your data here
SomeModel(
some_field=something.other_field,
).save()
It doesn't matter that your model is no longer defined in models.py, django can rebuild that model based on migration history. But remember: save method from your models (and other customized methods) won't be called in migrations. Also any pre_save or post_save signals won't be triggered.