pax_global_header00006660000000000000000000000064142722377330014524gustar00rootroot0000000000000052 comment=cf6e48d2158989ff0baf55dddb9e9ffe3e70c9ff model_bakery-1.7.0/000077500000000000000000000000001427223773300141665ustar00rootroot00000000000000model_bakery-1.7.0/.editorconfig000066400000000000000000000004541427223773300166460ustar00rootroot00000000000000# http://editorconfig.org root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.py] indent_style = space indent_size = 4 [*.{rst,ini}] indent_style = space indent_size = 4 [*.{yml,html,xml,xsl,json}] indent_style = space indent_size = 2 model_bakery-1.7.0/.github/000077500000000000000000000000001427223773300155265ustar00rootroot00000000000000model_bakery-1.7.0/.github/CODE_OF_CONDUCT.md000066400000000000000000000001311427223773300203200ustar00rootroot00000000000000This project follows [Django's Code of Conduct](https://www.djangoproject.com/conduct/). model_bakery-1.7.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001427223773300177115ustar00rootroot00000000000000model_bakery-1.7.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000006261427223773300224070ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the issue** A clear and concise description of what the issue is. **To Reproduce** Steps to reproduce the behavior. **Expected behavior** A clear and concise description of what you expected to happen. **Versions** - Python: [e.g. 3.10] - Django [e.g. 4.1] - Model Bakery [e.g. 1.6.0] model_bakery-1.7.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000002621427223773300213270ustar00rootroot00000000000000**Describe the change** A clear and concise description of what the change is. **PR Checklist** - [ ] Change is covered with tests - [ ] [CHANGELOG.md](CHANGELOG.md) is updated model_bakery-1.7.0/.github/dependabot.yml000066400000000000000000000003311427223773300203530ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "monthly" rebase-strategy: "auto" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" model_bakery-1.7.0/.github/workflows/000077500000000000000000000000001427223773300175635ustar00rootroot00000000000000model_bakery-1.7.0/.github/workflows/changelog.yml000066400000000000000000000007231427223773300222370ustar00rootroot00000000000000name: Changelog Reminder on: pull_request jobs: remind: runs-on: ubuntu-latest if: | !contains(github.event.pull_request.body, '[skip changelog]') && (github.actor != 'dependabot[bot]') steps: - uses: actions/checkout@v3 with: fetch-depth: ${{ github.event.pull_request.commits }} + 1 - name: Check that CHANGELOG is updated run: git diff origin/${{ github.base_ref }} --name-only | grep CHANGELOG.md model_bakery-1.7.0/.github/workflows/linter.yml000066400000000000000000000011251427223773300216020ustar00rootroot00000000000000name: Code Linter on: push: branches: - main pull_request: jobs: tests: name: Python ${{ matrix.python-version }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.10 uses: actions/setup-python@v4 with: python-version: "3.10" cache: pip - name: Install dependencies run: | python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade tox tox-gh-actions - name: Run tox linter run: python -m tox -c tox-linter.ini model_bakery-1.7.0/.github/workflows/release.yml000066400000000000000000000012221427223773300217230ustar00rootroot00000000000000name: PyPI release on: [release] jobs: package: name: Build & verify package runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.10" cache: pip - name: Install Python dependencies run: python -m pip install --upgrade pip setuptools wheel twine - name: Build dist packages run: python setup.py sdist bdist_wheel - name: Upload packages run: python -m twine upload dist/* env: TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} model_bakery-1.7.0/.github/workflows/tests.yml000066400000000000000000000026531427223773300214560ustar00rootroot00000000000000name: Tests on: push: branches: - main pull_request: jobs: tests: name: Python ${{ matrix.python-version }} runs-on: ubuntu-latest strategy: matrix: python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"] steps: - uses: actions/checkout@v3 - name: Setup PostgreSQL with PostGIS uses: huaxk/postgis-action@v1 with: postgresql version: '11' postgresql db: 'postgres' postgresql user: 'postgres' - name: Install PostgreSQL dependencies run: | sudo apt-get update sudo apt-get install -y gdal-bin psql -c "CREATE ROLE runner SUPERUSER LOGIN CREATEDB;" -U postgres -h localhost -p 5432 psql template1 -c "CREATE EXTENSION citext;" -U postgres -h localhost -p 5432 psql template1 -c "CREATE EXTENSION hstore;" -U postgres -h localhost -p 5432 psql template1 -c "CREATE EXTENSION postgis;" -U postgres -h localhost -p 5432 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: pip - name: Install dependencies run: | python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade tox tox-gh-actions - name: Run tox targets for ${{ matrix.python-version }} run: python -m tox model_bakery-1.7.0/.gitignore000066400000000000000000000001561427223773300161600ustar00rootroot00000000000000*.pyc *.swp *.swo *.kpf bin/ include/ lib/ local/ share/ *.egg-info/ .idea .tox build/ lib64 pyvenv.cfg dist/ model_bakery-1.7.0/.pre-commit-config.yaml000066400000000000000000000013761427223773300204560ustar00rootroot00000000000000repos: - repo: git@github.com:pre-commit/pre-commit-hooks rev: v3.2.0 hooks: - id: check-added-large-files - id: debug-statements - id: end-of-file-fixer - id: requirements-txt-fixer - id: trailing-whitespace - repo: local hooks: - id: flake8 name: flake8 entry: flake8 language: system types: - python - id: isort name: isort entry: isort language: system types: - python - id: pydocstyle name: pydocstyle entry: pydocstyle language: system types: - python - id: black name: black entry: black language: system types: - python model_bakery-1.7.0/CHANGELOG.md000066400000000000000000000307101427223773300160000ustar00rootroot00000000000000# Changelog All notable changes to `model_bakery` will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased](https://github.com/model-bakers/model_bakery/tree/main) ### Added ### Changed ### Removed ## [1.7.0](https://pypi.org/project/model-bakery/1.7.0/) ### Changed - Fixed a bug with overwritten `_save_kwargs` and other custom arguments [PR #330](https://github.com/model-bakers/model_bakery/pull/330) ## [1.6.0](https://pypi.org/project/model-bakery/1.6.0/) ### Added - Python 3.11 support [PR #327](https://github.com/model-bakers/model_bakery/pull/327) - Django 4.1 support [PR #327](https://github.com/model-bakers/model_bakery/pull/327) - Added documentation for callables, iterables, sequences [PR #309](https://github.com/model-bakers/model_bakery/pull/309) ### Changed - [dev] Replace changelog reminder action with a custom solution that can ignore Dependabot PRs [PR #328](https://github.com/model-bakers/model_bakery/pull/328) ### Removed - Drop Python 3.6 support [PR #325](https://github.com/model-bakers/model_bakery/pull/325) - Drop Django 2.2 support [PR #326](https://github.com/model-bakers/model_bakery/pull/326) ## [1.5.0](https://pypi.org/project/model-bakery/1.5.0/) ### Added - Add py.typed export per [PEP 561](https://www.python.org/dev/peps/pep-0561/) [PR #158](https://github.com/model-bakers/model_bakery/pull/158) ### Changed - Extend type hints in `model_bakery.recipe` module, make `Recipe` class generic [PR #292](https://github.com/model-bakers/model_bakery/pull/292) - Explicitly add _fill_optional parameters to baker.make and baker.prepare to aid IDE autocomplete function. [PR #264](https://github.com/model-bakers/model_bakery/pull/264) - Fixed errors with reverse M2M relationships [PR #299](https://github.com/model-bakers/model_bakery/pull/299) - Fixed errors with reverse M2O relationships [PR #300](https://github.com/model-bakers/model_bakery/pull/300) - Improve exception message for unknown field types [PR #301](https://github.com/model-bakers/model_bakery/pull/301) - Fixed random generation of ContentType values when there is no database access [#290](https://github.com/model-bakers/model_bakery/pull/290) ## [1.4.0](https://pypi.org/project/model-bakery/1.4.0/) ### Added - Added postgis version to test settings - Add support for Python 3.10 [PR #244](https://github.com/model-bakers/model_bakery/pull/244) - Support for Django 4.0 [PR #236](https://github.com/model-bakers/model_bakery/pull/236) ### Changed - Validate `increment_by` parameter of `seq` helper when `value` is an instance of `datetime` [PR #247](https://github.com/model-bakers/model_bakery/pull/247) - Fix a simple typo in `bulk_create` disclaimer in docs [PR #245](https://github.com/model-bakers/model_bakery/pull/245) - Allow relation `_id` fields to use sequences [PR #253](https://github.com/model-bakers/model_bakery/pull/253) - Fix bulk_create not working with multi-database setup [PR #252](https://github.com/model-bakers/model_bakery/pull/252) - Conditionally support NullBooleanField, it's under deprecation and will be removed in Django 4.0 [PR #250](https://github.com/model-bakers/model_bakery/pull/250) - Fix Django max version pin in requirements file [PR #251](https://github.com/model-bakers/model_bakery/pull/251) - Improve type hinting to return the correct type depending on `_quantity` usage [PR #261](https://github.com/model-bakers/model_bakery/pull/261) ### Removed - Drop official Django 3.1 support. Django 2.2 is still supported, and 3.1 will likely keep working, but it’s not tested [PR #236](https://github.com/model-bakers/model_bakery/pull/236) ## [1.3.3](https://pypi.org/project/model-bakery/1.3.3/) ### Added - `_bulk_create` flag is not populating related objects as well [PR #206](https://github.com/model-bakers/model_bakery/pull/206) - Add support for iterators on GFK fields when using _quantity param [PR #207](https://github.com/model-bakers/model_bakery/pull/207) - Add support for iterators on many-to-many fields [PR#237](https://github.com/model-bakers/model_bakery/pull/237) ### Changed - Fix typos in Recipes documentation page [PR #212](https://github.com/model-bakers/model_bakery/pull/212) - Add `venv` to ignored folders of `flake8` and `pydocstyle` [PR#214](https://github.com/model-bakers/model_bakery/pull/214) - Run `flake8` after code modifications when linting [PR#214](https://github.com/model-bakers/model_bakery/pull/214) - Add typing for `baker.make` and `baker.prepare` [PR#213](https://github.com/model-bakers/model_bakery/pull/213) ## [1.3.2](https://pypi.org/project/model-bakery/1.3.2/) ### Changed - Fixed a bug (introduced in [1.2.1](https://pypi.org/project/model-bakery/1.2.1/)) that was breaking imports of recipes from non-installed-app modules [PR #201](https://github.com/model-bakers/model_bakery/pull/201) - Dependencies updates ## [1.3.1](https://pypi.org/project/model-bakery/1.3.1/) ### Added - [dev] Add explanations to imports in `generators.py` to match with current supported Django versions [PR #179](https://github.com/model-bakers/model_bakery/pull/179) ### Changed - Fix `requirements.txt` to cover Django 3.2 (everything from 2.2 till 4.0) [PR #182](https://github.com/model-bakers/model_bakery/pull/182) ## [1.3.0](https://pypi.org/project/model-bakery/1.3.0/) ### Added - Add Django 3.2 LTS support [PR #176](https://github.com/model-bakers/model_bakery/pull/176) - Add new `_bulk_create` parameter to `make` for using Django manager `bulk_create` with `_quantity` [PR #134](https://github.com/model-bakers/model_bakery/pull/134) - Add the functionality to import Django models using the `app_name.ModelName` convention in `import_from_str` [PR #140](https://github.com/model-bakers/model_bakery/pull/140) - Add the functionality to import recipes using `app_name.recipe_name` [PR #140](https://github.com/model-bakers/model_bakery/pull/140) - Add new `one_to_one` parameter to `foreign_key` to allow usage of `_quantity` for recipes based on models with OneToOne fields [PR #169](https://github.com/model-bakers/model_bakery/pull/169) - [docs] Improved documentation on Recipe's import string [PR #175](https://github.com/model-bakers/model_bakery/pull/175/) - [dev] Add a unit test for `utils.seq` [PR #143](https://github.com/model-bakers/model_bakery/pull/143) - [dev] Run CI against `main` Django branch to cover possible upcoming changes/deprecations [PR #159](https://github.com/model-bakers/model_bakery/pull/159) - [dev] Add GH Action for package releasing [PR #168](https://github.com/model-bakers/model_bakery/pull/168) ### Changed - Fixed a bug (introduced in 1.2.1) that was breaking creation of model instances with related model fields [PR #164](https://github.com/model-bakers/model_bakery/pull/164) - Type hinting fixed for Recipe "_model" parameter [PR #124](https://github.com/model-bakers/model_bakery/pull/124) - Dependencies updates from [dependabot](https://dependabot.com/) PRs [#170](https://github.com/model-bakers/model_bakery/pull/170) - [#171](https://github.com/model-bakers/model_bakery/pull/171) - [#172](https://github.com/model-bakers/model_bakery/pull/172) - [#173](https://github.com/model-bakers/model_bakery/pull/173) - [#174](https://github.com/model-bakers/model_bakery/pull/174) - [dev] Modify `setup.py` to not import the whole module for package data, but get it from `__about__.py` [PR #142](https://github.com/model-bakers/model_bakery/pull/142) - [dev] Add Dependabot config file [PR #146](https://github.com/model-bakers/model_bakery/pull/146) - [dev] Update Dependabot config file to support GH Actions and auto-rebase [PR #160](https://github.com/model-bakers/model_bakery/pull/160) ### Removed - `model_bakery.timezone.now` fallback (use `django.utils.timezone.now` instead) [PR #141](https://github.com/model-bakers/model_bakery/pull/141) - `model_bakery.timezone.smart_datetime` function (directly use `model_bakery.timezone.tz_aware` instead) [PR #147](https://github.com/model-bakers/model_bakery/pull/147) - Remove all signs of Django 1.11 (as we dropped it in 1.2.1) [PR #157](https://github.com/model-bakers/model_bakery/pull/157) - Drop unsupported Django 3.0 from CI (https://www.djangoproject.com/download/#unsupported-versions) [PR #176](https://github.com/model-bakers/model_bakery/pull/176) ## [1.2.1](https://pypi.org/project/model-bakery/1.2.1/) ### Added - Add ability to pass `str` values to `foreign_key` for recipes from other modules [PR #120](https://github.com/model-bakers/model_bakery/pull/120) - Add new parameter `_using` to support multi database Django applications [PR #126](https://github.com/model-bakers/model_bakery/pull/126) - [dev] Add instructions and script for running `postgres` and `postgis` tests. [PR #118](https://github.com/model-bakers/model_bakery/pull/118) ### Changed - Fixed _model parameter annotations [PR #115](https://github.com/model-bakers/model_bakery/pull/115) - Fixes bug when field has callable `default` [PR #117](https://github.com/model-bakers/model_bakery/pull/117) ### Removed - [dev] Drop Python 3.5 support as it is retired (https://www.python.org/downloads/release/python-3510/) [PR #119](https://github.com/model-bakers/model_bakery/pull/119) - [dev] Remove support for Django<2.2 ([more about Django supported versions](https://www.djangoproject.com/download/#supported-versions)) [PR #126](https://github.com/model-bakers/model_bakery/pull/126) ## [1.2.0](https://pypi.org/project/model-bakery/1.2.0/) ### Added - Support to django 3.1 `JSONField` [PR #85](https://github.com/model-bakers/model_bakery/pull/85) and [PR #106](https://github.com/model-bakers/model_bakery/pull/106) - Added type annotations [PR #100](https://github.com/model-bakers/model_bakery/pull/100) - Support for Python 3.9 [PR #113](https://github.com/model-bakers/model_bakery/pull/113/) - [dev] Changelog reminder (GitHub action) - Add pytest example ### Changed - Support for `prefix` in `seq` values ([PR #111](https://github.com/model-bakers/model_bakery/pull/111) fixes [Issue #93](https://github.com/model-bakers/model_bakery/issues/93)) - [dev] CI switched to GitHub Actions - [dev] Freeze dev requirements - [dev] Add Django 3.1 to test matrix [PR #103](https://github.com/model-bakers/model_bakery/pull/103) and [PR #112](https://github.com/model-bakers/model_bakery/pull/112) - [dev] pre-commit to use local packages (so versions will match) - [dev] consistent use of pydocstyle - [dev] Updates to MANIFEST.in - [dev] Correct field in recipe docs - [dev] Adjust imports for Django 3.1 compatibility [PR #112](https://github.com/model-bakers/model_bakery/pull/112) ### Removed ## [1.1.1](https://pypi.org/project/model-bakery/1.1.1/) ### Added - Support to Postgres fields: `DecimalRangeField`, `FloatRangeField`, `IntegerRangeField`, `BigIntegerRangeField`, `DateRangeField`, `DateTimeRangeField` [PR #80](https://github.com/model-bakers/model_bakery/pull/80) ### Changed - Add isort and fix imports [PR #77](https://github.com/model-bakers/model_bakery/pull/77) - Enable `seq` to be imported from `baker` [PR #76](https://github.com/model-bakers/model_bakery/pull/76) - Fix PostGIS model registration [PR #67](https://github.com/model-bakers/model_bakery/pull/67) ### Removed ## [1.1.0](https://pypi.org/project/model-bakery/1.1.0/) ### Added - Django 3.0 and Python 3.8 to CI [PR #48](https://github.com/model-bakers/model_bakery/pull/48/) ### Changed - Improve code comments [PR #31](https://github.com/model-bakers/model_bakery/pull/31) - Switch to tox-travis [PR #43](https://github.com/model-bakers/model_bakery/pull/43) - Add black job [PR #42](https://github.com/model-bakers/model_bakery/pull/42) - README.md instead of rst [PR #44](https://github.com/model-bakers/model_bakery/pull/44) - New `start` argument in `baker.seq` [PR #56](https://github.com/model-bakers/model_bakery/pull/56) - Fixes bug when registering custom fields generator via `settings.py` [PR #58](https://github.com/model-bakers/model_bakery/pull/58) - The different IntegerField types now will generate values on their min/max range [PR #59](https://github.com/model-bakers/model_bakery/pull/59) ### Removed ## [1.0.2](https://pypi.org/project/model-bakery/1.0.2/) ### Added ### Changed - Improvements on the migrations script ### Removed ## [1.0.1](https://pypi.org/project/model-bakery/1.0.1/) ### Added - Python script to help developers on migrating from Model Mommy to Model Bakery ### Changed ### Removed ## [1.0.0](https://pypi.org/project/model-bakery/1.0.0/) ### Added ### Changed - Rename model_mommy code to model_bakery ### Removed model_bakery-1.7.0/CONTRIBUTING.md000066400000000000000000000024731427223773300164250ustar00rootroot00000000000000## How to Contribute This page list all the steps you need to follow to set up `model_bakery` and be able to code it. Here they are: 1. [Fork this repo](https://github.com/model-bakers/model_bakery/fork) and clone it to your computer: ``` git clone git@github.com:YOUR_USER/model_bakery.git ``` 2. Install the dev dependencies: ``` pip install -r requirements_dev.txt ``` 3. Change the code and run your tests with: ``` make test ``` 4. We use [pre-commit](https://pre-commit.com/) to ensure a unique code formatting for the project. But, if you ran into any CI issues with that, make sure your code changes respect it: ``` make lint ``` If you don't follow the step 4, your PR may fail due to `black`, `isort`, `flake8` or `pydocstyle` warnings. To run `postgresql` and `postgis` specific tests: 1. [Install `docker`](https://docs.docker.com/get-docker/). 2. Install the `postgis` dependencies. Follow the [instructions from the Django docs](https://docs.djangoproject.com/en/3.1/ref/contrib/gis/install/geolibs/): If you are on Ubuntu/Debian you run the following: ```shell sudo apt update -y && sudo apt install -y binutils libproj-dev gdal-bin ``` 3. Run the following script: ```shell ./postgis-tests.sh ``` That will spin up a `docker` container with `postgresql` and `postgis` enabled and run the full test suite. model_bakery-1.7.0/LICENSE000066400000000000000000000011061427223773300151710ustar00rootroot00000000000000Copyright 2019 Vanderson Mota Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. model_bakery-1.7.0/MANIFEST.in000066400000000000000000000004521427223773300157250ustar00rootroot00000000000000include model_bakery/mock_file.txt include model_bakery/mock_img.jpeg include LICENSE include README.md include CHANGELOG.md include CONTRIBUTING.md include requirements.txt requirements_dev.txt include tox.ini recursive-include tests *.py recursive-include docs *.rst recursive-include utils *.py model_bakery-1.7.0/Makefile000066400000000000000000000004231427223773300156250ustar00rootroot00000000000000help: @echo "Available Targets:" @cat Makefile | egrep '^(\w+?):' | sed 's/:\(.*\)//g' | sed 's/^/- /g' test: @python -m pytest release: @python setup.py sdist bdist_wheel @twine upload dist/* lint: @black . @isort . @flake8 . @pydocstyle . .PHONY: test release model_bakery-1.7.0/README.md000066400000000000000000000046411427223773300154520ustar00rootroot00000000000000# Model Bakery: Smart fixtures for better tests *Model Bakery* offers you a smart way to create fixtures for testing in Django. With a simple and powerful API you can create many objects with a single line of code. Model Bakery is a rename of the legacy [Model Mommy project](https://pypi.org/project/model_mommy/). ![Tests](https://github.com/model-bakers/model_bakery/workflows/Tests/badge.svg) [![Latest PyPI version](https://img.shields.io/pypi/v/model_bakery.svg)](https://pypi.python.org/pypi/model_bakery/) [![Documentation Status](https://readthedocs.org/projects/model-bakery/badge/?version=latest)](https://model-bakery.readthedocs.io/en/latest/?badge=latest) ## Install ```bash pip install model_bakery ``` ## Usage and Info ### Basic usage ```python # models.py class Customer(models.Model): enjoy_jards_macale = models.BooleanField() name = models.CharField(max_length=30) email = models.EmailField() age = models.IntegerField() bio = models.TextField() days_since_last_login = models.BigIntegerField() birthday = models.DateField() last_shopping = models.DateTimeField() # test_models.py from django.test import TestCase from model_bakery import baker from pprint import pprint class TestCustomerModel(TestCase): def setUp(self): self.customer = baker.make('shop.Customer') pprint(self.customer.__dict__) """ {'_state': , 'age': 3841, 'bio': 'vUFzMUMyKzlnTyiCxfgODIhrnkjzgQwHtzIbtnVDKflqevczfnaOACkDNqvCHwvtWdLwoiKrCqfppAlogSLECtMmfleeveyqefkGyTGnpbkVQTtviQVDESpXascHAluGHYEotSypSiHvHzFteKIcUebrzUVigiOacfnGdvijEPrZdSCIIBjuXZMaWLrMXyrsUCdKPLRBRYklRdtZhgtxuASXdhNGhDsrnPHrYRClhrSJSVFojMkUHBvSZhoXoCrTfHsAjenCEHvcLeCecsXwXgWJcnJPSFdOmOpiHRnhSgRF', 'birthday': datetime.date(2019, 12, 3), 'enjoy_jards_macale': True, 'id': 1, 'last_shopping': datetime.datetime(2019, 12, 3, 21, 42, 34, 77019), 'name': 'qiayYnESvqcYLLBzxpFOcGBIfnQEPx', 'days_since_last_login': 6016} """ ``` Check out [documentation]() for more complete examples. ## Contributing Detailed info [here](https://github.com/model-bakers/model_bakery/blob/main/CONTRIBUTING.md). ## Maintainers - [Bernardo Fontes](https://github.com/berinhard/) - [Rustem Saiargaliev](https://github.com/amureki/) - [Tim Klein](https://github.com/timjklein36) ## Creator - [Vanderson Mota](https://github.com/vandersonmota/) model_bakery-1.7.0/docs/000077500000000000000000000000001427223773300151165ustar00rootroot00000000000000model_bakery-1.7.0/docs/Makefile000066400000000000000000000152031427223773300165570ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ModelMommy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ModelMommy.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/ModelMommy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ModelMommy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." model_bakery-1.7.0/docs/make.bat000066400000000000000000000150761427223773300165340ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source set I18NSPHINXOPTS=%SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ModelMommy.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ModelMommy.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end model_bakery-1.7.0/docs/source/000077500000000000000000000000001427223773300164165ustar00rootroot00000000000000model_bakery-1.7.0/docs/source/basic_usage.rst000066400000000000000000000226411427223773300214220ustar00rootroot00000000000000Basic Usage =========== Let's say you have an app **shop** with a model like this: File: **models.py** :: class Customer(models.Model): """ Model class Customer of shop app """ enjoy_jards_macale = models.BooleanField() name = models.CharField(max_length=30) email = models.EmailField() age = models.IntegerField() bio = models.TextField() days_since_last_login = models.BigIntegerField() birthday = models.DateField() last_shopping = models.DateTimeField() To create a persisted instance, just call Model Bakery: File: **test_models.py** :: #Core Django imports from django.test import TestCase #Third-party app imports from model_bakery import baker from shop.models import Customer class CustomerTestModel(TestCase): """ Class to test the model Customer """ def setUp(self): self.customer = baker.make(Customer) Importing every model over and over again is boring. So let Model Bakery import them for you: :: from model_bakery import baker # 1st form: app_label.model_name customer = baker.make('shop.Customer') # 2nd form: model_name product = baker.make('Product') .. note:: You can only use the 2nd form on unique model names. If you have an app shop with a Product, and an app stock with a Product, you must use the app_label.model_name form. .. note:: model_name is case insensitive. Model Relationships ------------------- Model Bakery also handles relationships. Let's say the customer has a purchase history: File: **models.py** :: class Customer(models.Model): """ Model class Customer of shop app """ enjoy_jards_macale = models.BooleanField() name = models.CharField(max_length=30) email = models.EmailField() age = models.IntegerField() bio = models.TextField() days_since_last_login = models.BigIntegerField() birthday = models.DateField() appointment = models.DateTimeField() class PurchaseHistory(models.Model): """ Model class PurchaseHistory of shop app """ customer = models.ForeignKey('Customer') products = models.ManyToManyField('Product') year = models.IntegerField() You can use Model Bakery as: :: from django.test import TestCase from model_bakery import baker class PurchaseHistoryTestModel(TestCase): def setUp(self): self.history = baker.make('shop.PurchaseHistory') print(self.history.customer) It will also create the Customer, automagically. **NOTE: ForeignKeys and OneToOneFields** - Since Django 1.8, ForeignKey and OneToOne fields don't accept unpersisted model instances anymore. This means that if you run: :: baker.prepare('shop.PurchaseHistory') You'll end up with a persisted "Customer" instance. M2M Relationships ----------------- By default Model Bakery doesn't create related instances for many-to-many relationships. If you want them to be created, you have to turn it on as following: :: from django.test import TestCase from model_bakery import baker class PurchaseHistoryTestModel(TestCase): def setUp(self): self.history = baker.make('shop.PurchaseHistory', make_m2m=True) print(self.history.products.count()) Explicit M2M Relationships -------------------------- If you want to, you can prepare your own set of related object and pass it to Model Bakery. Here's an example: :: products_set = baker.prepare(Product, _quantity=5) history = baker.make(PurchaseHistory, products=products_set) Explicit values for fields -------------------------- By default, Model Bakery uses random values to populate the model's fields. But it's possible to explicitly set values for them as well. :: from django.test import TestCase from model_bakery import baker class CustomerTestModel(TestCase): def setUp(self): self.customer = baker.make( 'shop.Customer', age=21 ) self.older_customer = baker.make( 'shop.Customer', age=42 ) You can use callable to explicitly set values as: :: import random from django.test import TestCase from model_bakery import baker class CustomerTestModel(TestCase): def get_random_name(self): return random.choice(["Suraj Magdum", "Avadhut More", "Rohit Chile"]) def setUp(self): self.customer = baker.make( 'shop.Customer', age=21, name = self.get_random_name ) You can also use iterable to explicitly set values as: :: from django.test import TestCase from model_bakery import baker class CustomerTestModel(TestCase): def setUp(self): names = ("Onkar Awale", "Pruthviraj Patil", "Shubham Ojha") self.customer = baker.make( 'shop.Customer', age=21, name = itertools.cycle(names) ) Sometimes, you have a field with an unique value and using ``make`` can cause random errors. Also, passing an attribute value just to avoid uniqueness validation problems can be tedious. To solve this you can define a sequence with ``seq`` :: from django.test import TestCase from model_bakery import baker from model_bakery.recipe import seq class CustomerTestModel(TestCase): def setUp(self): self.customer = baker.make( 'shop.Customer', age=21, name = seq('Joe') ) Related objects fields are also reachable by their name or related names in a very similar way as Django does with `field lookups `_: :: from django.test import TestCase from model_bakery import baker class PurchaseHistoryTestModel(TestCase): def setUp(self): self.bob_history = baker.make( 'shop.PurchaseHistory', customer__name='Bob' ) Creating Files -------------- Model Bakery does not create files for FileField types. If you need to have the files created, you can pass the flag ``_create_files=True`` (defaults to ``False``) to either ``baker.make`` or ``baker.make_recipe``. **Important**: the lib does not do any kind of file clean up, so it's up to you to delete the files created by it. Non persistent objects ---------------------- If you don't need a persisted object, Model Bakery can handle this for you as well with the **prepare** method: .. code-block:: python from model_bakery import baker customer = baker.prepare('shop.Customer') It works like ``make`` method, but it doesn't persist the instance neither the related instances. If you want to persist only the related instances but not your model, you can use the ``_save_related`` parameter for it: .. code-block:: python from model_bakery import baker history = baker.prepare('shop.PurchaseHistory', _save_related=True) assert history.id is None assert bool(history.customer.id) is True More than one instance ---------------------- If you need to create more than one instance of the model, you can use the ``_quantity`` parameter for it: .. code-block:: python from model_bakery import baker customers = baker.make('shop.Customer', _quantity=3) assert len(customers) == 3 It also works with ``prepare``: .. code-block:: python from model_bakery import baker customers = baker.prepare('shop.Customer', _quantity=3) assert len(customers) == 3 The ``make`` method also accepts a parameter ``_bulk_create`` to use Django's `bulk_create `_ method instead of calling ``obj.save()`` for each created instance. .. note:: Django's ``bulk_create`` does not update the created object primary key as explained in their docs. Because of that, there's no way for model-bakery to avoid calling ``save`` method for all the foreign keys. But this behavior can depends on which Django version and database backend you're using. So, for example, if you're trying to create 20 instances of a model with a foreign key using ``_bulk_create`` this will result in 21 queries (20 for each foreign key object and one to bulk create your 20 instances). If you want to avoid that, you'll have to perform individual bulk creations per foreign keys as the following example: .. code-block:: python from model_bakery import baker baker.prepare(User, _quantity=5, _bulk_create=True) user_iter = User.objects.all().iterator() baker.prepare(Profile, user=user_iter, _quantity=5, _bulk_create=True) Multi-database support ---------------------- Model Bakery supports django application with more than one database. If you want to determine which database bakery should use, you have the ``_using`` parameter: .. code-block:: python from model_bakery import baker custom_db = "your_custom_db" assert custom_db in settings.DATABASES history = baker.make('shop.PurchaseHistory', _using=custom_db) assert history in PurchaseHistory.objects.using(custom_db).all() assert history.customer in Customer.objects.using(custom_db).all() # default database tables with no data assert not PurchaseHistory.objects.exists() assert not Customer.objects.exists() model_bakery-1.7.0/docs/source/conf.py000066400000000000000000000250211427223773300177150ustar00rootroot00000000000000# Model Bakery documentation build configuration file, created by # sphinx-quickstart on Wed Apr 30 11:58:35 2014. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "Model Bakery" copyright = "2014, Vanderson Mota" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y and the full version, including alpha/beta/rc tags. version = release = "1.7.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "ModelBakerydoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( "index", "ModelBakery.tex", "Model Bakery Documentation", "Lucas Simon Rodrigues Magalhaes", "manual", ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ( "index", "modelmommy", "Model Bakery Documentation", ["Lucas Simon Rodrigues Magalhaes"], 1, ) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "ModelBakery", "Model Bakery Documentation", "Lucas Simon Rodrigues Magalhaes", "ModelBakery", "One line description of project.", "Miscellaneous", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. epub_title = "Model Bakery" epub_author = "Lucas Simon Rodrigues Magalhaes" epub_publisher = "Lucas Simon Rodrigues Magalhaes" epub_copyright = "2014, Lucas Simon Rodrigues Magalhaes" # The basename for the epub file. It defaults to the project name. # epub_basename = 'Model Bakery' # The HTML theme for the epub output. Since the default themes are not optimized # for small screen space, using the same theme for HTML and epub output is # usually not wise. This defaults to 'epub', a theme designed to save visual # space. # epub_theme = 'epub' # The language of the text. It defaults to the language option # or en if the language is not set. # epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. # epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. # epub_identifier = '' # A unique identification for the text. # epub_uid = '' # A tuple containing the cover image and cover page html template filenames. # epub_cover = () # A sequence of (type, uri, title) tuples for the guide element of content.opf. # epub_guide = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_post_files = [] # A list of files that should not be packed into the epub file. # epub_exclude_files = [] # The depth of the table of contents in toc.ncx. # epub_tocdepth = 3 # Allow duplicate toc entries. # epub_tocdup = True # Choose between 'default' and 'includehidden'. # epub_tocscope = 'default' # Fix unsupported image types using the PIL. # epub_fix_images = False # Scale large images. # epub_max_image_width = 0 # How to display URL addresses: 'footnote', 'no', or 'inline'. # epub_show_urls = 'inline' # If false, no index is generated. # epub_use_index = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {"http://docs.python.org/": None} model_bakery-1.7.0/docs/source/how_bakery_behaves.rst000066400000000000000000000116301427223773300230000ustar00rootroot00000000000000How Model Bakery behaves? ========================= By default, Model Bakery skips fields with ``null=True`` or ``blank=True``. Also if a field has a ``default`` value, it will be used. You can override this behavior by: 1. Explicitly defining values .. code-block:: python # from "Basic Usage" page, assume all fields either null=True or blank=True from model_bakery import baker customer = baker.make('shop.Customer', enjoy_jards_macale=True, bio="A fan of Jards MalacĂ©") 2. Passing ``_fill_optional`` with a list of fields to fill with random data .. code-block:: python customer = baker.make('shop.Customer', _fill_optional=['enjoy_jards_macale', 'bio']) 3. Passing ``_fill_optional=True`` to fill all fields with random data .. code-block:: python customer = baker.make('shop.Customer', _fill_optional=True) When shouldn't you let Baker generate things for you? ----------------------------------------------------- If you have fields with special validation, you should set their values by yourself. Model Bakery should handle fields that: 1. don't matter for the test you're writing; 2. don't require special validation (like unique, etc); 3. are required to create the object. Currently supported fields -------------------------- * ``BooleanField``, ``NullBooleanField``, ``IntegerField``, ``BigIntegerField``, ``SmallIntegerField``, ``PositiveIntegerField``, ``PositiveSmallIntegerField``, ``FloatField``, ``DecimalField`` * ``CharField``, ``TextField``, ``BinaryField``, ``SlugField``, ``URLField``, ``EmailField``, ``IPAddressField``, ``GenericIPAddressField``, ``ContentType`` * ``ForeignKey``, ``OneToOneField``, ``ManyToManyField`` (even with through model) * ``DateField``, ``DateTimeField``, ``TimeField``, ``DurationField`` * ``FileField``, ``ImageField`` * ``JSONField``, ``ArrayField``, ``HStoreField`` * ``CICharField``, ``CIEmailField``, ``CITextField`` * ``DecimalRangeField``, ``IntegerRangeField``, ``BigIntegerRangeField``, ``FloatRangeField``, ``DateRangeField``, ``DateTimeRangeField`` Require ``django.contrib.gis`` in ``INSTALLED_APPS``: * ``GeometryField``, ``PointField``, ``LineStringField``, ``PolygonField``, ``MultiPointField``, ``MultiLineStringField``, ``MultiPolygonField``, ``GeometryCollectionField`` Custom fields ------------- Model Bakery allows you to define generators methods for your custom fields or overrides its default generators. This can be achieved by specifying the field and generator function for the ``generators.add`` function. Both can be the real python objects imported in settings or just specified as import path string. Examples: .. code-block:: python from model_bakery import baker def gen_func(): return 'value' baker.generators.add('test.generic.fields.CustomField', gen_func) .. code-block:: python # in the module code.path: def gen_func(): return 'value' # in your tests.py file: from model_bakery import baker baker.generators.add('test.generic.fields.CustomField', 'code.path.gen_func') Customizing Baker ----------------- In some rare cases, you might need to customize the way Baker base class behaves. This can be achieved by creating a new class and specifying it in your settings files. It is likely that you will want to extend Baker, however the minimum requirement is that the custom class have ``make`` and ``prepare`` functions. In order for the custom class to be used, make sure to use the ``model_bakery.baker.make`` and ``model_bakery.baker.prepare`` functions, and not ``model_bakery.baker.Baker`` directly. Examples: .. code-block:: python # in the module code.path: class CustomBaker(baker.Baker): def get_fields(self): return [ field for field in super(CustomBaker, self).get_fields() if not field isinstance CustomField ] # in your settings.py file: BAKER_CUSTOM_CLASS = 'code.path.CustomBaker' Additionaly, if you want to your created instance to be returned respecting one of your custom ModelManagers, you can use the ``_from_manager`` parameter as the example bellow: .. code-block:: python movie = baker.make(Movie, title='Old Boys', _from_manager='availables') # This will use the Movie.availables model manager Save method custom parameters ----------------------------- If you have overwritten the ``save`` method for a model, you can pass custom parameters to it using Model Bakery. Example: .. code-block:: python class ProjectWithCustomSave(models.Model) # some model fields created_by = models.ForeignKey(settings.AUTH_USER_MODEL) def save(self, user, *args, **kwargs): self.created_by = user return super(ProjectWithCustomSave, self).save(*args, **kwargs) #with model baker: user = baker.make(settings.AUTH_USER_MODEL) project = baker.make(ProjectWithCustomSave, _save_kwargs={'user': user}) assert user == project.user model_bakery-1.7.0/docs/source/index.rst000066400000000000000000000030431427223773300202570ustar00rootroot00000000000000Model Bakery: Smart fixtures for better tests ============================================= Model Bakery offers you a smart way to create fixtures for testing in Django. With a simple and powerful API you can create many objects with a single line of code. Model Bakery is a rename of the legacy `model_mommy's project `_. This is because the project's creator and maintainers decided to not reinforce gender stereotypes for women in technology. You can read more about this subject `here `_. Compatibility ============= Model Bakery supports Django >= 3.2. Install ======= Install it with ``pip`` .. code-block:: console $ pip install model_bakery Contributing to Model Bakery ============================ As an open source project, Model Bakery welcomes contributions of many forms. Examples of contributions include: * Code Patches * Documentation improvements * Bug reports Take a look in our `Github repo `_ for more instructions on how to set up your local environment to help Model Bakery to grow. Doubts? Loved it? Hated it? Suggestions? ======================================== Feel free to `open an issue `_ for support, development or ideas! Contents -------- .. toctree:: :maxdepth: 4 basic_usage recipes how_bakery_behaves test_runners migrating_from_mommy model_bakery-1.7.0/docs/source/migrating_from_mommy.rst000066400000000000000000000016741427223773300234020ustar00rootroot00000000000000Migrating from Model Mommy ========================== Model Bakery has a `Python script `_ to help you to migrate your project's test code from Model Mommy to Model Bakery. This script will rename recipe files and replace legacy imports by the new ones. **From your project's root dir**, execute the following commands: .. code-block:: console $ pip uninstall model_mommy $ pip install model_bakery $ wget https://raw.githubusercontent.com/model-bakers/model_bakery/main/utils/from_mommy_to_bakery.py $ python from_mommy_to_bakery.py --dry-run # will list the files that'll be changed $ python from_mommy_to_bakery.py # migrate from model_mommy to model_bakery $ python manage.py test This command will only migrate ``*.py`` files. Any other file type such as ``tox.ini``, ``requirements.txt`` etc, have to be updated manually. model_bakery-1.7.0/docs/source/recipes.rst000066400000000000000000000255141427223773300206110ustar00rootroot00000000000000Recipes ======= If you're not comfortable with random data or even if you just want to improve the semantics of the generated data, there's hope for you. You can define a **Recipe**, which is a set of rules to generate data for your models. It's also possible to store the Recipes in a module called *baker_recipes.py* at your app's root directory. This recipes can later be used with the ``make_recipe`` function: :: shop/ migrations/ __init__.py admin.py apps.py baker_recipes.py <--- where you should place your Recipes models.py tests.py views.py File: **baker_recipes.py** :: from model_bakery.recipe import Recipe from shop.models import Customer customer_joe = Recipe( Customer, name='John Doe', nickname='joe', age=18, birthday=date.today(), last_shopping=datetime.now() ) .. note:: You don't have to declare all the fields if you don't want to. Omitted fields will be generated automatically. File: **test_model.py** :: from django.test import TestCase from model_bakery import baker from shop.models import Customer, Contact class CustomerTestModel(TestCase): def setUp(self): # Load the recipe 'customer_joe' from 'shop/baker_recipes.py' self.customer_one = baker.make_recipe( 'shop.customer_joe' ) Or if you don't want a persisted instance: :: from model_bakery import baker baker.prepare_recipe('shop.customer_joe') .. note:: You don't have to place necessarily your ``baker_recipes.py`` file inside your app's root directory. If you have a tests directory within the app, for example, you can add your recipes inside it and still use ``make_recipe``/``prepare_recipe`` by adding the tests module to the string you've passed as an argument. For example: ``baker.make_recipe("shop.tests.customer_joe")`` So, short summary, you can place your ``baker_recipes.py`` **anywhere** you want to and to use it having in mind you'll only have to simulate an import but obfuscating the ``baker_recipes`` module from the import string. .. note:: You can use the _quantity parameter as well if you want to create more than one object from a single recipe. You can define recipes locally to your module or test case as well. This can be useful for cases where a particular set of values may be unique to a particular test case, but used repeatedly there. For example: File: **baker_recipes.py** :: company_recipe = Recipe(Company, name='WidgetCo') File: **test_model.py** :: class EmployeeTest(TestCase): def setUp(self): self.employee_recipe = Recipe( Employee, name=seq('Employee '), company=baker.make_recipe('app.company_recipe') ) def test_employee_list(self): self.employee_recipe.make(_quantity=3) # test stuff.... def test_employee_tasks(self): employee1 = self.employee_recipe.make() task_recipe = Recipe(Task, employee=employee1) task_recipe.make(status='done') task_recipe.make(due_date=datetime(2014, 1, 1)) # test stuff.... Recipes with foreign keys ------------------------- You can define ``foreign_key`` relations: .. code-block:: python from model_bakery.recipe import Recipe, foreign_key from shop.models import Customer, PurchaseHistory customer = Recipe(Customer, name='John Doe', nickname='joe', age=18, birthday=date.today(), appointment=datetime.now() ) history = Recipe(PurchaseHistory, owner=foreign_key(customer) ) Notice that ``customer`` is a *recipe*. You may be thinking: "I can put the Customer model instance directly in the owner field". That's not recommended. Using the ``foreign_key`` is important for 2 reasons: * Semantics. You'll know that attribute is a foreign key when you're reading; * The associated instance will be created only when you call ``make_recipe`` and not during recipe definition; You can also use ``related``, when you want two or more models to share the same parent: .. code-block:: python from model_bakery.recipe import related, Recipe from shop.models import Customer, PurchaseHistory history = Recipe(PurchaseHistory) customer_with_2_histories = Recipe(Customer, name='Albert', purchasehistory_set=related('history', 'history'), ) Note this will only work when calling ``make_recipe`` because the related manager requires the objects in the related_set to be persisted. That said, calling ``prepare_recipe`` the related_set will be empty. If you want to set m2m relationship you can use ``related`` as well: .. code-block:: python from model_bakery.recipe import related, Recipe pencil = Recipe(Product, name='Pencil') pen = Recipe(Product, name='Pen') history = Recipe(PurchaseHistory) history_with_prods = history.extend( products=related(pencil, pen) ) When creating models based on a ``foreign_key`` recipe using the ``_quantity`` argument, only one related model will be created for all new instances. .. code-block:: python from model_baker.recipe import foreign_key, Recipe person = Recipe(Person, name='Albert') dog = Recipe(Dog, owner=foreign_key(person)) # All dogs share the same owner dogs = dog.make_recipe(_quantity=2) assert dogs[0].owner.id == dogs[1].owner.id This will cause an issue if your models use ``OneToOneField``. In that case, you can provide ``one_to_one=True`` to the recipe to make sure every instance created by ``_quantity`` has a unique id. .. code-block:: python from model_baker.recipe import foreign_key, Recipe person = Recipe(Person, name='Albert') dog = Recipe(Dog, owner=foreign_key(person, one_to_one=True)) # Each dog has a unique owner dogs = dog.make_recipe(_quantity=2) assert dogs[0].owner.id != dogs[1].owner.id Recipes with callables ---------------------- It's possible to use ``callables`` as recipe's attribute value. .. code-block:: python from datetime import date from model_bakery.recipe import Recipe from shop.models import Customer customer = Recipe( Customer, birthday=date.today, ) When you call ``make_recipe``, Model Bakery will set the attribute to the value returned by the callable. Recipes with iterators ---------------------- You can also use *iterators* (including *generators*) to provide multiple values to a recipe. .. code-block:: python from itertools import cycle names = ['Ada Lovelace', 'Grace Hopper', 'Ida Rhodes', 'Barbara Liskov'] customer = Recipe(Customer, name=cycle(names) ) Model Bakery will use the next value in the *iterator* every time you create a model from the recipe. Sequences in recipes -------------------- Sometimes, you have a field with an unique value and using ``make`` can cause random errors. Also, passing an attribute value just to avoid uniqueness validation problems can be tedious. To solve this you can define a sequence with ``seq`` .. code-block:: python >>> from model_bakery.recipe import Recipe, seq >>> from shop.models import Customer >>> customer = Recipe(Customer, name=seq('Joe'), age=seq(15) ) >>> customer = baker.make_recipe('shop.customer') >>> customer.name 'Joe1' >>> customer.age 16 >>> new_customer = baker.make_recipe('shop.customer') >>> new_customer.name 'Joe2' >>> new_customer.age 17 This will append a counter to strings to avoid uniqueness problems and it will sum the counter with numerical values. An optional ``suffix`` parameter can be supplied to augment the value for cases like generating emails or other strings with common suffixes. .. code-block:: python >>> from model_bakery import.recipe import Recipe, seq >>> from shop.models import Customer >>> customer = Recipe(Customer, email=seq('user', suffix='@example.com')) >>> customer = baker.make_recipe('shop.customer') >>> customer.email 'user1@example.com' >>> customer = baker.make_recipe('shop.customer') >>> customer.email 'user2@example.com' Sequences and iterables can be used not only for recipes, but with ``baker`` as well: .. code-block:: python >>> from model_bakery import baker >>> customer = baker.make('Customer', name=baker.seq('Joe')) >>> customer.name 'Joe1' >>> customers = baker.make('Customer', name=baker.seq('Chad'), _quantity=3) >>> for customer in customers: ... print(customer.name) 'Chad1' 'Chad2' 'Chad3' You can also provide an optional ``increment_by`` argument which will modify incrementing behaviour. This can be an integer, float, Decimal or timedelta. If you want to start your increment differently, you can use the ``start`` argument, only if it's not a sequence for ``date``, ``datetime`` or ``time`` objects. .. code-block:: python >>> from datetime import date, timedelta >>> from model_bakery.recipe import Recipe, seq >>> from shop.models import Customer >>> customer = Recipe(Customer, age=seq(15, increment_by=3) height_ft=seq(5.5, increment_by=.25) # assume today's date is 21/07/2014 appointment=seq(date(2014, 7, 21), timedelta(days=1)), name=seq('Custom num: ', increment_by=2, start=5), ) >>> customer = baker.make_recipe('shop.customer') >>> customer.age 18 >>> customer.height_ft 5.75 >>> customer.appointment datetime.date(2014, 7, 22) >>> customer.name 'Custom num: 5' >>> new_customer = baker.make_recipe('shop.customer') >>> new_customer.age 21 >>> new_customer.height_ft 6.0 >>> new_customer.appointment datetime.date(2014, 7, 23) >>> customer.name 'Custom num: 7' Be aware that ``seq`` may query the database to determine when to reset. Therefore, a ``SimpleTestCase`` test method (which disallows database access) can call ``prepare_recipe`` on a Recipe with a ``seq`` once, but not not more than once within a test, even though the record itself is never saved to the database. Overriding recipe definitions ----------------------------- Passing values when calling ``make_recipe`` or ``prepare_recipe`` will override the recipe rule. .. code-block:: python from model_bakery import baker baker.make_recipe('shop.customer', name='Ada Lovelace') This is useful when you have to create multiple objects and you have some unique field, for instance. Recipe inheritance ------------------ If you need to reuse and override existent recipe call extend method: .. code-block:: python customer = Recipe( Customer, bio='Some customer bio', age=30, enjoy_jards_macale=True, ) sad_customer = customer.extend( enjoy_jards_macale=False, ) model_bakery-1.7.0/docs/source/test_runners.rst000066400000000000000000000027711427223773300217120ustar00rootroot00000000000000Test Runners ============ Most of the code examples shown so far have used the `Django TestCase `_ to explain how Model Bakery is used. However `pytest `_ (with the `pytest-django `_ plugin) is often preferred for it's simplicity and other benefits. See `here `_. The following examples show Model Bakery usage with different test runners. Django ------ :: # Core Django imports from django.test import TestCase # Third-party app imports from model_bakery import baker from shop.models import Customer class CustomerTestModel(TestCase): """ Class to test the model Customer """ def setUp(self): """Set up test class.""" self.customer = baker.make(Customer) def test_using_customer(self): """Test function using baked model.""" self.assertIsInstance(self.customer, Customer) pytest ------ :: # pytest import import pytest # Third-party app imports from model_bakery import baker from shop.models import Customer @pytest.fixture def customer(): """Fixture for baked Customer model.""" return baker.make(Customer) def test_using_customer(customer): """Test function using fixture of baked model.""" assert isinstance(customer, Customer) model_bakery-1.7.0/model_bakery/000077500000000000000000000000001427223773300166235ustar00rootroot00000000000000model_bakery-1.7.0/model_bakery/__about__.py000066400000000000000000000002431427223773300211020ustar00rootroot00000000000000__version__ = "1.7.0" __author__ = "berin" __email__ = "bernardoxhc@gmail.com" __url__ = "https://github.com/model-bakers/model_bakery" __license__ = "Apache 2.0" model_bakery-1.7.0/model_bakery/__init__.py000066400000000000000000000000371427223773300207340ustar00rootroot00000000000000from .utils import seq # NoQA model_bakery-1.7.0/model_bakery/_types.py000066400000000000000000000002021427223773300204720ustar00rootroot00000000000000from typing import TypeVar from django.db.models import Model M = TypeVar("M", bound=Model) NewM = TypeVar("NewM", bound=Model) model_bakery-1.7.0/model_bakery/baker.py000066400000000000000000000627341427223773300202750ustar00rootroot00000000000000from os.path import dirname, join from typing import ( Any, Callable, Dict, Generic, Iterator, List, Optional, Type, Union, cast, overload, ) from django.apps import apps from django.conf import settings from django.contrib import contenttypes from django.db.models import ( AutoField, BooleanField, Field, FileField, ForeignKey, ManyToManyField, Model, OneToOneField, ) from django.db.models.fields.proxy import OrderWrt from django.db.models.fields.related import ( ReverseManyToOneDescriptor as ForeignRelatedObjectsDescriptor, ) from django.db.models.fields.reverse_related import ( ManyToManyRel, ManyToOneRel, OneToOneRel, ) from . import generators, random_gen from ._types import M, NewM from .exceptions import ( AmbiguousModelName, CustomBakerNotFound, InvalidCustomBaker, InvalidQuantityException, ModelNotFound, RecipeIteratorEmpty, ) from .utils import seq # NoQA: enable seq to be imported from baker from .utils import import_from_str recipes = None # FIXME: use pkg_resource mock_file_jpeg = join(dirname(__file__), "mock_img.jpeg") mock_file_txt = join(dirname(__file__), "mock_file.txt") MAX_MANY_QUANTITY = 5 def _valid_quantity(quantity: Optional[Union[str, int]]) -> bool: return quantity is not None and (not isinstance(quantity, int) or quantity < 1) @overload def make( _model: Union[str, Type[M]], _quantity: None = None, make_m2m: bool = False, _save_kwargs: Optional[Dict] = None, _refresh_after_create: bool = False, _create_files: bool = False, _using: str = "", _bulk_create: bool = False, **attrs: Any, ) -> M: ... @overload def make( _model: Union[str, Type[M]], _quantity: int, make_m2m: bool = False, _save_kwargs: Optional[Dict] = None, _refresh_after_create: bool = False, _create_files: bool = False, _using: str = "", _bulk_create: bool = False, _fill_optional: Union[List[str], bool] = False, **attrs: Any, ) -> List[M]: ... def make( _model, _quantity: Optional[int] = None, make_m2m: bool = False, _save_kwargs: Optional[Dict] = None, _refresh_after_create: bool = False, _create_files: bool = False, _using: str = "", _bulk_create: bool = False, _fill_optional: Union[List[str], bool] = False, **attrs: Any, ): """Create a persisted instance from a given model its associated models. It fill the fields with random values or you can specify which fields you want to define its values by yourself. """ _save_kwargs = _save_kwargs or {} attrs.update({"_fill_optional": _fill_optional}) baker: Baker = Baker.create( _model, make_m2m=make_m2m, create_files=_create_files, _using=_using ) if _valid_quantity(_quantity): raise InvalidQuantityException if _quantity and _bulk_create: return bulk_create(baker, _quantity, _save_kwargs=_save_kwargs, **attrs) elif _quantity: return [ baker.make( _save_kwargs=_save_kwargs, _refresh_after_create=_refresh_after_create, **attrs, ) for _ in range(_quantity) ] return baker.make( _save_kwargs=_save_kwargs, _refresh_after_create=_refresh_after_create, **attrs ) @overload def prepare( _model: Union[str, Type[M]], _quantity: None = None, _save_related: bool = False, _using: str = "", **attrs, ) -> M: ... @overload def prepare( _model: Union[str, Type[M]], _quantity: int, _save_related: bool = False, _using: str = "", _fill_optional: Union[List[str], bool] = False, **attrs, ) -> List[M]: ... def prepare( _model: Union[str, Type[M]], _quantity: Optional[int] = None, _save_related: bool = False, _using: str = "", _fill_optional: Union[List[str], bool] = False, **attrs, ): """Create but do not persist an instance from a given model. It fill the fields with random values or you can specify which fields you want to define its values by yourself. """ attrs.update({"_fill_optional": _fill_optional}) baker = Baker.create(_model, _using=_using) if _valid_quantity(_quantity): raise InvalidQuantityException if _quantity: return [ baker.prepare(_save_related=_save_related, **attrs) for i in range(_quantity) ] else: return baker.prepare(_save_related=_save_related, **attrs) def _recipe(name: str) -> Any: app_name, recipe_name = name.rsplit(".", 1) try: module = apps.get_app_config(app_name).module pkg = module.__package__ if module else app_name except LookupError: pkg = app_name return import_from_str(".".join((pkg, "baker_recipes", recipe_name))) def make_recipe(baker_recipe_name, _quantity=None, _using="", **new_attrs): return _recipe(baker_recipe_name).make( _quantity=_quantity, _using=_using, **new_attrs ) def prepare_recipe( baker_recipe_name, _quantity=None, _save_related=False, _using="", **new_attrs ): return _recipe(baker_recipe_name).prepare( _quantity=_quantity, _save_related=_save_related, _using=_using, **new_attrs ) class ModelFinder: """Encapsulates all the logic for finding a model to Baker.""" _unique_models: Optional[Dict[str, Type[Model]]] = None _ambiguous_models: Optional[List[str]] = None def get_model(self, name: str) -> Type[Model]: """Get a model. Args: name (str): A name on the form 'applabel.modelname' or 'modelname' Returns: object: a model class """ try: if "." in name: app_label, model_name = name.split(".") model = apps.get_model(app_label, model_name) else: model = self.get_model_by_name(name) except LookupError: model = None if not model: raise ModelNotFound("Could not find model '%s'." % name.title()) return model def get_model_by_name(self, name: str) -> Optional[Type[Model]]: """Get a model by name. If a model with that name exists in more than one app, raises AmbiguousModelName. """ name = name.lower() if self._unique_models is None or self._ambiguous_models is None: self._populate() if name in cast(List, self._ambiguous_models): raise AmbiguousModelName( "%s is a model in more than one app. " 'Use the form "app.model".' % name.title() ) return cast(Dict, self._unique_models).get(name) def _populate(self) -> None: """Cache models for faster self._get_model.""" unique_models = {} ambiguous_models = [] all_models = apps.all_models for app_model in all_models.values(): for name, model in app_model.items(): if name not in unique_models: unique_models[name] = model else: ambiguous_models.append(name) for name in ambiguous_models: unique_models.pop(name, None) self._ambiguous_models = ambiguous_models self._unique_models = unique_models def is_iterator(value: Any) -> bool: if not hasattr(value, "__iter__"): return False return hasattr(value, "__next__") def _custom_baker_class() -> Optional[Type]: """Return the specified custom baker class. Returns: object: The custom class is specified by BAKER_CUSTOM_CLASS in Django's settings, or None if no custom class is defined. """ custom_class_string = getattr(settings, "BAKER_CUSTOM_CLASS", None) if custom_class_string is None: return None try: baker_class = import_from_str(custom_class_string) for required_function_name in ["make", "prepare"]: if not hasattr(baker_class, required_function_name): raise InvalidCustomBaker( 'Custom Baker classes must have a "%s" function' % required_function_name ) return baker_class except ImportError: raise CustomBakerNotFound( "Could not find custom baker class '%s'" % custom_class_string ) class Baker(Generic[M]): attr_mapping: Dict[str, Any] = {} type_mapping: Dict = {} # Note: we're using one finder for all Baker instances to avoid # rebuilding the model cache for every make_* or prepare_* call. finder = ModelFinder() @classmethod def create( cls, _model: Union[str, Type[NewM]], make_m2m: bool = False, create_files: bool = False, _using: str = "", ) -> "Baker[NewM]": """Create the baker class defined by the `BAKER_CUSTOM_CLASS` setting.""" baker_class = _custom_baker_class() or cls return cast(Type[Baker[NewM]], baker_class)( _model, make_m2m, create_files, _using=_using ) def __init__( self, _model: Union[str, Type[M]], make_m2m: bool = False, create_files: bool = False, _using: str = "", ) -> None: self.make_m2m = make_m2m self.create_files = create_files self.m2m_dict: Dict[str, List] = {} self.iterator_attrs: Dict[str, Iterator] = {} self.model_attrs: Dict[str, Any] = {} self.rel_attrs: Dict[str, Any] = {} self.rel_fields: List[str] = [] self._using = _using if isinstance(_model, str): self.model = cast(Type[M], self.finder.get_model(_model)) else: self.model = cast(Type[M], _model) self.init_type_mapping() def init_type_mapping(self) -> None: self.type_mapping = generators.get_type_mapping() generators_from_settings = getattr(settings, "BAKER_CUSTOM_FIELDS_GEN", {}) for k, v in generators_from_settings.items(): field_class = import_from_str(k) generator = import_from_str(v) self.type_mapping[field_class] = generator def make( self, _save_kwargs: Optional[Dict[str, Any]] = None, _refresh_after_create: bool = False, _from_manager=None, _fill_optional: Union[List[str], bool] = False, **attrs: Any, ): """Create and persist an instance of the model associated with Baker instance.""" params = { "commit": True, "commit_related": True, "_save_kwargs": _save_kwargs, "_refresh_after_create": _refresh_after_create, "_from_manager": _from_manager, "_fill_optional": _fill_optional, } params.update(attrs) return self._make(**params) def prepare( self, _save_related=False, _fill_optional: Union[List[str], bool] = False, **attrs: Any, ) -> M: """Create, but do not persist, an instance of the associated model.""" params = { "commit": False, "commit_related": _save_related, "_fill_optional": _fill_optional, } params.update(attrs) return self._make(**params) def get_fields(self) -> Any: return set(self.model._meta.get_fields()) - set(self.get_related()) def get_related( self, ) -> List[Union[ManyToOneRel, OneToOneRel, ManyToManyRel]]: return [r for r in self.model._meta.related_objects] def _make( self, commit=True, commit_related=True, _save_kwargs=None, _refresh_after_create=False, _from_manager=None, **attrs: Any, ) -> M: _save_kwargs = _save_kwargs or {} if self._using: _save_kwargs["using"] = self._using self._clean_attrs(attrs) for field in self.get_fields(): if self._skip_field(field): continue if isinstance(field, ManyToManyField): if field.name not in self.model_attrs: self.m2m_dict[field.name] = self.m2m_value(field) else: if field.name in self.iterator_attrs: self.model_attrs[field.name] = [ next(self.iterator_attrs[field.name]) ] else: self.m2m_dict[field.name] = self.model_attrs.pop(field.name) # is an _id relation that has a sequence defined elif ( (isinstance(field, OneToOneField) or isinstance(field, ForeignKey)) and hasattr(field, "attname") and field.attname in self.iterator_attrs ): self.model_attrs[field.attname] = next( self.iterator_attrs[field.attname] ) elif field.name not in self.model_attrs: if ( not isinstance(field, ForeignKey) or hasattr(field, "attname") and field.attname not in self.model_attrs ): self.model_attrs[field.name] = self.generate_value( field, commit_related ) elif callable(self.model_attrs[field.name]): self.model_attrs[field.name] = self.model_attrs[field.name]() elif field.name in self.iterator_attrs: try: self.model_attrs[field.name] = next(self.iterator_attrs[field.name]) except StopIteration: raise RecipeIteratorEmpty( "{0} iterator is empty.".format(field.name) ) instance = self.instance( self.model_attrs, _commit=commit, _from_manager=_from_manager, _save_kwargs=_save_kwargs, ) if commit: for related in self.get_related(): self.create_by_related_name(instance, related) if _refresh_after_create: instance.refresh_from_db() return instance def m2m_value(self, field: ManyToManyField) -> List[Any]: if field.name in self.rel_fields: return self.generate_value(field) if not self.make_m2m or field.null and not field.fill_optional: return [] return self.generate_value(field) def instance( self, attrs: Dict[str, Any], _commit, _save_kwargs, _from_manager ) -> M: one_to_many_keys = {} for k in tuple(attrs.keys()): field = getattr(self.model, k, None) if isinstance(field, ForeignRelatedObjectsDescriptor): one_to_many_keys[k] = attrs.pop(k) instance = self.model(**attrs) # m2m only works for persisted instances if _commit: instance.save(**_save_kwargs) self._handle_one_to_many(instance, one_to_many_keys) self._handle_m2m(instance) if _from_manager: # Fetch the instance using the given Manager, e.g. # 'objects'. This will ensure any additional code # within its get_queryset() method (e.g. annotations) # is run. manager = getattr(self.model, _from_manager) instance = cast(M, manager.get(pk=instance.pk)) return instance def create_by_related_name( self, instance: Model, related: Union[ManyToOneRel, OneToOneRel] ) -> None: rel_name = related.get_accessor_name() if not rel_name or rel_name not in self.rel_fields: return kwargs = filter_rel_attrs(rel_name, **self.rel_attrs) kwargs[related.field.name] = instance make(related.field.model, **kwargs) def _clean_attrs(self, attrs: Dict[str, Any]) -> None: def is_rel_field(x: str): return "__" in x self.fill_in_optional = attrs.pop("_fill_optional", False) # error for non existing fields if isinstance(self.fill_in_optional, (tuple, list, set)): # parents and relations wrong_fields = set(self.fill_in_optional) - set( f.name for f in self.get_fields() ) if wrong_fields: raise AttributeError( "_fill_optional field(s) %s are not related to model %s" % (list(wrong_fields), self.model.__name__) ) self.iterator_attrs = dict((k, v) for k, v in attrs.items() if is_iterator(v)) self.model_attrs = dict((k, v) for k, v in attrs.items() if not is_rel_field(k)) self.rel_attrs = dict((k, v) for k, v in attrs.items() if is_rel_field(k)) self.rel_fields = [ x.split("__")[0] for x in self.rel_attrs.keys() if is_rel_field(x) ] def _skip_field(self, field: Field) -> bool: from django.contrib.contenttypes.fields import GenericRelation # check for fill optional argument if isinstance(self.fill_in_optional, bool): field.fill_optional = self.fill_in_optional else: field.fill_optional = field.name in self.fill_in_optional if isinstance(field, FileField) and not self.create_files: return True # Don't Skip related _id fields defined in the iterator attributes if ( (isinstance(field, OneToOneField) or isinstance(field, ForeignKey)) and hasattr(field, "attname") and field.attname in self.iterator_attrs ): return False # Skip links to parent so parent is not created twice. if isinstance(field, OneToOneField) and self._remote_field(field).parent_link: return True if isinstance(field, (AutoField, GenericRelation, OrderWrt)): return True if all( [ field.name not in self.model_attrs, field.name not in self.rel_fields, field.name not in self.attr_mapping, ] ): # Django is quirky in that BooleanFields are always "blank", # but have no default. if not field.fill_optional and ( not issubclass(field.__class__, Field) or field.has_default() or (field.blank and not isinstance(field, BooleanField)) ): return True if field.name not in self.model_attrs: if field.name not in self.rel_fields and ( field.null and not field.fill_optional ): return True return False def _handle_one_to_many(self, instance: Model, attrs: Dict[str, Any]): for key, values in attrs.items(): manager = getattr(instance, key) for value in values: # Django will handle any operation to persist nested non-persisted FK because # save doesn't do so and, thus, raises constraint errors. That's why save() # only gets called if the object doesn't have a pk and also doesn't hold fk # pointers. fks = any( [ fk for fk in value._meta.fields if isinstance(fk, ForeignKey) or isinstance(fk, OneToOneField) ] ) if not value.pk and not fks: value.save() try: manager.set(values, bulk=False, clear=True) except TypeError: # for many-to-many relationships the bulk keyword argument doesn't exist manager.set(values, clear=True) def _handle_m2m(self, instance: Model): for key, values in self.m2m_dict.items(): for value in values: if not value.pk: value.save() m2m_relation = getattr(instance, key) through_model = m2m_relation.through # using related manager to fire m2m_changed signal if through_model._meta.auto_created: m2m_relation.add(*values) else: for value in values: base_kwargs = { m2m_relation.source_field_name: instance, m2m_relation.target_field_name: value, } make(through_model, _using=self._using, **base_kwargs) def _remote_field( self, field: Union[ForeignKey, OneToOneField] ) -> Union[OneToOneRel, ManyToOneRel]: return field.remote_field def generate_value(self, field: Field, commit: bool = True) -> Any: """Call the associated generator with a field passing all required args. Generator Resolution Precedence Order: -- `field.default` - model field default value, unless explicitly overwritten during baking -- `attr_mapping` - mapping per attribute name -- `choices` -- mapping from available field choices -- `type_mapping` - mapping from user defined type associated generators -- `default_mapping` - mapping from pre-defined type associated generators `attr_mapping` and `type_mapping` can be defined easily overwriting the model. """ is_content_type_fk = isinstance(field, ForeignKey) and issubclass( self._remote_field(field).model, contenttypes.models.ContentType ) # we only use default unless the field is overwritten in `self.rel_fields` if field.has_default() and field.name not in self.rel_fields: if callable(field.default): return field.default() return field.default elif field.name in self.attr_mapping: generator = self.attr_mapping[field.name] elif getattr(field, "choices"): generator = random_gen.gen_from_choices(field.choices) elif is_content_type_fk: generator = self.type_mapping[contenttypes.models.ContentType] elif generators.get(field.__class__): generator = generators.get(field.__class__) elif field.__class__ in self.type_mapping: generator = self.type_mapping[field.__class__] else: raise TypeError( f"field {field.name} type {field.__class__} is not supported by baker." ) # attributes like max_length, decimal_places are taken into account when # generating the value. field._using = self._using generator_attrs = get_required_values(generator, field) if field.name in self.rel_fields: generator_attrs.update(filter_rel_attrs(field.name, **self.rel_attrs)) if not commit: generator = getattr(generator, "prepare", generator) return generator(**generator_attrs) def get_required_values( generator: Callable, field: Field ) -> Dict[str, Union[bool, int, str, List[Callable]]]: """Get required values for a generator from the field. If required value is a function, calls it with field as argument. If required value is a string, simply fetch the value from the field and return. """ # FIXME: avoid abbreviations rt = {} # type: Dict[str, Any] if hasattr(generator, "required"): for item in generator.required: # type: ignore[attr-defined] if callable(item): # baker can deal with the nasty hacking too! key, value = item(field) rt[key] = value elif isinstance(item, str): rt[item] = getattr(field, item) else: raise ValueError( "Required value '%s' is of wrong type. \ Don't make baker sad." % str(item) ) return rt def filter_rel_attrs(field_name: str, **rel_attrs) -> Dict[str, Any]: clean_dict = {} for k, v in rel_attrs.items(): if k.startswith(field_name + "__"): splitted_key = k.split("__") key = "__".join(splitted_key[1:]) clean_dict[key] = v else: clean_dict[k] = v return clean_dict def bulk_create(baker: Baker[M], quantity: int, **kwargs) -> List[M]: """ Bulk create entries and all related FKs as well. Important: there's no way to avoid save calls since Django does not return the created objects after a bulk_insert call. """ _save_kwargs = {} if baker._using: _save_kwargs = {"using": baker._using} def _save_related_objs(model, objects) -> None: fk_fields = [ f for f in model._meta.fields if isinstance(f, OneToOneField) or isinstance(f, ForeignKey) ] for fk in fk_fields: fk_objects = [] for obj in objects: fk_obj = getattr(obj, fk.name, None) if fk_obj and not fk_obj.pk: fk_objects.append(fk_obj) if fk_objects: _save_related_objs(fk.related_model, fk_objects) for i, fk_obj in enumerate(fk_objects): fk_obj.save(**_save_kwargs) setattr(objects[i], fk.name, fk_obj) entries = [ baker.prepare( **kwargs, ) for _ in range(quantity) ] _save_related_objs(baker.model, entries) if baker._using: # Try to use the desired DB and let Django fail if spanning # relationships without the proper router setup manager = baker.model._base_manager.using(baker._using) else: manager = baker.model._base_manager return manager.bulk_create(entries) model_bakery-1.7.0/model_bakery/exceptions.py000066400000000000000000000005151427223773300213570ustar00rootroot00000000000000class RecipeNotFound(Exception): pass class RecipeIteratorEmpty(Exception): pass class ModelNotFound(Exception): pass class AmbiguousModelName(Exception): pass class InvalidQuantityException(Exception): pass class CustomBakerNotFound(Exception): pass class InvalidCustomBaker(Exception): pass model_bakery-1.7.0/model_bakery/generators.py000066400000000000000000000150311427223773300213460ustar00rootroot00000000000000from decimal import Decimal from typing import Any, Callable, Dict, Optional, Type, Union from django.db.backends.base.operations import BaseDatabaseOperations from django.db.models import ( BigIntegerField, BinaryField, BooleanField, CharField, DateField, DateTimeField, DecimalField, DurationField, EmailField, FileField, FloatField, ForeignKey, GenericIPAddressField, ImageField, IntegerField, IPAddressField, ManyToManyField, OneToOneField, PositiveIntegerField, PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField, TimeField, URLField, UUIDField, ) from . import random_gen from .utils import import_from_str try: # Proper support starts with Django 3.0 # (as it uses `django/db/backends/base/operations.py` for matching ranges) from django.db.models import AutoField, BigAutoField, SmallAutoField except ImportError: AutoField = None BigAutoField = None SmallAutoField = None try: # added in Django 3.1 from django.db.models import PositiveBigIntegerField except ImportError: PositiveBigIntegerField = None try: # Replaced `django.contrib.postgres.fields.JSONField` in Django 3.1 from django.db.models import JSONField except ImportError: JSONField = None try: # PostgreSQL-specific field (only available when psycopg2 is installed) from django.contrib.postgres.fields import ArrayField except ImportError: ArrayField = None try: # Deprecated since Django 3.1 # PostgreSQL-specific field (only available when psycopg2 is installed) from django.contrib.postgres.fields import JSONField as PostgresJSONField except ImportError: PostgresJSONField = None try: # PostgreSQL-specific field (only available when psycopg2 is installed) from django.contrib.postgres.fields import HStoreField except ImportError: HStoreField = None try: # PostgreSQL-specific fields (only available when psycopg2 is installed) from django.contrib.postgres.fields.citext import ( CICharField, CIEmailField, CITextField, ) except ImportError: CICharField = None CIEmailField = None CITextField = None try: # Deprecated since Django 3.1 from django.db.models import NullBooleanField except ImportError: NullBooleanField = None try: # PostgreSQL-specific fields (only available when psycopg2 is installed) from django.contrib.postgres.fields.ranges import ( BigIntegerRangeField, DateRangeField, DateTimeRangeField, DecimalRangeField, IntegerRangeField, ) except ImportError: BigIntegerRangeField = None DateRangeField = None DateTimeRangeField = None DecimalRangeField = None IntegerRangeField = None try: # Deprecated since Django 2.2 from django.contrib.postgres.fields.ranges import FloatRangeField except ImportError: FloatRangeField = None def _make_integer_gen_by_range(field_type: Any) -> Callable: min_int, max_int = BaseDatabaseOperations.integer_field_ranges[field_type.__name__] def gen_integer(): return random_gen.gen_integer(min_int=min_int, max_int=max_int) return gen_integer default_mapping = { ForeignKey: random_gen.gen_related, OneToOneField: random_gen.gen_related, ManyToManyField: random_gen.gen_m2m, BooleanField: random_gen.gen_boolean, IntegerField: _make_integer_gen_by_range(IntegerField), BigIntegerField: _make_integer_gen_by_range(BigIntegerField), SmallIntegerField: _make_integer_gen_by_range(SmallIntegerField), PositiveIntegerField: _make_integer_gen_by_range(PositiveIntegerField), PositiveSmallIntegerField: _make_integer_gen_by_range(PositiveSmallIntegerField), FloatField: random_gen.gen_float, DecimalField: random_gen.gen_decimal, BinaryField: random_gen.gen_byte_string, CharField: random_gen.gen_string, TextField: random_gen.gen_text, SlugField: random_gen.gen_slug, UUIDField: random_gen.gen_uuid, DateField: random_gen.gen_date, DateTimeField: random_gen.gen_datetime, TimeField: random_gen.gen_time, URLField: random_gen.gen_url, EmailField: random_gen.gen_email, IPAddressField: random_gen.gen_ipv4, GenericIPAddressField: random_gen.gen_ip, FileField: random_gen.gen_file_field, ImageField: random_gen.gen_image_field, DurationField: random_gen.gen_interval, } # type: Dict[Type, Callable] if ArrayField: default_mapping[ArrayField] = random_gen.gen_array if JSONField: default_mapping[JSONField] = random_gen.gen_json if PostgresJSONField: default_mapping[PostgresJSONField] = random_gen.gen_json if HStoreField: default_mapping[HStoreField] = random_gen.gen_hstore if CICharField: default_mapping[CICharField] = random_gen.gen_string if CIEmailField: default_mapping[CIEmailField] = random_gen.gen_email if CITextField: default_mapping[CITextField] = random_gen.gen_text if AutoField: default_mapping[AutoField] = _make_integer_gen_by_range(AutoField) if BigAutoField: default_mapping[BigAutoField] = _make_integer_gen_by_range(BigAutoField) if SmallAutoField: default_mapping[SmallAutoField] = _make_integer_gen_by_range(SmallAutoField) if PositiveBigIntegerField: default_mapping[PositiveBigIntegerField] = _make_integer_gen_by_range( PositiveBigIntegerField ) if DecimalRangeField: default_mapping[DecimalRangeField] = random_gen.gen_pg_numbers_range(Decimal) if IntegerRangeField: default_mapping[IntegerRangeField] = random_gen.gen_pg_numbers_range(int) if BigIntegerRangeField: default_mapping[BigIntegerRangeField] = random_gen.gen_pg_numbers_range(int) if FloatRangeField: default_mapping[FloatRangeField] = random_gen.gen_pg_numbers_range(float) if DateRangeField: default_mapping[DateRangeField] = random_gen.gen_date_range if DateTimeRangeField: default_mapping[DateTimeRangeField] = random_gen.gen_datetime_range if NullBooleanField: default_mapping[NullBooleanField] = random_gen.gen_boolean # Add GIS fields def get_type_mapping() -> Dict[Type, Callable]: from django.contrib.contenttypes.models import ContentType from .gis import default_gis_mapping mapping = default_mapping.copy() mapping[ContentType] = random_gen.gen_content_type mapping.update(default_gis_mapping) return mapping.copy() user_mapping = {} def add(field: str, func: Optional[Union[Callable, str]]) -> None: user_mapping[import_from_str(field)] = import_from_str(func) def get(field: Any) -> Optional[Callable]: return user_mapping.get(field) model_bakery-1.7.0/model_bakery/gis.py000066400000000000000000000020541427223773300177600ustar00rootroot00000000000000from __future__ import absolute_import from django.apps import apps BAKER_GIS = apps.is_installed("django.contrib.gis") default_gis_mapping = {} __all__ = ["BAKER_GIS", "default_gis_mapping"] if BAKER_GIS: from django.contrib.gis.db.models import ( GeometryCollectionField, GeometryField, LineStringField, MultiLineStringField, MultiPointField, MultiPolygonField, PointField, PolygonField, ) from . import random_gen default_gis_mapping[GeometryField] = random_gen.gen_geometry default_gis_mapping[PointField] = random_gen.gen_point default_gis_mapping[LineStringField] = random_gen.gen_line_string default_gis_mapping[PolygonField] = random_gen.gen_polygon default_gis_mapping[MultiPointField] = random_gen.gen_multi_point default_gis_mapping[MultiLineStringField] = random_gen.gen_multi_line_string default_gis_mapping[MultiPolygonField] = random_gen.gen_multi_polygon default_gis_mapping[GeometryCollectionField] = random_gen.gen_geometry_collection model_bakery-1.7.0/model_bakery/mock_file.txt000066400000000000000000000000121427223773300213050ustar00rootroot00000000000000mock file model_bakery-1.7.0/model_bakery/mock_img.jpeg000066400000000000000000000035741427223773300212700ustar00rootroot00000000000000ÿØÿàJFIFÿÛ„  ! '(*%$%"/,#3)8,/ 25<,A27-5 2$$-,4./50,,/55)5.4*4*,,)),))-.4),)),,,,),),),,)))),)ÿÀ8‚"ÿÄÿÄ>!1AQa"qt¡³2Srs‘¢%46T±²Á#$5BÿÄÿÄ !1AQa"ÿÚ ?ÅøÏÆsêWṆ;ƒTY!U3̣ü¾d¤Ÿ?¸Vz”ª|ÖÛíV‹‡vë&³j’*È­. ° ØƯÁïT.:hđAgn`‚(KNÀ”P‘Ë= P*῭î#t«¿—ô,ÿđ\îJíạ̊wîÈÎƯ½sßµB*’çh¥)CJR€R” ¥(r´ÍVkiD–̣¼.§!•ˆ₫~£Øäẩ…-–|{‹”œèɱw•8ñóm™Í*'JóXª' xWô|EÓ4váˆP½B;àÊL÷$ØÍNÅ}9à•Ûáûo‡Æï‚ˆW'ú³PiJ§ÙÀ̉ü!¢Ú̃ự’ê&Ê)ºf“vÿúôÏLWEúAq¶ûC~Y©¿ ËzÔ¾KÎ́÷Ưµ³»>yïT̉ û¿Ụ́Í rœ>OŒ¸]ak¤Mq n²G²“+°É#ÈŸzê| à-5ôØï5)öóŒ˜W˜@Ÿ#•èF —=üê‡ÄđơÇÙ×ưËXođ¦×èơ½Ơd;Z! Mü´HQ½‡RH9À#¾:ĐT­Ư/ƒC 4[øÙc¡Û̀†w}­0̀Ăߨ¬Gxk Ưåä7Œÿø2*b6ïu$䓦1̃ª>ºÓZ9“HDeæadbeêz èxcÿ¹Ö~Ö?2j̀¶º9pÿA†Q ¢%•°=Û‡$öùw₫]:ÖK‰ü(†ÎÔƯÙU”I6đˆPÈO^„Œƒùé¹₫2íé¿rÊZ°qSü?uü$üÅ¡15¹cÂ_Ăî5üBâéÚơ½Àóä*ú₫'aĂ=ÈaEIåŒe×âœpK*0ÇR<‡zî¼J Qi•1Yb2½Âªc+0¹#ê©_Ïö¬̃öoù±Đbe©ÁÙñÀ:}ºG Œl/.eD1;6®êäápÏN§Ø×w§p‹M²¶æjN% ï$¦Ă%¯ơOá_½Z5ÿ­mw‘b¿¼ßñê¿H6~] Ù¾b}7€»sï‚Øûè•ÁÏÖ¸9awm̀ÓDÅI–S4N}K`yeOOCÚ¡·6ÍŒ’)G²á”àƒơW.³ư8ĺGg×Ë]Øü?™©öư9w³·;ú¶ßhg¨–ƠH̀̉”ª`*™ĂN,-„? x¬Đ†&7Q¸¦ă’¬¾k’ON£'¿”Δ:ṛ¡Æzœ\î€MÜχu}̃¹ÙœûÖG₫1µƠ#··±v₫#önƒç]  àg©)­ß 5]6ÚäI~fSÿiØn‰}È^¡½Î@ïÓ½Ce¨ëù}Î)¸Mç?³E|e¼+Ľ:ăJKMḤB±:²±WU ™Ç@\`öơ¬ïx•̣­­™- >é$ÁØ~¨Pzị́{œzd̀è[ÔÅt}¢x³AӕŤË‚ÛḌ’FqúÁ½Mfüă»+}KRy¹iwp"cî]̣ʤŒ;ă½HéCW×F§‰̀7´³[?67HÀm¬¹+£{¤øûˆÚ}Ö<\s$‘*̣å\êOVP;PÚđhrµÏégá÷ Ơ-ơÑ´*e ]Y‡ ’;ă=+aá=gIí×LH¹¦"̉4væ/“rŒ*;’:JÏØđ‡M¼µ†d2F^ËeKíº0lụ®ûKÑ4ïÀîdåïsÊá€́¨ïÙGZ™̃½đŸq‡X{o[Í ĂÛÛDĂÓ<É:b±­m¿´JÔG²,à´S) 0óG¸=°¨×ŒüHu B[ŒW "â5P}üϹ5̉PĂ•ªxđ½jÜVÓ´ûNVfPDiIóvÀÏ^§'Û½Bn®ZYI»È̀̀Ç͘ä“ơ“^ºU9»uè¥)C3Că?O¦Ư:HŒb,yRà•dÏËóy:|₫ăYêR†’¦°…)JR””¥¥)@~£•—ơX¯ÔHÿJđîIÉ9>§©¥(SÅ)JW+L̉¦¹”Gò́pW?ÏĐ{œJPêV^ Ÿ"å':vl]áFFü|ÛO¦sJR¡íâ£ÿÙmodel_bakery-1.7.0/model_bakery/py.typed000066400000000000000000000000001427223773300203100ustar00rootroot00000000000000model_bakery-1.7.0/model_bakery/random_gen.py000066400000000000000000000215261427223773300213140ustar00rootroot00000000000000"""Generators are callables that return a value used to populate a field. If this callable has a `required` attribute (a list, mostly), for each item in the list, if the item is a string, the field attribute with the same name will be fetched from the field and used as argument for the generator. If it is a callable (which will receive `field` as first argument), it should return a list in the format (key, value) where key is the argument name for generator and value is the value for that argument. """ import string import warnings from datetime import date, datetime, time, timedelta from decimal import Decimal from os.path import abspath, dirname, join from random import choice, randint, random, uniform from typing import Any, Callable, List, Optional, Tuple, Union from uuid import UUID from django.core.files.base import ContentFile from django.db.models import Field, Model from django.utils.timezone import now MAX_LENGTH = 300 # Using sys.maxint here breaks a bunch of tests when running against a # Postgres database. MAX_INT = 100000000000 def get_content_file(content: bytes, name: str) -> ContentFile: return ContentFile(content, name=name) def gen_file_field() -> ContentFile: name = "mock_file.txt" file_path = abspath(join(dirname(__file__), name)) with open(file_path, "rb") as f: return get_content_file(f.read(), name=name) def gen_image_field() -> ContentFile: name = "mock_img.jpeg" file_path = abspath(join(dirname(__file__), name)) with open(file_path, "rb") as f: return get_content_file(f.read(), name=name) def gen_from_list(a_list: Union[List[str], range]) -> Callable: """Make sure all values of the field are generated from a list. Examples: Here how to use it. >>> from baker import Baker >>> class ExperienceBaker(Baker): >>> attr_mapping = {'some_field': gen_from_list(['A', 'B', 'C'])} """ return lambda: choice(list(a_list)) # -- DEFAULT GENERATORS -- def gen_from_choices(choices: List) -> Callable: choice_list = [] for value, label in choices: if isinstance(label, (list, tuple)): for val, lbl in label: choice_list.append(val) else: choice_list.append(value) return gen_from_list(choice_list) def gen_integer(min_int: int = -MAX_INT, max_int: int = MAX_INT) -> int: return randint(min_int, max_int) def gen_float() -> float: return random() * gen_integer() def gen_decimal(max_digits: int, decimal_places: int) -> Decimal: def num_as_str(x: int): return "".join([str(randint(0, 9)) for _ in range(x)]) if decimal_places: return Decimal( "%s.%s" % (num_as_str(max_digits - decimal_places - 1), num_as_str(decimal_places)) ) return Decimal(num_as_str(max_digits)) gen_decimal.required = ["max_digits", "decimal_places"] # type: ignore[attr-defined] def gen_date() -> date: return now().date() def gen_datetime() -> datetime: return now() def gen_time() -> time: return now().time() def gen_string(max_length: int) -> str: return str("".join(choice(string.ascii_letters) for _ in range(max_length))) gen_string.required = ["max_length"] # type: ignore[attr-defined] def gen_slug(max_length: int) -> str: valid_chars = string.ascii_letters + string.digits + "_-" return str("".join(choice(valid_chars) for _ in range(max_length))) gen_slug.required = ["max_length"] # type: ignore[attr-defined] def gen_text() -> str: return gen_string(MAX_LENGTH) def gen_boolean() -> bool: return choice((True, False)) def gen_null_boolean(): return choice((True, False, None)) def gen_url() -> str: return str("http://www.%s.com/" % gen_string(30)) def gen_email() -> str: return "%s@example.com" % gen_string(10) def gen_ipv6() -> str: return ":".join(format(randint(1, 65535), "x") for _ in range(8)) def gen_ipv4() -> str: return ".".join(str(randint(1, 255)) for _ in range(4)) def gen_ipv46() -> str: ip_gen = choice([gen_ipv4, gen_ipv6]) return ip_gen() def gen_ip(protocol: str, default_validators: List[Callable]) -> str: from django.core.exceptions import ValidationError protocol = (protocol or "").lower() if not protocol: field_validator = default_validators[0] dummy_ipv4 = "1.1.1.1" dummy_ipv6 = "FE80::0202:B3FF:FE1E:8329" try: field_validator(dummy_ipv4) field_validator(dummy_ipv6) generator = gen_ipv46 except ValidationError: try: field_validator(dummy_ipv4) generator = gen_ipv4 except ValidationError: generator = gen_ipv6 elif protocol == "ipv4": generator = gen_ipv4 elif protocol == "ipv6": generator = gen_ipv6 else: generator = gen_ipv46 return generator() gen_ip.required = ["protocol", "default_validators"] # type: ignore[attr-defined] def gen_byte_string(max_length: int = 16) -> bytes: generator = (randint(0, 255) for x in range(max_length)) return bytes(generator) def gen_interval(interval_key: str = "milliseconds", offset: int = 0) -> timedelta: interval = gen_integer() + offset kwargs = {interval_key: interval} return timedelta(**kwargs) def gen_content_type(): from django.apps import apps from django.contrib.contenttypes.models import ContentType try: return ContentType.objects.get_for_model(choice(apps.get_models())) except (AssertionError, RuntimeError): # AssertionError is raised by Django's test framework when db access is not available: # https://github.com/django/django/blob/stable/4.0.x/django/test/testcases.py#L150 # RuntimeError is raised by pytest-django when db access is not available: # https://github.com/pytest-dev/pytest-django/blob/v4.5.2/pytest_django/plugin.py#L709 warnings.warn("Database access disabled, returning ContentType raw instance") return ContentType() def gen_uuid() -> UUID: import uuid return uuid.uuid4() def gen_array(): return [] def gen_json(): return {} def gen_hstore(): return {} def _fk_model(field: Field) -> Tuple[str, Optional[Model]]: try: return ("model", field.related_model) except AttributeError: return ("model", field.related.parent_model) def _prepare_related(model: str, **attrs: Any) -> Union[Model, List[Model]]: from .baker import prepare return prepare(model, **attrs) def gen_related(model, **attrs): from .baker import make return make(model, **attrs) gen_related.required = [_fk_model, "_using"] # type: ignore[attr-defined] gen_related.prepare = _prepare_related # type: ignore[attr-defined] def gen_m2m(model, **attrs): from .baker import MAX_MANY_QUANTITY, make return make(model, _quantity=MAX_MANY_QUANTITY, **attrs) gen_m2m.required = [_fk_model, "_using"] # type: ignore[attr-defined] # GIS generators def gen_coord(): return uniform(0, 1) def gen_coords(): return "{x} {y}".format(x=gen_coord(), y=gen_coord()) def gen_point(): return "POINT ({})".format( gen_coords(), ) def _gen_line_string_without_prefix(): return "({}, {})".format( gen_coords(), gen_coords(), ) def gen_line_string(): return "LINESTRING {}".format(_gen_line_string_without_prefix()) def _gen_polygon_without_prefix(): start = gen_coords() return "(({}, {}, {}, {}))".format(start, gen_coords(), gen_coords(), start) def gen_polygon(): return "POLYGON {}".format( _gen_polygon_without_prefix(), ) def gen_multi_point(): return "MULTIPOINT (({}))".format( gen_coords(), ) def gen_multi_line_string(): return "MULTILINESTRING ({})".format( _gen_line_string_without_prefix(), ) def gen_multi_polygon(): return "MULTIPOLYGON ({})".format( _gen_polygon_without_prefix(), ) def gen_geometry(): return gen_point() def gen_geometry_collection(): return "GEOMETRYCOLLECTION ({})".format( gen_point(), ) def gen_pg_numbers_range(number_cast: Callable[[int], Any]) -> Callable: def gen_range(): from psycopg2._range import NumericRange base_num = gen_integer(1, 100000) return NumericRange(number_cast(-1 * base_num), number_cast(base_num)) return gen_range def gen_date_range(): from psycopg2.extras import DateRange base_date = gen_date() interval = gen_interval(offset=24 * 60 * 60 * 1000) # force at least 1 day interval args = sorted([base_date - interval, base_date + interval]) return DateRange(*args) def gen_datetime_range(): from psycopg2.extras import DateTimeTZRange base_datetime = gen_datetime() interval = gen_interval() args = sorted([base_datetime - interval, base_datetime + interval]) return DateTimeTZRange(*args) model_bakery-1.7.0/model_bakery/recipe.py000066400000000000000000000201461427223773300204470ustar00rootroot00000000000000import itertools from typing import ( Any, Dict, Generic, List, Optional, Type, TypeVar, Union, cast, overload, ) from django.db.models import Model from . import baker from ._types import M from .exceptions import RecipeNotFound from .utils import ( # NoQA: Enable seq to be imported from recipes get_calling_module, seq, ) finder = baker.ModelFinder() class Recipe(Generic[M]): _T = TypeVar("_T", bound="Recipe[M]") def __init__(self, _model: Union[str, Type[M]], **attrs: Any) -> None: self.attr_mapping = attrs self._model = _model # _iterator_backups will hold values of the form (backup_iterator, usable_iterator). self._iterator_backups = {} # type: Dict[str, Any] def _mapping(self, _using: str, new_attrs: Dict[str, Any]) -> Dict[str, Any]: _save_related = new_attrs.get("_save_related", True) _quantity = new_attrs.get("_quantity") if _quantity is None: _quantity = 1 rel_fields_attrs = dict((k, v) for k, v in new_attrs.items() if "__" in k) new_attrs = dict((k, v) for k, v in new_attrs.items() if "__" not in k) mapping = self.attr_mapping.copy() for k, v in self.attr_mapping.items(): # do not generate values if field value is provided if new_attrs.get(k): continue elif baker.is_iterator(v): if isinstance(self._model, str): m = finder.get_model(self._model) else: m = self._model if k not in self._iterator_backups or m.objects.count() == 0: self._iterator_backups[k] = itertools.tee( self._iterator_backups.get(k, [v])[0] ) mapping[k] = self._iterator_backups[k][1] elif isinstance(v, RecipeForeignKey): a = {} for key, value in list(rel_fields_attrs.items()): if key.startswith("%s__" % k): a[key] = rel_fields_attrs.pop(key) recipe_attrs = baker.filter_rel_attrs(k, **a) if _save_related: # Create a unique foreign key for each quantity if one_to_one required if v.one_to_one is True: rel_gen = [] for i in range(_quantity): rel_gen.append(v.recipe.make(_using=_using, **recipe_attrs)) mapping[k] = itertools.cycle(rel_gen) # Otherwise create shared foreign key for each quantity else: mapping[k] = v.recipe.make(_using=_using, **recipe_attrs) else: mapping[k] = v.recipe.prepare(_using=_using, **recipe_attrs) elif isinstance(v, related): mapping[k] = v.make() mapping.update(new_attrs) mapping.update(rel_fields_attrs) return mapping @overload def make( self, _quantity: None = None, make_m2m: bool = False, _refresh_after_create: bool = False, _create_files: bool = False, _using: str = "", _bulk_create: bool = False, _save_kwargs: Optional[Dict[str, Any]] = None, **attrs: Any, ) -> M: ... @overload def make( self, _quantity: int, make_m2m: bool = False, _refresh_after_create: bool = False, _create_files: bool = False, _using: str = "", _bulk_create: bool = False, _save_kwargs: Optional[Dict[str, Any]] = None, **attrs: Any, ) -> List[M]: ... def make( self, _quantity: Optional[int] = None, make_m2m: Optional[bool] = None, _refresh_after_create: Optional[bool] = None, _create_files: Optional[bool] = None, _using: str = "", _bulk_create: Optional[bool] = None, _save_kwargs: Optional[Dict[str, Any]] = None, **attrs: Any, ) -> Union[M, List[M]]: defaults = {} if _quantity is not None: defaults["_quantity"] = _quantity if make_m2m is not None: defaults["make_m2m"] = make_m2m if _refresh_after_create is not None: defaults["_refresh_after_create"] = _refresh_after_create if _create_files is not None: defaults["_create_files"] = _create_files if _bulk_create is not None: defaults["_bulk_create"] = _bulk_create if _save_kwargs is not None: defaults["_save_kwargs"] = _save_kwargs # type: ignore[assignment] defaults.update(attrs) return baker.make(self._model, _using=_using, **self._mapping(_using, defaults)) @overload def prepare( self, _quantity: None = None, _save_related: bool = False, _using: str = "", **attrs: Any, ) -> M: ... @overload def prepare( self, _quantity: int, _save_related: bool = False, _using: str = "", **attrs: Any, ) -> List[M]: ... def prepare( self, _quantity: Optional[int] = None, _save_related: bool = False, _using: str = "", **attrs: Any, ) -> Union[M, List[M]]: defaults = { "_quantity": _quantity, "_save_related": _save_related, } defaults.update(attrs) return baker.prepare( self._model, _using=_using, **self._mapping(_using, defaults) ) def extend(self: _T, **attrs: Any) -> _T: attr_mapping = self.attr_mapping.copy() attr_mapping.update(attrs) return type(self)(self._model, **attr_mapping) def _load_recipe_from_calling_module(recipe: str) -> Recipe[Model]: """Load `Recipe` from the string attribute given from the calling module. Args: recipe (str): the name of the recipe attribute within the module from which it should be loaded Returns: (Recipe): recipe resolved from calling module """ recipe = getattr(get_calling_module(2), recipe) if recipe: return cast(Recipe[Model], recipe) else: raise RecipeNotFound class RecipeForeignKey(Generic[M]): """A `Recipe` to use for making ManyToOne and OneToOne related objects.""" def __init__(self, recipe: Recipe[M], one_to_one: bool) -> None: if isinstance(recipe, Recipe): self.recipe = recipe self.one_to_one = one_to_one else: raise TypeError("Not a recipe") def foreign_key( recipe: Union[Recipe[M], str], one_to_one: bool = False ) -> RecipeForeignKey[M]: """Return a `RecipeForeignKey`. Return the callable, so that the associated `_model` will not be created during the recipe definition. This resolves recipes supplied as strings from other module paths or from the calling code's module. """ if isinstance(recipe, str): # Load `Recipe` from string before handing off to `RecipeForeignKey` try: # Try to load from another module recipe = baker._recipe(recipe) except (AttributeError, ImportError, ValueError): # Probably not in another module, so load it from calling module recipe = _load_recipe_from_calling_module(cast(str, recipe)) return RecipeForeignKey(cast(Recipe[M], recipe), one_to_one) class related(Generic[M]): # FIXME def __init__(self, *args: Union[str, Recipe[M]]) -> None: self.related = [] # type: List[Recipe[M]] for recipe in args: if isinstance(recipe, Recipe): self.related.append(recipe) elif isinstance(recipe, str): recipe = _load_recipe_from_calling_module(recipe) if recipe: self.related.append(recipe) else: raise RecipeNotFound else: raise TypeError("Not a recipe") def make(self) -> List[Union[M, List[M]]]: """Persist objects to m2m relation.""" return [m.make() for m in self.related] model_bakery-1.7.0/model_bakery/timezone.py000066400000000000000000000005301427223773300210250ustar00rootroot00000000000000"""Utility functions to manage timezone code.""" from datetime import datetime from django.conf import settings from django.utils.timezone import utc def tz_aware(value: datetime) -> datetime: """Return an UTC-aware datetime in case of USE_TZ=True.""" if settings.USE_TZ: value = value.replace(tzinfo=utc) return value model_bakery-1.7.0/model_bakery/utils.py000066400000000000000000000105461427223773300203430ustar00rootroot00000000000000import datetime import importlib import inspect import itertools import warnings from types import ModuleType from typing import Any, Callable, Optional, Union from django.apps import apps from .timezone import tz_aware __all__ = ["import_from_str", "get_calling_module", "seq"] def import_from_str(import_string: Optional[Union[Callable, str]]) -> Any: """Import an object defined as import if it is an string. If `import_string` follows the format `path.to.module.object_name`, this method imports it; else it just return the object. """ if isinstance(import_string, str): path, field_name = import_string.rsplit(".", 1) if apps: model = apps.all_models.get(path, {}).get(field_name.lower()) if model: return model module = importlib.import_module(path) return getattr(module, field_name) else: return import_string def get_calling_module(levels_back: int) -> Optional[ModuleType]: """Get the module some number of stack frames back from the current one. Make sure to account for the number of frames between the "calling" code and the one that calls this function. Args: levels_back (int): Number of stack frames back from the current Returns: (ModuleType): the module from which the code was called """ frame = inspect.stack()[levels_back + 1][0] return inspect.getmodule(frame) def seq(value, increment_by=1, start=None, suffix=None): """Generate a sequence of values based on a running count. This function can be used to generate sequences of `int`, `float`, `datetime`, `date`, `time`, or `str`: whatever the `type` is of the provided `value`. Args: value (object): the value at which to begin generation (this will be ignored for types `datetime`, `date`, and `time`) increment_by (`int` or `float`, optional): the amount by which to increment for each generated value (defaults to `1`) start (`int` or `float`, optional): the value at which the sequence will begin to add to `value` (if `value` is a `str`, `start` will be appended to it) suffix (`str`, optional): for `str` `value` sequences, this will be appended to the end of each generated value (after the counting value is appended) Returns: object: generated values for sequential data """ _validate_sequence_parameters(value, increment_by, start, suffix) if type(value) in [datetime.datetime, datetime.date, datetime.time]: if type(value) is datetime.date: date = datetime.datetime.combine(value, datetime.datetime.now().time()) elif type(value) is datetime.time: date = datetime.datetime.combine(datetime.date.today(), value) else: date = value # convert to epoch time start = (date - datetime.datetime(1970, 1, 1)).total_seconds() increment_by = increment_by.total_seconds() for n in itertools.count(increment_by, increment_by): series_date = tz_aware(datetime.datetime.utcfromtimestamp(start + n)) if type(value) is datetime.time: yield series_date.time() elif type(value) is datetime.date: yield series_date.date() else: yield series_date else: for n in itertools.count(start or increment_by, increment_by): if suffix: yield value + str(n) + suffix else: yield value + type(value)(n) def _validate_sequence_parameters(value, increment_by, start, suffix) -> None: if suffix: if not isinstance(suffix, str): raise TypeError("Sequences suffix can only be a string") if not isinstance(value, str): raise TypeError("Sequences with suffix can only be used with text values") if type(value) in [datetime.datetime, datetime.date, datetime.time]: if not isinstance(increment_by, datetime.timedelta): raise TypeError( "Sequences with values datetime.datetime, datetime.date and datetime.time, " "incremente_by must be a datetime.timedelta." ) if start: warnings.warn( "start parameter is ignored when using seq with date, time or datetime objects" ) model_bakery-1.7.0/postgis-tests.sh000077500000000000000000000055701427223773300173640ustar00rootroot00000000000000#!/usr/bin/env bash # -*- coding: utf-8 -*- set -euo pipefail ### ### Run test suite against PostgreSQL DB with Postgis installed. ### Note postgis version is also explicitly set in django test settings ### ### This script will attempt to spin up a PostgreSQL Docker container against ### which the tests will be run. It will spin it down once the tests are finished, ### so if you are already running PostgreSQL locally, instead of using this ### script, simply run: ### ### # TEST_DB=postgis PGUSER=postgres python -m pytest ### ### This script uses the `python` on the current `$PATH`, but can be overridden ### by setting the `PYTHON_CLI` environment variable. ### ### Usage: ### ### ./postgis-tests.sh [-h|--help] ### ### Options: ### ### -h, --help print (this) help and exit ### function help { # Print this file's contents, but only the lines that start # with `###` (documentation lines, above). sed -rn 's/^### ?//;T;p' "$0" } function HAS { # Check if the system has a certain program ($1) installed. # Output is silenced, but the function returns the result of # the `command` statement. command -v $1 > /dev/null 2>&1 return $? } # Script variables PROJECT_ROOT="$(dirname $(readlink -f "$0"))" # Same level as this file # Dependency variables (e.g. PYTHON3_CLI=python3) PYTHON=${PYTHON_CLI:-python} DEPS="$PYTHON" # Arg defaults export PGPORT=${PGPORT:-5432} export PGUSER=${PGUSER:-postgres} export PGPASSWORD=${PGPASSWORD:-postgres} export TEST_DB=${TEST_DB:-postgis} # Argument parsing using "getopt" OPTIONS=h LONGOPTS=help PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") PARSE_EXIT=$? if [[ $PARSE_EXIT -ne 0 ]]; then exit $PARSE_EXIT fi eval set -- "$PARSED" while true; do case "$1" in -h|--help) help shift exit 0 ;; --) shift break ;; *) echo "Error. Exiting." exit 3 ;; esac done # Grab the remaining positional argument(s) (not needed here) # if [[ $# -ne 0 ]]; then # POSITIONAL_1=$1 # POSITIONAL_1=$2 # else # echo "Missing positional args?" # exit 1 # fi echo "Checking dependencies [$(echo ${DEPS} | sed 's/ /, /g')]..." for dep in $DEPS; do MISSING_DEP=false if ! HAS $dep; then echo "You do not have '${dep}' installed, or it is not available on your PATH!" echo "If '${dep}' is not what it is called on your system, set the ${dep^^}_CLI environment variable." MISSING_DEP=true fi if [[ $MISSING_DEP = true ]]; then echo "Missing dependencies." exit 1 fi done # Run the postgres container with all extensions installed $PROJECT_ROOT/postgres-docker --port $PGPORT # Run the tests $PYTHON -m pytest # Spin down the postgis container $PROJECT_ROOT/postgres-docker --kill model_bakery-1.7.0/postgres-docker000077500000000000000000000104031427223773300172250ustar00rootroot00000000000000#!/usr/bin/env bash # -*- coding: utf-8 -*- set -euo pipefail ### ### Start a PostgreSQL docker container with the proper extensions installed ### for the full test suite. ### ### To kill an already running container, run the same command again with the ### `--kill` flag. ### ### Usage: ### ### ./postgres-docker [-h|--help] [-p|--port PORT] [-k|--kill] ### ### Options: ### ### -h, --help print (this) help and exit ### ### -p, --port PORT specify the host port to which PostgreSQL will be bound ### (defaults to 5432) ### ### -k, --kill given the container is already running, kill it ### function help { # Print this file's contents, but only the lines that start # with `###` (documentation lines, above). sed -rn 's/^### ?//;T;p' "$0" } function HAS { # Check if the system has a certain program ($1) installed. # Output is silenced, but the function returns the result of # the `command` statement. command -v $1 > /dev/null 2>&1 return $? } # Script variables PROJECT_ROOT="$(dirname $(readlink -f "$0"))" # Same level as this file # Dependency variables (e.g. PYTHON3_CLI=python3) DOCKER=${DOCKER_CLI:-docker} DEPS="$DOCKER" # Arg defaults KILL=false CONTAINER_NAME=model-bakery-postgres PORT=${PGPORT:-5432} # Argument parsing using "getopt" OPTIONS=h,k,p LONGOPTS=help,kill,port PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") PARSE_EXIT=$? if [[ $PARSE_EXIT -ne 0 ]]; then exit $PARSE_EXIT fi eval set -- "$PARSED" while true; do case "$1" in -p|--port) PGPORT=$2 shift ;; -k|--kill) KILL=true shift ;; -h|--help) help shift exit 0 ;; --) shift break ;; *) echo "Error. Exiting." exit 3 ;; esac done # Grab the remaining positional argument(s) (not needed here) # if [[ $# -ne 0 ]]; then # POSITIONAL_1=$1 # POSITIONAL_1=$2 # else # echo "Missing positional args?" # exit 1 # fi echo "Checking dependencies [$(echo ${DEPS} | sed 's/ /, /g')]..." for dep in $DEPS; do MISSING_DEP=false if ! HAS $dep; then echo "You do not have '${dep}' installed, or it is not available on your PATH!" echo "If '${dep}' is not what it is called on your system, set the ${dep^^}_CLI environment variable." MISSING_DEP=true fi if [[ $MISSING_DEP = true ]]; then echo "Missing dependencies." exit 1 fi done # Check if already running if $DOCKER container inspect $CONTAINER_NAME > /dev/null 2>&1; then RUNNING_ALREADY=true else RUNNING_ALREADY=false fi # If stop desired, stop container if [[ $KILL = true ]]; then if [[ $RUNNING_ALREADY = true ]]; then echo "Stopping '${CONTAINER_NAME}'..." $DOCKER stop $CONTAINER_NAME exit 0 else echo "Container '${CONTAINER_NAME}' is not currently running." exit 1 fi fi # If running already, do nothing but check port if [[ $RUNNING_ALREADY = true ]]; then RUNNING_PORT=$($DOCKER container inspect $CONTAINER_NAME | grep -m 1 -Po "(?<=HostPort\": \")\d+") if [[ $RUNNING_PORT -ne $PORT ]]; then echo "Container '${CONTAINER_NAME}' is already running, but on the wrong port!" echo "Conainer is using port ${RUNNING_PORT} and the specified port is ${PORT}." exit 1 else echo "Container '${CONTAINER_NAME}' is already running." exit 0 fi fi # Start postgres container echo "Starting '${CONTAINER_NAME}'..." $DOCKER run --rm --name ${CONTAINER_NAME} -e POSTGRES_HOST_AUTH_METHOD=trust -p ${PORT}:5432 -d postgis/postgis:11-3.0 # Wait a few seconds so the DB container can start up echo "Waiting for DB container..." sleep 5s # Enable all of the extensions needed on the template1 database $DOCKER exec $CONTAINER_NAME /bin/bash -c "psql template1 -c \"CREATE EXTENSION IF NOT EXISTS citext;\" -U postgres" $DOCKER exec $CONTAINER_NAME /bin/bash -c "psql template1 -c \"CREATE EXTENSION IF NOT EXISTS hstore;\" -U postgres" $DOCKER exec $CONTAINER_NAME /bin/bash -c "psql template1 -c \"CREATE EXTENSION IF NOT EXISTS postgis;\" -U postgres" model_bakery-1.7.0/readthedocs.yml000066400000000000000000000000301427223773300171670ustar00rootroot00000000000000python: version: 3.8 model_bakery-1.7.0/requirements.txt000066400000000000000000000000141427223773300174450ustar00rootroot00000000000000django>=3.2 model_bakery-1.7.0/requirements_dev.txt000066400000000000000000000003511427223773300203070ustar00rootroot00000000000000-r requirements.txt black==22.6.0 flake8==5.0.3 isort==5.10.1 mypy==0.971 pillow==9.2.0 pip-tools==6.8.0 pre-commit==2.20.0 psycopg2-binary==2.9.3 pycodestyle==2.9.0 pydocstyle==6.1.1 pytest==7.1.2 pytest-django==4.5.2 click==8.1.3 model_bakery-1.7.0/setup.cfg000066400000000000000000000006501427223773300160100ustar00rootroot00000000000000[bdist_wheel] universal = 1 [flake8] max-line-length = 99 statistics = true show-source = true exclude = docs/*, venv [tool:pytest] addopts = --tb=short -rxs --nomigrations [isort] multi_line_output=3 include_trailing_comma=True force_grid_wrap=0 use_parentheses=True line_length=88 [pydocstyle] add_ignore = D1 match-dir = (?!test|docs|venv|\.).* [mypy] ignore_missing_imports=True disallow_untyped_calls=True model_bakery-1.7.0/setup.py000077500000000000000000000031261427223773300157050ustar00rootroot00000000000000from os.path import abspath, dirname, join import setuptools about: dict = {} here = abspath(dirname(__file__)) with open(join(here, "model_bakery", "__about__.py")) as f: # type: ignore exec(f.read(), about) setuptools.setup( name="model_bakery", version=about["__version__"], author=about["__author__"], author_email=about["__email__"], url=about["__url__"], license=about["__license__"], packages=["model_bakery"], include_package_data=True, # declarations in MANIFEST.in package_data={"model_bakery": ["py.typed"]}, install_requires=open(join(here, "requirements.txt")).readlines(), description="Smart object creation facility for Django.", long_description=open(join(dirname(__file__), "README.md")).read(), long_description_content_type="text/markdown", keywords="django testing factory python", classifiers=[ "Development Status :: 5 - Production/Stable", "Framework :: Django", "Framework :: Django :: 3.2", "Framework :: Django :: 4.0", "Framework :: Django :: 4.1", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Topic :: Software Development", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", ], ) model_bakery-1.7.0/tests/000077500000000000000000000000001427223773300153305ustar00rootroot00000000000000model_bakery-1.7.0/tests/ambiguous/000077500000000000000000000000001427223773300173235ustar00rootroot00000000000000model_bakery-1.7.0/tests/ambiguous/__init__.py000066400000000000000000000000001427223773300214220ustar00rootroot00000000000000model_bakery-1.7.0/tests/ambiguous/models.py000066400000000000000000000001511427223773300211550ustar00rootroot00000000000000from django.db import models class Ambiguous(models.Model): name = models.CharField(max_length=20) model_bakery-1.7.0/tests/ambiguous2/000077500000000000000000000000001427223773300174055ustar00rootroot00000000000000model_bakery-1.7.0/tests/ambiguous2/__init__.py000066400000000000000000000000001427223773300215040ustar00rootroot00000000000000model_bakery-1.7.0/tests/ambiguous2/models.py000066400000000000000000000001511427223773300212370ustar00rootroot00000000000000from django.db import models class Ambiguous(models.Model): name = models.CharField(max_length=20) model_bakery-1.7.0/tests/conftest.py000066400000000000000000000052071427223773300175330ustar00rootroot00000000000000import os import django from django.conf import settings def pytest_configure(): test_db = os.environ.get("TEST_DB", "sqlite") installed_apps = [ "django.contrib.contenttypes", "django.contrib.auth", "tests.generic", "tests.ambiguous", "tests.ambiguous2", ] using_postgres_flag = False postgis_version = () if test_db == "sqlite": db_engine = "django.db.backends.sqlite3" db_name = ":memory:" extra_db_name = ":memory:" elif test_db == "postgresql": using_postgres_flag = True db_engine = "django.db.backends.postgresql_psycopg2" db_name = "postgres" installed_apps = ["django.contrib.postgres"] + installed_apps extra_db_name = "extra_db" elif test_db == "postgis": using_postgres_flag = True db_engine = "django.contrib.gis.db.backends.postgis" db_name = "postgres" extra_db_name = "extra_db" installed_apps = [ "django.contrib.postgres", "django.contrib.gis", ] + installed_apps postgis_version = (11, 3, 0) else: raise NotImplementedError("Tests for % are not supported", test_db) EXTRA_DB = "extra" settings.configure( EXTRA_DB=EXTRA_DB, DATABASES={ "default": { "ENGINE": db_engine, "NAME": db_name, "HOST": "localhost", # The following DB settings are only used for `postgresql` and `postgis` "PORT": os.environ.get("PGPORT", ""), "USER": os.environ.get("PGUSER", ""), "PASSWORD": os.environ.get("PGPASSWORD", ""), }, # Extra DB used to test multi database support EXTRA_DB: { "ENGINE": db_engine, "NAME": extra_db_name, "HOST": "localhost", "PORT": os.environ.get("PGPORT", ""), "USER": os.environ.get("PGUSER", ""), "PASSWORD": os.environ.get("PGPASSWORD", ""), }, }, INSTALLED_APPS=installed_apps, LANGUAGE_CODE="en", SITE_ID=1, MIDDLEWARE=(), USE_TZ=os.environ.get("USE_TZ", False), USING_POSTGRES=using_postgres_flag, # Set the version explicitly otherwise Django does extra queries # to get the version via SQL when using POSTGIS POSTGIS_VERSION=postgis_version, ) from model_bakery import baker def gen_same_text(): return "always the same text" baker.generators.add("tests.generic.fields.CustomFieldViaSettings", gen_same_text) django.setup() model_bakery-1.7.0/tests/generic/000077500000000000000000000000001427223773300167445ustar00rootroot00000000000000model_bakery-1.7.0/tests/generic/__init__.py000066400000000000000000000000001427223773300210430ustar00rootroot00000000000000model_bakery-1.7.0/tests/generic/baker_recipes.py000066400000000000000000000055721427223773300221250ustar00rootroot00000000000000# ATTENTION: Recipes defined for testing purposes only from datetime import timedelta from decimal import Decimal from django.utils.timezone import now from model_bakery.recipe import Recipe, foreign_key, related, seq from tests.generic.models import ( TEST_TIME, Dog, DummyDefaultFieldsModel, DummyUniqueIntegerFieldModel, LonelyPerson, Person, School, ) person = Recipe( Person, name="John Doe", nickname="joe", age=18, bio="Someone in the crowd", blog="http://joe.blogspot.com", days_since_last_login=4, birthday=now().date(), appointment=now(), birth_time=now(), ) lonely_person = Recipe(LonelyPerson, only_friend=foreign_key(person, one_to_one=True)) serial_person = Recipe( Person, name=seq("joe"), ) serial_numbers = Recipe( DummyDefaultFieldsModel, default_decimal_field=seq(Decimal("20.1")), default_int_field=seq(10), default_float_field=seq(1.23), ) serial_numbers_by = Recipe( DummyDefaultFieldsModel, default_decimal_field=seq(Decimal("20.1"), increment_by=Decimal("2.4")), default_int_field=seq(10, increment_by=3), default_float_field=seq(1.23, increment_by=1.8), ) serial_datetime = Recipe( DummyDefaultFieldsModel, default_date_field=seq(TEST_TIME.date(), timedelta(days=1)), default_date_time_field=seq(TEST_TIME, timedelta(hours=3)), default_time_field=seq(TEST_TIME.time(), timedelta(seconds=15), start="xpto"), ) dog = Recipe(Dog, breed="Pug", owner=foreign_key(person)) homeless_dog = Recipe( Dog, breed="Pug", ) other_dog = Recipe(Dog, breed="Basset", owner=foreign_key("person")) dog_with_friends = dog.extend( friends_with=related(dog, dog), ) dog_with_more_friends = dog.extend( friends_with=related(dog_with_friends), ) extended_dog = dog.extend( breed="Super basset", ) paulo_freire_school = Recipe(School, name="Escola Municipal Paulo Freire") class SmallDogRecipe(Recipe): pass small_dog = SmallDogRecipe(Dog) pug = small_dog.extend( breed="Pug", ) other_dog_unicode = Recipe(Dog, breed="Basset", owner=foreign_key("person")) dummy_unique_field = Recipe( DummyUniqueIntegerFieldModel, value=seq(10), ) dog_lady = Recipe(Person, dog_set=related("dog", other_dog)) nullable_related = Recipe( "generic.DummyBlankFieldsModel", dummynullfieldsmodel_set=related(Recipe("generic.DummyNullFieldsModel")), ) cast_member = Recipe("generic.CastMember", person=foreign_key(person)) movie_with_cast = Recipe( "generic.Movie", cast_members=related(cast_member, cast_member) ) overrided_save = Recipe("generic.ModelWithOverridedSave") with_save_kwargs = Recipe( "generic.ModelWithSaveKwargs", _save_kwargs={"breed": "updated_breed"} ) ip_fields = Recipe( "generic.DummyGenericIPAddressFieldModel", ipv4_field=seq("127.0.0.", increment_by=2), ipv6_field=seq("2001:12f8:0:28::", start=4, increment_by=2), ) model_bakery-1.7.0/tests/generic/fields.py000066400000000000000000000006661427223773300205740ustar00rootroot00000000000000from django.db import models class CustomFieldWithGenerator(models.TextField): pass class CustomFieldWithoutGenerator(models.TextField): pass class CustomFieldViaSettings(models.TextField): pass class FakeListField(models.TextField): def to_python(self, value): return value.split() def get_prep_value(self, value): return " ".join(value) class CustomForeignKey(models.ForeignKey): pass model_bakery-1.7.0/tests/generic/forms.py000066400000000000000000000004251427223773300204450ustar00rootroot00000000000000from django.forms import ModelForm from tests.generic.models import DummyGenericIPAddressFieldModel class DummyGenericIPAddressFieldForm(ModelForm): class Meta: fields = ("ipv4_field", "ipv6_field", "ipv46_field") model = DummyGenericIPAddressFieldModel model_bakery-1.7.0/tests/generic/generators.py000066400000000000000000000000531427223773300214650ustar00rootroot00000000000000def gen_value_string(): return "value" model_bakery-1.7.0/tests/generic/models.py000077500000000000000000000331221427223773300206050ustar00rootroot00000000000000####################################### # TESTING PURPOSE ONLY MODELS!! # # DO NOT ADD THE APP TO INSTALLED_APPS# ####################################### import datetime from decimal import Decimal from tempfile import gettempdir from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.core.files.storage import FileSystemStorage from django.utils.timezone import now from model_bakery.gis import BAKER_GIS from model_bakery.timezone import tz_aware from .fields import ( CustomFieldViaSettings, CustomFieldWithGenerator, CustomFieldWithoutGenerator, CustomForeignKey, FakeListField, ) # check whether or not PIL is installed try: from PIL import ImageFile as PilImageFile # NoQA except ImportError: has_pil = False else: has_pil = True if BAKER_GIS: from django.contrib.gis.db import models else: from django.db import models GENDER_CHOICES = [ ("M", "male"), ("F", "female"), ("N", "non-binary"), ] OCCUPATION_CHOICES = ( ("Service Industry", (("waitress", "Waitress"), ("bartender", "Bartender"))), ("Education", (("teacher", "Teacher"), ("principal", "Principal"))), ) TEST_TIME = datetime.datetime(2014, 7, 21, 15, 39, 58, 457698) class ModelWithImpostorField(models.Model): pass class Profile(models.Model): email = models.EmailField() class User(models.Model): profile = models.ForeignKey( Profile, blank=True, null=True, on_delete=models.CASCADE ) username = models.CharField(max_length=32) class PaymentBill(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) value = models.FloatField() class Person(models.Model): gender = models.CharField(max_length=1, choices=GENDER_CHOICES) # Jards MacalĂ© is an amazing brazilian musician! =] enjoy_jards_macale = models.BooleanField(default=True) like_metal_music = models.BooleanField(default=False) name = models.CharField(max_length=30) nickname = models.SlugField(max_length=36) age = models.IntegerField() bio = models.TextField() birthday = models.DateField() birth_time = models.TimeField() appointment = models.DateTimeField() blog = models.URLField() occupation = models.CharField(max_length=10, choices=OCCUPATION_CHOICES) uuid = models.UUIDField(primary_key=False) name_hash = models.BinaryField(max_length=16) days_since_last_login = models.BigIntegerField() duration_of_sleep = models.DurationField() email = models.EmailField() id_document = models.CharField(unique=True, max_length=10) try: from django.db.models import JSONField data = JSONField() except ImportError: # Skip JSONField-related fields pass try: from django.contrib.postgres.fields import ArrayField, HStoreField from django.contrib.postgres.fields import JSONField as PostgresJSONField from django.contrib.postgres.fields.citext import ( CICharField, CIEmailField, CITextField, ) from django.contrib.postgres.fields.ranges import ( BigIntegerRangeField, DateRangeField, DateTimeRangeField, IntegerRangeField, ) if settings.USING_POSTGRES: acquaintances = ArrayField(models.IntegerField()) postgres_data = PostgresJSONField() hstore_data = HStoreField() ci_char = CICharField(max_length=30) ci_email = CIEmailField() ci_text = CITextField() int_range = IntegerRangeField() bigint_range = BigIntegerRangeField() date_range = DateRangeField() datetime_range = DateTimeRangeField() except ImportError: # Skip PostgreSQL-related fields pass try: from django.contrib.postgres.fields.ranges import FloatRangeField if settings.USING_POSTGRES: float_range = FloatRangeField() except ImportError: # Django version greater or equal than 3.1 pass try: from django.contrib.postgres.fields.ranges import DecimalRangeField if settings.USING_POSTGRES: decimal_range = DecimalRangeField() except ImportError: # Django version lower than 2.2 pass if BAKER_GIS: geom = models.GeometryField() point = models.PointField() line_string = models.LineStringField() polygon = models.PolygonField() multi_point = models.MultiPointField() multi_line_string = models.MultiLineStringField() multi_polygon = models.MultiPolygonField() geom_collection = models.GeometryCollectionField() class Dog(models.Model): class Meta: order_with_respect_to = "owner" owner = models.ForeignKey("Person", on_delete=models.CASCADE) breed = models.CharField(max_length=50) created = models.DateTimeField(auto_now_add=True) friends_with = models.ManyToManyField("Dog") class GuardDog(Dog): pass class Home(models.Model): address = models.CharField(max_length=200) owner = models.ForeignKey("Person", on_delete=models.CASCADE) dogs = models.ManyToManyField("Dog") class LonelyPerson(models.Model): only_friend = models.OneToOneField(Person, on_delete=models.CASCADE) class Cake(models.Model): name = models.CharField(max_length=64) class RelatedNamesModel(models.Model): name = models.CharField(max_length=256) one_to_one = models.OneToOneField( Person, related_name="one_related", on_delete=models.CASCADE ) foreign_key = models.ForeignKey( Person, related_name="fk_related", on_delete=models.CASCADE ) def get_default_cake_id(): instance, _ = Cake.objects.get_or_create(name="Muffin") return instance.id class RelatedNamesWithDefaultsModel(models.Model): name = models.CharField(max_length=256, default="Bravo") cake = models.ForeignKey( Cake, on_delete=models.CASCADE, default=get_default_cake_id, ) class RelatedNamesWithEmptyDefaultsModel(models.Model): name = models.CharField(max_length=256, default="Bravo") cake = models.ForeignKey( Cake, on_delete=models.CASCADE, null=True, default=None, ) class ModelWithOverridedSave(Dog): def save(self, *args, **kwargs): self.owner = kwargs.pop("owner") return super().save(*args, **kwargs) class ModelWithSaveKwargs(Dog): def save(self, *args, **kwargs): self.breed = kwargs.pop("breed") return super().save(*args, **kwargs) class Classroom(models.Model): students = models.ManyToManyField(Person, null=True) active = models.BooleanField(null=True) class ClassroomM2MRelated(models.Model): """ This model was created in order to reproduce the scenario described at issue 248 that is: a model with a M2M field (Classroom) being also used as a M2M field from another model (ClassroomM2MRelated) """ related_classrooms = models.ManyToManyField(Classroom) class Store(models.Model): customers = models.ManyToManyField(Person, related_name="favorite_stores") employees = models.ManyToManyField(Person, related_name="employers") suppliers = models.ManyToManyField( Person, related_name="suppliers", blank=True, null=True ) class DummyEmptyModel(models.Model): pass class DummyIntModel(models.Model): int_field = models.IntegerField() small_int_field = models.SmallIntegerField() big_int_field = models.BigIntegerField() class DummyPositiveIntModel(models.Model): positive_small_int_field = models.PositiveSmallIntegerField() positive_int_field = models.PositiveIntegerField() class DummyNumbersModel(models.Model): float_field = models.FloatField() class DummyDecimalModel(models.Model): decimal_field = models.DecimalField(max_digits=1, decimal_places=0) class UnsupportedField(models.Field): description = "I'm bad company, baker doesn't know me" def __init__(self, *args, **kwargs): super(UnsupportedField, self).__init__(*args, **kwargs) class UnsupportedModel(models.Model): unsupported_field = UnsupportedField() class DummyGenericForeignKeyModel(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey("content_type", "object_id") class DummyGenericRelationModel(models.Model): relation = GenericRelation(DummyGenericForeignKeyModel) class DummyNullFieldsModel(models.Model): null_foreign_key = models.ForeignKey( "DummyBlankFieldsModel", null=True, on_delete=models.CASCADE ) null_integer_field = models.IntegerField(null=True) class DummyBlankFieldsModel(models.Model): blank_char_field = models.CharField(max_length=50, blank=True) blank_text_field = models.TextField(max_length=300, blank=True) class ExtendedDefaultField(models.IntegerField): pass class DummyDefaultFieldsModel(models.Model): default_id = models.AutoField(primary_key=True) default_char_field = models.CharField(max_length=50, default="default") default_text_field = models.TextField(default="default") default_int_field = models.IntegerField(default=123) default_float_field = models.FloatField(default=123.0) default_date_field = models.DateField(default="2012-01-01") default_date_time_field = models.DateTimeField( default=tz_aware(datetime.datetime(2012, 1, 1)) ) default_time_field = models.TimeField(default="00:00:00") default_decimal_field = models.DecimalField( max_digits=5, decimal_places=2, default=Decimal("0") ) default_email_field = models.EmailField(default="foo@bar.org") default_slug_field = models.SlugField(default="a-slug") default_unknown_class_field = ExtendedDefaultField(default=42) default_callable_int_field = models.IntegerField(default=lambda: 12) default_callable_datetime_field = models.DateTimeField(default=now) class DummyFileFieldModel(models.Model): fs = FileSystemStorage(location=gettempdir()) file_field = models.FileField(upload_to="%Y/%m/%d", storage=fs) if has_pil: class DummyImageFieldModel(models.Model): fs = FileSystemStorage(location=gettempdir()) image_field = models.ImageField(upload_to="%Y/%m/%d", storage=fs) else: # doesn't matter, won't be using class DummyImageFieldModel(models.Model): pass class DummyMultipleInheritanceModel(DummyDefaultFieldsModel, Person): my_id = models.AutoField(primary_key=True) my_dummy_field = models.IntegerField() class Ambiguous(models.Model): name = models.CharField(max_length=20) class School(models.Model): name = models.CharField(max_length=50) students = models.ManyToManyField(Person, through="SchoolEnrollment") class SchoolEnrollment(models.Model): start_date = models.DateField(auto_now_add=True) school = models.ForeignKey(School, on_delete=models.CASCADE) student = models.ForeignKey(Person, on_delete=models.CASCADE) class NonAbstractPerson(Person): dummy_count = models.IntegerField() class CustomFieldWithGeneratorModel(models.Model): custom_value = CustomFieldWithGenerator() class CustomFieldWithoutGeneratorModel(models.Model): custom_value = CustomFieldWithoutGenerator() class CustomFieldViaSettingsModel(models.Model): custom_value = CustomFieldViaSettings() class CustomForeignKeyWithGeneratorModel(models.Model): custom_fk = CustomForeignKey( Profile, blank=True, null=True, on_delete=models.CASCADE ) class DummyUniqueIntegerFieldModel(models.Model): value = models.IntegerField(unique=True) class ModelWithNext(models.Model): attr = models.CharField(max_length=10) def next(self): return "foo" class BaseModelForNext(models.Model): fk = models.ForeignKey(ModelWithNext, on_delete=models.CASCADE) class BaseModelForList(models.Model): fk = FakeListField() class Movie(models.Model): title = models.CharField(max_length=30) class MovieManager(models.Manager): def get_queryset(self): """ Annotate queryset with an alias field 'name'. We want to test whether this annotation has been run after calling `baker.make()`. """ return super(MovieManager, self).get_queryset().annotate(name=models.F("title")) class MovieWithAnnotation(Movie): objects = MovieManager() class CastMember(models.Model): movie = models.ForeignKey( Movie, related_name="cast_members", on_delete=models.CASCADE ) person = models.ForeignKey(Person, on_delete=models.CASCADE) class DummyGenericIPAddressFieldModel(models.Model): ipv4_field = models.GenericIPAddressField(protocol="IPv4") ipv6_field = models.GenericIPAddressField(protocol="IPv6") ipv46_field = models.GenericIPAddressField(protocol="both") class AbstractModel(models.Model): class Meta(object): abstract = True name = models.CharField(max_length=30) class SubclassOfAbstract(AbstractModel): height = models.IntegerField() class NonStandardManager(models.Model): name = models.CharField(max_length=30) manager = models.Manager() # The followin models were added after issue 291 # Since they don't hold much meaning, they are only numbered ones class Issue291Model1(models.Model): pass class Issue291Model2(models.Model): m2m_model_1 = models.ManyToManyField(Issue291Model1) class Issue291Model3(models.Model): fk_model_2 = models.ForeignKey( Issue291Model2, related_name="bazs", on_delete=models.CASCADE ) name = models.CharField(max_length=32) model_bakery-1.7.0/tests/generic/tests/000077500000000000000000000000001427223773300201065ustar00rootroot00000000000000model_bakery-1.7.0/tests/generic/tests/sub_package/000077500000000000000000000000001427223773300223525ustar00rootroot00000000000000model_bakery-1.7.0/tests/generic/tests/sub_package/__init__.py000066400000000000000000000000001427223773300244510ustar00rootroot00000000000000model_bakery-1.7.0/tests/generic/tests/sub_package/baker_recipes.py000066400000000000000000000006051427223773300255230ustar00rootroot00000000000000from datetime import date, datetime from model_bakery.recipe import Recipe from tests.generic.models import Person person = Recipe( Person, name="John Deeper", nickname="joe", age=18, bio="Someone in the crowd", blog="http://joe.blogspot.com", days_since_last_login=4, birthday=date.today(), appointment=datetime.now(), birth_time=datetime.now, ) model_bakery-1.7.0/tests/test_baker.py000066400000000000000000001156301427223773300200330ustar00rootroot00000000000000import datetime import itertools from decimal import Decimal from unittest.mock import patch import pytest from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.db.models import Manager from django.db.models.signals import m2m_changed from django.test import TestCase, override_settings from model_bakery import baker, random_gen from model_bakery.baker import MAX_MANY_QUANTITY from model_bakery.exceptions import ( AmbiguousModelName, InvalidQuantityException, ModelNotFound, ) from model_bakery.timezone import tz_aware from tests.generic import models from tests.generic.forms import DummyGenericIPAddressFieldForm def test_import_seq_from_baker(): """Test import seq method from baker module.""" try: from model_bakery.baker import seq # NoQA except ImportError: pytest.fail("{} raised".format(ImportError.__name__)) class TestsModelFinder: def test_unicode_regression(self): obj = baker.prepare("generic.Person") assert isinstance(obj, models.Person) def test_model_class(self): obj = baker.prepare(models.Person) assert isinstance(obj, models.Person) def test_app_model_string(self): obj = baker.prepare("generic.Person") assert isinstance(obj, models.Person) def test_model_string(self): obj = baker.prepare("Person") assert isinstance(obj, models.Person) def test_raise_on_ambiguous_model_string(self): with pytest.raises(AmbiguousModelName): baker.prepare("Ambiguous") def test_raise_model_not_found(self): with pytest.raises(ModelNotFound): baker.Baker("non_existing.Model") with pytest.raises(ModelNotFound): baker.Baker("NonExistingModel") class TestRecipeFinder: def test_from_app_module(self): obj = baker.prepare_recipe("generic.person") assert isinstance(obj, models.Person) assert obj.name == "John Doe" def test_full_path_from_app_module(self): obj = baker.prepare_recipe("tests.generic.person") assert isinstance(obj, models.Person) assert obj.name == "John Doe" def test_from_non_app_module(self): obj = baker.prepare_recipe("uninstalled.person") assert isinstance(obj, models.Person) assert obj.name == "Uninstalled" def test_full_path_from_non_app_module(self): obj = baker.prepare_recipe("tests.uninstalled.person") assert isinstance(obj, models.Person) assert obj.name == "Uninstalled" def test_raise_on_non_module_path(self): # Error trying to parse "app_name" + "recipe_name" from provided string with pytest.raises(ValueError): baker.prepare_recipe("person") @pytest.mark.django_db class TestsBakerCreatesSimpleModel: def test_consider_real_django_fields_only(self): id_ = models.ModelWithImpostorField._meta.get_field("id") with patch.object(baker.Baker, "get_fields") as mock: f = Manager() f.name = "foo" mock.return_value = [id_, f] try: baker.make(models.ModelWithImpostorField) except TypeError: assert False, "TypeError raised" def test_make_should_create_one_object(self): person = baker.make(models.Person) assert isinstance(person, models.Person) # makes sure it is the person we created assert models.Person.objects.filter(id=person.id).exists() def test_prepare_should_not_persist_one_object(self): person = baker.prepare(models.Person) assert isinstance(person, models.Person) # makes sure database is clean assert not models.Person.objects.all().exists() assert person.id is None def test_non_abstract_model_creation(self): person = baker.make( models.NonAbstractPerson, name="bob", enjoy_jards_macale=False ) assert isinstance(person, models.NonAbstractPerson) assert "bob" == person.name assert person.enjoy_jards_macale is False def test_abstract_model_subclass_creation(self): instance = baker.make(models.SubclassOfAbstract) assert isinstance(instance, models.SubclassOfAbstract) assert isinstance(instance, models.AbstractModel) assert isinstance(instance.name, type("")) assert len(instance.name) == 30 assert isinstance(instance.height, int) def test_multiple_inheritance_creation(self): multiple = baker.make(models.DummyMultipleInheritanceModel) assert isinstance(multiple, models.DummyMultipleInheritanceModel) assert models.Person.objects.filter(id=multiple.id).exists() assert models.DummyDefaultFieldsModel.objects.filter( default_id=multiple.default_id ).exists() @pytest.mark.django_db class TestsBakerRepeatedCreatesSimpleModel(TestCase): def test_make_should_create_objects_respecting_quantity_parameter(self): with self.assertNumQueries(5): baker.make(models.Person, _quantity=5) assert models.Person.objects.count() == 5 with self.assertNumQueries(5): people = baker.make(models.Person, _quantity=5, name="George Washington") assert all(p.name == "George Washington" for p in people) def test_make_quantity_respecting_bulk_create_parameter(self): with self.assertNumQueries(1): baker.make(models.Person, _quantity=5, _bulk_create=True) assert models.Person.objects.count() == 5 with self.assertNumQueries(1): people = baker.make( models.Person, name="George Washington", _quantity=5, _bulk_create=True ) assert all(p.name == "George Washington" for p in people) with self.assertNumQueries(1): baker.make(models.NonStandardManager, _quantity=3, _bulk_create=True) assert getattr(models.NonStandardManager, "objects", None) is None assert ( models.NonStandardManager._base_manager == models.NonStandardManager.manager ) assert ( models.NonStandardManager._default_manager == models.NonStandardManager.manager ) assert models.NonStandardManager.manager.count() == 3 def test_make_raises_correct_exception_if_invalid_quantity(self): with pytest.raises(InvalidQuantityException): baker.make(_model=models.Person, _quantity="hi") with pytest.raises(InvalidQuantityException): baker.make(_model=models.Person, _quantity=-1) with pytest.raises(InvalidQuantityException): baker.make(_model=models.Person, _quantity=0) def test_prepare_should_create_objects_respecting_quantity_parameter(self): people = baker.prepare(models.Person, _quantity=5) assert len(people) == 5 assert all(not p.id for p in people) people = baker.prepare(models.Person, _quantity=5, name="George Washington") assert all(p.name == "George Washington" for p in people) def test_prepare_raises_correct_exception_if_invalid_quantity(self): with pytest.raises(InvalidQuantityException): baker.prepare(_model=models.Person, _quantity="hi") with pytest.raises(InvalidQuantityException): baker.prepare(_model=models.Person, _quantity=-1) with pytest.raises(InvalidQuantityException): baker.prepare(_model=models.Person, _quantity=0) def test_accepts_generators_with_quantity(self): baker.make( models.Person, name=itertools.cycle(["a", "b", "c"]), id_document=itertools.cycle(["d1", "d2", "d3", "d4", "d5"]), _quantity=5, ) assert models.Person.objects.count() == 5 p1, p2, p3, p4, p5 = models.Person.objects.all().order_by("pk") assert "a" == p1.name assert "d1" == p1.id_document assert "b" == p2.name assert "d2" == p2.id_document assert "c" == p3.name assert "d3" == p3.id_document assert "a" == p4.name assert "d4" == p4.id_document assert "b" == p5.name assert "d5" == p5.id_document def test_accepts_generators_with_quantity_for_unique_fields(self): baker.make( models.DummyUniqueIntegerFieldModel, value=itertools.cycle([1, 2, 3]), _quantity=3, ) assert models.DummyUniqueIntegerFieldModel.objects.count() == 3 num_1, num_2, num_3 = models.DummyUniqueIntegerFieldModel.objects.all() assert 1 == num_1.value assert 2 == num_2.value assert 3 == num_3.value def test_generators_work_with_user_model(self): from django.contrib.auth import get_user_model User = get_user_model() baker.make(User, username=itertools.cycle(["a", "b", "c"]), _quantity=3) assert User.objects.count() == 3 u1, u2, u3 = User.objects.all() assert "a" == u1.username assert "b" == u2.username assert "c" == u3.username @pytest.mark.django_db class TestBakerPrepareSavingRelatedInstances: def test_default_behaviour_for_and_fk(self): dog = baker.prepare(models.Dog) assert dog.pk is None assert dog.owner.pk is None with pytest.raises(ValueError): assert dog.friends_with def test_create_fk_instances(self): dog = baker.prepare(models.Dog, _save_related=True) assert dog.pk is None assert dog.owner.pk with pytest.raises(ValueError): assert dog.friends_with def test_create_fk_instances_with_quantity(self): dog1, dog2 = baker.prepare(models.Dog, _save_related=True, _quantity=2) assert dog1.pk is None assert dog1.owner.pk with pytest.raises(ValueError): assert dog1.friends_with assert dog2.pk is None assert dog2.owner.pk with pytest.raises(ValueError): assert dog2.friends_with def test_create_one_to_one(self): lonely_person = baker.prepare(models.LonelyPerson, _save_related=True) assert lonely_person.pk is None assert lonely_person.only_friend.pk @pytest.mark.django_db class TestBakerCreatesAssociatedModels(TestCase): def test_dependent_models_with_ForeignKey(self): dog = baker.make(models.Dog) assert isinstance(dog.owner, models.Person) def test_foreign_key_on_parent_should_create_one_object(self): person_count = models.Person.objects.count() baker.make(models.GuardDog) assert models.Person.objects.count() == person_count + 1 def test_foreign_key_on_parent_is_not_created(self): """Foreign key on parent doesn't get created using owner.""" owner = baker.make(models.Person) person_count = models.Person.objects.count() dog = baker.make(models.GuardDog, owner=owner) assert models.Person.objects.count() == person_count assert dog.owner == owner def test_foreign_key_on_parent_id_is_not_created(self): """Foreign key on parent doesn't get created using owner_id.""" owner = baker.make(models.Person) person_count = models.Person.objects.count() dog = baker.make(models.GuardDog, owner_id=owner.id) assert models.Person.objects.count() == person_count assert models.GuardDog.objects.get(pk=dog.pk).owner == owner def test_auto_now_add_on_parent_should_work(self): person_count = models.Person.objects.count() dog = baker.make(models.GuardDog) assert models.Person.objects.count() == person_count + 1 assert dog.created def test_attrs_on_related_model_through_parent(self): baker.make(models.GuardDog, owner__name="john") for person in models.Person.objects.all(): assert person.name == "john" def test_access_related_name_of_m2m(self): try: baker.make(models.Person, classroom_set=[baker.make(models.Classroom)]) except TypeError: assert False, "type error raised" def test_save_object_instances_when_handling_one_to_many_relations(self): owner = baker.make(models.Person) dogs_set = baker.prepare( models.Dog, owner=owner, _quantity=2, ) assert 0 == models.Dog.objects.count() # ensure there're no dogs in our db home = baker.make( models.Home, owner=owner, dogs=dogs_set, ) assert home.dogs.count() == 2 assert 2 == models.Dog.objects.count() # dogs in dogs_set were created def test_prepare_fk(self): dog = baker.prepare(models.Dog) assert isinstance(dog, models.Dog) assert isinstance(dog.owner, models.Person) assert models.Person.objects.all().count() == 0 assert models.Dog.objects.all().count() == 0 def test_create_one_to_one(self): lonely_person = baker.make(models.LonelyPerson) assert models.LonelyPerson.objects.all().count() == 1 assert isinstance(lonely_person.only_friend, models.Person) assert models.Person.objects.all().count() == 1 def test_create_multiple_one_to_one(self): baker.make(models.LonelyPerson, _quantity=5) assert models.LonelyPerson.objects.all().count() == 5 assert models.Person.objects.all().count() == 5 def test_bulk_create_multiple_one_to_one(self): with self.assertNumQueries(6): baker.make(models.LonelyPerson, _quantity=5, _bulk_create=True) assert models.LonelyPerson.objects.all().count() == 5 assert models.Person.objects.all().count() == 5 def test_chaining_bulk_create_reduces_query_count(self): qtd = 5 with self.assertNumQueries(3): baker.make(models.Person, _quantity=qtd, _bulk_create=True) person_iter = models.Person.objects.all().iterator() baker.make( models.LonelyPerson, only_friend=person_iter, _quantity=5, _bulk_create=True, ) # 2 bulk create and 1 select on Person assert models.LonelyPerson.objects.all().count() == 5 assert models.Person.objects.all().count() == 5 def test_bulk_create_multiple_fk(self): with self.assertNumQueries(6): baker.make(models.PaymentBill, _quantity=5, _bulk_create=True) assert models.PaymentBill.objects.all().count() == 5 assert models.User.objects.all().count() == 5 def test_create_many_to_many_if_flagged(self): store = baker.make(models.Store, make_m2m=True) assert store.employees.count() == 5 assert store.customers.count() == 5 def test_regresstion_many_to_many_field_is_accepted_as_kwargs(self): employees = baker.make(models.Person, _quantity=3) customers = baker.make(models.Person, _quantity=3) store = baker.make(models.Store, employees=employees, customers=customers) assert store.employees.count() == 3 assert store.customers.count() == 3 assert models.Person.objects.count() == 6 def test_create_many_to_many_with_iter(self): students = baker.make(models.Person, _quantity=3) classrooms = baker.make(models.Classroom, _quantity=3, students=iter(students)) assert classrooms[0].students.count() == 1 assert classrooms[0].students.first() == students[0] assert classrooms[1].students.count() == 1 assert classrooms[1].students.first() == students[1] assert classrooms[2].students.count() == 1 assert classrooms[2].students.first() == students[2] def test_create_many_to_many_with_unsaved_iter(self): students = baker.prepare(models.Person, _quantity=3) classrooms = baker.make(models.Classroom, _quantity=3, students=iter(students)) assert students[0].pk is not None assert students[1].pk is not None assert students[2].pk is not None assert classrooms[0].students.count() == 1 assert classrooms[0].students.first() == students[0] assert classrooms[1].students.count() == 1 assert classrooms[1].students.first() == students[1] assert classrooms[2].students.count() == 1 assert classrooms[2].students.first() == students[2] def test_create_many_to_many_with_through_and_iter(self): students = baker.make(models.Person, _quantity=3) schools = baker.make( models.School, _quantity=3, students=iter(students), ) assert schools[0].students.count() == 1 assert schools[0].students.first() == students[0] assert schools[1].students.count() == 1 assert schools[1].students.first() == students[1] assert schools[2].students.count() == 1 assert schools[2].students.first() == students[2] def test_create_many_to_many_with_set_default_quantity(self): store = baker.make(models.Store, make_m2m=True) assert store.employees.count() == baker.MAX_MANY_QUANTITY assert store.customers.count() == baker.MAX_MANY_QUANTITY def test_create_many_to_many_with_through_option(self): # School student's attr is a m2m relationship with a model through school = baker.make(models.School, make_m2m=True) assert models.School.objects.count() == 1 assert school.students.count() == baker.MAX_MANY_QUANTITY assert models.SchoolEnrollment.objects.count() == baker.MAX_MANY_QUANTITY assert models.Person.objects.count() == baker.MAX_MANY_QUANTITY def test_does_not_create_many_to_many_as_default(self): store = baker.make(models.Store) assert store.employees.count() == 0 assert store.customers.count() == 0 def test_does_not_create_nullable_many_to_many_for_relations(self): classroom = baker.make(models.Classroom, make_m2m=False) assert classroom.students.count() == 0 def test_nullable_many_to_many_is_not_created_even_if_flagged(self): classroom = baker.make(models.Classroom, make_m2m=True) assert not classroom.students.count() def test_m2m_changed_signal_is_fired(self): # TODO: Use object attrs instead of mocks for Django 1.4 compat self.m2m_changed_fired = False def test_m2m_changed(*args, **kwargs): self.m2m_changed_fired = True m2m_changed.connect(test_m2m_changed, dispatch_uid="test_m2m_changed") baker.make(models.Store, make_m2m=True) assert self.m2m_changed_fired def test_simple_creating_person_with_parameters(self): kid = baker.make(models.Person, enjoy_jards_macale=True, age=10, name="Mike") assert kid.age == 10 assert kid.enjoy_jards_macale is True assert kid.name == "Mike" def test_creating_person_from_factory_using_paramters(self): person_baker_ = baker.Baker(models.Person) person = person_baker_.make( enjoy_jards_macale=False, age=20, gender="M", name="John" ) assert person.age == 20 assert person.enjoy_jards_macale is False assert person.name == "John" assert person.gender == "M" def test_ForeignKey_model_field_population(self): dog = baker.make(models.Dog, breed="X1", owner__name="Bob") assert "X1" == dog.breed assert "Bob" == dog.owner.name def test_ForeignKey_model_field_population_should_work_with_prepare(self): dog = baker.prepare(models.Dog, breed="X1", owner__name="Bob") assert "X1" == dog.breed assert "Bob" == dog.owner.name def test_ForeignKey_model_field_population_for_not_required_fk(self): user = baker.make(models.User, profile__email="a@b.com") assert "a@b.com" == user.profile.email def test_does_not_creates_null_ForeignKey(self): user = baker.make(models.User) assert not user.profile def test_passing_m2m_value(self): store = baker.make(models.Store, customers=[baker.make(models.Person)]) assert store.customers.count() == 1 def test_ensure_recursive_ForeignKey_population(self): bill = baker.make(models.PaymentBill, user__profile__email="a@b.com") assert "a@b.com" == bill.user.profile.email def test_field_lookup_for_m2m_relationship(self): store = baker.make(models.Store, suppliers__gender="M") suppliers = store.suppliers.all() assert suppliers for supplier in suppliers: assert "M" == supplier.gender def test_field_lookup_for_one_to_one_relationship(self): lonely_person = baker.make(models.LonelyPerson, only_friend__name="Bob") assert "Bob" == lonely_person.only_friend.name def test_allow_create_fkey_related_model(self): try: person = baker.make( models.Person, dog_set=[baker.make(models.Dog), baker.make(models.Dog)] ) except TypeError: assert False, "type error raised" assert person.dog_set.count() == 2 def test_field_lookup_for_related_field(self): person = baker.make( models.Person, one_related__name="Foo", fk_related__name="Bar", ) assert person.pk assert person.one_related.pk assert 1, person.fk_related.count() assert "Foo" == person.one_related.name assert "Bar" == person.fk_related.get().name def test_field_lookup_for_related_field_does_not_work_with_prepare(self): person = baker.prepare( models.Person, one_related__name="Foo", fk_related__name="Bar", ) assert not person.pk assert 0 == models.RelatedNamesModel.objects.count() def test_ensure_reverse_fk_for_many_to_one_is_working(self): """This is a regression test to make sure issue 291 is fixed.""" fk1, fk2 = baker.prepare( models.Issue291Model3, fk_model_2=None, name="custom name", _quantity=2 ) obj = baker.make( models.Issue291Model2, m2m_model_1=[baker.make(models.Issue291Model1)], bazs=[fk1, fk2], ) assert obj.bazs.count() == 2 related_1, related_2 = obj.bazs.all() assert related_1.name == "custom name" assert related_2.name == "custom name" @pytest.mark.django_db class TestHandlingUnsupportedModels: def test_unsupported_model_raises_an_explanatory_exception(self): try: baker.make(models.UnsupportedModel) assert False, "Should have raised a TypeError" except TypeError as e: assert "not supported" in repr(e) assert "field unsupported_field" in repr(e) @pytest.mark.django_db class TestHandlingModelsWithGenericRelationFields: def test_create_model_with_generic_relation(self): dummy = baker.make(models.DummyGenericRelationModel) assert isinstance(dummy, models.DummyGenericRelationModel) @pytest.mark.django_db class TestHandlingContentTypeField: def test_create_model_with_contenttype_field(self): dummy = baker.make(models.DummyGenericForeignKeyModel) assert isinstance(dummy, models.DummyGenericForeignKeyModel) assert isinstance(dummy.content_type, ContentType) class TestHandlingContentTypeFieldNoQueries: def test_create_model_with_contenttype_field(self): # Clear ContentType's internal cache so that it *will* try to connect to # the database in order to fetch the corresponding ContentType model for # a randomly chosen model. ContentType.objects.clear_cache() with pytest.warns( UserWarning, match="Database access disabled, returning ContentType raw instance", ): dummy = baker.prepare(models.DummyGenericForeignKeyModel) assert isinstance(dummy, models.DummyGenericForeignKeyModel) assert isinstance(dummy.content_type, ContentType) @pytest.mark.django_db class TestSkipNullsTestCase: def test_skip_null(self): dummy = baker.make(models.DummyNullFieldsModel) assert dummy.null_foreign_key is None assert dummy.null_integer_field is None @pytest.mark.django_db class TestFillNullsTestCase: def test_create_nullable_many_to_many_if_flagged_and_fill_field_optional(self): classroom = baker.make( models.Classroom, make_m2m=True, _fill_optional=["students"] ) assert classroom.students.count() == 5 def test_create_nullable_many_to_many_if_flagged_and_fill_optional(self): classroom = baker.make(models.Classroom, make_m2m=True, _fill_optional=True) assert classroom.students.count() == 5 def test_nullable_many_to_many_is_not_created_if_not_flagged_and_fill_optional( self, ): classroom = baker.make(models.Classroom, make_m2m=False, _fill_optional=True) assert classroom.students.count() == 0 @pytest.mark.django_db class TestSkipBlanksTestCase: def test_skip_blank(self): dummy = baker.make(models.DummyBlankFieldsModel) assert dummy.blank_char_field == "" assert dummy.blank_text_field == "" def test_skip_blank_with_argument(self): dummy = baker.make(models.DummyBlankFieldsModel, _fill_optional=False) assert dummy.blank_char_field == "" assert dummy.blank_text_field == "" def test_skip_blank_when_preparing(self): dummy = baker.prepare(models.DummyBlankFieldsModel) assert dummy.blank_char_field == "" assert dummy.blank_text_field == "" def test_skip_blank_when_preparing_with_argument(self): dummy = baker.prepare(models.DummyBlankFieldsModel, _fill_optional=False) assert dummy.blank_char_field == "" assert dummy.blank_text_field == "" @pytest.mark.django_db class TestFillBlanksTestCase: def test_fill_field_optional(self): dummy = baker.make( models.DummyBlankFieldsModel, _fill_optional=["blank_char_field"] ) assert len(dummy.blank_char_field) == 50 def test_fill_field_optinal_when_preparing(self): dummy = baker.prepare( models.DummyBlankFieldsModel, _fill_optional=["blank_char_field"] ) assert len(dummy.blank_char_field) == 50 def test_fill_wrong_field(self): with pytest.raises(AttributeError) as exc_info: baker.make( models.DummyBlankFieldsModel, _fill_optional=["blank_char_field", "wrong"], ) msg = "_fill_optional field(s) ['wrong'] are not related to model DummyBlankFieldsModel" assert msg in str(exc_info.value) def test_fill_wrong_fields_with_parent(self): with pytest.raises(AttributeError): baker.make(models.SubclassOfAbstract, _fill_optional=["name", "wrong"]) def test_fill_many_optional(self): dummy = baker.make( models.DummyBlankFieldsModel, _fill_optional=["blank_char_field", "blank_text_field"], ) assert len(dummy.blank_text_field) == 300 def test_fill_all_optional(self): dummy = baker.make(models.DummyBlankFieldsModel, _fill_optional=True) assert len(dummy.blank_char_field) == 50 assert len(dummy.blank_text_field) == 300 def test_fill_all_optional_when_preparing(self): dummy = baker.prepare(models.DummyBlankFieldsModel, _fill_optional=True) assert len(dummy.blank_char_field) == 50 assert len(dummy.blank_text_field) == 300 def test_fill_optional_with_integer(self): with pytest.raises(TypeError): baker.make(models.DummyBlankFieldsModel, _fill_optional=1) def test_fill_optional_with_default(self): dummy = baker.make(models.DummyDefaultFieldsModel, _fill_optional=True) assert dummy.default_callable_int_field == 12 assert isinstance(dummy.default_callable_datetime_field, datetime.datetime) def test_fill_optional_with_default_unknown_class(self): dummy = baker.make(models.DummyDefaultFieldsModel, _fill_optional=True) assert dummy.default_unknown_class_field == 42 @pytest.mark.django_db class TestFillAutoFieldsTestCase: def test_fill_autofields_with_provided_value(self): baker.make(models.DummyEmptyModel, id=237) saved_dummy = models.DummyEmptyModel.objects.get() assert saved_dummy.id == 237 def test_keeps_prepare_autovalues(self): dummy = baker.prepare(models.DummyEmptyModel, id=543) assert dummy.id == 543 dummy.save() saved_dummy = models.DummyEmptyModel.objects.get() assert saved_dummy.id == 543 @pytest.mark.django_db class TestSkipDefaultsTestCase: def test_skip_fields_with_default(self): dummy = baker.make(models.DummyDefaultFieldsModel) assert dummy.default_char_field == "default" assert dummy.default_text_field == "default" assert dummy.default_int_field == 123 assert dummy.default_float_field == 123.0 assert dummy.default_date_field == "2012-01-01" assert dummy.default_date_time_field == tz_aware(datetime.datetime(2012, 1, 1)) assert dummy.default_time_field == "00:00:00" assert dummy.default_decimal_field == Decimal("0") assert dummy.default_email_field == "foo@bar.org" assert dummy.default_slug_field == "a-slug" assert dummy.default_unknown_class_field == 42 assert dummy.default_callable_int_field == 12 assert isinstance(dummy.default_callable_datetime_field, datetime.datetime) @pytest.mark.django_db class TestBakerHandlesModelWithNext: def test_creates_instance_for_model_with_next(self): instance = baker.make( models.BaseModelForNext, fk=baker.make(models.ModelWithNext), ) assert instance.id assert instance.fk.id assert instance.fk.attr assert "foo" == instance.fk.next() @pytest.mark.django_db class TestBakerHandlesModelWithList: def test_creates_instance_for_model_with_list(self): instance = baker.make(models.BaseModelForList, fk=["foo"]) assert instance.id assert ["foo"] == instance.fk @pytest.mark.django_db class TestBakerGeneratesIPAdresses: def test_create_model_with_valid_ips(self): form_data = { "ipv4_field": random_gen.gen_ipv4(), "ipv6_field": random_gen.gen_ipv6(), "ipv46_field": random_gen.gen_ipv46(), } assert DummyGenericIPAddressFieldForm(form_data).is_valid() class TestBakerAllowsSaveParameters(TestCase): databases = ["default", settings.EXTRA_DB] def test_allows_save_kwargs_on_baker_make(self): owner = baker.make(models.Person) dog = baker.make(models.ModelWithOverridedSave, _save_kwargs={"owner": owner}) assert owner == dog.owner dog1, dog2 = baker.make( models.ModelWithOverridedSave, _save_kwargs={"owner": owner}, _quantity=2 ) assert dog1.owner == owner assert dog2.owner == owner def test_allow_user_to_specify_database_via_save_kwargs_for_retro_compatibility( self, ): profile = baker.make(models.Profile, _save_kwargs={"using": settings.EXTRA_DB}) qs = models.Profile.objects.using(settings.EXTRA_DB).all() assert qs.count() == 1 assert profile in qs class TestBakerSupportsMultiDatabase(TestCase): databases = ["default", settings.EXTRA_DB] def test_allow_user_to_specify_database_via_using(self): profile = baker.make(models.Profile, _using=settings.EXTRA_DB) qs = models.Profile.objects.using(settings.EXTRA_DB).all() assert qs.count() == 1 assert profile in qs def test_related_fk_database_specified_via_using_kwarg(self): dog = baker.make(models.Dog, _using=settings.EXTRA_DB) dog_qs = models.Dog.objects.using(settings.EXTRA_DB).all() assert dog_qs.count() == 1 assert dog in dog_qs person_qs = models.Person.objects.using(settings.EXTRA_DB).all() assert person_qs.count() == 1 assert dog.owner in person_qs def test_allow_user_to_specify_database_via_using_combined_with_bulk_create( self, ): baker.make( models.Profile, _using=settings.EXTRA_DB, _quantity=10, _bulk_create=True ) qs = models.Profile.objects.using(settings.EXTRA_DB).all() assert qs.count() == 10 def test_related_fk_database_specified_via_using_kwarg_combined_with_quantity(self): dogs = baker.make(models.Dog, _using=settings.EXTRA_DB, _quantity=5) dog_qs = models.Dog.objects.using(settings.EXTRA_DB).all() person_qs = models.Person.objects.using(settings.EXTRA_DB).all() assert person_qs.count() == 5 assert dog_qs.count() == 5 for dog in dogs: assert dog in dog_qs assert dog.owner in person_qs def test_related_fk_database_specified_via_using_kwarg_combined_with_bulk_create( self, ): # A custom router must be used when using bulk create and saving # related objects in a multi-database setting. class AllowRelationRouter: """Custom router that allows saving of relations.""" def allow_relation(self, obj1, obj2, **hints): """Allow all relations.""" return True with override_settings(DATABASE_ROUTERS=[AllowRelationRouter()]): baker.make( models.PaymentBill, _quantity=5, _bulk_create=True, _using=settings.EXTRA_DB, ) assert models.PaymentBill.objects.all().using(settings.EXTRA_DB).count() == 5 assert models.User.objects.all().using(settings.EXTRA_DB).count() == 5 # Default router will not be able to save the related objects with pytest.raises(ValueError): baker.make( models.PaymentBill, _quantity=5, _bulk_create=True, _using=settings.EXTRA_DB, ) def test_allow_recipe_to_specify_database_via_using(self): dog = baker.make_recipe("generic.homeless_dog", _using=settings.EXTRA_DB) qs = models.Dog.objects.using(settings.EXTRA_DB).all() assert qs.count() == 1 assert dog in qs def test_recipe_related_fk_database_specified_via_using_kwarg(self): dog = baker.make_recipe("generic.dog", _using=settings.EXTRA_DB) dog_qs = models.Dog.objects.using(settings.EXTRA_DB).all() person_qs = models.Person.objects.using(settings.EXTRA_DB).all() dog.refresh_from_db() assert dog.owner assert dog_qs.count() == 1 assert dog in dog_qs assert person_qs.count() == 1 assert dog.owner in person_qs def test_recipe_related_fk_database_specified_via_using_kwarg_and_quantity(self): dogs = baker.make_recipe("generic.dog", _using=settings.EXTRA_DB, _quantity=5) dog_qs = models.Dog.objects.using(settings.EXTRA_DB).all() person_qs = models.Person.objects.using(settings.EXTRA_DB).all() assert dog_qs.count() == 5 # since we're using recipes, all dogs belongs to the same owner assert person_qs.count() == 1 for dog in dogs: dog.refresh_from_db() assert dog in dog_qs assert dog.owner in person_qs def test_related_m2m_database_specified_via_using_kwarg(self): baker.make(models.Dog, _using=settings.EXTRA_DB, make_m2m=True) dog_qs = models.Dog.objects.using(settings.EXTRA_DB).all() assert dog_qs.count() == MAX_MANY_QUANTITY + 1 assert not models.Dog.objects.exists() def test_related_m2m_database_specified_via_using_kwarg_and_quantity(self): qtd = 3 baker.make(models.Dog, _using=settings.EXTRA_DB, make_m2m=True, _quantity=qtd) dog_qs = models.Dog.objects.using(settings.EXTRA_DB).all() assert dog_qs.count() == MAX_MANY_QUANTITY * qtd + qtd assert not models.Dog.objects.exists() def test_create_many_to_many_with_through_option_and_custom_db(self): # School student's attr is a m2m relationship with a model through school = baker.make(models.School, make_m2m=True, _using=settings.EXTRA_DB) assert models.School.objects.using(settings.EXTRA_DB).count() == 1 assert ( school.students.using(settings.EXTRA_DB).count() == baker.MAX_MANY_QUANTITY ) assert ( models.SchoolEnrollment.objects.using(settings.EXTRA_DB).count() == baker.MAX_MANY_QUANTITY ) assert ( models.Person.objects.using(settings.EXTRA_DB).count() == baker.MAX_MANY_QUANTITY ) def test_recipe_m2m_with_custom_db(self): school = baker.make_recipe( "generic.paulo_freire_school", _using=settings.EXTRA_DB, make_m2m=True ) school.refresh_from_db() assert school.pk assert school.name == "Escola Municipal Paulo Freire" assert models.School.objects.using(settings.EXTRA_DB).count() == 1 assert ( school.students.using(settings.EXTRA_DB).count() == baker.MAX_MANY_QUANTITY ) assert ( models.SchoolEnrollment.objects.using(settings.EXTRA_DB).count() == baker.MAX_MANY_QUANTITY ) assert ( models.Person.objects.using(settings.EXTRA_DB).count() == baker.MAX_MANY_QUANTITY ) assert not models.School.objects.exists() assert not models.SchoolEnrollment.objects.exists() assert not models.Person.objects.exists() @pytest.mark.django_db class TestBakerAutomaticallyRefreshFromDB: def test_refresh_from_db_if_true(self): person = baker.make( models.Person, birthday="2017-02-01", _refresh_after_create=True ) assert person.birthday == datetime.date(2017, 2, 1) def test_do_not_refresh_from_db_if_false(self): person = baker.make( models.Person, birthday="2017-02-01", _refresh_after_create=False ) assert person.birthday == "2017-02-01" assert person.birthday != datetime.date(2017, 2, 1) def test_do_not_refresh_from_db_by_default(self): person = baker.make(models.Person, birthday="2017-02-01") assert person.birthday == "2017-02-01" assert person.birthday != datetime.date(2017, 2, 1) @pytest.mark.django_db class TestBakerMakeCanFetchInstanceFromDefaultManager: def test_annotation_within_manager_get_queryset_are_run_on_make(self): """A custom model Manager can be used within make(). Passing ``_from_manager='objects'`` will force ``baker.make()`` to return an instance that has been going through a given Manager, thus calling its ``get_queryset()`` method and associated code, like default annotations. As such the instance will have the same fields as one created in the application. """ movie = baker.make(models.MovieWithAnnotation) with pytest.raises(AttributeError): assert movie.name movie = baker.make( models.MovieWithAnnotation, title="Old Boy", _from_manager="objects", ) assert movie.title == movie.name model_bakery-1.7.0/tests/test_extending_bakery.py000066400000000000000000000072421427223773300222700ustar00rootroot00000000000000import pytest from model_bakery import baker from model_bakery.exceptions import CustomBakerNotFound, InvalidCustomBaker from model_bakery.random_gen import gen_from_list from tests.generic.models import Person def gen_opposite(default): return not default def gen_age(): # forever young return 18 class ExperientBaker(baker.Baker): age_list = range(40, 60) attr_mapping = {"age": gen_from_list(age_list)} class TeenagerBaker(baker.Baker): attr_mapping = {"age": gen_age} class SadPeopleBaker(baker.Baker): attr_mapping = { "enjoy_jards_macale": gen_opposite, "like_metal_music": gen_opposite, "name": gen_opposite, # Use a field without `default` } @pytest.mark.django_db class TestSimpleExtendBaker: def test_list_generator_respects_values_from_list(self): experient_bakers_factory = ExperientBaker(Person) experient_baker = experient_bakers_factory.make() assert experient_baker.age in ExperientBaker.age_list @pytest.mark.django_db class TestLessSimpleExtendBaker: def test_nonexistent_required_field(self): gen_opposite.required = ["house"] sad_people_factory = SadPeopleBaker(Person) with pytest.raises(AttributeError): sad_people_factory.make() def test_string_to_generator_required(self): gen_opposite.required = ["default"] enjoy_jards_macale_field = Person._meta.get_field("enjoy_jards_macale") like_metal_music_field = Person._meta.get_field("like_metal_music") sad_people_factory = SadPeopleBaker(Person) person = sad_people_factory.make() assert person.enjoy_jards_macale is enjoy_jards_macale_field.default assert person.like_metal_music is like_metal_music_field.default def test_kwarg_used_over_attr_mapping_generator(self): sad_people_factory = SadPeopleBaker(Person) person = sad_people_factory.make(name="test") assert person.name == "test" @pytest.mark.parametrize("value", [18, 18.5, [], {}, True]) def test_fail_pass_non_string_to_generator_required(self, value): teens_bakers_factory = TeenagerBaker(Person) gen_age.required = [value] with pytest.raises(ValueError): teens_bakers_factory.make() class ClassWithoutMake: def prepare(self): pass class ClassWithoutPrepare: def make(self): pass class BakerSubclass(baker.Baker): pass class BakerDuck: def __init__(*args, **kwargs): pass def make(self): pass def prepare(self): pass class TestCustomizeBakerClassViaSettings: def class_to_import_string(self, class_to_convert): return "%s.%s" % (self.__module__, class_to_convert.__name__) def test_create_vanilla_baker_used_by_default(self): baker_instance = baker.Baker.create(Person) assert baker_instance.__class__ == baker.Baker def test_create_fail_on_custom_baker_load_error(self, settings): settings.BAKER_CUSTOM_CLASS = "invalid_module.invalid_class" with pytest.raises(CustomBakerNotFound): baker.Baker.create(Person) @pytest.mark.parametrize("cls", [ClassWithoutMake, ClassWithoutPrepare]) def test_create_fail_on_missing_required_functions(self, settings, cls): settings.BAKER_CUSTOM_CLASS = self.class_to_import_string(cls) with pytest.raises(InvalidCustomBaker): baker.Baker.create(Person) @pytest.mark.parametrize("cls", [BakerSubclass, BakerDuck]) def test_create_succeeds_with_valid_custom_baker(self, settings, cls): settings.BAKER_CUSTOM_CLASS = self.class_to_import_string(cls) assert baker.Baker.create(Person).__class__ == cls model_bakery-1.7.0/tests/test_filling_fields.py000066400000000000000000000555521427223773300217270ustar00rootroot00000000000000import uuid from datetime import date, datetime, time, timedelta from decimal import Decimal from os.path import abspath from tempfile import gettempdir import pytest from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.validators import ( validate_ipv4_address, validate_ipv6_address, validate_ipv46_address, ) from django.db import connection from django.db.models import FileField, ImageField, fields from model_bakery import baker from model_bakery.gis import BAKER_GIS from model_bakery.random_gen import gen_related from tests.generic import generators, models try: from django.db.models import JSONField except ImportError: JSONField = None try: from django.contrib.postgres.fields import ( ArrayField, CICharField, CIEmailField, CITextField, HStoreField, ) from django.contrib.postgres.fields import JSONField as PostgresJSONField from django.contrib.postgres.fields.ranges import ( BigIntegerRangeField, DateRangeField, DateTimeRangeField, IntegerRangeField, ) except ImportError: ArrayField = None PostgresJSONField = None HStoreField = None CICharField = None CIEmailField = None CITextField = None IntegerRangeField = None BigIntegerRangeField = None DateRangeField = None DateTimeRangeField = None try: from django.contrib.postgres.fields.ranges import FloatRangeField except ImportError: FloatRangeField = None try: from django.contrib.postgres.fields.ranges import DecimalRangeField except ImportError: DecimalRangeField = None @pytest.fixture def person(db): return baker.make("generic.Person") @pytest.fixture() def custom_cfg(): yield None if hasattr(settings, "BAKER_CUSTOM_FIELDS_GEN"): delattr(settings, "BAKER_CUSTOM_FIELDS_GEN") baker.generators.add("tests.generic.fields.CustomFieldWithGenerator", None) baker.generators.add("django.db.models.fields.CharField", None) class TestFillingFromChoice: def test_if_gender_is_populated_from_choices(self, person): from tests.generic.models import GENDER_CHOICES assert person.gender in map(lambda x: x[0], GENDER_CHOICES) def test_if_occupation_populated_from_choices(self, person): from tests.generic.models import OCCUPATION_CHOICES occupations = [item[0] for lst in OCCUPATION_CHOICES for item in lst[1]] assert person.occupation in occupations class TestStringFieldsFilling: def test_fill_CharField_with_a_random_str(self, person): person_name_field = models.Person._meta.get_field("name") assert isinstance(person_name_field, fields.CharField) assert isinstance(person.name, str) assert len(person.name) == person_name_field.max_length def test_fill_SlugField_with_a_random_str(self, person): person_nickname_field = models.Person._meta.get_field("nickname") assert isinstance(person_nickname_field, fields.SlugField) assert isinstance(person.nickname, str) assert len(person.nickname) == person_nickname_field.max_length def test_fill_TextField_with_a_random_str(self, person): person_bio_field = models.Person._meta.get_field("bio") assert isinstance(person_bio_field, fields.TextField) assert isinstance(person.bio, str) class TestBinaryFieldsFilling: def test_fill_BinaryField_with_random_binary(self, person): name_hash_field = models.Person._meta.get_field("name_hash") assert isinstance(name_hash_field, fields.BinaryField) name_hash = person.name_hash assert isinstance(name_hash, bytes) assert len(name_hash) == name_hash_field.max_length class TestsDurationFieldsFilling: def test_fill_DurationField_with_random_interval_in_miliseconds(self, person): duration_of_sleep_field = models.Person._meta.get_field("duration_of_sleep") assert isinstance(duration_of_sleep_field, fields.DurationField) duration_of_sleep = person.duration_of_sleep assert isinstance(duration_of_sleep, timedelta) class TestBooleanFieldsFilling: def test_fill_BooleanField_with_boolean(self, person): enjoy_jards_macale_field = models.Person._meta.get_field("enjoy_jards_macale") assert isinstance(enjoy_jards_macale_field, fields.BooleanField) assert isinstance(person.enjoy_jards_macale, bool) assert person.enjoy_jards_macale is True def test_fill_BooleanField_with_false_if_default_is_false(self, person): like_metal_music_field = models.Person._meta.get_field("like_metal_music") assert isinstance(like_metal_music_field, fields.BooleanField) assert isinstance(person.like_metal_music, bool) assert person.like_metal_music is False class TestDateFieldsFilling: def test_fill_DateField_with_a_date(self, person): birthday_field = models.Person._meta.get_field("birthday") assert isinstance(birthday_field, fields.DateField) assert isinstance(person.birthday, date) class TestDateTimeFieldsFilling: def test_fill_DateTimeField_with_a_datetime(self, person): appointment_field = models.Person._meta.get_field("appointment") assert isinstance(appointment_field, fields.DateTimeField) assert isinstance(person.appointment, datetime) class TestTimeFieldsFilling: def test_fill_TimeField_with_a_time(self, person): birth_time_field = models.Person._meta.get_field("birth_time") assert isinstance(birth_time_field, fields.TimeField) assert isinstance(person.birth_time, time) class TestUUIDFieldsFilling: def test_fill_UUIDField_with_uuid_object(self, person): uuid_field = models.Person._meta.get_field("uuid") assert isinstance(uuid_field, fields.UUIDField) assert isinstance(person.uuid, uuid.UUID) @pytest.mark.skipif(JSONField is None, reason="JSONField could not be imported") class TestJSONFieldsFilling: def test_fill_JSONField_with_uuid_object(self, person): json_field = models.Person._meta.get_field("data") assert isinstance(json_field, JSONField) assert isinstance(person.data, dict) @pytest.mark.django_db class TestFillingIntFields: def test_fill_IntegerField_with_a_random_number(self): dummy_int_model = baker.make(models.DummyIntModel) int_field = models.DummyIntModel._meta.get_field("int_field") assert isinstance(int_field, fields.IntegerField) assert isinstance(dummy_int_model.int_field, int) def test_fill_BigIntegerField_with_a_random_number(self): dummy_int_model = baker.make(models.DummyIntModel) big_int_field = models.DummyIntModel._meta.get_field("big_int_field") assert isinstance(big_int_field, fields.BigIntegerField) assert isinstance(dummy_int_model.big_int_field, int) def test_fill_SmallIntegerField_with_a_random_number(self): dummy_int_model = baker.make(models.DummyIntModel) small_int_field = models.DummyIntModel._meta.get_field("small_int_field") assert isinstance(small_int_field, fields.SmallIntegerField) assert isinstance(dummy_int_model.small_int_field, int) @pytest.mark.django_db class TestFillingPositiveIntFields: def test_fill_PositiveSmallIntegerField_with_a_random_number(self): dummy_positive_int_model = baker.make(models.DummyPositiveIntModel) field = models.DummyPositiveIntModel._meta.get_field("positive_small_int_field") positive_small_int_field = field assert isinstance(positive_small_int_field, fields.PositiveSmallIntegerField) assert isinstance(dummy_positive_int_model.positive_small_int_field, int) assert dummy_positive_int_model.positive_small_int_field > 0 def test_fill_PositiveIntegerField_with_a_random_number(self): dummy_positive_int_model = baker.make(models.DummyPositiveIntModel) positive_int_field = models.DummyPositiveIntModel._meta.get_field( "positive_int_field" ) assert isinstance(positive_int_field, fields.PositiveIntegerField) assert isinstance(dummy_positive_int_model.positive_int_field, int) assert dummy_positive_int_model.positive_int_field > 0 @pytest.mark.django_db class TestFillingOthersNumericFields: def test_filling_FloatField_with_a_random_float(self): self.dummy_numbers_model = baker.make(models.DummyNumbersModel) float_field = models.DummyNumbersModel._meta.get_field("float_field") assert isinstance(float_field, fields.FloatField) assert isinstance(self.dummy_numbers_model.float_field, float) def test_filling_DecimalField_with_random_decimal(self): self.dummy_decimal_model = baker.make(models.DummyDecimalModel) decimal_field = models.DummyDecimalModel._meta.get_field("decimal_field") assert isinstance(decimal_field, fields.DecimalField) assert isinstance(self.dummy_decimal_model.decimal_field, Decimal) class TestURLFieldsFilling: def test_fill_URLField_with_valid_url(self, person): blog_field = models.Person._meta.get_field("blog") assert isinstance(blog_field, fields.URLField) assert isinstance(person.blog, str) class TestFillingEmailField: def test_filling_EmailField(self, person): field = models.Person._meta.get_field("email") assert isinstance(field, fields.EmailField) assert isinstance(person.email, str) @pytest.mark.django_db class TestFillingIPAddressField: def test_filling_IPAddressField(self): obj = baker.make(models.DummyGenericIPAddressFieldModel) field = models.DummyGenericIPAddressFieldModel._meta.get_field("ipv4_field") assert isinstance(field, fields.GenericIPAddressField) assert isinstance(obj.ipv4_field, str) validate_ipv4_address(obj.ipv4_field) if hasattr(obj, "ipv6_field"): assert isinstance(obj.ipv6_field, str) assert isinstance(obj.ipv46_field, str) validate_ipv6_address(obj.ipv6_field) validate_ipv46_address(obj.ipv46_field) @pytest.mark.django_db class TestFillingGenericForeignKeyField: def test_filling_content_type_field(self): dummy = baker.make(models.DummyGenericForeignKeyModel) assert isinstance(dummy.content_type, ContentType) assert dummy.content_type.model_class() is not None def test_iteratively_filling_generic_foreign_key_field(self): """ Ensures private_fields are included in ``Baker.get_fields()``. Otherwise, calling ``next()`` when a GFK is in ``iterator_attrs`` would be bypassed. """ objects = baker.make(models.Profile, _quantity=2) dummies = baker.make( models.DummyGenericForeignKeyModel, content_object=iter(objects), _quantity=2, ) expected_content_type = ContentType.objects.get_for_model(models.Profile) assert dummies[0].content_object == objects[0] assert dummies[0].content_type == expected_content_type assert dummies[0].object_id == objects[0].pk assert dummies[1].content_object == objects[1] assert dummies[1].content_type == expected_content_type assert dummies[1].object_id == objects[1].pk @pytest.mark.django_db class TestFillingForeignKeyFieldWithDefaultFunctionReturningId: def test_filling_foreignkey_with_default_id(self): dummy = baker.make(models.RelatedNamesWithDefaultsModel) assert dummy.cake.__class__.objects.count() == 1 assert dummy.cake.id == models.get_default_cake_id() assert dummy.cake.name == "Muffin" def test_filling_foreignkey_with_default_id_with_custom_arguments(self): dummy = baker.make( models.RelatedNamesWithDefaultsModel, cake__name="Baumkuchen" ) assert dummy.cake.__class__.objects.count() == 1 assert dummy.cake.id == dummy.cake.__class__.objects.get().id assert dummy.cake.name == "Baumkuchen" @pytest.mark.django_db class TestFillingOptionalForeignKeyField: def test_not_filling_optional_foreignkey(self): dummy = baker.make(models.RelatedNamesWithEmptyDefaultsModel) assert dummy.cake is None def test_filling_optional_foreignkey_implicitly(self): dummy = baker.make( models.RelatedNamesWithEmptyDefaultsModel, cake__name="Carrot cake" ) assert dummy.cake.__class__.objects.count() == 1 assert dummy.cake.name == "Carrot cake" @pytest.mark.django_db class TestsFillingFileField: def test_filling_file_field(self): dummy = baker.make(models.DummyFileFieldModel, _create_files=True) field = models.DummyFileFieldModel._meta.get_field("file_field") assert isinstance(field, FileField) import time path = "%s/%s/mock_file.txt" % (gettempdir(), time.strftime("%Y/%m/%d")) assert abspath(path) == abspath(dummy.file_field.path) dummy.file_field.delete() def test_does_not_create_file_if_not_flagged(self): dummy = baker.make(models.DummyFileFieldModel) with pytest.raises(ValueError): # Django raises ValueError if file does not exist assert dummy.file_field.path @pytest.mark.django_db class TestFillingCustomFields: def test_raises_unsupported_field_for_custom_field(self, custom_cfg): """Should raise an exception if a generator is not provided for a custom field.""" with pytest.raises(TypeError): baker.make(models.CustomFieldWithoutGeneratorModel) def test_uses_generator_defined_on_settings_for_custom_field(self, custom_cfg): """Should use the function defined in settings as a generator.""" generator_dict = { "tests.generic.fields.CustomFieldWithGenerator": generators.gen_value_string } setattr(settings, "BAKER_CUSTOM_FIELDS_GEN", generator_dict) obj = baker.make(models.CustomFieldWithGeneratorModel) assert "value" == obj.custom_value def test_uses_generator_defined_as_string_on_settings_for_custom_field( self, custom_cfg ): """Should import and use the function present in the import path defined in settings.""" # fmt: off generator_dict = { "tests.generic.fields.CustomFieldWithGenerator": "tests.generic.generators.gen_value_string" } # fmt: on setattr(settings, "BAKER_CUSTOM_FIELDS_GEN", generator_dict) obj = baker.make(models.CustomFieldWithGeneratorModel) assert "value" == obj.custom_value def test_uses_generator_defined_on_settings_for_custom_foreignkey(self, custom_cfg): """Should use the function defined in the import path for a foreign key field.""" generator_dict = { "tests.generic.fields.CustomForeignKey": "model_bakery.random_gen.gen_related" } setattr(settings, "BAKER_CUSTOM_FIELDS_GEN", generator_dict) obj = baker.make( models.CustomForeignKeyWithGeneratorModel, custom_fk__email="a@b.com" ) assert "a@b.com" == obj.custom_fk.email def test_uses_generator_defined_as_string_for_custom_field(self, custom_cfg): """Should import and use the generator function used in the add method.""" baker.generators.add( "tests.generic.fields.CustomFieldWithGenerator", "tests.generic.generators.gen_value_string", ) obj = baker.make(models.CustomFieldWithGeneratorModel) assert "value" == obj.custom_value def test_uses_generator_function_for_custom_foreignkey(self, custom_cfg): """Should use the generator function passed as a value for the add method.""" baker.generators.add("tests.generic.fields.CustomForeignKey", gen_related) obj = baker.make( models.CustomForeignKeyWithGeneratorModel, custom_fk__email="a@b.com" ) assert "a@b.com" == obj.custom_fk.email def test_can_override_django_default_field_functions_generator(self, custom_cfg): def gen_char(): return "Some value" baker.generators.add("django.db.models.fields.CharField", gen_char) person = baker.make(models.Person) assert "Some value" == person.name def test_ensure_adding_generators_via_settings_works(self): obj = baker.make(models.CustomFieldViaSettingsModel) assert "always the same text" == obj.custom_value @pytest.mark.django_db class TestFillingAutoFields: def test_filling_AutoField(self): obj = baker.make(models.DummyEmptyModel) field = models.DummyEmptyModel._meta.get_field("id") assert isinstance(field, fields.AutoField) assert isinstance(obj.id, int) @pytest.mark.django_db @pytest.mark.skipif(not models.has_pil, reason="PIL is required to test ImageField") class TestFillingImageFileField: def test_filling_image_file_field(self): dummy = baker.make(models.DummyImageFieldModel, _create_files=True) field = models.DummyImageFieldModel._meta.get_field("image_field") assert isinstance(field, ImageField) import time path = "%s/%s/mock_img.jpeg" % (gettempdir(), time.strftime("%Y/%m/%d")) # These require the file to exist in earlier versions of Django assert abspath(path) == abspath(dummy.image_field.path) assert dummy.image_field.width assert dummy.image_field.height dummy.image_field.delete() def test_does_not_create_file_if_not_flagged(self): dummy = baker.make(models.DummyImageFieldModel) with pytest.raises(ValueError): # Django raises ValueError if image does not exist assert dummy.image_field.path @pytest.mark.skipif( connection.vendor != "postgresql", reason="PostgreSQL specific tests" ) class TestCIStringFieldsFilling: def test_fill_cicharfield_with_a_random_str(self, person): ci_char_field = models.Person._meta.get_field("ci_char") assert isinstance(ci_char_field, CICharField) assert isinstance(person.ci_char, str) assert len(person.ci_char) == ci_char_field.max_length def test_filling_ciemailfield(self, person): ci_email_field = models.Person._meta.get_field("ci_email") assert isinstance(ci_email_field, CIEmailField) assert isinstance(person.ci_email, str) def test_filling_citextfield(self, person): ci_text_field = models.Person._meta.get_field("ci_text") assert isinstance(ci_text_field, CITextField) assert isinstance(person.ci_text, str) def test_filling_decimal_range_field(self, person): from psycopg2._range import NumericRange decimal_range_field = models.Person._meta.get_field("decimal_range") assert isinstance(decimal_range_field, DecimalRangeField) assert isinstance(person.decimal_range, NumericRange) assert isinstance(person.decimal_range.lower, Decimal) assert isinstance(person.decimal_range.upper, Decimal) assert person.decimal_range.lower < person.decimal_range.upper def test_filling_integer_range_field(self, person): from psycopg2._range import NumericRange int_range_field = models.Person._meta.get_field("int_range") assert isinstance(int_range_field, IntegerRangeField) assert isinstance(person.int_range, NumericRange) assert isinstance(person.int_range.lower, int) assert isinstance(person.int_range.upper, int) assert person.int_range.lower < person.int_range.upper def test_filling_integer_range_field_for_big_int(self, person): from psycopg2._range import NumericRange bigint_range_field = models.Person._meta.get_field("bigint_range") assert isinstance(bigint_range_field, BigIntegerRangeField) assert isinstance(person.bigint_range, NumericRange) assert isinstance(person.bigint_range.lower, int) assert isinstance(person.bigint_range.upper, int) assert person.bigint_range.lower < person.bigint_range.upper @pytest.mark.skipif( FloatRangeField is None, reason="FloatRangeField is deprecated since Django 2.2", ) def test_filling_float_range_field(self, person): from psycopg2._range import NumericRange float_range_field = models.Person._meta.get_field("float_range") assert isinstance(float_range_field, FloatRangeField) assert isinstance(person.float_range, NumericRange) assert isinstance(person.float_range.lower, float) assert isinstance(person.float_range.upper, float) assert person.float_range.lower < person.float_range.upper def test_filling_date_range_field(self, person): from psycopg2.extras import DateRange date_range_field = models.Person._meta.get_field("date_range") assert isinstance(date_range_field, DateRangeField) assert isinstance(person.date_range, DateRange) assert isinstance(person.date_range.lower, date) assert isinstance(person.date_range.upper, date) assert person.date_range.lower < person.date_range.upper def test_filling_datetime_range_field(self, person): from psycopg2.extras import DateTimeTZRange datetime_range_field = models.Person._meta.get_field("datetime_range") assert isinstance(datetime_range_field, DateTimeRangeField) assert isinstance(person.datetime_range, DateTimeTZRange) assert isinstance(person.datetime_range.lower, datetime) assert isinstance(person.datetime_range.upper, datetime) assert person.datetime_range.lower < person.datetime_range.upper @pytest.mark.skipif( connection.vendor != "postgresql", reason="PostgreSQL specific tests" ) class TestPostgreSQLFieldsFilling: def test_fill_arrayfield_with_empty_array(self, person): assert person.acquaintances == [] def test_fill_jsonfield_with_empty_dict(self, person): assert person.postgres_data == {} def test_fill_hstorefield_with_empty_dict(self, person): assert person.hstore_data == {} @pytest.mark.skipif(not BAKER_GIS, reason="GIS support required for GIS fields") class TestGisFieldsFilling: def assertGeomValid(self, geom): assert geom.valid is True, geom.valid_reason def test_fill_PointField_valid(self, person): print(BAKER_GIS) self.assertGeomValid(person.point) def test_fill_LineStringField_valid(self, person): self.assertGeomValid(person.line_string) def test_fill_PolygonField_valid(self, person): self.assertGeomValid(person.polygon) def test_fill_MultiPointField_valid(self, person): self.assertGeomValid(person.multi_point) def test_fill_MultiLineStringField_valid(self, person): self.assertGeomValid(person.multi_line_string) def test_fill_MultiPolygonField_valid(self, person): self.assertGeomValid(person.multi_polygon) def test_fill_GeometryField_valid(self, person): self.assertGeomValid(person.geom) def test_fill_GeometryCollectionField_valid(self, person): self.assertGeomValid(person.geom_collection) model_bakery-1.7.0/tests/test_recipes.py000066400000000000000000000625341427223773300204050ustar00rootroot00000000000000import itertools from datetime import timedelta from decimal import Decimal from random import choice # noqa from unittest.mock import patch import pytest from django.utils.timezone import now from model_bakery import baker from model_bakery.exceptions import InvalidQuantityException, RecipeIteratorEmpty from model_bakery.recipe import Recipe, RecipeForeignKey, foreign_key from model_bakery.timezone import tz_aware from tests.generic.baker_recipes import SmallDogRecipe, pug from tests.generic.models import ( TEST_TIME, Dog, DummyBlankFieldsModel, DummyNumbersModel, LonelyPerson, Person, Profile, User, ) recipe_attrs = { "name": "John Doe", "nickname": "joe", "age": 18, "bio": "Someone in the crowd", "birthday": now().date(), "appointment": now(), "blog": "https://joe.example.com", "days_since_last_login": 4, "birth_time": now(), } person_recipe = Recipe(Person, **recipe_attrs) user_recipe = Recipe(User) lonely_person_recipe = Recipe(LonelyPerson) def test_import_seq_from_recipe(): """Import seq method directly from recipe module.""" try: from model_bakery.recipe import seq # NoQA except ImportError: pytest.fail("{} raised".format(ImportError.__name__)) def test_import_recipes(): """Test imports works both for full import paths and for `app_name.recipe_name` strings.""" assert baker.prepare_recipe("generic.dog"), baker.prepare_recipe( "tests.generic.dog" ) @pytest.mark.django_db class TestDefiningRecipes: def test_flat_model_make_recipe_with_the_correct_attributes(self): """Test a 'flat model' - without associations, like FK, M2M and O2O.""" person = person_recipe.make() assert person.name == recipe_attrs["name"] assert person.nickname == recipe_attrs["nickname"] assert person.age == recipe_attrs["age"] assert person.bio == recipe_attrs["bio"] assert person.birthday == recipe_attrs["birthday"] assert person.appointment == recipe_attrs["appointment"] assert person.blog == recipe_attrs["blog"] assert person.days_since_last_login == recipe_attrs["days_since_last_login"] assert person.id is not None def test_flat_model_prepare_recipe_with_the_correct_attributes(self): person = person_recipe.prepare() assert person.name == recipe_attrs["name"] assert person.nickname == recipe_attrs["nickname"] assert person.age == recipe_attrs["age"] assert person.bio == recipe_attrs["bio"] assert person.birthday == recipe_attrs["birthday"] assert person.appointment == recipe_attrs["appointment"] assert person.blog == recipe_attrs["blog"] assert person.days_since_last_login == recipe_attrs["days_since_last_login"] assert person.id is None def test_accepts_callable(self): r = Recipe(DummyBlankFieldsModel, blank_char_field=lambda: "callable!!") value = r.make().blank_char_field assert value == "callable!!" def test_always_calls_when_creating(self): with patch("tests.test_recipes.choice") as choice_mock: choice_mock.return_value = "foo" lst = ["foo", "bar", "spam", "eggs"] r = Recipe(DummyBlankFieldsModel, blank_char_field=lambda: choice_mock(lst)) assert r.make().blank_char_field assert r.make().blank_char_field assert choice_mock.call_count == 2 def test_always_calls_with_quantity(self): with patch("tests.test_recipes.choice") as choice_mock: choice_mock.return_value = "foo" lst = ["foo", "bar", "spam", "eggs"] r = Recipe(DummyBlankFieldsModel, blank_char_field=lambda: choice_mock(lst)) r.make(_quantity=3) assert choice_mock.call_count == 3 def test_make_recipes_with_args(self): """Overriding some fields values at recipe execution.""" person = person_recipe.make(name="Guido", age=56) assert person.name != recipe_attrs["name"] assert person.name == "Guido" assert person.age != recipe_attrs["age"] assert person.age == 56 assert person.nickname == recipe_attrs["nickname"] assert person.bio == recipe_attrs["bio"] assert person.birthday == recipe_attrs["birthday"] assert person.appointment == recipe_attrs["appointment"] assert person.blog == recipe_attrs["blog"] assert person.days_since_last_login == recipe_attrs["days_since_last_login"] assert person.id is not None def test_prepare_recipes_with_args(self): """Overriding some fields values at recipe execution.""" person = person_recipe.prepare(name="Guido", age=56) assert person.name != recipe_attrs["name"] assert person.name == "Guido" assert person.age != recipe_attrs["age"] assert person.age == 56 assert person.nickname == recipe_attrs["nickname"] assert person.bio == recipe_attrs["bio"] assert person.birthday == recipe_attrs["birthday"] assert person.appointment == recipe_attrs["appointment"] assert person.blog == recipe_attrs["blog"] assert person.days_since_last_login == recipe_attrs["days_since_last_login"] assert person.id is None def test_make_recipe_without_all_model_needed_data(self): person_recipe = Recipe(Person, name="John Doe") person = person_recipe.make() assert "John Doe" == person.name assert person.nickname assert person.age assert person.bio assert person.birthday assert person.appointment assert person.blog assert person.days_since_last_login assert person.id def test_prepare_recipe_without_all_model_needed_data(self): person_recipe = Recipe(Person, name="John Doe") person = person_recipe.prepare() assert "John Doe" == person.name assert person.nickname assert person.age assert person.bio assert person.birthday assert person.appointment assert person.blog assert person.days_since_last_login assert not person.id def test_defining_recipes_str(self): from model_bakery.recipe import seq p = Recipe("generic.Person", name=seq("foo")) try: p.make(_quantity=5) except AttributeError as e: pytest.fail("%s" % e) @pytest.mark.django_db class TestExecutingRecipes: """Tests for calling recipes defined in baker_recipes.py.""" def test_model_with_foreign_key(self): dog = baker.make_recipe("tests.generic.dog") assert dog.breed == "Pug" assert isinstance(dog.owner, Person) assert dog.owner.id dog = baker.prepare_recipe("tests.generic.dog") assert dog.breed == "Pug" assert isinstance(dog.owner, Person) assert dog.owner.id is None dog = baker.prepare_recipe("tests.generic.dog", _save_related=True) assert dog.breed == "Pug" assert isinstance(dog.owner, Person) assert dog.owner.id dogs = baker.make_recipe("tests.generic.dog", _quantity=2) owner = dogs[0].owner for dog in dogs: assert dog.breed == "Pug" assert dog.owner == owner def test_model_with_foreign_key_as_str(self): dog = baker.make_recipe("tests.generic.other_dog") assert dog.breed == "Basset" assert isinstance(dog.owner, Person) assert dog.owner.id dog = baker.prepare_recipe("tests.generic.other_dog") assert dog.breed == "Basset" assert isinstance(dog.owner, Person) assert dog.owner.id is None def test_model_with_foreign_key_as_unicode(self): dog = baker.make_recipe("tests.generic.other_dog_unicode") assert dog.breed == "Basset" assert isinstance(dog.owner, Person) assert dog.owner.id dog = baker.prepare_recipe("tests.generic.other_dog_unicode") assert dog.breed == "Basset" assert isinstance(dog.owner, Person) assert dog.owner.id is None def test_make_recipe(self): person = baker.make_recipe("tests.generic.person") assert isinstance(person, Person) assert person.id def test_make_recipe_with_quantity_parameter(self): people = baker.make_recipe("tests.generic.person", _quantity=3) assert len(people) == 3 for person in people: assert isinstance(person, Person) assert person.id def test_make_extended_recipe(self): extended_dog = baker.make_recipe("tests.generic.extended_dog") assert extended_dog.breed == "Super basset" # No side effects happened due to extension base_dog = baker.make_recipe("tests.generic.dog") assert base_dog.breed == "Pug" def test_extended_recipe_type(self): assert isinstance(pug, SmallDogRecipe) def test_save_related_instances_on_prepare_recipe(self): dog = baker.prepare_recipe("tests.generic.homeless_dog") assert not dog.id assert not dog.owner_id dog = baker.prepare_recipe("tests.generic.homeless_dog", _save_related=True) assert not dog.id assert dog.owner.id def test_make_recipe_with_quantity_parameter_respection_model_args(self): people = baker.make_recipe( "tests.generic.person", _quantity=3, name="Dennis Ritchie", age=70 ) assert len(people) == 3 for person in people: assert person.name == "Dennis Ritchie" assert person.age == 70 def test_make_recipe_raises_correct_exception_if_invalid_quantity(self): with pytest.raises(InvalidQuantityException): baker.make_recipe("tests.generic.person", _quantity="hi") with pytest.raises(InvalidQuantityException): baker.make_recipe("tests.generic.person", _quantity=-1) def test_prepare_recipe_with_foreign_key(self): person_recipe = Recipe(Person, name="John Doe") dog_recipe = Recipe( Dog, owner=foreign_key(person_recipe), ) dog = dog_recipe.prepare() assert dog.id is None assert dog.owner.id is None def test_prepare_recipe_with_quantity_parameter(self): people = baker.prepare_recipe("tests.generic.person", _quantity=3) assert len(people) == 3 for person in people: assert isinstance(person, Person) assert person.id is None def test_prepare_recipe_with_quantity_parameter_respection_model_args(self): people = baker.prepare_recipe( "tests.generic.person", _quantity=3, name="Dennis Ritchie", age=70 ) assert len(people) == 3 for person in people: assert person.name == "Dennis Ritchie" assert person.age == 70 def test_prepare_recipe_raises_correct_exception_if_invalid_quantity(self): with pytest.raises(InvalidQuantityException): baker.prepare_recipe("tests.generic.person", _quantity="hi") with pytest.raises(InvalidQuantityException): baker.prepare_recipe("tests.generic.person", _quantity=-1) def test_prepare_recipe(self): person = baker.prepare_recipe("tests.generic.person") assert isinstance(person, Person) assert not person.id def test_make_recipe_with_args(self): person = baker.make_recipe( "tests.generic.person", name="Dennis Ritchie", age=70 ) assert person.name == "Dennis Ritchie" assert person.age == 70 def test_prepare_recipe_with_args(self): person = baker.prepare_recipe( "tests.generic.person", name="Dennis Ritchie", age=70 ) assert person.name == "Dennis Ritchie" assert person.age == 70 def test_import_recipe_inside_deeper_modules(self): recipe_name = "tests.generic.tests.sub_package.person" person = baker.prepare_recipe(recipe_name) assert person.name == "John Deeper" def test_pass_save_kwargs(self): owner = baker.make(Person) dog = baker.make_recipe( "tests.generic.overrided_save", _save_kwargs={"owner": owner} ) assert owner == dog.owner def test_pass_save_kwargs_in_recipe_definition(self): dog = baker.make_recipe("tests.generic.with_save_kwargs") assert dog.breed == "updated_breed" def test_ip_fields_with_start(self): first, second = baker.make_recipe("tests.generic.ip_fields", _quantity=2) assert "127.0.0.2" == first.ipv4_field assert "2001:12f8:0:28::4" == first.ipv6_field assert "127.0.0.4" == second.ipv4_field assert "2001:12f8:0:28::6" == second.ipv6_field @pytest.mark.django_db class TestForeignKey: def test_foreign_key_method_returns_a_recipe_foreign_key_object(self): number_recipe = Recipe(DummyNumbersModel, float_field=1.6) obj = foreign_key(number_recipe) assert isinstance(obj, RecipeForeignKey) def test_not_accept_other_type(self): with pytest.raises(TypeError) as c: foreign_key(2) exception = c.value assert str(exception) == "Not a recipe" def test_load_from_other_module_recipe(self): dog = Recipe(Dog, owner=foreign_key("tests.generic.person")).make() assert dog.owner.name == "John Doe" def test_fail_load_invalid_recipe(self): with pytest.raises(AttributeError): foreign_key("tests.generic.nonexisting_recipe") def test_class_directly_with_string(self): with pytest.raises(TypeError): RecipeForeignKey("foo") def test_do_not_create_related_model(self): """It should not create another object when passing the object as argument.""" person = baker.make_recipe("tests.generic.person") assert Person.objects.count() == 1 baker.make_recipe("tests.generic.dog", owner=person) assert Person.objects.count() == 1 baker.prepare_recipe("tests.generic.dog", owner=person) assert Person.objects.count() == 1 def test_do_query_lookup_for_recipes_make_method(self): """It should not create another object when using query lookup syntax.""" dog = baker.make_recipe("tests.generic.dog", owner__name="James") assert Person.objects.count() == 1 assert dog.owner.name == "James" def test_do_query_lookup_for_recipes_prepare_method(self): """It should not create another object when using query lookup syntax.""" dog = baker.prepare_recipe("tests.generic.dog", owner__name="James") assert dog.owner.name == "James" def test_do_query_lookup_empty_recipes(self): """It should not create another object when using query lookup syntax.""" dog_recipe = Recipe(Dog) dog = dog_recipe.make(owner__name="James") assert Person.objects.count() == 1 assert dog.owner.name == "James" dog = dog_recipe.prepare(owner__name="Zezin") assert Person.objects.count() == 1 assert dog.owner.name == "Zezin" def test_related_models_recipes(self): lady = baker.make_recipe("tests.generic.dog_lady") assert lady.dog_set.count() == 2 assert lady.dog_set.all()[0].breed == "Pug" assert lady.dog_set.all()[1].breed == "Basset" def test_nullable_related(self): nullable = baker.make_recipe("tests.generic.nullable_related") assert nullable.dummynullfieldsmodel_set.count() == 1 def test_chained_related(self): movie = baker.make_recipe("tests.generic.movie_with_cast") assert movie.cast_members.count() == 2 def test_one_to_one_relationship(self): lonely_people = baker.make_recipe("tests.generic.lonely_person", _quantity=2) friend_ids = set([x.only_friend.id for x in lonely_people]) assert len(friend_ids) == 2 @pytest.mark.django_db class TestM2MField: def test_create_many_to_many(self): dog = baker.make_recipe("tests.generic.dog_with_friends") assert len(dog.friends_with.all()) == 2 for friend in dog.friends_with.all(): assert friend.breed == "Pug" assert friend.owner.name == "John Doe" def test_create_nested(self): dog = baker.make_recipe("tests.generic.dog_with_more_friends") assert len(dog.friends_with.all()) == 1 friend = dog.friends_with.all()[0] assert len(friend.friends_with.all()) == 2 @pytest.mark.django_db class TestSequences: def test_increment_for_strings(self): person = baker.make_recipe("tests.generic.serial_person") assert person.name == "joe1" person = baker.prepare_recipe("tests.generic.serial_person") assert person.name == "joe2" person = baker.make_recipe("tests.generic.serial_person") assert person.name == "joe3" def test_increment_for_strings_with_suffix(self): from model_bakery.recipe import seq # NoQA fred_person = person_recipe.extend(email=seq("fred", suffix="@example.com")) person = fred_person.make() assert person.email == "fred1@example.com" person = fred_person.make() assert person.email == "fred2@example.com" person = fred_person.make() assert person.email == "fred3@example.com" def test_increment_for_fks(self): from model_bakery.recipe import seq # NoQA profiles = baker.make(Profile, _quantity=3) start_id = profiles[0].id seq_user = user_recipe.extend(username="name", profile_id=seq(start_id)) user = seq_user.make() assert user.profile_id == start_id + 1 user = seq_user.make() assert user.profile_id == start_id + 2 def test_increment_for_one_to_one(self): from model_bakery.recipe import seq # NoQA people = baker.make(Person, _quantity=3) start_id = people[0].id seq_lonely_person = lonely_person_recipe.extend(only_friend_id=seq(start_id)) person = seq_lonely_person.make() assert person.only_friend_id == start_id + 1 user = seq_lonely_person.make() assert user.only_friend_id == start_id + 2 def test_increment_for_strings_with_bad_suffix(self): from model_bakery.recipe import seq # NoQA # Bad suffix bob_person = person_recipe.extend(email=seq("bob", suffix=42)) with pytest.raises(TypeError) as exc: bob_person.make() assert str(exc.value) == "Sequences suffix can only be a string" def test_increment_for_strings_with_suffix_and_start(self): from model_bakery.recipe import seq # NoQA fred_person = person_recipe.extend( email=seq("fred", start=5, suffix="@example.com") ) person = fred_person.make() assert person.email == "fred5@example.com" person = fred_person.make() assert person.email == "fred6@example.com" person = fred_person.make() assert person.email == "fred7@example.com" def test_increment_for_numbers(self): dummy = baker.make_recipe("tests.generic.serial_numbers") assert dummy.default_int_field == 11 assert dummy.default_decimal_field == Decimal("21.1") assert dummy.default_float_field == 2.23 dummy = baker.make_recipe("tests.generic.serial_numbers") assert dummy.default_int_field == 12 assert dummy.default_decimal_field == Decimal("22.1") assert dummy.default_float_field == 3.23 dummy = baker.prepare_recipe("tests.generic.serial_numbers") assert dummy.default_int_field == 13 assert dummy.default_decimal_field == Decimal("23.1") assert dummy.default_float_field == 4.23 def test_increment_for_numbers_2(self): """Repeated test to ensure Sequences atomicity.""" dummy = baker.make_recipe("tests.generic.serial_numbers") assert dummy.default_int_field == 11 assert dummy.default_decimal_field == Decimal("21.1") assert dummy.default_float_field == 2.23 dummy = baker.make_recipe("tests.generic.serial_numbers") assert dummy.default_int_field == 12 assert dummy.default_decimal_field == Decimal("22.1") assert dummy.default_float_field == 3.23 dummy = baker.prepare_recipe("tests.generic.serial_numbers") assert dummy.default_int_field == 13 assert dummy.default_decimal_field == Decimal("23.1") assert dummy.default_float_field == 4.23 def test_increment_for_numbers_with_suffix(self): from model_bakery.recipe import seq # NoQA with pytest.raises(TypeError) as exc: baker.make_recipe( "tests.generic.serial_numbers", default_int_field=seq(1, suffix="will not work"), ) assert ( str(exc.value) == "Sequences with suffix can only be used with text values" ) def test_creates_unique_field_recipe_using_for_iterator(self): for i in range(1, 4): dummy = baker.make_recipe("tests.generic.dummy_unique_field") assert dummy.value == 10 + i def test_creates_unique_field_recipe_using_quantity_argument(self): dummies = baker.make_recipe("tests.generic.dummy_unique_field", _quantity=3) assert 11 == dummies[0].value assert 12 == dummies[1].value assert 13 == dummies[2].value def test_increment_by_3(self): dummy = baker.make_recipe("tests.generic.serial_numbers_by") assert dummy.default_int_field == 13 assert dummy.default_decimal_field == Decimal("22.5") assert dummy.default_float_field == pytest.approx(3.030000) dummy = baker.make_recipe("tests.generic.serial_numbers_by") assert dummy.default_int_field == 16 assert dummy.default_decimal_field == Decimal("24.9") assert dummy.default_float_field == pytest.approx(4.83) dummy = baker.prepare_recipe("tests.generic.serial_numbers_by") assert dummy.default_int_field == 19 assert dummy.default_decimal_field == Decimal("27.3") assert dummy.default_float_field == pytest.approx(6.63) def test_increment_by_timedelta(self): dummy = baker.make_recipe("tests.generic.serial_datetime") assert dummy.default_date_field == (TEST_TIME.date() + timedelta(days=1)) assert dummy.default_date_time_field == tz_aware(TEST_TIME + timedelta(hours=3)) assert dummy.default_time_field == (TEST_TIME + timedelta(seconds=15)).time() dummy = baker.make_recipe("tests.generic.serial_datetime") assert dummy.default_date_field == (TEST_TIME.date() + timedelta(days=2)) assert dummy.default_date_time_field == tz_aware(TEST_TIME + timedelta(hours=6)) assert dummy.default_time_field == (TEST_TIME + timedelta(seconds=30)).time() def test_increment_by_timedelta_seq_combined_with_quantity(self): quantity = 5 entries = baker.make_recipe("tests.generic.serial_datetime", _quantity=quantity) for i, e in enumerate(entries): index = i + 1 assert e.default_date_field == ( TEST_TIME.date() + timedelta(days=1 * index) ) assert e.default_date_time_field == tz_aware( TEST_TIME + timedelta(hours=3 * index) ) assert ( e.default_time_field == (TEST_TIME + timedelta(seconds=15 * index)).time() ) def test_creates_unique_timedelta_recipe_using_quantity_argument(self): dummies = baker.make_recipe("tests.generic.serial_datetime", _quantity=3) assert dummies[0].default_date_field == TEST_TIME.date() + timedelta(days=1) assert dummies[1].default_date_field == TEST_TIME.date() + timedelta(days=2) assert dummies[2].default_date_field == TEST_TIME.date() + timedelta(days=3) def test_increment_after_override_definition_field(self): person = baker.make_recipe("tests.generic.serial_person", name="tom") assert person.name == "tom" person = baker.make_recipe("tests.generic.serial_person") assert person.name == "joe4" person = baker.prepare_recipe("tests.generic.serial_person") assert person.name == "joe5" @pytest.mark.django_db class TestIterators: def test_accepts_generators(self): r = Recipe(DummyBlankFieldsModel, blank_char_field=itertools.cycle(["a", "b"])) assert "a" == r.make().blank_char_field assert "b" == r.make().blank_char_field assert "a" == r.make().blank_char_field def test_accepts_iterators(self): r = Recipe(DummyBlankFieldsModel, blank_char_field=iter(["a", "b", "c"])) assert "a" == r.make().blank_char_field assert "b" == r.make().blank_char_field assert "c" == r.make().blank_char_field def test_empty_iterator_exception(self): r = Recipe(DummyBlankFieldsModel, blank_char_field=iter(["a", "b"])) assert "a" == r.make().blank_char_field assert "b" == r.make().blank_char_field with pytest.raises(RecipeIteratorEmpty): r.make() def test_only_iterators_not_iteratables_are_iterated(self): """Ensure we only iterate explicit iterators. Consider "iterable" vs "iterator": Something like a string is "iterable", but not an "iterator". We don't want to iterate "iterables", only explicit "iterators". """ r = Recipe( DummyBlankFieldsModel, blank_text_field="not an iterator, so don't iterate!" ) assert r.make().blank_text_field == "not an iterator, so don't iterate!" model_bakery-1.7.0/tests/test_utils.py000066400000000000000000000140741427223773300201070ustar00rootroot00000000000000import datetime from decimal import Decimal from inspect import getmodule import pytest from django.utils.timezone import utc from model_bakery.utils import get_calling_module, import_from_str, seq from tests.generic.models import User def test_import_from_str(): with pytest.raises(AttributeError): import_from_str("tests.generic.UndefinedObject") with pytest.raises(ImportError): import_from_str("tests.generic.undefined_path.User") assert import_from_str("tests.generic.models.User") == User assert import_from_str(User) == User assert import_from_str("generic.User") == User def test_get_calling_module(): # Reference to this very module this_module = getmodule(test_get_calling_module) # Once removed is the `pytest` module calling this function pytest_module = get_calling_module(1) assert pytest_module != this_module assert "pytest" in pytest_module.__name__ # Test functions def dummy_secondary_method(): return get_calling_module(2), get_calling_module(3) def dummy_method(): return (*dummy_secondary_method(), get_calling_module(1), get_calling_module(2)) # Unpack results from the function chain sec_mod, sec_pytest_mod, dummy_mod, pytest_mod = dummy_method() assert sec_mod == this_module assert "pytest" in sec_pytest_mod.__name__ assert dummy_mod == this_module assert "pytest" in pytest_mod.__name__ # Raise an `IndexError` when attempting to access too many frames removed with pytest.raises(IndexError): assert get_calling_module(100) class TestSeq: def test_string(self): sequence = seq("muffin") assert next(sequence) == "muffin1" assert next(sequence) == "muffin2" assert next(sequence) == "muffin3" def test_string_start(self): sequence = seq("muffin", start=9) assert next(sequence) == "muffin9" assert next(sequence) == "muffin10" assert next(sequence) == "muffin11" def test_string_suffix(self): sequence = seq("cookie", suffix="@example.com") assert next(sequence) == "cookie1@example.com" assert next(sequence) == "cookie2@example.com" assert next(sequence) == "cookie3@example.com" def test_string_suffix_and_start(self): sequence = seq("cookie", start=111, suffix="@example.com") assert next(sequence) == "cookie111@example.com" assert next(sequence) == "cookie112@example.com" assert next(sequence) == "cookie113@example.com" def test_string_invalid_suffix(self): with pytest.raises(TypeError) as exc: next(seq("cookie", suffix=42)) assert str(exc.value) == "Sequences suffix can only be a string" def test_int(self): sequence = seq(1) assert next(sequence) == 2 assert next(sequence) == 3 assert next(sequence) == 4 def test_int_increment_by(self): sequence = seq(1, increment_by=3) assert next(sequence) == 4 assert next(sequence) == 7 assert next(sequence) == 10 def test_decimal(self): sequence = seq(Decimal("36.6")) assert next(sequence) == Decimal("37.6") assert next(sequence) == Decimal("38.6") assert next(sequence) == Decimal("39.6") def test_decimal_increment_by(self): sequence = seq(Decimal("36.6"), increment_by=Decimal("2.4")) assert next(sequence) == Decimal("39.0") assert next(sequence) == Decimal("41.4") assert next(sequence) == Decimal("43.8") def test_float(self): sequence = seq(1.23) assert next(sequence) == 2.23 assert next(sequence) == 3.23 assert next(sequence) == 4.23 def test_float_increment_by(self): sequence = seq(1.23, increment_by=1.8) assert next(sequence) == pytest.approx(3.03) assert next(sequence) == pytest.approx(4.83) assert next(sequence) == pytest.approx(6.63) def test_numbers_with_suffix(self): with pytest.raises(TypeError) as exc: next(seq(1, suffix="iamnotanumber")) assert ( str(exc.value) == "Sequences with suffix can only be used with text values" ) def test_date(self): sequence = seq( datetime.date(2021, 2, 11), increment_by=datetime.timedelta(days=6) ) assert next(sequence) == datetime.date(2021, 2, 17) assert next(sequence) == datetime.date(2021, 2, 23) assert next(sequence) == datetime.date(2021, 3, 1) def test_time(self): sequence = seq( datetime.time(15, 39, 58, 457698), increment_by=datetime.timedelta(minutes=59), ) assert next(sequence) == datetime.time(16, 38, 58, 457698) assert next(sequence) == datetime.time(17, 37, 58, 457698) assert next(sequence) == datetime.time(18, 36, 58, 457698) @pytest.mark.parametrize("use_tz", [False, True]) def test_datetime(self, settings, use_tz): settings.USE_TZ = use_tz tzinfo = utc if use_tz else None sequence = seq( datetime.datetime(2021, 2, 11, 15, 39, 58, 457698), increment_by=datetime.timedelta(hours=3), ) assert next(sequence) == datetime.datetime( 2021, 2, 11, 18, 39, 58, 457698 ).replace(tzinfo=tzinfo) assert next(sequence) == datetime.datetime( 2021, 2, 11, 21, 39, 58, 457698 ).replace(tzinfo=tzinfo) assert next(sequence) == datetime.datetime( 2021, 2, 12, 00, 39, 58, 457698 ).replace(tzinfo=tzinfo) @pytest.mark.parametrize( "value", [ datetime.datetime(2021, 2, 11, 15, 39, 58, 457698), datetime.date(2021, 2, 11), datetime.time(15, 39, 58, 457698), ], ) def test_should_raise_exception_for_datetime_instances(self, value): with pytest.raises(TypeError) as exc: next(seq(value)) assert str(exc.value) == ( "Sequences with values datetime.datetime, datetime.date and datetime.time, " "incremente_by must be a datetime.timedelta." ) model_bakery-1.7.0/tests/uninstalled/000077500000000000000000000000001427223773300176525ustar00rootroot00000000000000model_bakery-1.7.0/tests/uninstalled/baker_recipes.py000066400000000000000000000005651427223773300230300ustar00rootroot00000000000000from django.utils.timezone import now from model_bakery.recipe import Recipe from tests.generic.models import Person person = Recipe( Person, name="Uninstalled", nickname="uninstalled", age=18, bio="Uninstalled", blog="http://uninstalled.com", days_since_last_login=4, birthday=now().date(), appointment=now(), birth_time=now(), ) model_bakery-1.7.0/tox-linter.ini000066400000000000000000000010541427223773300167740ustar00rootroot00000000000000[tox] envlist = flake8 isort pydocstyle black mypy [gh-actions] python = 3.10: flake8,isort,pydocstyle,black,mypy [testenv:flake8] deps=flake8 basepython=python3 commands=flake8 model_bakery {posargs} [testenv:isort] deps=isort basepython=python3 commands=isort model_bakery --check-only {posargs} [testenv:black] deps=black basepython=python3 commands=black . --check [testenv:mypy] deps=mypy basepython=python3 commands=python -m mypy model_bakery [testenv:pydocstyle] deps=pydocstyle basepython=python3 commands=pydocstyle model_bakery-1.7.0/tox.ini000066400000000000000000000012511427223773300155000ustar00rootroot00000000000000[tox] envlist = py37-django{32}-{postgresql,sqlite} py38-django{32,40,41}-{postgresql,sqlite} py39-django{32,40,41}-{postgresql,sqlite} py310-django{32,40,41}-{postgresql,sqlite} py311-django{41}-{postgresql,sqlite} [gh-actions] python = 3.7: py37 3.8: py38 3.9: py39 3.10: py310 3.11: py311 [testenv] setenv = PYTHONPATH={toxinidir} postgresql: TEST_DB=postgis postgresql: PGUSER=postgres sqlite: TEST_DB=sqlite sqlite: USE_TZ=True deps = pillow pytest pytest-django django32: Django==3.2 django40: Django>=4.0,<4.1 django41: Django==4.1b1 postgresql: psycopg2-binary commands = pytest model_bakery-1.7.0/utils/000077500000000000000000000000001427223773300153265ustar00rootroot00000000000000model_bakery-1.7.0/utils/from_mommy_to_bakery.py000066400000000000000000000062131427223773300221220ustar00rootroot00000000000000""" Migrate from model_mommy to model_bakery. ``python from_mommy_to_bakery.py --dry-run`` Please check your dependency files. """ import argparse import os import re PACKAGE_NAME = r"\bmodel_mommy\b" PACKAGE_NAME_PATTERN = re.compile(PACKAGE_NAME) PACKAGE_RECIPES = r"\bmommy_recipes\b" PACKAGE_RECIPES_PATTERN = re.compile(PACKAGE_RECIPES) PACKAGE_LEGACY_MODULE = r"\bmommy\b" PACKAGE_LEGACY_MODULE_PATTERN = re.compile(PACKAGE_LEGACY_MODULE) LEGACY_AND_NEW = [ {"old": PACKAGE_NAME_PATTERN, "new": r"model_bakery"}, {"old": PACKAGE_RECIPES_PATTERN, "new": r"baker_recipes"}, {"old": PACKAGE_LEGACY_MODULE_PATTERN, "new": r"baker"}, ] EXCLUDE = [ "node_modules", "venv", ".git", "sql", "docs", "from_mommy_to_bakery.py", "Pipfile", "Pipfile.lock", ] def _find_changes(content, pattern, new_value): new_content, substitutions = re.subn(pattern, new_value, content) return new_content, substitutions > 0 def _rename_recipe_file(to_be_renamed, dry_run): if dry_run is True: print("Will be renamed:") for old_recipe_file in to_be_renamed: root = old_recipe_file[: old_recipe_file.rfind("/")] if dry_run is False: os.rename(old_recipe_file, f"{root}/baker_recipes.py") else: print(old_recipe_file) def _replace_legacy_terms(file_path, dry_run): try: content = open(file_path, "r").read() except UnicodeDecodeError: return changed = [] for patterns in LEGACY_AND_NEW: old, new = patterns["old"], patterns["new"] content, has_changed = _find_changes(content, old, new) changed.append(has_changed) if any(changed): if dry_run is False: open(file_path, "w").write(content) else: print(file_path) def _sanitize_folder_or_file(folder_or_file): folder_or_file = folder_or_file.strip() if folder_or_file.endswith("/"): # Remove trailing slash e.g.: '.tox/' -> '.tox' folder_or_file = folder_or_file[:-1] return folder_or_file def check_files(dry_run): excluded_by_gitignore = [ _sanitize_folder_or_file(folder_or_file) for folder_or_file in open(".gitignore").readlines() ] exclude = EXCLUDE[:] exclude.extend(excluded_by_gitignore) to_be_renamed = [] for root, dirs, files in os.walk(".", topdown=True): dirs[:] = [directory for directory in dirs if directory not in exclude] for file_ in files: if file_ in exclude or not file_.endswith(".py"): continue file_path = f"{root}/{file_}" if file_ == "mommy_recipes.py": to_be_renamed.append(file_path) _replace_legacy_terms(file_path, dry_run) _rename_recipe_file(to_be_renamed, dry_run) if __name__ == "__main__": description = "Help you to migrate from model_mommy to model_bakery." parser = argparse.ArgumentParser(description=description) parser.add_argument( "--dry-run", dest="dry_run", action="store_true", help="See which files will be changed.", ) args = parser.parse_args() check_files(args.dry_run)