Forking shipping app has no effect in Django oscar - python

I had forked my shipping app into a folder called forked_apps using oscar_fork_app command and also added in settings.py get_core_apps(['forked_apps.shipping']), I just want to create two shipping methods mentioned, standard and express in the docs in this link:https://django-oscar.readthedocs.io/en/latest/howto/how_to_configure_shipping.html.
In the init.py I have this code pre-existing:
default_app_config = 'forked_apps.shipping.config.ShippingConfig'
In repository.py I have written like this:
from oscar.apps.shipping import repository
from .methods import *
class Repository(repository.Repository):
def get_available_shipping_methods(
self, basket, user=None, shipping_addr=None,
request=None, **kwargs):
methods = (Standard())
print("\n\nFetch availble shipping methods")
if shipping_addr:
# Express is only available in the UK
methods = (Standard(), Express())
return methods
And in the methods.py I had written:
from decimal import Decimal as D
from oscar.apps.shipping import methods
from oscar.core import prices
class Standard(methods.FixedPrice):
code = 'standard'
name = 'Standard shipping'
charge_excl_tax = D('5.00')
class Express(methods.FixedPrice):
code = 'express'
name = 'Express shipping'
charge_excl_tax = D('10.00')
What should happen is, the shipping_methods.html page should show up, but instead, after entering the shipping address it goes to the payment details page directly; this would usually happen only if there are no shipping methods defined, but I have implemented two shipping methods, standard and Express in the above code.I can't figure out how to make this work, even the print statement isn't working.
Is there any other additional code that I must write?
Can someone provide a solution with some code, if you have implemented it?

Remove oscar apps from settings.
For example:
#oscar.apps.checkout
#oscar.apps.shipping
etc.

This section gives me error. I can't fix that.
get_available_shipping_methods(
self, basket, user=None, shipping_addr=None,
request=None, **kwargs):
...
Django ver. > 2.1 | Oscar ver. > Latest
I using it like this;
mkdir customapp
touch customapp/__init__.py
python manage.py oscar_fork_app shipping customapp/
Edit settings.py
from oscar import get_core_apps
INSTALLED_APPS = INSTALLED_APPS + get_core_apps(
['customapp.shipping'])
In our customapp/shipping directory added new file, called (repository.py)
from oscar.apps.shipping import repository
from . import methods
class Repository(repository.Repository):
methods = (methods.Standard(),)
Then add new file in same directory, customapp/shipping, called (methods.py)
from oscar.apps.shipping import methods
from oscar.core import prices
from decimal import Decimal as D
class Standard(methods.Base):
code = 'standard'
name = 'Shipping (Standard)'
def calculate(self, basket):
return prices.Price(
currency=basket.currency,
excl_tax=D('5.00'), incl_tax=D('5.00'))
You can add more methods.
Then run these commands;
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
Hope this helps.

Related

How to insert data to Django database from python file periodically

