mirror of
https://github.com/jazzband/django-eav2.git
synced 2026-04-28 19:04:41 +00:00
Merge pull request #70 from Dresdn/issue-69-tooling
New tooling and test refactoring
This commit is contained in:
commit
ac05b63690
29 changed files with 2857 additions and 300 deletions
|
|
@ -1,4 +0,0 @@
|
|||
[run]
|
||||
omit =
|
||||
*/migrations/*
|
||||
eav/__init__.py
|
||||
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Check http://editorconfig.org for more information
|
||||
# This is the main config file for this project:
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
[*.pyi]
|
||||
indent_size = 4
|
||||
30
.travis.yml
30
.travis.yml
|
|
@ -1,30 +0,0 @@
|
|||
language: python
|
||||
matrix:
|
||||
include:
|
||||
- python: 3.6
|
||||
env: TOXENV=py36-django31
|
||||
- python: 3.6
|
||||
env: TOXENV=py36-django32
|
||||
- python: 3.7
|
||||
env: TOXENV=py37-django31
|
||||
- python: 3.7
|
||||
env: TOXENV=py37-django32
|
||||
- python: 3.8
|
||||
env: TOXENV=py38-django31
|
||||
- python: 3.8
|
||||
env: TOXENV=py38-django32
|
||||
- python: 3.9
|
||||
env: TOXENV=py39-django31
|
||||
- python: 3.9
|
||||
env: TOXENV=py39-django32
|
||||
install:
|
||||
- pip install Django>=3.1
|
||||
- pip install coveralls==1.3.0
|
||||
- pip install coverage==4.5.1
|
||||
- pip install tox-travis==0.10
|
||||
before_script:
|
||||
- coverage erase
|
||||
script:
|
||||
- coverage run --source=eav runtests; tox
|
||||
after_success:
|
||||
- COVERALLS_REPO_TOKEN=71NkMDQFpFKB9QYXoK12LYuWUEmQ2wD6V coveralls
|
||||
35
README.md
35
README.md
|
|
@ -1,6 +1,6 @@
|
|||
[](https://travis-ci.org/lvm/django-eav2)
|
||||

|
||||

|
||||

|
||||
[](https://jazzband.co/)
|
||||
|
||||
## Django EAV 2 - Entity-Attribute-Value storage for Django
|
||||
|
|
@ -14,16 +14,16 @@ You can find documentation <a href="https://django-eav2.rtfd.io">here</a>.
|
|||
|
||||
Data in EAV is stored as a 3-tuple (typically corresponding to three distinct tables):
|
||||
|
||||
* The entity: the item being described, e.g. `Person(name='Mike')`.
|
||||
* The attribute: often a foreign key into a table of attributes, e.g. `Attribute(slug='height', datatype=FLOAT)`.
|
||||
* The value of the attribute, with links both an attribute and an entity, e.g. `Value(value_float=15.5, person=mike, attr=height)`.
|
||||
- The entity: the item being described, e.g. `Person(name='Mike')`.
|
||||
- The attribute: often a foreign key into a table of attributes, e.g. `Attribute(slug='height', datatype=FLOAT)`.
|
||||
- The value of the attribute, with links both an attribute and an entity, e.g. `Value(value_float=15.5, person=mike, attr=height)`.
|
||||
|
||||
Entities in **django-eav2** are your typical Django model instances. Attributes (name and type) are stored in their own table, which makes it easy to manipulate the list of available attributes in the system. Values are an intermediate table between attributes and entities, each instance holding a single value.
|
||||
This implementation also makes it easy to edit attributes in Django Admin and form instances.
|
||||
|
||||
You will find detailed description of the EAV here:
|
||||
|
||||
* [Wikipedia - Entity–attribute–value model](https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model)
|
||||
- [Wikipedia - Entity–attribute–value model](https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model)
|
||||
|
||||
## EAV - The Good, the Bad or the Ugly?
|
||||
|
||||
|
|
@ -35,24 +35,24 @@ Originally, EAV was introduced to workaround a problem which cannot be easily so
|
|||
|
||||
Typical application of the EAV model sets to solve the problem of sparse data with a large number of applicable attributes, but only a small fraction that applies to a given entity that may not be known beforehand. Consider the classic example:
|
||||
|
||||
> A problem that data modelers commonly encounter in the biomedical domain is organizing and storing highly diverse and heterogeneous data. For example, a single patient may have thousands of applicable descriptive parameters, all of which need to be easily accessible in an electronic patient record system. These requirements pose significant modeling and implementation challenges. [1]
|
||||
> A problem that data modelers commonly encounter in the biomedical domain is organizing and storing highly diverse and heterogeneous data. For example, a single patient may have thousands of applicable descriptive parameters, all of which need to be easily accessible in an electronic patient record system. These requirements pose significant modeling and implementation challenges. [1]
|
||||
|
||||
And:
|
||||
And:
|
||||
|
||||
> [...] what do you do when you have customers that demand real-time, on-demand addition of attributes that they want to store? In one of the systems I manage, our customers wanted to do exactly this. Since we run a SaaS (software as a service) application, we have many customers across several different industries, who in turn want to use our system to store different types of information about *their* customers. A salon chain might want to record facts such as 'hair color,' 'hair type,' and 'haircut frequency'; while an investment company might want to record facts such as 'portfolio name,' 'last portfolio adjustment date,' and 'current portfolio balance.' [2]
|
||||
> [...] what do you do when you have customers that demand real-time, on-demand addition of attributes that they want to store? In one of the systems I manage, our customers wanted to do exactly this. Since we run a SaaS (software as a service) application, we have many customers across several different industries, who in turn want to use our system to store different types of information about _their_ customers. A salon chain might want to record facts such as 'hair color,' 'hair type,' and 'haircut frequency'; while an investment company might want to record facts such as 'portfolio name,' 'last portfolio adjustment date,' and 'current portfolio balance.' [2]
|
||||
|
||||
In both of these problems we have to deal with sparse and heterogeneous properties that apply only to potentially different subsets of particular entities. Applying EAV to a sub-schema of the database allows to model the desired behaviour. Traditional solution would involves wide tables with many columns storing NULL values for attributes that don't apply to an entity.
|
||||
In both of these problems we have to deal with sparse and heterogeneous properties that apply only to potentially different subsets of particular entities. Applying EAV to a sub-schema of the database allows to model the desired behaviour. Traditional solution would involves wide tables with many columns storing NULL values for attributes that don't apply to an entity.
|
||||
|
||||
Very common use case for EAV are custom product attributes in E-commerce implementations, such as Magento. [3]
|
||||
|
||||
As a rule of thumb, EAV can be used when:
|
||||
|
||||
* Model attributes are to be added and removed by end users (or are unknowable in some different way). EAV supports these without ALTER TABLE statements and allows the attributes to be strongly typed and easily searchable.
|
||||
* There will be many attributes and values are sparse, in contrast to having tables with mostly-null columns.
|
||||
* The data is highly dynamic/volatile/vulnerable to change. This problem is present in the second example given above. Other example would be rapidly evolving system, such as a prototype with constantly changing requirements.
|
||||
* We want to store meta-data or supporting information, e.g. to customize system's behavior.
|
||||
* Numerous classes of data need to be represented, each class has a limited number of attributes, but the number of instances of each class is very small.
|
||||
* We want to minimise programmer's input when changing the data model.
|
||||
As a rule of thumb, EAV can be used when:
|
||||
|
||||
- Model attributes are to be added and removed by end users (or are unknowable in some different way). EAV supports these without ALTER TABLE statements and allows the attributes to be strongly typed and easily searchable.
|
||||
- There will be many attributes and values are sparse, in contrast to having tables with mostly-null columns.
|
||||
- The data is highly dynamic/volatile/vulnerable to change. This problem is present in the second example given above. Other example would be rapidly evolving system, such as a prototype with constantly changing requirements.
|
||||
- We want to store meta-data or supporting information, e.g. to customize system's behavior.
|
||||
- Numerous classes of data need to be represented, each class has a limited number of attributes, but the number of instances of each class is very small.
|
||||
- We want to minimise programmer's input when changing the data model.
|
||||
|
||||
For more throughout discussion on the appriopriate use-cases see:
|
||||
|
||||
|
|
@ -85,6 +85,7 @@ In some use-cases, JSONB (binary JSON data) datatype (Postgres 9.4+ and analogou
|
|||
## Installation
|
||||
|
||||
You can install **django-eav2** from three sources:
|
||||
|
||||
```bash
|
||||
# From PyPI via pip
|
||||
pip install django-eav2
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
__version__ = '0.14.1'
|
||||
|
||||
def register(model_cls, config_cls=None):
|
||||
from .registry import Registry
|
||||
Registry.register(model_cls, config_cls)
|
||||
|
|
|
|||
28
manage.py
28
manage.py
|
|
@ -1,13 +1,31 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.test_settings")
|
||||
def main() -> None:
|
||||
"""
|
||||
Main function.
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
It does several things:
|
||||
1. Sets default settings module, if it is not set
|
||||
2. Warns if Django is not installed
|
||||
3. Executes any given command
|
||||
"""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test_project.settings')
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
try:
|
||||
from django.core import management # noqa: WPS433
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
+ 'available on your PYTHONPATH environment variable? Did you '
|
||||
+ 'forget to activate a virtual environment?',
|
||||
)
|
||||
|
||||
management.execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
|||
2220
poetry.lock
generated
Normal file
2220
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
79
pyproject.toml
Normal file
79
pyproject.toml
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
|
||||
[tool.nitpick]
|
||||
style = "https://raw.githubusercontent.com/wemake-services/wemake-python-styleguide/master/styles/nitpick-style-wemake.toml"
|
||||
|
||||
|
||||
[tool.black]
|
||||
target-version = ['py36', 'py37', 'py38', 'py39']
|
||||
skip-string-normalization = true
|
||||
include = '\.pyi?$'
|
||||
|
||||
|
||||
[tool.poetry]
|
||||
name = "eav"
|
||||
description = "Entity-Attribute-Value storage for Django"
|
||||
version = "0.14.1"
|
||||
license = "GNU Lesser General Public License (LGPL), Version 3"
|
||||
|
||||
authors = [
|
||||
"Mauro Lizaur <mauro@sdf.org>",
|
||||
]
|
||||
|
||||
readme = "README.md"
|
||||
|
||||
repository = "https://github.com/jazzband/django-eav2"
|
||||
|
||||
keywords = [
|
||||
"django",
|
||||
"django-eav2",
|
||||
"database",
|
||||
"eav",
|
||||
"sql",
|
||||
]
|
||||
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Environment :: Web Environment",
|
||||
"Framework :: Django",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Database",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Framework :: Django",
|
||||
"Framework :: Django :: 2.2",
|
||||
"Framework :: Django :: 3.1",
|
||||
"Framework :: Django :: 3.2",
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.6.2"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
django = "^3.2"
|
||||
|
||||
mypy = "^0.902"
|
||||
|
||||
wemake-python-styleguide = "^0.15"
|
||||
flake8-pytest-style = "^1.4"
|
||||
nitpick = "^0.26"
|
||||
|
||||
safety = "^1.10"
|
||||
|
||||
pytest = "^6.2"
|
||||
pytest-cov = "^2.12"
|
||||
pytest-randomly = "^3.0"
|
||||
|
||||
sphinx = "^4.0"
|
||||
sphinx-autodoc-typehints = "^1.12"
|
||||
doc8 = "^0.8"
|
||||
m2r2 = "^0.2"
|
||||
tomlkit = "^0.7"
|
||||
pytest-pythonpath = "^0.7.3"
|
||||
pytest-django = "^4.4.0"
|
||||
tox-poetry-installer = "^0.8.1"
|
||||
black = "^21.6b0"
|
||||
|
|
@ -1 +0,0 @@
|
|||
Django>=3.1
|
||||
129
setup.cfg
Normal file
129
setup.cfg
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# All configuration for plugins and other utils is defined here.
|
||||
# Read more about `setup.cfg`:
|
||||
# https://docs.python.org/3/distutils/configfile.html
|
||||
|
||||
|
||||
[flake8]
|
||||
format = wemake
|
||||
show-source = True
|
||||
doctests = False
|
||||
statistics = False
|
||||
|
||||
# darglint configuration:
|
||||
# https://github.com/terrencepreilly/darglint
|
||||
strictness = long
|
||||
docstring-style = numpy
|
||||
|
||||
# Plugins:
|
||||
max-complexity = 6
|
||||
max-line-length = 80
|
||||
|
||||
exclude =
|
||||
# Trash and cache:
|
||||
.git
|
||||
__pycache__
|
||||
.venv
|
||||
.eggs
|
||||
*.egg
|
||||
temp
|
||||
|
||||
ignore =
|
||||
D100,
|
||||
D104,
|
||||
D401,
|
||||
W504,
|
||||
X100,
|
||||
RST303,
|
||||
RST304,
|
||||
DAR103,
|
||||
DAR203
|
||||
|
||||
per-file-ignores =
|
||||
test_project/migrations/*.py: N806, WPS102, WPS114
|
||||
test_project/settings.py: S105, WPS226, WPS407
|
||||
tests/test_*.py: N806, S101, S404, S603, S607, WPS118, WPS226, WPS432, WPS442
|
||||
|
||||
|
||||
[isort]
|
||||
# isort configuration:
|
||||
# https://github.com/timothycrosley/isort/wiki/isort-Settings
|
||||
include_trailing_comma = true
|
||||
use_parentheses = true
|
||||
# See https://github.com/timothycrosley/isort#multi-line-output-modes
|
||||
multi_line_output = 3
|
||||
line_length = 80
|
||||
|
||||
# Useful for our test app:
|
||||
known_first_party = test_project
|
||||
|
||||
|
||||
[tool:pytest]
|
||||
# Django options:
|
||||
# https://pytest-django.readthedocs.io/en/latest/
|
||||
DJANGO_SETTINGS_MODULE = test_project.settings
|
||||
|
||||
# PYTHONPATH configuration:
|
||||
# https://github.com/bigsassy/pytest-pythonpath
|
||||
python_paths = ./eav
|
||||
|
||||
# py.test options:
|
||||
norecursedirs =
|
||||
*.egg
|
||||
.eggs
|
||||
dist
|
||||
build
|
||||
docs
|
||||
.tox
|
||||
.git
|
||||
__pycache__
|
||||
|
||||
# You will need to measure your tests speed with `-n auto` and without it,
|
||||
# so you can see whether it gives you any performance gain, or just gives
|
||||
# you an overhead. See `docs/template/development-process.rst`.
|
||||
addopts =
|
||||
-p no:randomly
|
||||
--strict-markers
|
||||
--strict-config
|
||||
--doctest-modules
|
||||
--cov=eav
|
||||
--cov-report=term-missing:skip-covered
|
||||
--cov-report=html
|
||||
--cov-report=xml
|
||||
--cov-branch
|
||||
--cov-fail-under=10
|
||||
|
||||
|
||||
[coverage:run]
|
||||
# Exclude tox output from coverage calculation
|
||||
omit = */.tox/*
|
||||
|
||||
[coverage:report]
|
||||
skip_covered = True
|
||||
show_missing = True
|
||||
sort = Cover
|
||||
exclude_lines =
|
||||
pragma: no cover
|
||||
# type hinting related code
|
||||
if TYPE_CHECKING:
|
||||
|
||||
|
||||
[mypy]
|
||||
# mypy configurations: http://bit.ly/2zEl9WI
|
||||
|
||||
allow_redefinition = False
|
||||
check_untyped_defs = True
|
||||
disallow_any_explicit = True
|
||||
disallow_any_generics = True
|
||||
disallow_untyped_calls = True
|
||||
ignore_errors = False
|
||||
ignore_missing_imports = True
|
||||
implicit_reexport = False
|
||||
strict_optional = True
|
||||
strict_equality = True
|
||||
local_partial_types = True
|
||||
no_implicit_optional = True
|
||||
warn_no_return = True
|
||||
warn_unused_ignores = True
|
||||
warn_redundant_casts = True
|
||||
warn_unused_configs = True
|
||||
warn_unreachable = True
|
||||
25
setup.py
25
setup.py
|
|
@ -1,25 +0,0 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name = 'django-eav2',
|
||||
version = __import__('eav').__version__,
|
||||
license = 'GNU Lesser General Public License (LGPL), Version 3',
|
||||
requires = ['python (>= 3.6)', 'django (>= 3.1)'],
|
||||
provides = ['eav'],
|
||||
description = 'Entity-Attribute-Value storage for Django',
|
||||
url = 'http://github.com/lvm/django-eav2',
|
||||
packages = find_packages(),
|
||||
maintainer = 'Mauro Lizaur',
|
||||
maintainer_email = 'mauro@sdf.org',
|
||||
|
||||
classifiers = [
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Environment :: Web Environment',
|
||||
'Framework :: Django',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Database',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
],
|
||||
)
|
||||
0
test_project/__init__.py
Normal file
0
test_project/__init__.py
Normal file
5
test_project/apps.py
Normal file
5
test_project/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TestAppConfig(AppConfig):
|
||||
name = 'test_project'
|
||||
120
test_project/migrations/0001_initial.py
Normal file
120
test_project/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# Generated by Django 3.2.4 on 2021-06-17 22:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ExampleMetaclassModel',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('name', models.CharField(max_length=12)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExampleModel',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('name', models.CharField(max_length=12)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RegisterTestModel',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('name', models.CharField(max_length=12)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Patient',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('name', models.CharField(max_length=12)),
|
||||
(
|
||||
'example',
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.deletion.PROTECT,
|
||||
to='examplemodel',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='M2MModel',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('name', models.CharField(max_length=12)),
|
||||
('models', models.ManyToManyField(to='ExampleModel')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Encounter',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('num', models.PositiveSmallIntegerField()),
|
||||
(
|
||||
'patient',
|
||||
models.ForeignKey(
|
||||
on_delete=models.deletion.PROTECT,
|
||||
to='patient',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
test_project/migrations/__init__.py
Normal file
0
test_project/migrations/__init__.py
Normal file
|
|
@ -1,11 +1,24 @@
|
|||
from django.db import models
|
||||
|
||||
from eav.decorators import register_eav
|
||||
from eav.models import EAVModelMeta
|
||||
|
||||
|
||||
class Patient(models.Model):
|
||||
class TestBase(models.Model):
|
||||
"""Base class for test models."""
|
||||
|
||||
class Meta(object):
|
||||
"""Define common options."""
|
||||
|
||||
app_label = 'test_project'
|
||||
abstract = True
|
||||
|
||||
|
||||
class Patient(TestBase):
|
||||
name = models.CharField(max_length=12)
|
||||
example = models.ForeignKey(
|
||||
'ExampleModel', null=True, blank=True, on_delete=models.PROTECT)
|
||||
'ExampleModel', null=True, blank=True, on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
|
@ -14,7 +27,7 @@ class Patient(models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
class Encounter(models.Model):
|
||||
class Encounter(TestBase):
|
||||
num = models.PositiveSmallIntegerField()
|
||||
patient = models.ForeignKey(Patient, on_delete=models.PROTECT)
|
||||
|
||||
|
|
@ -26,7 +39,7 @@ class Encounter(models.Model):
|
|||
|
||||
|
||||
@register_eav()
|
||||
class ExampleModel(models.Model):
|
||||
class ExampleModel(TestBase):
|
||||
name = models.CharField(max_length=12)
|
||||
|
||||
def __unicode__(self):
|
||||
|
|
@ -34,9 +47,23 @@ class ExampleModel(models.Model):
|
|||
|
||||
|
||||
@register_eav()
|
||||
class M2MModel(models.Model):
|
||||
class M2MModel(TestBase):
|
||||
name = models.CharField(max_length=12)
|
||||
models = models.ManyToManyField(ExampleModel)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ExampleMetaclassModel(TestBase, metaclass=EAVModelMeta):
|
||||
name = models.CharField(max_length=12)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class RegisterTestModel(TestBase, metaclass=EAVModelMeta):
|
||||
name = models.CharField(max_length=12)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
99
test_project/settings.py
Normal file
99
test_project/settings.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'secret!' # noqa: S105
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS: List[str] = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.postgres',
|
||||
# Test Project:
|
||||
'test_project.apps.TestAppConfig',
|
||||
# Our app:
|
||||
'eav',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': ':memory:',
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = []
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = False
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
from django.db import models
|
||||
from eav.models import EAVModelMeta
|
||||
|
||||
|
||||
class ExampleMetaclassModel(models.Model):
|
||||
__metaclass__ = EAVModelMeta
|
||||
name = models.CharField(max_length=12)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class RegisterTestModel(models.Model):
|
||||
__metaclass__ = EAVModelMeta
|
||||
name = models.CharField(max_length=12)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
from django.db import models
|
||||
from eav.models import EAVModelMeta
|
||||
|
||||
|
||||
class ExampleMetaclassModel(models.Model, metaclass=EAVModelMeta):
|
||||
name = models.CharField(max_length=12)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class RegisterTestModel(models.Model, metaclass=EAVModelMeta):
|
||||
name = models.CharField(max_length=12)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
|
@ -1,18 +1,11 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase
|
||||
|
||||
import sys
|
||||
import eav
|
||||
from eav.exceptions import IllegalAssignmentException
|
||||
from eav.models import Attribute, Value
|
||||
from eav.registry import EavConfig
|
||||
|
||||
from .models import Encounter, Patient
|
||||
|
||||
if sys.version_info[0] > 2:
|
||||
from .metaclass_models3 import RegisterTestModel
|
||||
else:
|
||||
from .metaclass_models2 import RegisterTestModel
|
||||
from test_project.models import Encounter, Patient, RegisterTestModel
|
||||
|
||||
|
||||
class Attributes(TestCase):
|
||||
|
|
@ -1,17 +1,14 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
import eav
|
||||
from eav.models import Attribute, Value, EnumValue, EnumGroup
|
||||
|
||||
from .models import Patient
|
||||
from eav.models import Attribute, EnumGroup, EnumValue, Value
|
||||
from test_project.models import Patient
|
||||
|
||||
|
||||
class DataValidation(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
eav.register(Patient)
|
||||
|
||||
|
|
@ -32,7 +29,9 @@ class DataValidation(TestCase):
|
|||
p.eav.age = 5
|
||||
p.save()
|
||||
|
||||
Attribute.objects.create(name='Weight', datatype=Attribute.TYPE_INT, required=True)
|
||||
Attribute.objects.create(
|
||||
name='Weight', datatype=Attribute.TYPE_INT, required=True
|
||||
)
|
||||
p.eav.age = 6
|
||||
self.assertRaises(ValidationError, p.save)
|
||||
p = Patient.objects.get(name='Bob')
|
||||
|
|
@ -43,10 +42,12 @@ class DataValidation(TestCase):
|
|||
self.assertEqual(p.eav.weight, 23)
|
||||
|
||||
def test_create_required_field(self):
|
||||
Attribute.objects.create(name='Weight', datatype=Attribute.TYPE_INT, required=True)
|
||||
self.assertRaises(ValidationError,
|
||||
Patient.objects.create,
|
||||
name='Joe', eav__age=5)
|
||||
Attribute.objects.create(
|
||||
name='Weight', datatype=Attribute.TYPE_INT, required=True
|
||||
)
|
||||
self.assertRaises(
|
||||
ValidationError, Patient.objects.create, name='Joe', eav__age=5
|
||||
)
|
||||
self.assertEqual(Patient.objects.count(), 0)
|
||||
self.assertEqual(Value.objects.count(), 0)
|
||||
|
||||
|
|
@ -55,9 +56,9 @@ class DataValidation(TestCase):
|
|||
self.assertEqual(Value.objects.count(), 2)
|
||||
|
||||
def test_validation_error_create(self):
|
||||
self.assertRaises(ValidationError,
|
||||
Patient.objects.create,
|
||||
name='Joe', eav__age='df')
|
||||
self.assertRaises(
|
||||
ValidationError, Patient.objects.create, name='Joe', eav__age='df'
|
||||
)
|
||||
self.assertEqual(Patient.objects.count(), 0)
|
||||
self.assertEqual(Value.objects.count(), 0)
|
||||
|
||||
|
|
@ -108,7 +109,7 @@ class DataValidation(TestCase):
|
|||
p.eav.height = 15
|
||||
p.save()
|
||||
self.assertEqual(Patient.objects.get(pk=p.pk).eav.height, 15)
|
||||
p.eav.height='2.3'
|
||||
p.eav.height = '2.3'
|
||||
p.save()
|
||||
self.assertEqual(Patient.objects.get(pk=p.pk).eav.height, 2.3)
|
||||
|
||||
|
|
@ -150,7 +151,9 @@ class DataValidation(TestCase):
|
|||
ynu.values.add(yes)
|
||||
ynu.values.add(no)
|
||||
ynu.values.add(unkown)
|
||||
Attribute.objects.create(name='Fever?', datatype=Attribute.TYPE_ENUM, enum_group=ynu)
|
||||
Attribute.objects.create(
|
||||
name='Fever?', datatype=Attribute.TYPE_ENUM, enum_group=ynu
|
||||
)
|
||||
|
||||
p = Patient.objects.create(name='Joe')
|
||||
p.eav.fever = 5
|
||||
|
|
@ -204,4 +207,6 @@ class DataValidation(TestCase):
|
|||
self.assertRaises(ValidationError, p.save)
|
||||
p.eav.multi = "one;two;three"
|
||||
p.save()
|
||||
self.assertEqual(Patient.objects.get(pk=p.pk).eav.multi, ["one","two","three"])
|
||||
self.assertEqual(
|
||||
Patient.objects.get(pk=p.pk).eav.multi, ["one", "two", "three"]
|
||||
)
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
from django.test import TestCase
|
||||
import sys
|
||||
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.core.handlers.base import BaseHandler
|
||||
from django.forms import ModelForm
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
import eav
|
||||
import sys
|
||||
from eav.admin import *
|
||||
from .models import Patient, M2MModel, ExampleModel
|
||||
from eav.models import Attribute
|
||||
from eav.forms import BaseDynamicEntityForm
|
||||
from django.contrib import admin
|
||||
from django.core.handlers.base import BaseHandler
|
||||
from django.test.client import RequestFactory
|
||||
from django.forms import ModelForm
|
||||
from eav.models import Attribute
|
||||
from test_project.models import ExampleModel, M2MModel, Patient
|
||||
|
||||
|
||||
class MockRequest(RequestFactory):
|
||||
|
|
@ -24,8 +24,10 @@ class MockRequest(RequestFactory):
|
|||
if sys.version_info[0] < 2:
|
||||
for middleware_method in handler._request_middleware:
|
||||
if middleware_method(request):
|
||||
raise Exception("Couldn't create request mock object - "
|
||||
"request middleware returned a response")
|
||||
raise Exception(
|
||||
"Couldn't create request mock object - "
|
||||
"request middleware returned a response"
|
||||
)
|
||||
return request
|
||||
|
||||
|
||||
|
|
@ -66,9 +68,7 @@ class Forms(TestCase):
|
|||
gender_group.values.add(self.female, self.male)
|
||||
|
||||
Attribute.objects.create(
|
||||
name='gender',
|
||||
datatype=Attribute.TYPE_ENUM,
|
||||
enum_group=gender_group
|
||||
name='gender', datatype=Attribute.TYPE_ENUM, enum_group=gender_group
|
||||
)
|
||||
|
||||
self.instance = Patient.objects.create(name='Jim Morrison')
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from eav.models import EnumGroup, Attribute, Value, EnumValue
|
||||
|
||||
import eav
|
||||
from .models import Patient
|
||||
from eav.models import Attribute, EnumGroup, EnumValue, Value
|
||||
from test_project.models import Patient
|
||||
|
||||
|
||||
class MiscModels(TestCase):
|
||||
|
|
@ -14,7 +13,9 @@ class MiscModels(TestCase):
|
|||
|
||||
def test_attribute_help_text(self):
|
||||
desc = 'Patient Age'
|
||||
a = Attribute.objects.create(name='age', description=desc, datatype=Attribute.TYPE_INT)
|
||||
a = Attribute.objects.create(
|
||||
name='age', description=desc, datatype=Attribute.TYPE_INT
|
||||
)
|
||||
self.assertEqual(a.help_text, desc)
|
||||
|
||||
def test_setting_to_none_deletes_value(self):
|
||||
|
|
@ -32,7 +33,9 @@ class MiscModels(TestCase):
|
|||
ynu = EnumGroup.objects.create(name='Yes / No / Unknown')
|
||||
ynu.values.add(yes)
|
||||
ynu.values.add(no)
|
||||
Attribute.objects.create(name='is_patient', datatype=Attribute.TYPE_ENUM, enum_group=ynu)
|
||||
Attribute.objects.create(
|
||||
name='is_patient', datatype=Attribute.TYPE_ENUM, enum_group=ynu
|
||||
)
|
||||
eav.register(Patient)
|
||||
p = Patient.objects.create(name='Joe')
|
||||
p.eav.is_patient = 'yes'
|
||||
|
|
@ -6,8 +6,7 @@ from django.test import TestCase
|
|||
import eav
|
||||
from eav.models import Attribute, EnumGroup, EnumValue, Value
|
||||
from eav.registry import EavConfig
|
||||
|
||||
from .models import Encounter, Patient
|
||||
from test_project.models import Encounter, Patient
|
||||
|
||||
|
||||
class Queries(TestCase):
|
||||
|
|
@ -32,7 +31,9 @@ class Queries(TestCase):
|
|||
ynu.values.add(self.no)
|
||||
ynu.values.add(self.unknown)
|
||||
|
||||
Attribute.objects.create(name='fever', datatype=Attribute.TYPE_ENUM, enum_group=ynu)
|
||||
Attribute.objects.create(
|
||||
name='fever', datatype=Attribute.TYPE_ENUM, enum_group=ynu
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
eav.unregister(Encounter)
|
||||
|
|
@ -46,26 +47,27 @@ class Queries(TestCase):
|
|||
# Name, age, fever,
|
||||
# city, country, extras
|
||||
# possible illness
|
||||
['Anne', 3, no,
|
||||
'New York', 'USA', {"chills": "yes"},
|
||||
"cold"
|
||||
],
|
||||
['Bob', 15, no,
|
||||
'Bamako', 'Mali', {},
|
||||
""
|
||||
],
|
||||
['Cyrill', 15, yes,
|
||||
'Kisumu', 'Kenya', {"chills": "yes", "headache": "no"},
|
||||
"flu"
|
||||
],
|
||||
['Daniel', 3, no,
|
||||
'Nice', 'France', {"headache": "yes"},
|
||||
"cold"
|
||||
],
|
||||
['Eugene', 2, yes,
|
||||
'France', 'Nice', {"chills": "no", "headache": "yes"},
|
||||
"flu;cold"
|
||||
]
|
||||
['Anne', 3, no, 'New York', 'USA', {"chills": "yes"}, "cold"],
|
||||
['Bob', 15, no, 'Bamako', 'Mali', {}, ""],
|
||||
[
|
||||
'Cyrill',
|
||||
15,
|
||||
yes,
|
||||
'Kisumu',
|
||||
'Kenya',
|
||||
{"chills": "yes", "headache": "no"},
|
||||
"flu",
|
||||
],
|
||||
['Daniel', 3, no, 'Nice', 'France', {"headache": "yes"}, "cold"],
|
||||
[
|
||||
'Eugene',
|
||||
2,
|
||||
yes,
|
||||
'France',
|
||||
'Nice',
|
||||
{"chills": "no", "headache": "yes"},
|
||||
"flu;cold",
|
||||
],
|
||||
]
|
||||
|
||||
for row in data:
|
||||
|
|
@ -76,7 +78,7 @@ class Queries(TestCase):
|
|||
eav__city=row[3],
|
||||
eav__country=row[4],
|
||||
eav__extras=row[5],
|
||||
eav__illness=row[6]
|
||||
eav__illness=row[6],
|
||||
)
|
||||
|
||||
def test_get_or_create_with_eav(self):
|
||||
|
|
@ -95,7 +97,9 @@ class Queries(TestCase):
|
|||
self.assertEqual(Patient.objects.get(eav__age=6), p1)
|
||||
|
||||
Patient.objects.create(name='Fred', eav__age=6)
|
||||
self.assertRaises(MultipleObjectsReturned, lambda: Patient.objects.get(eav__age=6))
|
||||
self.assertRaises(
|
||||
MultipleObjectsReturned, lambda: Patient.objects.get(eav__age=6)
|
||||
)
|
||||
|
||||
def test_filtering_on_normal_and_eav_fields(self):
|
||||
self.init_data()
|
||||
|
|
@ -110,8 +114,8 @@ class Queries(TestCase):
|
|||
self.assertEqual(p.count(), 0)
|
||||
|
||||
# Anne, Daniel
|
||||
q1 = Q(eav__age__gte=3) # Everyone except Eugene
|
||||
q2 = Q(eav__age__lt=15) # Anne, Daniel, Eugene
|
||||
q1 = Q(eav__age__gte=3) # Everyone except Eugene
|
||||
q2 = Q(eav__age__lt=15) # Anne, Daniel, Eugene
|
||||
p = Patient.objects.filter(q2 & q1)
|
||||
self.assertEqual(p.count(), 2)
|
||||
|
||||
|
|
@ -140,11 +144,11 @@ class Queries(TestCase):
|
|||
self.assertEqual(p.count(), 5)
|
||||
|
||||
# Anne, Bob, Daniel
|
||||
q1 = Q(eav__fever=self.no) # Anne, Bob, Daniel
|
||||
q2 = Q(eav__fever=self.yes) # Cyrill, Eugene
|
||||
q3 = Q(eav__country__contains='e') # Cyrill, Daniel, Eugene
|
||||
q4 = q2 & q3 # Cyrill, Daniel, Eugene
|
||||
q5 = (q1 | q4) & q1 # Anne, Bob, Daniel
|
||||
q1 = Q(eav__fever=self.no) # Anne, Bob, Daniel
|
||||
q2 = Q(eav__fever=self.yes) # Cyrill, Eugene
|
||||
q3 = Q(eav__country__contains='e') # Cyrill, Daniel, Eugene
|
||||
q4 = q2 & q3 # Cyrill, Daniel, Eugene
|
||||
q5 = (q1 | q4) & q1 # Anne, Bob, Daniel
|
||||
p = Patient.objects.filter(q5)
|
||||
self.assertEqual(p.count(), 3)
|
||||
|
||||
|
|
@ -218,7 +222,6 @@ class Queries(TestCase):
|
|||
p = Patient.objects.filter(~q1)
|
||||
self.assertEqual(p.count(), 1)
|
||||
|
||||
|
||||
def _order(self, ordering):
|
||||
query = Patient.objects.all().order_by(*ordering)
|
||||
return list(query.values_list('name', flat=True))
|
||||
|
|
@ -226,32 +229,32 @@ class Queries(TestCase):
|
|||
def assert_order_by_results(self, eav_attr='eav'):
|
||||
self.assertEqual(
|
||||
['Bob', 'Eugene', 'Cyrill', 'Anne', 'Daniel'],
|
||||
self._order(['%s__city' % eav_attr])
|
||||
self._order(['%s__city' % eav_attr]),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
['Eugene', 'Anne', 'Daniel', 'Bob', 'Cyrill'],
|
||||
self._order(['%s__age' % eav_attr, '%s__city' % eav_attr])
|
||||
self._order(['%s__age' % eav_attr, '%s__city' % eav_attr]),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
['Eugene', 'Cyrill', 'Anne', 'Daniel', 'Bob'],
|
||||
self._order(['%s__fever' % eav_attr, '%s__age' % eav_attr])
|
||||
self._order(['%s__fever' % eav_attr, '%s__age' % eav_attr]),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
['Eugene', 'Cyrill', 'Daniel', 'Bob', 'Anne'],
|
||||
self._order(['%s__fever' % eav_attr, '-name'])
|
||||
self._order(['%s__fever' % eav_attr, '-name']),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
['Eugene', 'Daniel', 'Cyrill', 'Bob', 'Anne'],
|
||||
self._order(['-name', '%s__age' % eav_attr])
|
||||
self._order(['-name', '%s__age' % eav_attr]),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
['Anne', 'Bob', 'Cyrill', 'Daniel', 'Eugene'],
|
||||
self._order(['example__name'])
|
||||
self._order(['example__name']),
|
||||
)
|
||||
|
||||
with self.assertRaises(NotSupportedError):
|
||||
|
|
@ -265,7 +268,6 @@ class Queries(TestCase):
|
|||
self.assert_order_by_results()
|
||||
|
||||
def test_order_by_with_custom_config(self):
|
||||
|
||||
class CustomConfig(EavConfig):
|
||||
eav_attr = "data"
|
||||
generic_relation_attr = "data_values"
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
from django.test import TestCase
|
||||
|
||||
import sys
|
||||
import eav
|
||||
from eav.registry import EavConfig
|
||||
|
||||
from .models import Encounter, ExampleModel, Patient
|
||||
|
||||
if sys.version_info[0] > 2:
|
||||
from .metaclass_models3 import ExampleMetaclassModel
|
||||
else:
|
||||
from .metaclass_models2 import ExampleMetaclassModel
|
||||
from test_project.models import (
|
||||
Encounter,
|
||||
ExampleMetaclassModel,
|
||||
ExampleModel,
|
||||
Patient,
|
||||
)
|
||||
|
||||
|
||||
class RegistryTests(TestCase):
|
||||
|
|
@ -81,9 +79,13 @@ class RegistryTests(TestCase):
|
|||
self.assertFalse(ExampleModel.objects.__class__.__name__ == 'EntityManager')
|
||||
|
||||
def test_unregistering_via_metaclass(self):
|
||||
self.assertTrue(ExampleMetaclassModel.objects.__class__.__name__ == 'EntityManager')
|
||||
self.assertTrue(
|
||||
ExampleMetaclassModel.objects.__class__.__name__ == 'EntityManager'
|
||||
)
|
||||
eav.unregister(ExampleMetaclassModel)
|
||||
self.assertFalse(ExampleMetaclassModel.objects.__class__.__name__ == 'EntityManager')
|
||||
self.assertFalse(
|
||||
ExampleMetaclassModel.objects.__class__.__name__ == 'EntityManager'
|
||||
)
|
||||
|
||||
def test_unregistering_unregistered_model_proceeds_silently(self):
|
||||
eav.unregister(Patient)
|
||||
|
|
@ -94,6 +96,7 @@ class RegistryTests(TestCase):
|
|||
|
||||
def test_doesnt_register_nonmodel(self):
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
@eav.decorators.register_eav()
|
||||
class Foo(object):
|
||||
pass
|
||||
|
|
@ -2,8 +2,7 @@ from django.test import TestCase
|
|||
|
||||
import eav
|
||||
from eav.registry import EavConfig
|
||||
|
||||
from .models import Patient, Encounter
|
||||
from test_project.models import Encounter, Patient
|
||||
|
||||
|
||||
class RegistryTests(TestCase):
|
||||
|
|
@ -19,8 +18,8 @@ class RegistryTests(TestCase):
|
|||
eav_attr = 'eav_field'
|
||||
generic_relation_attr = 'encounter_eav_values'
|
||||
generic_relation_related_name = 'encounters'
|
||||
eav.register(Encounter, EncounterEav)
|
||||
|
||||
eav.register(Encounter, EncounterEav)
|
||||
|
||||
def test_registering_with_defaults(self):
|
||||
eav.register(Patient)
|
||||
|
|
@ -28,10 +27,8 @@ class RegistryTests(TestCase):
|
|||
self.assertEqual(Patient._eav_config_cls.manager_attr, 'objects')
|
||||
self.assertFalse(Patient._eav_config_cls.manager_only)
|
||||
self.assertEqual(Patient._eav_config_cls.eav_attr, 'eav')
|
||||
self.assertEqual(Patient._eav_config_cls.generic_relation_attr,
|
||||
'eav_values')
|
||||
self.assertEqual(Patient._eav_config_cls.generic_relation_related_name,
|
||||
None)
|
||||
self.assertEqual(Patient._eav_config_cls.generic_relation_attr, 'eav_values')
|
||||
self.assertEqual(Patient._eav_config_cls.generic_relation_related_name, None)
|
||||
eav.unregister(Patient)
|
||||
|
||||
def test_registering_overriding_defaults(self):
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
SECRET_KEY = 'fake-key'
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.auth',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.messages', # Required for admin app.
|
||||
'django.contrib.contenttypes',
|
||||
'tests',
|
||||
'eav'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
# Following 3 middleware required for admin app.
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware'
|
||||
]
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
'TEST_NAME': os.path.join(BASE_DIR, 'test_db.sqlite3'),
|
||||
}
|
||||
}
|
||||
36
tox.ini
36
tox.ini
|
|
@ -1,32 +1,16 @@
|
|||
[tox]
|
||||
isolated_build = true
|
||||
envlist =
|
||||
py36-django{31,32,tip},
|
||||
py37-django{31,32,tip},
|
||||
py38-django{31,32,tip}
|
||||
py39-django{31,32,tip}
|
||||
migrationscheck
|
||||
py{36,37,38,39}-django22
|
||||
py{36,37,38,39}-django{31,32}
|
||||
py{38,39}-djangomain
|
||||
|
||||
[testenv]
|
||||
pip_pre=True
|
||||
|
||||
allowlist_externals = pytest
|
||||
deps =
|
||||
django31: Django >= 3.1, <3.2
|
||||
django32: Django >= 3.2
|
||||
djangotip: https://github.com/django/django/archive/refs/heads/main.tar.gz
|
||||
|
||||
django22: django ~= 2.2.0
|
||||
django31: django ~= 3.1.0
|
||||
django32: django ~= 3.2.0
|
||||
djangomain: https://github.com/django/django/archive/main.tar.gz
|
||||
commands =
|
||||
./runtests
|
||||
|
||||
[testenv:migrationscheck]
|
||||
pip_pre=True
|
||||
|
||||
deps =
|
||||
Django
|
||||
|
||||
setenv =
|
||||
DJANGO_SETTINGS_MODULE=tests.test_settings
|
||||
|
||||
# make test fail if missing migrations
|
||||
commands =
|
||||
django-admin makemigrations --check --dry-run
|
||||
|
||||
pytest --cov {envsitepackagesdir}/eav
|
||||
|
|
|
|||
Loading…
Reference in a new issue