I'm trying to port my project to use Django 1.7. Everything is fine except 1 thing. Models inside tests folders.
Django 1.7 new migrations run migrate command internally. Before syncdb was ran. That means if a model is not included in migrations - it won't be populated to DB (and also to test DB). That's exactly what I'm experiencing right now.
What I do is:
In my /app/tests/models.py I have dummy model: class TestBaseImage(BaseImage): pass
All it does is to inherit from an abstract BaseImage model.
Then in tests I create instances of that dummy model to test it.
The problem is that it doesn't work any more. It's not included in migrations (that's obvious as I don't want to keep my test models in a production DB). Running my tests causes DB error saying that table does not exist. That makes sense as it's not included in migrations.
Is there any way to make it work with new migrations system? I can't find a way to "fix" that.
Code I use:
app/tests/models.py
from ..models import BaseImage
class TestBaseImage(BaseImage):
"""Dummy model just to test BaseImage abstract class"""
pass
app/models.py
class BaseImage(models.Model):
# ... fields ...
class Meta:
abstract = True
factories:
class BaseImageFactory(factory.django.DjangoModelFactory):
"""Factory class for Vessel model"""
FACTORY_FOR = BaseImage
ABSTRACT_FACTORY = True
class PortImageFactory(BaseImageFactory):
FACTORY_FOR = PortImage
example test:
def get_model_field(model, field_name):
"""Returns field instance"""
return model._meta.get_field_by_name(field_name)[0]
def test_owner_field(self):
"""Tests owner field"""
field = get_model_field(BaseImage, "owner")
self.assertIsInstance(field, models.ForeignKey)
self.assertEqual(field.rel.to, get_user_model())
There is a ticket requesting a way to do test-only models here
As a workaround, you can decouple your tests.py and make it an app.
tests
|--migrations
|--__init__.py
|--models.py
|--tests.py
You will end up with something like this:
myapp
|-migrations
|-tests
|--migrations
|--__init__.py
|--models.py
|--tests.py
|-__init__.py
|-models.py
|-views.py
Then you should add it to your INSTALLED_APPS
INSTALLED_APPS = (
# ...
'myapp',
'myapp.tests',
)
You probably don't want to install myapp.tests in production, so you can keep separate settings files. Something like this:
INSTALLED_APPS = (
# ...
'myapp',
)
try:
from local_settings import *
except ImportError:
pass
Or better yet, create a test runner and install your tests there.
Last but not least, remember to run python manage.py makemigrations
Here's a workaround that seems to work. Trick the migration framework into thinking that there are no migrations for your app. In settings.py:
if 'test' in sys.argv:
# Only during unittests...
# myapp uses a test-only model, which won't be loaded if we only load
# our real migration files, so point to a nonexistent one, which will make
# the test runner fall back to 'syncdb' behavior.
MIGRATION_MODULES = {
'myapp': 'myapp.migrations_not_used_in_tests'
}
I found the idea on the first post in ths Django dev mailing list thread, and it's also currently being used in Django itself, but it may not work in future versions of Django where migrations are required and the "syncdb fallback" is removed.
Related
I'm creating a data-migration for the new_app with the possibility to roll it back.
# This is `new_app` migration
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.RunPython(import_data, reverse_code=delete_data)
]
This migration adds some data to the model defined in other app: my_other_app. To import the model where I want to update or delete records I use apps.get_model() method.
# This is `new_app` migration
def import_data(apps, schema_editor):
model = apps.get_model('my_other_app', 'MyModel')
It works like charm when I apply migrations. But when I run try to roll back the migration with the :~> manage.py migrate new_app zero I get exception: LookupError: No installed app with label 'my_other_app'. Model import in roll back code:
# This is `new_app` migration
def delete_data(apps, schema_editor):
schema_model = apps.get_model('my_other_app', 'MyModel')
The code for model import is identical, but why it doesn't work during migration roll back? For now I have a workaround with straight model import during roll-back. Don't know if it may cause troubles in future.
Make sure that dependencies includes the latest migration from the other app that you're referencing. eg:
dependencies = [
'my_other_app.0001_initial',
]
Also, make sure 'my_other_app' is in your INSTALLED_APPS setting.
I'm trying to update an old django app from 1.1.x to 1.8 LTS, which has involved updating paths, as I apps move apps around.
However, I'm unable to generate migrations for one app, and I can't see how to reference a namespaced app model correctly (assuming that's the problem)
If I've moved files from PROJECT/news/models to PROJECT/site_name/news/models, how should I be referencing these models in in foreign keys or ManyToManyFields?
My app
I have a projects app I want to make migrations for. Projects in some_org/projects, and listed in installed apps like so:
INSTALLED_APPS = (
'some_org.maps',
'some_org.library',
'some_org.extras',
'some_org.news',
'some_org.projects',
'some_org.members',
'some_org.comments',
)
All the apps with the namespace are within the some_org directory.
Here's an abridged view of the models file in the projects app:
# some_org/projects/models.py
from some_org.library import Paper
class Project(models.Model):
name = models.CharField(max_length=30)
def get_children(self):
return ProjectPage.objects.filter(level=1, publish=True, project=self)
def has_library(self):
return Paper.objects.filter(projects=self).count() > 0
Calling ./manage.py makemigrations library, gives me this error:
ValueError: Lookup failed for model referenced by field library.Paper.projects: projects.Project
When I look in the Paper model, it looks like this:
class Paper(models.Model):
# snip
# NewsLinkSubject, Projects et al used to in an app on
# the project root, like `./app_name/models.py`, but is now
# in `some_org/app_name/models.py`
subjects = models.ManyToManyField("news.NewsLinkSubject", blank=True)
projects = models.ManyToManyField("projects.Project", blank=True,)
country = models.ForeignKey("maps.Country", null=True, blank=True)
I initially wonder if the label for the app is wrong, and try the projects ManytoMany field to:
projects = models.ManyToManyField("some_org.projects.Project", blank=True,)
This gives a different error:
ERRORS:
library.Paper.subjects: (fields.E300) Field defines a relation with model some_org.projects.Project', which is either not installed, or is abstract.
As far as I can tell the app is installed, and the models aren't abstract.
I'm pretty stumped - what am I doing wrong, and how can I fix this so I can make migrations for these apps?
I'm using Django 1.8.17, and Python 2.7.13.
You should define an app_label in the Meta class for your models. Whatever you put in there becomes the first part of the name you use in the ManyToManyField definition.
Since Django 1.7 the AppConfig feature has been added suggesting that post_migrate signals should be put in the ready() part of its customized implementation - https://docs.djangoproject.com/en/stable/ref/signals/#post-migrate
The basic way to implement AppConfig described by the docs is to point to it in the __init__.py file using default_app_config setting. The docs would also suggest a way to override an existing AppConfig for any app:
https://docs.djangoproject.com/en/stable/ref/applications/#for-application-users
I have researched a bit and found out that django actually creates AppConfig instance for every app in INSTALLED_APPS even if its custom implementation is not implemented it would bootstrap the default for you.
My question is how one should provide a customized app configuration with post_migrate signal for an app that doesn't implement AppConfig (the easiest example would be a third party package without apps.py)?
I know that even for this app the django would go and create a default version of AppConfig but where and how should i tell it NOT to do so and use my custom AppConfig instead with overrided ready() method implementation to add post_migrate?
Let us suppose for a moment that you wished to create a custom AppConfig for django crispy_forms (which you have installed into your virtualenv).
Step 1: Create a folder named crispy_forms in your project.
Step 2: Create an __init__.py in that folder and paste the following
from django.apps import AppConfig
default_app_config = 'crispy_forms.apps.MyAppConfig'
step 3: Create an apps.py in crispy_forms folder and paste
from django.apps import AppConfig
class MyAppConfig(AppConfig):
verbose_name = 'customized crispy_forms'
name = "crispy_forms"
def __init__(self, app_name, app_module):
AppConfig.__init__(self,app_name, app_module)
print 'My app config', app_name, app_module
If django doesn't reload classes, restart the development server and then you will see something like:
My app config crispy_forms <module 'crispy_forms' from '/home/xxx/project/crispy_forms/__init__.pyc'>
My problem was quite the same, and answer here helped me to find a solution. I use in my Django app a dependency to "django-constance" to manage some application parameter inside Admin site.
But I wanted to be sure that some parameters are setup in constance as soon as you use my webapp application.
Unfortunatly, solution provided by #canufeel didn't worked, I could use constance subpackages in my app, 'import errors'.
Here is how I did it :
I created under my app a subpackage named 'myApp/constance', where I defined the same as mentioned by #canufeel.
But the change was mainly that I don't import anymore 'constance' package in INSTALLED_APPS, but 'myapp.constance' where I could override the settings like this :
MYAPP_CONSTANCE_CONFIG_VAR = {
'ACCOUNT_ACTIVATION_DAYS': (7, 'Number of days before activation is kept available'),
'BOOTSTRAP_THEME': ('slate', 'Bootstrap theme for front end', 'select_theme'),
...
}
Then override :
BASE_CONSTANCE_CONFIG = getattr(settings, 'CONSTANCE_CONFIG', {})
BASE_CONSTANCE_CONFIG.update(MYAPP_CONSTANCE_CONFIG_VAR)
settings.CONSTANCE_CONFIG = BASE_CONSTANCE_CONFIG_VAR
settings.CONSTANCE_ADDITIONAL_FIELDS = BASE_CONSTANCE_ADDITIONAL_FIELDS
settings.CONSTANCE_CONFIG_FIELDSETS = WAVES_CONSTANCE_CONFIG_FIELDSETS
that does the trick, still I think this is not a 'state of the art' solution :-)
I tried the other suggestions and they didn't work for me. But the official documentation did. The solution is so simple: For application users
To quote the Manual:
If you’re using “Rock ’n’ roll” in a project called anthology, but you
want it to show up as “Jazz Manouche” instead, you can provide your
own configuration:
# anthology/apps.py
from rock_n_roll.apps import RockNRollConfig
class JazzManoucheConfig(RockNRollConfig):
verbose_name = "Jazz Manouche"
# anthology/settings.py
INSTALLED_APPS = [
'anthology.apps.JazzManoucheConfig',
# ...
]
From the oficial documentation:
For tests involving models with managed=False, it’s up to you to ensure the correct tables are created as part of the test setup.
I don't know how to create the tables as part of the test setup. I found this question and the accepted answer doesn't work for me. I think this is because the migrations files. The configuration is in the migrations files, to change the values "on the fly" don't have any effect.
What's the way to solve this in Django 1.7+?
I found a way. Modify the fixtures and add the SQL to generate the tables:
#0001_initial.py (or followings)
class Migration(migrations.Migration):
operations = [
migrations.RunSQL("CREATE TABLE..."),
...
]
I'm a "migrations newbie" so I don't know if this is the best option. But it works.
I think it should be similar in Django 1.7+. When you are going to run the tests you should manage those models with Django (just for testing purposes).
This conversion should be done before creating tables and Django allows you to give a class instance setting up TEST_RUNNER in your settings.py
# settings_test.py
TEST_RUNNER = 'utils.test_runner.ManagedModelTestRunner'
# test_runner.py
from django.test.runner import DiscoverRunner
class ManagedModelTestRunner(DiscoverRunner):
"""
Test runner that automatically makes all unmanaged models in your Django
project managed for the duration of the test run, so that one doesn't need
to execute the SQL manually to create them.
"""
def setup_test_environment(self, *args, **kwargs):
from django.db.models.loading import get_models
super(ManagedModelTestRunner, self).setup_test_environment(*args,
**kwargs)
self.unmanaged_models = [m for m in get_models(only_installed=False)
if not m._meta.managed]
for m in self.unmanaged_models:
m._meta.managed = True
def teardown_test_environment(self, *args, **kwargs):
super(ManagedModelTestRunner, self).teardown_test_environment(*args, **kwargs)
# reset unmanaged models
for m in self.unmanaged_models:
m._meta.managed = False
Currently all my models are in models.py. Ist becomming very messy.
Can i have the separate file like base_models.py so that i put my main models there which i don't want to touch
Also same case for views and put in separate folder rather than develop a new app
Yes, it's doable. It's not particularly pretty though:
make models a module, so your directory structure looks like this:
- models
|- __init__.py
|- some_model.py
|- some_other_model.py
|- ...
now, the magic lies in __init__.py and some little extras in the models. __init__.py:
from some_model import SomeModel
from some_other_model import SomeOtherModel
__all__ = [
'SomeModel',
'SomeOtherModel',
]
some_model.py:
class SomeModel(models.Model):
class Meta(object):
app_label = 'yourapplabel'
db_table = 'yourapplabel_somemodel'
Everything acjohnson55 said, plus the fact that you need to specify the app_label attribute in each model's Meta class.
A link to an actual example on github:
https://github.com/stefanfoulis/django-filer/tree/develop/filer/models
you can separate the model file like this :
-------models/
-------------- init.py
-------------- usermodels.py
--------------othermodel.py
in the init.py:
---------------from usermodels import *
---------------from othermodel import *
and in the *models.py , add META class:
--------class Meta:
--------------app_label = 'appName'
Yes, just make a folder called models and in that folder put all of your separated model files. You need to put a file called __init__.py in the models folder for it to be considered the models module. In __init__.py, use from ... import ... to put the names you want available directly in yourapp.models, otherwise you will have to import them as yourapp.models.base_model, or whatever submodule name you use.
Also, in each model, you will have to add a Meta attribute called app_label = 'yourapp' to make sure your models are recognized as part of the app.
This is how I normally do it:
# Folder structure:
#==================
# models/
# __init__.py
# products.py
# stocks.py
# In init.py (don't forget the period before model file name)
#===========
from .products import Product
from .stocks import Stock
__all__ = [
'Product',
'Stock',
]
# And run "py manage.py makemigrations" and "py manage.py migrate" as normal