can anyone please help me
I have created a model, like so
class TickerOHLC(models.Model):
date = models.DateField()
open = models.FloatField()
close = models.FloatField()
low = models.FloatField()
high = models.FloatField()
volume = models.FloatField()
def __str__(self):
return str(self.date), str(self.open)
and I can insert data into the database by uploading a file in the admin panel using import-export, like so
Screenshot of admin panel
Here are the admin.py content
from django.contrib import admin
from .models import *
from import_export.admin import ImportExportModelAdmin
#admin.register(Task, TickerOHLC)
class ViewAdmin(ImportExportModelAdmin):
pass
How can I import data to the database from a .py file? I have tried this
from tasks.models import TickerOHLC #This import gives an error: No module named 'tasks'
dataframe = generateDataframe() #function that returns a dataframe
datas = [
TickerOHLC(
date = dataframe.iloc[row]['Date'],
open = dataframe.iloc[row]['Open'],
close = dataframe.iloc[row]['Close'],
low = dataframe.iloc[row]['Low'],
high = dataframe.iloc[row]['High'],
volume = dataframe.iloc[row]['Volume'],
)
for row in dataframe.iterrows()
]
TickerOHLC.objects.bulk_create(datas)
Here are my folder structure
Screenshot of folder structure
I am new to Django and I don't even know if my approach is possible
My goal is to be able to run a script periodically that inserts into the database
Any kind of help is greatly appreciated, thank you
You are getting a ModuleNotFoundError because your project directory wasn't included in the PATH variable, which Django normally does for you via the python3 manage.py command.
If you were to set the PATH variable manually, you would end up getting an ImproperlyConfigured error because your DJANGO_SETTINGS_MODULE variable was not set (again, Django normally does this for you).
Instead of trying to configure everything outside of django, it's much easier to use the python3 manage.py utility.
In your case, the the easiest fix is to put this script in a custom Management Command, in the directory tasks/management/commands/my_command.py:
from django.core.management.base import BaseCommand
from tasks.models import TickerOHLC
class Command(BaseCommand):
def handle(self, **options):
dataframe = generateDataframe()
datas = [
TickerOHLC(
date = dataframe.iloc[row]['Date'],
open = dataframe.iloc[row]['Open'],
close = dataframe.iloc[row]['Close'],
low = dataframe.iloc[row]['Low'],
high = dataframe.iloc[row]['High'],
volume = dataframe.iloc[row]['Volume'],
)
for row in dataframe.iterrows()
]
TickerOHLC.objects.bulk_create(datas)
Now to execute this command, simply call python3 manage.py my_command, and Django will configure everything for you.
This good for testing and for tasks you typically use during development, but isn't suitable for periodic tasks that you mentioned in your question. For that I would recommend starting with django-background-tasks.
You need an empty __init__.py file in every folder so that python is able to import modules. the parent directory doesn't have an __init__.py file and that's why the import is failing.
Secondly, for periodic tasks, you should look into celery.
If you are executing this code outside of Django, please add below two lines of code at the beginning.
import django
django.setup()
If you are planning to schedule automatic execution of the file, it is better to set up Job Scheduling using django-extensions.
In that case above 2 lines of code are not required.

Django - How to dynamically add scripts/classes to the code?

I have a Django project where users can register and add their XML/CSV feeds.
What the project does is (everyday morning):
download the feed
parse the feed and store it to the model Product
generate XML/CSV export from these products in a different format
Now the problem is that users can register anytime during the project lifetime and many feeds have to be either downloaded, parsed or exported in a specific way.
So I need to be able to react quickly when the feed is added and write custom functions/classes/scripts to download/parse or export these sources.
Let's say it would be enough to add or extend some base classes like Downloader,Parser or Exporter
I'm looking for the most common way to do this.
I tried multiple approaches. For example, I've created a package parsers and there is a __init__.py with CHOICES attribute. When a user adds new feed, I create a parser__sourcename.py file which I add into parsers package which contains Parser class which extends BaseParser class with parse method where is the custom code.
Then I add import into __init__.py file so it looks like this:
from feeds_core.parsers.parser__source1 import Parser as ParserForSource1
from feeds_core.parsers.parser__source2 import Parser as ParserForSource2
from feeds_core.parsers.parser__source3 import Parser as ParserForSource3
PARSER__SOURCE1 = 'source1'
PARSER__SOURCE2 = 'source2'
PARSER__SOURCE3 = 'source3'
CHOICES = {
PARSER__SOURCE1: ParserForSource1,
PARSER__SOURCE2: ParserForSource2,
PARSER__SOURCE3: ParserForSource3,
}
def get_parser(choice):
return CHOICES[choice]
Then I have a model Source with this field:
PARSER_CHOICES = [(x, x) for x in CHOICES.keys()]
parser = models.CharField(max_length=128, choices=PARSER_CHOICES, null=True, blank=True)
def get_parser(self):
...
elif self.parser:
return self.get_parser_class()(self.get_last_filepaths(), self.user_eshop, self)
else:
raise self.NoParserDefinedException
def parse(self): # TODO raise spec exc
self.parse_started()
self.get_parser().parse()
self.parse_ended()
def get_parser_class(self) -> BaseParser:
return get_parser(self.parser)
And when there is a new feed, I create the file, modify the __init__.py and choose the parser for this source in the Django admin interface.
But it is little bit complicated and moreover, I'm afraid I have to restart production server every time.
Do you have any ideas/experiences? Is there some best practice how to do such things?
A very basic implementation where you would add a file to a directory (settings.PARSER_FILE_DIR), it would then be an available choice for Source.parser_file, then using importlib you would load the file and extract the Profile class out of it. settings.PARSER_FILE_DIR would probably have to be in your PYTHONPATH
This is an extremely basic example and you would need a lot of error handling and would need to make sure it was very secure
import importlib
class Source(models.Model):
parser_file = models.FilePathField(path=settings.PARSER_FILE_DIR, match='.*\.py$')
def get_parser_class(self):
return importlib.import_module(self.parser_file.replace('.py', '')).Parser

How do I run tests against a Django data migration?

Using the following example from the documentation:
def combine_names(apps, schema_editor):
Person = apps.get_model("yourappname", "Person")
for person in Person.objects.all():
person.name = "%s %s" % (person.first_name, person.last_name)
person.save()
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(combine_names),
]
How would I create and run a test against this migration, confirming that the data is migrated correctly?
I was doing some google to address the same question and found an article that nailed the hammer on the nail for me and seemed less hacky than existing answers. So, putting this here in case it helps anyone else coming though.
The proposed the following subclass of Django's TestCase:
from django.apps import apps
from django.test import TestCase
from django.db.migrations.executor import MigrationExecutor
from django.db import connection
class TestMigrations(TestCase):
#property
def app(self):
return apps.get_containing_app_config(type(self).__module__).name
migrate_from = None
migrate_to = None
def setUp(self):
assert self.migrate_from and self.migrate_to, \
"TestCase '{}' must define migrate_from and migrate_to properties".format(type(self).__name__)
self.migrate_from = [(self.app, self.migrate_from)]
self.migrate_to = [(self.app, self.migrate_to)]
executor = MigrationExecutor(connection)
old_apps = executor.loader.project_state(self.migrate_from).apps
# Reverse to the original migration
executor.migrate(self.migrate_from)
self.setUpBeforeMigration(old_apps)
# Run the migration to test
executor = MigrationExecutor(connection)
executor.loader.build_graph() # reload.
executor.migrate(self.migrate_to)
self.apps = executor.loader.project_state(self.migrate_to).apps
def setUpBeforeMigration(self, apps):
pass
And an example use case that they proposed was:
class TagsTestCase(TestMigrations):
migrate_from = '0009_previous_migration'
migrate_to = '0010_migration_being_tested'
def setUpBeforeMigration(self, apps):
BlogPost = apps.get_model('blog', 'Post')
self.post_id = BlogPost.objects.create(
title = "A test post with tags",
body = "",
tags = "tag1 tag2",
).id
def test_tags_migrated(self):
BlogPost = self.apps.get_model('blog', 'Post')
post = BlogPost.objects.get(id=self.post_id)
self.assertEqual(post.tags.count(), 2)
self.assertEqual(post.tags.all()[0].name, "tag1")
self.assertEqual(post.tags.all()[1].name, "tag2")
You can use django-test-migrations package. It is suited for testing: data migrations, schema migrations, and migrations' order.
Here's how it works:
from django_test_migrations.migrator import Migrator
# You can specify any database alias you need:
migrator = Migrator(database='default')
old_state = migrator.before(('main_app', '0002_someitem_is_clean'))
SomeItem = old_state.apps.get_model('main_app', 'SomeItem')
# One instance will be `clean`, the other won't be:
SomeItem.objects.create(string_field='a')
SomeItem.objects.create(string_field='a b')
assert SomeItem.objects.count() == 2
assert SomeItem.objects.filter(is_clean=True).count() == 2
new_state = migrator.after(('main_app', '0003_auto_20191119_2125'))
SomeItem = new_state.apps.get_model('main_app', 'SomeItem')
assert SomeItem.objects.count() == 2
# One instance is clean, the other is not:
assert SomeItem.objects.filter(is_clean=True).count() == 1
assert SomeItem.objects.filter(is_clean=False).count() == 1
We also have native integrations for both pytest:
#pytest.mark.django_db
def test_main_migration0002(migrator):
"""Ensures that the second migration works."""
old_state = migrator.before(('main_app', '0002_someitem_is_clean'))
SomeItem = old_state.apps.get_model('main_app', 'SomeItem')
...
And unittest:
from django_test_migrations.contrib.unittest_case import MigratorTestCase
class TestDirectMigration(MigratorTestCase):
"""This class is used to test direct migrations."""
migrate_from = ('main_app', '0002_someitem_is_clean')
migrate_to = ('main_app', '0003_auto_20191119_2125')
def prepare(self):
"""Prepare some data before the migration."""
SomeItem = self.old_state.apps.get_model('main_app', 'SomeItem')
SomeItem.objects.create(string_field='a')
SomeItem.objects.create(string_field='a b')
def test_migration_main0003(self):
"""Run the test itself."""
SomeItem = self.new_state.apps.get_model('main_app', 'SomeItem')
assert SomeItem.objects.count() == 2
assert SomeItem.objects.filter(is_clean=True).count() == 1
Full guide: https://sobolevn.me/2019/10/testing-django-migrations
Github: https://github.com/wemake-services/django-test-migrations
PyPI: https://pypi.org/project/django-test-migrations/
EDIT:
These other answers make more sense:
https://stackoverflow.com/a/56212859
https://stackoverflow.com/a/59016744, if you don't mind the extra (dev) dependency
ORIGINAL:
Running your data-migration functions (such as combine_names from the OP's example) through some basic unit-tests, before actually applying them, makes sense to me too.
At first glance this should not be much more difficult than your normal Django unit-tests: migrations are Python modules and the migrations/ folder is a package, so it is possible to import things from them. However, it took some time to get this working.
The first difficulty arises due to the fact that the default migration file names start with a number. For example, suppose the code from the OP's (i.e. Django's) data-migration example sits in 0002_my_data_migration.py, then it is tempting to use
from yourappname.migrations.0002_my_data_migration import combine_names
but that would raise a SyntaxError because the module name starts with a number (0).
There are at least two ways to make this work:
Rename the migration file so it does not start with a number. This should be perfectly fine according to the docs: "Django just cares that each migration has a different name." Then you can just use import as above.
If you want to stick to the default numbered migration file names, you can use Python's import_module (see docs and this SO question).
The second difficulty arises from the fact that your data-migration functions are designed to be passed into RunPython (docs), so they expect two input arguments by default: apps and schema_editor. To see where these come from, you can inspect the source.
Now, I'm not sure this works for every case (please, anyone, comment if you can clarify), but for our case, it was sufficient to import apps from django.apps and get the schema_editor from the active database connection (django.db.connection).
The following is a stripped-down example showing how you can implement this for the OP example, assuming the migration file is called 0002_my_data_migration.py:
from importlib import import_module
from django.test import TestCase
from django.apps import apps
from django.db import connection
from yourappname.models import Person
# Our filename starts with a number, so we use import_module
data_migration = import_module('yourappname.migrations.0002_my_data_migration')
class DataMigrationTests(TestCase):
def __init__(self, *args, **kwargs):
super(DataMigrationTests, self).__init__(*args, **kwargs)
# Some test values
self.first_name = 'John'
self.last_name = 'Doe'
def test_combine_names(self):
# Create a dummy Person
Person.objects.create(first_name=self.first_name,
last_name=self.last_name,
name=None)
# Run the data migration function
data_migration.combine_names(apps, connection.schema_editor())
# Test the result
person = Person.objects.get(id=1)
self.assertEqual('{} {}'.format(self.first_name, self.last_name), person.name)
You could add a crude if statement to a prior migration that tests if the test suite is running, and adds initial data if it is -- that way you can just write a test to check if the objects are in the final state you want them in. Just make sure your conditional is compatible with production, here's an example that would work with python manage.py test:
import sys
if 'test in sys.argv:
# do steps to update your operations
For a more "complete" solution, this older blog post has some good info and more up-to-date comments for inspiration:
https://micknelson.wordpress.com/2013/03/01/testing-django-migrations/#comments

beginner: python namespace collision?

Disclaimer: i am new to python but have drupal programming experience
I am reading the Definitive Guide to Django (http://www.djangobook.com/en/1.0/chapter07/). After issuing
python manage.py startapp books
python creates the books package with views.py inside. Later in the tutorial, we enter the following into that views.py file:
# Create your forms here.
from django import forms
from django.forms import form_for_model
from models import Publisher
PublisherForm = forms.form_for_model(Publisher)
TOPIC_CHOICES = (
('general', 'General enquiry'),
('bug', 'Bug report'),
('suggestion', 'Suggestion'),
)
class ContactForm(forms.Form):
topic = forms.ChoiceField(choices=TOPIC_CHOICES)
message = forms.CharField(widget=forms.Textarea())
sender = forms.EmailField(required=False)
def clean_message(self):
message = self.cleaned_data.get('message', '')
num_words = len(message.split())
if num_words < 4:
raise forms.ValidationError("Not enough words!")
return
Unless I typed something wrong or misunderstood something (either is likely), we now (seemingly) have a collision between forms (books/forms.py) and django forms. So, which does Python refer to above in
message = forms.CharField(widget=forms.Textarea())
This statement:
from django.forms import form_for_model
Really only pulls the name form_for_model into the module-global namespace, the already existing name forms is not affected. Even this:
import django.forms
would not be a problem, because it only makes the name available in its fully-qualified form, django.forms (which is unambigios and does not collide with forms).
Unlike PHP, Python only imports into the current namespace, and each module is its own namespace; imports in other modules do not affect the current namespace, and vice versa.
Although this example does not cause a conflict as pointed out if there is a conflict you can use from ____ import ___ as ___ to specify your own import name
http://docs.python.org/reference/simple_stmts.html#import

Django: How to create a model dynamically just for testing

I have a Django app that requires a settings attribute in the form of:
RELATED_MODELS = ('appname1.modelname1.attribute1',
'appname1.modelname2.attribute2',
'appname2.modelname3.attribute3', ...)
Then hooks their post_save signal to update some other fixed model depending on the attributeN defined.
I would like to test this behaviour and tests should work even if this app is the only one in the project (except for its own dependencies, no other wrapper app need to be installed). How can I create and attach/register/activate mock models just for the test database? (or is it possible at all?)
Solutions that allow me to use test fixtures would be great.
You can put your tests in a tests/ subdirectory of the app (rather than a tests.py file), and include a tests/models.py with the test-only models.
Then provide a test-running script (example) that includes your tests/ "app" in INSTALLED_APPS. (This doesn't work when running app tests from a real project, which won't have the tests app in INSTALLED_APPS, but I rarely find it useful to run reusable app tests from a project, and Django 1.6+ doesn't by default.)
(NOTE: The alternative dynamic method described below only works in Django 1.1+ if your test case subclasses TransactionTestCase - which slows down your tests significantly - and no longer works at all in Django 1.7+. It's left here only for historical interest; don't use it.)
At the beginning of your tests (i.e. in a setUp method, or at the beginning of a set of doctests), you can dynamically add "myapp.tests" to the INSTALLED_APPS setting, and then do this:
from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
Then at the end of your tests, you should clean up by restoring the old version of INSTALLED_APPS and clearing the app cache again.
This class encapsulates the pattern so it doesn't clutter up your test code quite as much.
#paluh's answer requires adding unwanted code to a non-test file and in my experience, #carl's solution does not work with django.test.TestCase which is needed to use fixtures. If you want to use django.test.TestCase, you need to make sure you call syncdb before the fixtures get loaded. This requires overriding the _pre_setup method (putting the code in the setUp method is not sufficient). I use my own version of TestCase that lets me add apps with test models. It is defined as follows:
from django.conf import settings
from django.core.management import call_command
from django.db.models import loading
from django import test
class TestCase(test.TestCase):
apps = ()
def _pre_setup(self):
# Add the models to the db.
self._original_installed_apps = list(settings.INSTALLED_APPS)
for app in self.apps:
settings.INSTALLED_APPS.append(app)
loading.cache.loaded = False
call_command('syncdb', interactive=False, verbosity=0)
# Call the original method that does the fixtures etc.
super(TestCase, self)._pre_setup()
def _post_teardown(self):
# Call the original method.
super(TestCase, self)._post_teardown()
# Restore the settings.
settings.INSTALLED_APPS = self._original_installed_apps
loading.cache.loaded = False
I shared my solution that I use in my projects. Maybe it helps someone.
pip install django-fake-model
Two simple steps to create fake model:
1) Define model in any file (I usualy define model in test file near a test case)
from django_fake_model import models as f
class MyFakeModel(f.FakeModel):
name = models.CharField(max_length=100)
2) Add decorator #MyFakeModel.fake_me to your TestCase or to test function.
class MyTest(TestCase):
#MyFakeModel.fake_me
def test_create_model(self):
MyFakeModel.objects.create(name='123')
model = MyFakeModel.objects.get(name='123')
self.assertEqual(model.name, '123')
This decorator creates table in your database before each test and remove the table after test.
Also you may create/delete table manually: MyFakeModel.create_table() / MyFakeModel.delete_table()
I've figured out a way for test-only models for django 1.7+.
The basic idea is, make your tests an app, and add your tests to INSTALLED_APPS.
Here's an example:
$ ls common
__init__.py admin.py apps.py fixtures models.py pagination.py tests validators.py views.py
$ ls common/tests
__init__.py apps.py models.py serializers.py test_filter.py test_pagination.py test_validators.py views.py
And I have different settings for different purposes(ref: splitting up the settings file), namely:
settings/default.py: base settings file
settings/production.py: for production
settings/development.py: for development
settings/testing.py: for testing.
And in settings/testing.py, you can modify INSTALLED_APPS:
settings/testing.py:
from default import *
DEBUG = True
INSTALLED_APPS += ['common', 'common.tests']
And make sure that you have set a proper label for your tests app, namely,
common/tests/apps.py
from django.apps import AppConfig
class CommonTestsConfig(AppConfig):
name = 'common.tests'
label = 'common_tests'
common/tests/__init__.py, set up proper AppConfig(ref: Django Applications).
default_app_config = 'common.tests.apps.CommonTestsConfig'
Then, generate db migration by
python manage.py makemigrations --settings=<your_project_name>.settings.testing tests
Finally, you can run your test with param --settings=<your_project_name>.settings.testing.
If you use py.test, you can even drop a pytest.ini file along with django's manage.py.
py.test
[pytest]
DJANGO_SETTINGS_MODULE=kungfu.settings.testing
Quoting from a related answer:
If you want models defined for testing only then you should check out
Django ticket #7835 in particular comment #24 part of which
is given below:
Apparently you can simply define models directly in your tests.py.
Syncdb never imports tests.py, so those models won't get synced to the
normal db, but they will get synced to the test database, and can be
used in tests.
This solution works only for earlier versions of django (before 1.7). You can check your version easily:
import django
django.VERSION < (1, 7)
Original response:
It's quite strange but form me works very simple pattern:
add tests.py to app which you are going to test,
in this file just define testing models,
below put your testing code (doctest or TestCase definition),
Below I've put some code which defines Article model which is needed only for tests (it exists in someapp/tests.py and I can test it just with: ./manage.py test someapp ):
class Article(models.Model):
title = models.CharField(max_length=128)
description = models.TextField()
document = DocumentTextField(template=lambda i: i.description)
def __unicode__(self):
return self.title
__test__ = {"doctest": """
#smuggling model for tests
>>> from .tests import Article
#testing data
>>> by_two = Article.objects.create(title="divisible by two", description="two four six eight")
>>> by_three = Article.objects.create(title="divisible by three", description="three six nine")
>>> by_four = Article.objects.create(title="divisible by four", description="four four eight")
>>> Article.objects.all().search(document='four')
[<Article: divisible by two>, <Article: divisible by four>]
>>> Article.objects.all().search(document='three')
[<Article: divisible by three>]
"""}
Unit tests also working with such model definition.
I chose a slightly different, albeit more coupled, approach to dynamically creating models just for testing.
I keep all my tests in a tests subdirectory that lives in my files app. The models.py file in the tests subdirectory contains my test-only models. The coupled part comes in here, where I need to add the following to my settings.py file:
# check if we are testing right now
TESTING = 'test' in sys.argv
if TESTING:
# add test packages that have models
INSTALLED_APPS += ['files.tests',]
I also set db_table in my test model, because otherwise Django would have created the table with the name tests_<model_name>, which may have caused a conflict with other test models in another app. Here's my my test model:
class Recipe(models.Model):
'''Test-only model to test out thumbnail registration.'''
dish_image = models.ImageField(upload_to='recipes/')
class Meta:
db_table = 'files_tests_recipe'
Here's the pattern that I'm using to do this.
I've written this method that I use on a subclassed version of TestCase. It goes as follows:
#classmethod
def create_models_from_app(cls, app_name):
"""
Manually create Models (used only for testing) from the specified string app name.
Models are loaded from the module "<app_name>.models"
"""
from django.db import connection, DatabaseError
from django.db.models.loading import load_app
app = load_app(app_name)
from django.core.management import sql
from django.core.management.color import no_style
sql = sql.sql_create(app, no_style(), connection)
cursor = connection.cursor()
for statement in sql:
try:
cursor.execute(statement)
except DatabaseError, excn:
logger.debug(excn.message)
pass
Then, I create a special test-specific models.py file in something like myapp/tests/models.py that's not included in INSTALLED_APPS.
In my setUp method, I call create_models_from_app('myapp.tests') and it creates the proper tables.
The only "gotcha" with this approach is that you don't really want to create the models ever time setUp runs, which is why I catch DatabaseError. I guess the call to this method could go at the top of the test file and that would work a little better.
Combining your answers, specially #slacy's, I did this:
class TestCase(test.TestCase):
initiated = False
#classmethod
def setUpClass(cls, *args, **kwargs):
if not TestCase.initiated:
TestCase.create_models_from_app('myapp.tests')
TestCase.initiated = True
super(TestCase, cls).setUpClass(*args, **kwargs)
#classmethod
def create_models_from_app(cls, app_name):
"""
Manually create Models (used only for testing) from the specified string app name.
Models are loaded from the module "<app_name>.models"
"""
from django.db import connection, DatabaseError
from django.db.models.loading import load_app
app = load_app(app_name)
from django.core.management import sql
from django.core.management.color import no_style
sql = sql.sql_create(app, no_style(), connection)
cursor = connection.cursor()
for statement in sql:
try:
cursor.execute(statement)
except DatabaseError, excn:
logger.debug(excn.message)
With this, you don't try to create db tables more than once, and you don't need to change your INSTALLED_APPS.
If you are writing a reusable django-app, create a minimal test-dedicated app for it!
$ django-admin.py startproject test_myapp_project
$ django-admin.py startapp test_myapp
add both myapp and test_myapp to the INSTALLED_APPS, create your models there and it's good to go!
I have gone through all these answers as well as django ticket 7835, and I finally went for a totally different approach.
I wanted my app (somehow extending queryset.values() ) to be able to be tested in isolation; also, my package does include some models and I wanted a clean distinction between test models and package ones.
That's when I realized it was easier to add a very small django project in the package!
This also allows a much cleaner separation of code IMHO:
In there you can cleanly and without any hack define your models, and you know they will be created when you run your tests from in there!
If you are not writing an independent, reusable app you can still go this way: create a test_myapp app, and add it to your INSTALLED_APPS only in a separate settings_test_myapp.py!
Someone already mentioned Django ticket #7835, but there appears to be a more recent reply that looks much more promising for more recent versions of Django. Specifically #42, which proposes a different TestRunner:
from importlib.util import find_spec
import unittest
from django.apps import apps
from django.conf import settings
from django.test.runner import DiscoverRunner
class TestLoader(unittest.TestLoader):
""" Loader that reports all successful loads to a runner """
def __init__(self, *args, runner, **kwargs):
self.runner = runner
super().__init__(*args, **kwargs)
def loadTestsFromModule(self, module, pattern=None):
suite = super().loadTestsFromModule(module, pattern)
if suite.countTestCases():
self.runner.register_test_module(module)
return suite
class RunnerWithTestModels(DiscoverRunner):
""" Test Runner that will add any test packages with a 'models' module to INSTALLED_APPS.
Allows test only models to be defined within any package that contains tests.
All test models should be set with app_label = 'tests'
"""
def __init__(self, *args, **kwargs):
self.test_packages = set()
self.test_loader = TestLoader(runner=self)
super().__init__(*args, **kwargs)
def register_test_module(self, module):
self.test_packages.add(module.__package__)
def setup_databases(self, **kwargs):
# Look for test models
test_apps = set()
for package in self.test_packages:
if find_spec('.models', package):
test_apps.add(package)
# Add test apps with models to INSTALLED_APPS that aren't already there
new_installed = settings.INSTALLED_APPS + tuple(ta for ta in test_apps if ta not in settings.INSTALLED_APPS)
apps.set_installed_apps(new_installed)
return super().setup_databases(**kwargs)

Categories

Resources