├── mysite
├── mysite
│ ├── __init__.py
│ ├── urls.py
│ ├── asgi.py
│ ├── wsgi.py
│ └── settings.py
├── polls
│ ├── __init__.py
│ ├── tests
│ │ ├── __init__.py
│ │ ├── test_rules
│ │ │ ├── __init__.py
│ │ │ ├── rule_1
│ │ │ │ ├── __init__.py
│ │ │ │ ├── bad_factories.py
│ │ │ │ ├── test_rule_1.py
│ │ │ │ └── good_factories.py
│ │ │ ├── rule_2
│ │ │ │ ├── __init__.py
│ │ │ │ ├── bad_factories.py
│ │ │ │ ├── good_factories.py
│ │ │ │ └── test_rule_2.py
│ │ │ ├── rule_3
│ │ │ │ ├── __init__.py
│ │ │ │ ├── bad_factories.py
│ │ │ │ ├── good_factories.py
│ │ │ │ └── test_rule_3.py
│ │ │ ├── rule_4
│ │ │ │ ├── __init__.py
│ │ │ │ ├── good_factories.py
│ │ │ │ └── test_rule_4.py
│ │ │ ├── rule_5
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_rule_5.py
│ │ │ │ └── good_factories.py
│ │ │ └── rule_6
│ │ │ │ ├── __init__.py
│ │ │ │ └── test_rule_6.py
│ │ ├── test_with_factories
│ │ │ ├── __init__.py
│ │ │ ├── factories.py
│ │ │ └── test_models.py
│ │ └── test_without_factory_boy
│ │ │ ├── __init__.py
│ │ │ └── test_models.py
│ ├── migrations
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ ├── static
│ │ └── polls
│ │ │ └── style.css
│ ├── apps.py
│ ├── templates
│ │ └── polls
│ │ │ ├── results.html
│ │ │ ├── index.html
│ │ │ └── detail.html
│ ├── urls.py
│ ├── admin.py
│ ├── views.py
│ └── models.py
├── pytest.ini
├── Makefile
├── .pre-commit-config.yaml
├── pyproject.toml
├── manage.py
├── README.md
└── poetry.lock
├── LICENSE
├── README.md
└── .gitignore
/mysite/mysite/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_1/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_2/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_3/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_4/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_5/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_6/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_with_factories/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_without_factory_boy/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mysite/polls/static/polls/style.css:
--------------------------------------------------------------------------------
1 | li a {
2 | color: green;
3 | }
4 |
--------------------------------------------------------------------------------
/mysite/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | DJANGO_SETTINGS_MODULE = mysite.settings
3 |
--------------------------------------------------------------------------------
/mysite/Makefile:
--------------------------------------------------------------------------------
1 | format:
2 | @black -l 80 . --exclude=.venv
3 |
4 | install:
5 | @poetry install
6 | @pre-commit install
7 |
8 | .PHONY: test format check install sh run
9 |
--------------------------------------------------------------------------------
/mysite/polls/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PollsConfig(AppConfig):
5 | default_auto_field = "django.db.models.BigAutoField"
6 | name = "polls"
7 |
--------------------------------------------------------------------------------
/mysite/mysite/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import include, path
3 |
4 | urlpatterns = [
5 | path("polls/", include("polls.urls")),
6 | path("admin/", admin.site.urls),
7 | ]
8 |
--------------------------------------------------------------------------------
/mysite/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/python/black
3 | # It must match black's version that is inside pyproject.toml
4 | rev: 22.8.0
5 | hooks:
6 | - id: black
7 | language_version: python3
8 | args: ["-l 80", "--exclude=.venv"]
9 |
--------------------------------------------------------------------------------
/mysite/polls/templates/polls/results.html:
--------------------------------------------------------------------------------
1 |
{{ poll.question.question_text }}
2 |
3 |
4 | {% for choice in poll.question.choices.all %}
5 | - {{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
6 | {% endfor %}
7 |
8 |
9 | Vote again?
10 |
--------------------------------------------------------------------------------
/mysite/polls/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from polls import views
4 |
5 | app_name = "polls"
6 | urlpatterns = [
7 | path("", views.IndexView.as_view(), name="index"),
8 | path("/", views.DetailView.as_view(), name="detail"),
9 | path("/results/", views.ResultsView.as_view(), name="results"),
10 | path("/vote/", views.vote, name="vote"),
11 | ]
12 |
--------------------------------------------------------------------------------
/mysite/mysite/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for mysite project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/mysite/mysite/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for mysite project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/mysite/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "factory-boy-best-practices"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Camila Maia "]
6 | license = "MIT"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.9"
10 | Django = "^3.2.4"
11 |
12 | [tool.poetry.dev-dependencies]
13 | factory-boy = "^3.2.1"
14 | pytest-django = "^4.5.2"
15 | black = "22.8.0"
16 | pre-commit = "^2.20.0"
17 | ipdb = "^0.13.9"
18 |
19 | [build-system]
20 | requires = ["poetry-core>=1.0.0"]
21 | build-backend = "poetry.core.masonry.api"
22 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_1/bad_factories.py:
--------------------------------------------------------------------------------
1 | import factory
2 | from django.utils import timezone
3 |
4 | from polls.models import Poll, Question
5 |
6 |
7 | class PollFactory(factory.django.DjangoModelFactory):
8 | class Meta:
9 | model = Poll
10 |
11 | pub_date = factory.LazyFunction(timezone.now)
12 |
13 |
14 | class QuestionFactory(factory.django.DjangoModelFactory):
15 | class Meta:
16 | model = Question
17 |
18 | question_text = "What's Up?"
19 | poll = factory.RelatedFactory(PollFactory, "question")
20 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_1/test_rule_1.py:
--------------------------------------------------------------------------------
1 | from polls.tests.test_rules.rule_1.bad_factories import (
2 | QuestionFactory as BadQuestionFactory,
3 | )
4 | from polls.tests.test_rules.rule_1.good_factories import (
5 | PollFactory as GoodPollFactory,
6 | )
7 |
8 |
9 | def test_bad_factories():
10 | question = BadQuestionFactory.build()
11 | assert question.poll
12 | assert question.poll.pub_date
13 |
14 |
15 | def test_good_factories():
16 | poll = GoodPollFactory.build()
17 | assert poll.question
18 | assert poll.question.question_text
19 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_2/bad_factories.py:
--------------------------------------------------------------------------------
1 | import factory
2 | from django.utils import timezone
3 |
4 | from polls.models import Poll, Question
5 |
6 |
7 | class PollFactory(factory.django.DjangoModelFactory):
8 | class Meta:
9 | model = Poll
10 |
11 | pub_date = factory.LazyFunction(timezone.now)
12 | question = factory.SubFactory(
13 | "polls.tests.test_rules.rule_2.bad_factories.QuestionFactory"
14 | )
15 |
16 |
17 | class QuestionFactory(factory.django.DjangoModelFactory):
18 | class Meta:
19 | model = Question
20 |
21 | question_text = "What's Up?"
22 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_1/good_factories.py:
--------------------------------------------------------------------------------
1 | import factory
2 | from django.utils import timezone
3 |
4 | from polls.models import Poll, Question
5 |
6 |
7 | class PollFactory(factory.django.DjangoModelFactory):
8 | class Meta:
9 | model = Poll
10 |
11 | pub_date = factory.LazyFunction(timezone.now)
12 | question = factory.SubFactory(
13 | "polls.tests.test_rules.rule_1.good_factories.QuestionFactory"
14 | )
15 |
16 |
17 | class QuestionFactory(factory.django.DjangoModelFactory):
18 | class Meta:
19 | model = Question
20 |
21 | question_text = "Coke or Pepsi?"
22 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_2/good_factories.py:
--------------------------------------------------------------------------------
1 | import factory
2 | from django.utils import timezone
3 |
4 | from polls.models import Poll, Question
5 |
6 |
7 | class PollFactory(factory.django.DjangoModelFactory):
8 | class Meta:
9 | model = Poll
10 |
11 | pub_date = factory.LazyFunction(timezone.now)
12 | question = factory.SubFactory(
13 | "polls.tests.test_rules.rule_2.good_factories.QuestionFactory"
14 | )
15 |
16 |
17 | class QuestionFactory(factory.django.DjangoModelFactory):
18 | class Meta:
19 | model = Question
20 |
21 | question_text = factory.Faker("sentence")
22 |
--------------------------------------------------------------------------------
/mysite/polls/templates/polls/index.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 |
4 |
5 | Polls
6 |
7 | {% if latest_poll_list %}
8 |
9 | {% for poll in latest_poll_list %}
10 | -
11 | {{ poll }}
12 | {% if poll.was_published_recently %}
13 | new!
14 | {% endif %}
15 |
16 | {% endfor %}
17 |
18 | {% else %}
19 | No polls are available.
20 | {% endif %}
21 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_2/test_rule_2.py:
--------------------------------------------------------------------------------
1 | from polls.tests.test_rules.rule_2.bad_factories import (
2 | PollFactory as BadPollFactory,
3 | )
4 | from polls.tests.test_rules.rule_2.good_factories import (
5 | PollFactory as GoodPollFactory,
6 | )
7 |
8 |
9 | def test_bad_to_string_with_non_premium_question_without_author():
10 | poll = BadPollFactory.build()
11 | assert str(poll) == "What's Up?"
12 |
13 |
14 | def test_good_to_string_with_non_premium_question_without_author():
15 | question = GoodPollFactory.build(
16 | premium=False, author=None, question__question_text="Pepsi or Coke?"
17 | )
18 | assert str(question) == "Pepsi or Coke?"
19 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_3/bad_factories.py:
--------------------------------------------------------------------------------
1 | import factory
2 | from django.utils import timezone
3 |
4 | from polls.models import Poll, Question
5 |
6 |
7 | class PollFactory(factory.django.DjangoModelFactory):
8 | class Meta:
9 | model = Poll
10 |
11 | pub_date = factory.LazyFunction(timezone.now)
12 | author = "João"
13 | premium = True
14 | question = factory.SubFactory(
15 | "polls.tests.test_rules.rule_3.bad_factories.QuestionFactory"
16 | )
17 |
18 |
19 | class QuestionFactory(factory.django.DjangoModelFactory):
20 | class Meta:
21 | model = Question
22 |
23 | question_text = factory.Faker("sentence")
24 | language = Question.PT_BR
25 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_3/good_factories.py:
--------------------------------------------------------------------------------
1 | import factory
2 | from django.utils import timezone
3 |
4 | from polls.models import Poll, Question
5 |
6 |
7 | class PollFactory(factory.django.DjangoModelFactory):
8 | class Meta:
9 | model = Poll
10 |
11 | pub_date = factory.LazyFunction(timezone.now)
12 | question = factory.SubFactory(
13 | "polls.tests.test_rules.rule_3.good_factories.QuestionFactory"
14 | )
15 |
16 | class Params:
17 | with_author = factory.Trait(author=factory.Faker("name"))
18 |
19 |
20 | class QuestionFactory(factory.django.DjangoModelFactory):
21 | class Meta:
22 | model = Question
23 |
24 | question_text = factory.Faker("sentence")
25 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_4/good_factories.py:
--------------------------------------------------------------------------------
1 | import factory
2 | from django.utils import timezone
3 |
4 | from polls.models import Poll, Question
5 |
6 |
7 | class PollFactory(factory.django.DjangoModelFactory):
8 | class Meta:
9 | model = Poll
10 |
11 | pub_date = factory.LazyFunction(timezone.now)
12 | question = factory.SubFactory(
13 | "polls.tests.test_rules.rule_3.good_factories.QuestionFactory"
14 | )
15 |
16 | class Params:
17 | with_author = factory.Trait(author=factory.Faker("name"))
18 |
19 |
20 | class QuestionFactory(factory.django.DjangoModelFactory):
21 | class Meta:
22 | model = Question
23 |
24 | question_text = factory.Faker("sentence")
25 |
--------------------------------------------------------------------------------
/mysite/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/mysite/polls/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from polls.models import Choice, Poll, Question
4 |
5 |
6 | class ChoiceInline(admin.TabularInline):
7 | model = Choice
8 | extra = 3
9 |
10 |
11 | class PollInline(admin.TabularInline):
12 | model = Poll
13 |
14 |
15 | class QuestionAdmin(admin.ModelAdmin):
16 | inlines = [PollInline, ChoiceInline]
17 | list_display = ("question_text", "language", "poll", "get_pub_date")
18 | list_filter = ("poll__pub_date", "language", "poll__premium")
19 | search_fields = ("question_text",)
20 | ordering = ("-poll__pub_date",)
21 |
22 | @admin.display(description="Pub Date")
23 | def get_pub_date(self, obj):
24 | return obj.poll.pub_date
25 |
26 |
27 | admin.site.register(Question, QuestionAdmin)
28 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_5/test_rule_5.py:
--------------------------------------------------------------------------------
1 | from pytest import mark
2 |
3 | from polls.tests.test_rules.rule_5.good_factories import (
4 | ChoiceFactory,
5 | QuestionFactory,
6 | )
7 |
8 |
9 | def test_build_choice():
10 | choice = ChoiceFactory.build(
11 | question__question_text="Pepsi or Coke?",
12 | )
13 |
14 | assert str(choice.question) == "Pepsi or Coke?"
15 |
16 |
17 | def test_build_question_without_choices():
18 | question = QuestionFactory.build()
19 |
20 | assert not question.choices.all()
21 |
22 |
23 | @mark.django_db
24 | def test_build_question_with_choices():
25 | # We need to use `create` here in order to create the relationships
26 | question = QuestionFactory.create(with_choices=True)
27 |
28 | assert question.choices.count() == 2
29 |
--------------------------------------------------------------------------------
/mysite/polls/templates/polls/detail.html:
--------------------------------------------------------------------------------
1 |
20 |
21 | See Results
22 | See all Polls
23 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_4/test_rule_4.py:
--------------------------------------------------------------------------------
1 | from pytest import mark
2 |
3 | from polls.models import Question
4 | from polls.tests.test_rules.rule_4.good_factories import PollFactory
5 |
6 |
7 | @mark.django_db
8 | def test_bad_to_string_with_non_premium_english_question_with_author():
9 | question = PollFactory.create(
10 | question__question_text="Pepsi or Coke?",
11 | question__language=Question.EN,
12 | premium=False,
13 | with_author=True,
14 | )
15 | assert str(question) == f"Pepsi or Coke? - By {question.author}"
16 |
17 |
18 | def test_good_to_string_with_non_premium_english_question_with_author():
19 | question = PollFactory.build(
20 | question__question_text="Pepsi or Coke?",
21 | question__language=Question.EN,
22 | premium=False,
23 | with_author=True,
24 | )
25 | assert str(question) == f"Pepsi or Coke? - By {question.author}"
26 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_5/good_factories.py:
--------------------------------------------------------------------------------
1 | import factory
2 |
3 | from polls.models import Choice, Question
4 |
5 |
6 | class ChoiceFactory(factory.django.DjangoModelFactory):
7 | class Meta:
8 | model = Choice
9 |
10 | question = factory.SubFactory(
11 | "polls.tests.test_rules.rule_5.good_factories.QuestionFactory"
12 | )
13 |
14 |
15 | class QuestionFactory(factory.django.DjangoModelFactory):
16 | class Meta:
17 | model = Question
18 |
19 | class Params:
20 | with_choices = factory.Trait(
21 | choice_1=factory.RelatedFactory(
22 | ChoiceFactory,
23 | "question",
24 | choice_text="Option 1",
25 | ),
26 | choice_2=factory.RelatedFactory(
27 | ChoiceFactory,
28 | "question",
29 | choice_text="Option 2",
30 | ),
31 | )
32 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_3/test_rule_3.py:
--------------------------------------------------------------------------------
1 | from polls.models import Question
2 | from polls.tests.test_rules.rule_3.bad_factories import (
3 | PollFactory as BadPollFactory,
4 | )
5 | from polls.tests.test_rules.rule_3.good_factories import (
6 | PollFactory as GoodPollFactory,
7 | )
8 |
9 |
10 | def test_bad_to_string_with_non_premium_english_question_with_author():
11 | poll = BadPollFactory.build(
12 | question__question_text="Pepsi or Coke?",
13 | question__language=Question.EN,
14 | premium=False,
15 | author="John",
16 | )
17 | assert str(poll) == "Pepsi or Coke? - By John"
18 |
19 |
20 | def test_good_to_string_with_non_premium_english_question_with_author():
21 | poll = GoodPollFactory.build(
22 | question__question_text="Pepsi or Coke?",
23 | question__language=Question.EN,
24 | premium=False,
25 | with_author=True,
26 | )
27 |
28 | assert str(poll) == f"Pepsi or Coke? - By {poll.author}"
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Camila Maia
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_rules/rule_6/test_rule_6.py:
--------------------------------------------------------------------------------
1 | from pytest import fixture
2 |
3 | from polls.models import Question
4 | from polls.tests.test_with_factories.factories import PollFactory
5 |
6 | # Good
7 |
8 |
9 | @fixture
10 | def english_non_premium_with_author_poll():
11 | return PollFactory.build(
12 | question__question_text="Pepsi or Coke?",
13 | question__language=Question.EN,
14 | premium=False,
15 | with_author=True,
16 | )
17 |
18 |
19 | def test_1(english_non_premium_with_author_poll):
20 | # ...
21 | pass
22 |
23 |
24 | def test_2(english_non_premium_with_author_poll):
25 | # ...
26 | pass
27 |
28 |
29 | # Bad
30 |
31 |
32 | def test_3():
33 | poll = PollFactory.build(
34 | question__question_text="Pepsi or Coke?",
35 | question__language=Question.EN,
36 | premium=False,
37 | with_author=True,
38 | )
39 | # ...
40 | pass
41 |
42 |
43 | def test_4():
44 | poll = PollFactory.build(
45 | question__question_text="Pepsi or Coke?",
46 | question__language=Question.EN,
47 | premium=False,
48 | with_author=True,
49 | )
50 | # ...
51 | pass
52 |
53 |
54 | # ...
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # factory_boy Best Practices
2 |
3 | 1. Factories should represent their models
4 |
5 | 1. Do not rely on defaults from factories
6 |
7 | * If a default value is changed, all tests that depend on it will break
8 | * The setup of a test should contain all the logic to ensure it will always pass
9 | * Explicit better than implicit
10 |
11 | 1. Factories should contain only the required data
12 |
13 | * If the field is nullable (null=True) the attribute should be under a trait
14 | and not as a default value
15 | * If we want to have explicitly the attribute, we can use MyFactory(with_myattr=True)
16 | * When are we going to remember to test the case MyFactory(myattr=None)?
17 | * We should not assume there is an author when DB actually allows to not have it.
18 |
19 | 1. Build over create
20 |
21 | * MyFactory.build(): creates a local object (memory)
22 | * MyFactory.create(): creates a local object + stores it in the DB
23 |
24 | 1. If FK is in the table: SubFactor
25 |
26 | If FK is in the other table: RelatedFactory + trait
27 |
28 |
29 | * SubFactory: builds/creates the SubFactory during the process of creation of the main factory
30 | * RelatedFactory: builds/creates the RelatedFactory after creating the main factory
31 |
32 | 1. Use fixtures to wrap factories to avoid duplication
33 |
34 | 1. Avoid sharing factories or fixtures among different files
35 |
36 | 1. get_or_create should be used only for unique keys
37 |
--------------------------------------------------------------------------------
/mysite/README.md:
--------------------------------------------------------------------------------
1 | # mysite
2 |
3 | This is a demo app built based on top of the [Django's Tutorial][django-tutorial].
4 |
5 | ## Install
6 |
7 | ### Requirements:
8 |
9 | - [Python 3.9+][python]
10 | - [Poetry][poetry]
11 |
12 | ### Fork camilamaia/factory-boy-best-practices repository
13 |
14 | [Fork][fork] the repo in your github account such as you get
15 | `_yourusername_/factory-boy-best-practices`.
16 |
17 | ### Clone
18 |
19 | Now that you own a copy of the repo, you can easily clone the repo with the command below:
20 |
21 | ```shell
22 | $ git clone https://github.com//factory-boy-best-practices.git
23 | ```
24 |
25 | ### Enter in the mysite folder
26 |
27 | ```shell
28 | $ cd factory-boy-best-practices/mysite
29 | ```
30 |
31 | ### Create and activate a new virtual environment
32 |
33 | ```shell
34 | $ poetry shell
35 | ```
36 |
37 | ### Install the dependencies
38 |
39 | ```shell
40 | $ make install
41 | ```
42 |
43 | ### Setup the Database
44 |
45 | ```shell
46 | $ python manage.py migrate
47 | ```
48 |
49 | ## Run
50 |
51 | ```shell
52 | $ python manage.py runserver
53 | ```
54 |
55 | If everything runs smoothly, you should see the polls index page live at
56 | `http://127.0.0.1:8000/polls`. Feel free to create an issue if run into problems while setting up
57 | the project.
58 |
59 | ## Tests
60 |
61 | ```shell
62 | $ pytest
63 | ```
64 |
65 | [django-tutorial]: https://docs.djangoproject.com/en/3.2/intro/tutorial01/
66 | [python]: https://www.python.org/downloads/
67 | [fork]: https://docs.github.com/en/github/getting-started-with-github/quickstart/fork-a-repo
68 | [poetry]: https://python-poetry.org/docs/#installation
69 |
--------------------------------------------------------------------------------
/mysite/polls/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponseRedirect
2 | from django.shortcuts import get_object_or_404, render
3 | from django.urls import reverse
4 | from django.utils import timezone
5 | from django.views import generic
6 |
7 | from polls.models import Choice, Poll, Question
8 |
9 |
10 | class IndexView(generic.ListView):
11 | template_name = "polls/index.html"
12 | context_object_name = "latest_poll_list"
13 |
14 | def get_queryset(self):
15 | """
16 | Return the published polls (not including those set to be
17 | published in the future) order by DESC pub_date
18 | """
19 | return Poll.objects.filter(pub_date__lte=timezone.now()).order_by(
20 | "-pub_date"
21 | )
22 |
23 |
24 | class DetailView(generic.DetailView):
25 | model = Poll
26 | template_name = "polls/detail.html"
27 |
28 |
29 | class ResultsView(generic.DetailView):
30 | model = Poll
31 | template_name = "polls/results.html"
32 |
33 |
34 | def vote(request, poll_id):
35 | poll = get_object_or_404(Poll, pk=poll_id)
36 |
37 | try:
38 | selected_choice = poll.question.choices.get(pk=request.POST["choice"])
39 | except (KeyError, Choice.DoesNotExist):
40 | # Redisplay the question voting form.
41 | return render(
42 | request,
43 | "polls/detail.html",
44 | {
45 | "poll": poll,
46 | "error_message": "You didn't select a choice.",
47 | },
48 | )
49 | else:
50 | selected_choice.votes += 1
51 | selected_choice.save()
52 | # Always return an HttpResponseRedirect after successfully dealing
53 | # with POST data. This prevents data from being posted twice if a
54 | # user hits the Back button.
55 | return HttpResponseRedirect(reverse("polls:results", args=(poll.id,)))
56 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_with_factories/factories.py:
--------------------------------------------------------------------------------
1 | from django.utils import timezone
2 | from factory import Faker, LazyFunction, RelatedFactory, SubFactory, Trait
3 | from factory.django import DjangoModelFactory
4 |
5 | from polls.models import Choice, Poll, Question
6 |
7 |
8 | class ChoiceFactory(DjangoModelFactory):
9 | class Meta:
10 | model = Choice
11 |
12 | question = SubFactory("polls.factories.QuestionFactory")
13 |
14 |
15 | class QuestionFactory(DjangoModelFactory):
16 | class Meta:
17 | model = Question
18 |
19 | question_text = Faker("sentence")
20 |
21 | class Params:
22 | english = Trait(language=Question.EN)
23 | portuguese = Trait(language=Question.PT_BR)
24 | with_choices = Trait(
25 | choice_1=RelatedFactory(
26 | ChoiceFactory,
27 | "question",
28 | choice_text="Option 1",
29 | ),
30 | choice_2=RelatedFactory(
31 | ChoiceFactory,
32 | "question",
33 | choice_text="Option 2",
34 | ),
35 | )
36 |
37 |
38 | class PollFactory(DjangoModelFactory):
39 | class Meta:
40 | model = Poll
41 |
42 | pub_date = LazyFunction(timezone.now)
43 | question = SubFactory(QuestionFactory)
44 |
45 | class Params:
46 | with_author = Trait(author=Faker("name"))
47 |
48 |
49 | class PastPollFactory(PollFactory):
50 | pub_date = Faker(
51 | "date_time_between",
52 | end_date="-2d",
53 | tzinfo=timezone.get_current_timezone(),
54 | )
55 |
56 |
57 | class RecentPollFactory(PollFactory):
58 | pub_date = LazyFunction(timezone.now)
59 |
60 |
61 | class FuturePollFactory(PollFactory):
62 | pub_date = Faker(
63 | "date_time_between",
64 | start_date="+1d",
65 | tzinfo=timezone.get_current_timezone(),
66 | )
67 |
--------------------------------------------------------------------------------
/mysite/polls/models.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | from django.contrib import admin
4 | from django.db import models
5 | from django.utils import timezone
6 |
7 |
8 | class Poll(models.Model):
9 | question = models.OneToOneField(
10 | "polls.Question",
11 | on_delete=models.CASCADE,
12 | related_name="poll",
13 | )
14 | pub_date = models.DateTimeField("date published")
15 | premium = models.BooleanField(default=False)
16 | author = models.CharField(max_length=100, null=True, blank=True)
17 |
18 | def __str__(self):
19 | question = str(self.question)
20 |
21 | if self.premium:
22 | question = f"⭐️ {question}"
23 |
24 | if self.author:
25 | by = self._get_preposition_by() # ["By", "Por"] | None
26 | question = f"{question} - {by} {self.author}"
27 |
28 | return question
29 |
30 | @property
31 | def was_published_recently(self):
32 | now = timezone.now()
33 | return now - datetime.timedelta(days=1) <= self.pub_date <= now
34 |
35 | def _get_preposition_by(self):
36 | if self.question.is_in_english:
37 | return "By"
38 |
39 | if self.question.is_in_portuguese:
40 | return "Por"
41 |
42 |
43 | class Question(models.Model):
44 | EN = "EN"
45 | PT_BR = "PT-BR"
46 | LANGUAGE_CHOICES = (
47 | (EN, "English"),
48 | (PT_BR, "Português (Brasil)"),
49 | )
50 |
51 | question_text = models.CharField(max_length=200)
52 | language = models.CharField(
53 | choices=LANGUAGE_CHOICES, default=EN, max_length=5
54 | )
55 |
56 | @property
57 | def is_in_english(self):
58 | return self.language == self.EN
59 |
60 | @property
61 | def is_in_portuguese(self):
62 | return self.language == self.PT_BR
63 |
64 | def __str__(self):
65 | return self.question_text
66 |
67 |
68 | class Choice(models.Model):
69 | question = models.ForeignKey(
70 | Question, on_delete=models.CASCADE, related_name="choices"
71 | )
72 | choice_text = models.CharField(max_length=200)
73 | votes = models.IntegerField(default=0)
74 |
75 | def __str__(self):
76 | return self.choice_text
77 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | .vscode/
132 |
--------------------------------------------------------------------------------
/mysite/polls/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.4 on 2021-10-06 17:28
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = []
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name="Question",
16 | fields=[
17 | (
18 | "id",
19 | models.BigAutoField(
20 | auto_created=True,
21 | primary_key=True,
22 | serialize=False,
23 | verbose_name="ID",
24 | ),
25 | ),
26 | ("question_text", models.CharField(max_length=200)),
27 | (
28 | "language",
29 | models.CharField(
30 | choices=[
31 | ("EN", "English"),
32 | ("PT-BR", "Português (Brasil)"),
33 | ],
34 | default="EN",
35 | max_length=5,
36 | ),
37 | ),
38 | ],
39 | ),
40 | migrations.CreateModel(
41 | name="Poll",
42 | fields=[
43 | (
44 | "id",
45 | models.BigAutoField(
46 | auto_created=True,
47 | primary_key=True,
48 | serialize=False,
49 | verbose_name="ID",
50 | ),
51 | ),
52 | (
53 | "pub_date",
54 | models.DateTimeField(verbose_name="date published"),
55 | ),
56 | ("premium", models.BooleanField(default=False)),
57 | (
58 | "author",
59 | models.CharField(blank=True, max_length=100, null=True),
60 | ),
61 | (
62 | "question",
63 | models.OneToOneField(
64 | on_delete=django.db.models.deletion.CASCADE,
65 | related_name="poll",
66 | to="polls.question",
67 | ),
68 | ),
69 | ],
70 | ),
71 | migrations.CreateModel(
72 | name="Choice",
73 | fields=[
74 | (
75 | "id",
76 | models.BigAutoField(
77 | auto_created=True,
78 | primary_key=True,
79 | serialize=False,
80 | verbose_name="ID",
81 | ),
82 | ),
83 | ("choice_text", models.CharField(max_length=200)),
84 | ("votes", models.IntegerField(default=0)),
85 | (
86 | "question",
87 | models.ForeignKey(
88 | on_delete=django.db.models.deletion.CASCADE,
89 | related_name="choices",
90 | to="polls.question",
91 | ),
92 | ),
93 | ],
94 | ),
95 | ]
96 |
--------------------------------------------------------------------------------
/mysite/mysite/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for mysite project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.2.4.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.2/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/3.2/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve().parent.parent
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = (
24 | "django-insecure-&fagamivpz1+qquydj@m#gh+fi0oot@qaa*=ft3yi$-3@$@zi6"
25 | )
26 |
27 | # SECURITY WARNING: don't run with debug turned on in production!
28 | DEBUG = True
29 |
30 | ALLOWED_HOSTS = []
31 |
32 |
33 | # Application definition
34 |
35 | INSTALLED_APPS = [
36 | "django.contrib.admin",
37 | "django.contrib.auth",
38 | "django.contrib.contenttypes",
39 | "django.contrib.sessions",
40 | "django.contrib.messages",
41 | "django.contrib.staticfiles",
42 | # internal apps
43 | "polls.apps.PollsConfig",
44 | ]
45 |
46 | MIDDLEWARE = [
47 | "django.middleware.security.SecurityMiddleware",
48 | "django.contrib.sessions.middleware.SessionMiddleware",
49 | "django.middleware.common.CommonMiddleware",
50 | "django.middleware.csrf.CsrfViewMiddleware",
51 | "django.contrib.auth.middleware.AuthenticationMiddleware",
52 | "django.contrib.messages.middleware.MessageMiddleware",
53 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
54 | ]
55 |
56 | ROOT_URLCONF = "mysite.urls"
57 |
58 | TEMPLATES = [
59 | {
60 | "BACKEND": "django.template.backends.django.DjangoTemplates",
61 | "DIRS": [],
62 | "APP_DIRS": True,
63 | "OPTIONS": {
64 | "context_processors": [
65 | "django.template.context_processors.debug",
66 | "django.template.context_processors.request",
67 | "django.contrib.auth.context_processors.auth",
68 | "django.contrib.messages.context_processors.messages",
69 | ],
70 | },
71 | },
72 | ]
73 |
74 | WSGI_APPLICATION = "mysite.wsgi.application"
75 |
76 |
77 | # Database
78 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases
79 |
80 | DATABASES = {
81 | "default": {
82 | "ENGINE": "django.db.backends.sqlite3",
83 | "NAME": BASE_DIR / "db.sqlite3",
84 | }
85 | }
86 |
87 |
88 | # Password validation
89 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
90 |
91 | AUTH_PASSWORD_VALIDATORS = [
92 | {
93 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
94 | },
95 | {
96 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
97 | },
98 | {
99 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
100 | },
101 | {
102 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
103 | },
104 | ]
105 |
106 |
107 | # Internationalization
108 | # https://docs.djangoproject.com/en/3.2/topics/i18n/
109 |
110 | LANGUAGE_CODE = "en-us"
111 |
112 | TIME_ZONE = "UTC"
113 |
114 | USE_I18N = True
115 |
116 | USE_L10N = True
117 |
118 | USE_TZ = True
119 |
120 |
121 | # Static files (CSS, JavaScript, Images)
122 | # https://docs.djangoproject.com/en/3.2/howto/static-files/
123 |
124 | STATIC_URL = "/static/"
125 |
126 | # Default primary key field type
127 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
128 |
129 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
130 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_with_factories/test_models.py:
--------------------------------------------------------------------------------
1 | from polls.tests.test_with_factories.factories import (
2 | FuturePollFactory,
3 | PastPollFactory,
4 | PollFactory,
5 | QuestionFactory,
6 | RecentPollFactory,
7 | )
8 |
9 |
10 | class TestPoll:
11 | def test_to_string_non_premium_and_without_author(self):
12 | poll = PollFactory.build(
13 | question__question_text="Summer or Winter?",
14 | premium=False,
15 | author=None,
16 | )
17 | assert str(poll) == "Summer or Winter?"
18 |
19 | def test_to_string_non_premium_and_with_author_on_english(self):
20 | poll = PollFactory.build(
21 | question__question_text="Summer or Winter?",
22 | question__english=True,
23 | premium=False,
24 | author="John",
25 | )
26 | assert str(poll) == "Summer or Winter? - By John"
27 |
28 | def test_to_string_non_premium_and_with_author_on_portuguese(self):
29 | poll = PollFactory.build(
30 | question__question_text="Summer or Winter?",
31 | question__portuguese=True,
32 | premium=False,
33 | author="João",
34 | )
35 | assert str(poll) == "Summer or Winter? - Por João"
36 |
37 | def test_to_string_premium_and_without_author(self):
38 | poll = PollFactory.build(
39 | question__question_text="Summer or Winter?",
40 | premium=True,
41 | author=None,
42 | )
43 | assert str(poll) == "⭐️ Summer or Winter?"
44 |
45 | def test_to_string_premium_and_with_author_on_english(self):
46 | poll = PollFactory.build(
47 | question__question_text="Summer or Winter?",
48 | question__english=True,
49 | premium=True,
50 | author="John",
51 | )
52 | assert str(poll) == "⭐️ Summer or Winter? - By John"
53 |
54 | def test_to_string_premium_and_with_author_on_portuguese(self):
55 | poll = PollFactory.build(
56 | question__question_text="Summer or Winter?",
57 | question__portuguese=True,
58 | premium=True,
59 | author="João",
60 | )
61 | assert str(poll) == "⭐️ Summer or Winter? - Por João"
62 |
63 | def test_was_published_recently_with_old_poll(self):
64 | """
65 | was_published_recently returns False for polls whose pub_date
66 | is older than 1 day.
67 | """
68 | old_poll = PastPollFactory.build()
69 | assert old_poll.was_published_recently is False
70 |
71 | def test_was_published_recently_with_recent_poll(self):
72 | """
73 | was_published_recently returns True for polls whose pub_date
74 | is within the last day.
75 | """
76 | recent_poll = RecentPollFactory.build()
77 | assert recent_poll.was_published_recently is True
78 |
79 | def test_was_published_recently_with_future_poll(self):
80 | """
81 | was_published_recently returns False for polls whose pub_date
82 | is in the future.
83 | """
84 | future_question = FuturePollFactory.build()
85 | assert future_question.was_published_recently is False
86 |
87 |
88 | class TestQuestion:
89 | def test_is_in_english_when_language_is_english(self):
90 | assert QuestionFactory.build(english=True).is_in_english is True
91 |
92 | def test_is_in_english_when_language_is_not_english(self):
93 | assert QuestionFactory.build(portuguese=True).is_in_english is False
94 |
95 | def test_is_in_portuguese_when_language_is_portuguese(self):
96 | assert QuestionFactory.build(portuguese=True).is_in_portuguese is True
97 |
98 | def test_is_in_portuguese_when_language_is_not_portuguese(self):
99 | assert QuestionFactory.build(english=True).is_in_portuguese is False
100 |
--------------------------------------------------------------------------------
/mysite/polls/tests/test_without_factory_boy/test_models.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | from django.utils import timezone
4 |
5 | from polls.models import Poll, Question
6 |
7 |
8 | class TestPoll:
9 | def test_to_string_non_premium_and_without_author(self):
10 | question = Question(question_text="Summer or Winter?")
11 | assert (
12 | str(Poll(question=question, premium=False, author=None))
13 | == "Summer or Winter?"
14 | )
15 |
16 | def test_to_string_non_premium_and_with_author_on_english(self):
17 | question = Question(
18 | question_text="Summer or Winter?", language=Question.EN
19 | )
20 | assert (
21 | str(Poll(question=question, premium=False, author="John"))
22 | == "Summer or Winter? - By John"
23 | )
24 |
25 | def test_to_string_non_premium_and_with_author_on_portuguese(self):
26 | question = Question(
27 | question_text="Summer or Winter?", language=Question.PT_BR
28 | )
29 | assert (
30 | str(Poll(question=question, premium=False, author="João"))
31 | == "Summer or Winter? - Por João"
32 | )
33 |
34 | def test_to_string_premium_and_without_author(self):
35 | question = Question(question_text="Summer or Winter?")
36 | assert (
37 | str(Poll(question=question, premium=True, author=None))
38 | == "⭐️ Summer or Winter?"
39 | )
40 |
41 | def test_to_string_premium_and_with_author_on_english(self):
42 | question = Question(
43 | question_text="Summer or Winter?", language=Question.EN
44 | )
45 | assert (
46 | str(Poll(question=question, premium=True, author="John"))
47 | == "⭐️ Summer or Winter? - By John"
48 | )
49 |
50 | def test_to_string_premium_and_with_author_on_portuguese(self):
51 | question = Question(
52 | question_text="Summer or Winter?", language=Question.PT_BR
53 | )
54 | assert (
55 | str(Poll(question=question, premium=True, author="João"))
56 | == "⭐️ Summer or Winter? - Por João"
57 | )
58 |
59 | def test_was_published_recently_with_old_poll(self):
60 | """
61 | was_published_recently returns False for polls whose pub_date
62 | is older than 1 day.
63 | """
64 | time = timezone.now() - datetime.timedelta(days=1, seconds=1)
65 | old_poll = Poll(pub_date=time)
66 | assert old_poll.was_published_recently is False
67 |
68 | def test_was_published_recently_with_recent_poll(self):
69 | """
70 | was_published_recently returns True for polls whose pub_date
71 | is within the last day.
72 | """
73 | time = timezone.now() - datetime.timedelta(
74 | hours=23, minutes=59, seconds=59
75 | )
76 | recent_poll = Poll(pub_date=time)
77 | assert recent_poll.was_published_recently is True
78 |
79 | def test_was_published_recently_with_future_poll(self):
80 | """
81 | was_published_recently returns False for polls whose pub_date
82 | is in the future.
83 | """
84 | time = timezone.now() + datetime.timedelta(days=30)
85 | future_question = Poll(pub_date=time)
86 | assert future_question.was_published_recently is False
87 |
88 |
89 | class TestQuestion:
90 | def test_is_in_english_when_language_is_english(self):
91 | question = Question(language=Question.EN)
92 | assert question.is_in_english is True
93 |
94 | def test_is_in_english_when_language_is_not_english(self):
95 | question = Question(language=Question.PT_BR)
96 | assert question.is_in_english is False
97 |
98 | def test_is_in_portuguese_when_language_is_portuguese(self):
99 | question = Question(language=Question.PT_BR)
100 | assert question.is_in_portuguese is True
101 |
102 | def test_is_in_portuguese_when_language_is_not_portuguese(self):
103 | question = Question(language=Question.EN)
104 | assert question.is_in_portuguese is False
105 |
--------------------------------------------------------------------------------
/mysite/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "appnope"
3 | version = "0.1.3"
4 | description = "Disable App Nap on macOS >= 10.9"
5 | category = "dev"
6 | optional = false
7 | python-versions = "*"
8 |
9 | [[package]]
10 | name = "asgiref"
11 | version = "3.5.2"
12 | description = "ASGI specs, helper code, and adapters"
13 | category = "main"
14 | optional = false
15 | python-versions = ">=3.7"
16 |
17 | [package.extras]
18 | tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
19 |
20 | [[package]]
21 | name = "asttokens"
22 | version = "2.0.8"
23 | description = "Annotate AST trees with source code positions"
24 | category = "dev"
25 | optional = false
26 | python-versions = "*"
27 |
28 | [package.dependencies]
29 | six = "*"
30 |
31 | [package.extras]
32 | test = ["astroid (<=2.5.3)", "pytest"]
33 |
34 | [[package]]
35 | name = "attrs"
36 | version = "22.1.0"
37 | description = "Classes Without Boilerplate"
38 | category = "dev"
39 | optional = false
40 | python-versions = ">=3.5"
41 |
42 | [package.extras]
43 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
44 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
45 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
46 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"]
47 |
48 | [[package]]
49 | name = "backcall"
50 | version = "0.2.0"
51 | description = "Specifications for callback functions passed in to an API"
52 | category = "dev"
53 | optional = false
54 | python-versions = "*"
55 |
56 | [[package]]
57 | name = "black"
58 | version = "22.8.0"
59 | description = "The uncompromising code formatter."
60 | category = "dev"
61 | optional = false
62 | python-versions = ">=3.6.2"
63 |
64 | [package.dependencies]
65 | click = ">=8.0.0"
66 | mypy-extensions = ">=0.4.3"
67 | pathspec = ">=0.9.0"
68 | platformdirs = ">=2"
69 | tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
70 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
71 |
72 | [package.extras]
73 | colorama = ["colorama (>=0.4.3)"]
74 | d = ["aiohttp (>=3.7.4)"]
75 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
76 | uvloop = ["uvloop (>=0.15.2)"]
77 |
78 | [[package]]
79 | name = "cfgv"
80 | version = "3.3.1"
81 | description = "Validate configuration and produce human readable error messages."
82 | category = "dev"
83 | optional = false
84 | python-versions = ">=3.6.1"
85 |
86 | [[package]]
87 | name = "click"
88 | version = "8.1.3"
89 | description = "Composable command line interface toolkit"
90 | category = "dev"
91 | optional = false
92 | python-versions = ">=3.7"
93 |
94 | [package.dependencies]
95 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
96 |
97 | [[package]]
98 | name = "colorama"
99 | version = "0.4.5"
100 | description = "Cross-platform colored terminal text."
101 | category = "dev"
102 | optional = false
103 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
104 |
105 | [[package]]
106 | name = "decorator"
107 | version = "5.1.1"
108 | description = "Decorators for Humans"
109 | category = "dev"
110 | optional = false
111 | python-versions = ">=3.5"
112 |
113 | [[package]]
114 | name = "distlib"
115 | version = "0.3.6"
116 | description = "Distribution utilities"
117 | category = "dev"
118 | optional = false
119 | python-versions = "*"
120 |
121 | [[package]]
122 | name = "django"
123 | version = "3.2.15"
124 | description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
125 | category = "main"
126 | optional = false
127 | python-versions = ">=3.6"
128 |
129 | [package.dependencies]
130 | asgiref = ">=3.3.2,<4"
131 | pytz = "*"
132 | sqlparse = ">=0.2.2"
133 |
134 | [package.extras]
135 | argon2 = ["argon2-cffi (>=19.1.0)"]
136 | bcrypt = ["bcrypt"]
137 |
138 | [[package]]
139 | name = "executing"
140 | version = "1.0.0"
141 | description = "Get the currently executing AST node of a frame, and other information"
142 | category = "dev"
143 | optional = false
144 | python-versions = "*"
145 |
146 | [[package]]
147 | name = "factory-boy"
148 | version = "3.2.1"
149 | description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby."
150 | category = "dev"
151 | optional = false
152 | python-versions = ">=3.6"
153 |
154 | [package.dependencies]
155 | Faker = ">=0.7.0"
156 |
157 | [package.extras]
158 | dev = ["coverage", "django", "flake8", "isort", "pillow", "sqlalchemy", "mongoengine", "wheel (>=0.32.0)", "tox", "zest.releaser"]
159 | doc = ["sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"]
160 |
161 | [[package]]
162 | name = "faker"
163 | version = "14.2.0"
164 | description = "Faker is a Python package that generates fake data for you."
165 | category = "dev"
166 | optional = false
167 | python-versions = ">=3.6"
168 |
169 | [package.dependencies]
170 | python-dateutil = ">=2.4"
171 |
172 | [[package]]
173 | name = "filelock"
174 | version = "3.8.0"
175 | description = "A platform independent file lock."
176 | category = "dev"
177 | optional = false
178 | python-versions = ">=3.7"
179 |
180 | [package.extras]
181 | docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
182 | testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
183 |
184 | [[package]]
185 | name = "identify"
186 | version = "2.5.3"
187 | description = "File identification library for Python"
188 | category = "dev"
189 | optional = false
190 | python-versions = ">=3.7"
191 |
192 | [package.extras]
193 | license = ["ukkonen"]
194 |
195 | [[package]]
196 | name = "iniconfig"
197 | version = "1.1.1"
198 | description = "iniconfig: brain-dead simple config-ini parsing"
199 | category = "dev"
200 | optional = false
201 | python-versions = "*"
202 |
203 | [[package]]
204 | name = "ipdb"
205 | version = "0.13.9"
206 | description = "IPython-enabled pdb"
207 | category = "dev"
208 | optional = false
209 | python-versions = ">=2.7"
210 |
211 | [package.dependencies]
212 | decorator = {version = "*", markers = "python_version > \"3.6\""}
213 | ipython = {version = ">=7.17.0", markers = "python_version > \"3.6\""}
214 | toml = {version = ">=0.10.2", markers = "python_version > \"3.6\""}
215 |
216 | [[package]]
217 | name = "ipython"
218 | version = "8.4.0"
219 | description = "IPython: Productive Interactive Computing"
220 | category = "dev"
221 | optional = false
222 | python-versions = ">=3.8"
223 |
224 | [package.dependencies]
225 | appnope = {version = "*", markers = "sys_platform == \"darwin\""}
226 | backcall = "*"
227 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
228 | decorator = "*"
229 | jedi = ">=0.16"
230 | matplotlib-inline = "*"
231 | pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
232 | pickleshare = "*"
233 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
234 | pygments = ">=2.4.0"
235 | stack-data = "*"
236 | traitlets = ">=5"
237 |
238 | [package.extras]
239 | all = ["black", "Sphinx (>=1.3)", "ipykernel", "nbconvert", "nbformat", "ipywidgets", "notebook", "ipyparallel", "qtconsole", "pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "numpy (>=1.19)", "pandas", "trio"]
240 | black = ["black"]
241 | doc = ["Sphinx (>=1.3)"]
242 | kernel = ["ipykernel"]
243 | nbconvert = ["nbconvert"]
244 | nbformat = ["nbformat"]
245 | notebook = ["ipywidgets", "notebook"]
246 | parallel = ["ipyparallel"]
247 | qtconsole = ["qtconsole"]
248 | test = ["pytest (<7.1)", "pytest-asyncio", "testpath"]
249 | test_extra = ["pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "trio"]
250 |
251 | [[package]]
252 | name = "jedi"
253 | version = "0.18.1"
254 | description = "An autocompletion tool for Python that can be used for text editors."
255 | category = "dev"
256 | optional = false
257 | python-versions = ">=3.6"
258 |
259 | [package.dependencies]
260 | parso = ">=0.8.0,<0.9.0"
261 |
262 | [package.extras]
263 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
264 | testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"]
265 |
266 | [[package]]
267 | name = "matplotlib-inline"
268 | version = "0.1.6"
269 | description = "Inline Matplotlib backend for Jupyter"
270 | category = "dev"
271 | optional = false
272 | python-versions = ">=3.5"
273 |
274 | [package.dependencies]
275 | traitlets = "*"
276 |
277 | [[package]]
278 | name = "mypy-extensions"
279 | version = "0.4.3"
280 | description = "Experimental type system extensions for programs checked with the mypy typechecker."
281 | category = "dev"
282 | optional = false
283 | python-versions = "*"
284 |
285 | [[package]]
286 | name = "nodeenv"
287 | version = "1.7.0"
288 | description = "Node.js virtual environment builder"
289 | category = "dev"
290 | optional = false
291 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
292 |
293 | [[package]]
294 | name = "packaging"
295 | version = "21.3"
296 | description = "Core utilities for Python packages"
297 | category = "dev"
298 | optional = false
299 | python-versions = ">=3.6"
300 |
301 | [package.dependencies]
302 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
303 |
304 | [[package]]
305 | name = "parso"
306 | version = "0.8.3"
307 | description = "A Python Parser"
308 | category = "dev"
309 | optional = false
310 | python-versions = ">=3.6"
311 |
312 | [package.extras]
313 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
314 | testing = ["docopt", "pytest (<6.0.0)"]
315 |
316 | [[package]]
317 | name = "pathspec"
318 | version = "0.10.1"
319 | description = "Utility library for gitignore style pattern matching of file paths."
320 | category = "dev"
321 | optional = false
322 | python-versions = ">=3.7"
323 |
324 | [[package]]
325 | name = "pexpect"
326 | version = "4.8.0"
327 | description = "Pexpect allows easy control of interactive console applications."
328 | category = "dev"
329 | optional = false
330 | python-versions = "*"
331 |
332 | [package.dependencies]
333 | ptyprocess = ">=0.5"
334 |
335 | [[package]]
336 | name = "pickleshare"
337 | version = "0.7.5"
338 | description = "Tiny 'shelve'-like database with concurrency support"
339 | category = "dev"
340 | optional = false
341 | python-versions = "*"
342 |
343 | [[package]]
344 | name = "platformdirs"
345 | version = "2.5.2"
346 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
347 | category = "dev"
348 | optional = false
349 | python-versions = ">=3.7"
350 |
351 | [package.extras]
352 | docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
353 | test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
354 |
355 | [[package]]
356 | name = "pluggy"
357 | version = "1.0.0"
358 | description = "plugin and hook calling mechanisms for python"
359 | category = "dev"
360 | optional = false
361 | python-versions = ">=3.6"
362 |
363 | [package.extras]
364 | testing = ["pytest-benchmark", "pytest"]
365 | dev = ["tox", "pre-commit"]
366 |
367 | [[package]]
368 | name = "pre-commit"
369 | version = "2.20.0"
370 | description = "A framework for managing and maintaining multi-language pre-commit hooks."
371 | category = "dev"
372 | optional = false
373 | python-versions = ">=3.7"
374 |
375 | [package.dependencies]
376 | cfgv = ">=2.0.0"
377 | identify = ">=1.0.0"
378 | nodeenv = ">=0.11.1"
379 | pyyaml = ">=5.1"
380 | toml = "*"
381 | virtualenv = ">=20.0.8"
382 |
383 | [[package]]
384 | name = "prompt-toolkit"
385 | version = "3.0.31"
386 | description = "Library for building powerful interactive command lines in Python"
387 | category = "dev"
388 | optional = false
389 | python-versions = ">=3.6.2"
390 |
391 | [package.dependencies]
392 | wcwidth = "*"
393 |
394 | [[package]]
395 | name = "ptyprocess"
396 | version = "0.7.0"
397 | description = "Run a subprocess in a pseudo terminal"
398 | category = "dev"
399 | optional = false
400 | python-versions = "*"
401 |
402 | [[package]]
403 | name = "pure-eval"
404 | version = "0.2.2"
405 | description = "Safely evaluate AST nodes without side effects"
406 | category = "dev"
407 | optional = false
408 | python-versions = "*"
409 |
410 | [package.extras]
411 | tests = ["pytest"]
412 |
413 | [[package]]
414 | name = "py"
415 | version = "1.11.0"
416 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
417 | category = "dev"
418 | optional = false
419 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
420 |
421 | [[package]]
422 | name = "pygments"
423 | version = "2.13.0"
424 | description = "Pygments is a syntax highlighting package written in Python."
425 | category = "dev"
426 | optional = false
427 | python-versions = ">=3.6"
428 |
429 | [package.extras]
430 | plugins = ["importlib-metadata"]
431 |
432 | [[package]]
433 | name = "pyparsing"
434 | version = "3.0.9"
435 | description = "pyparsing module - Classes and methods to define and execute parsing grammars"
436 | category = "dev"
437 | optional = false
438 | python-versions = ">=3.6.8"
439 |
440 | [package.extras]
441 | diagrams = ["railroad-diagrams", "jinja2"]
442 |
443 | [[package]]
444 | name = "pytest"
445 | version = "7.1.3"
446 | description = "pytest: simple powerful testing with Python"
447 | category = "dev"
448 | optional = false
449 | python-versions = ">=3.7"
450 |
451 | [package.dependencies]
452 | attrs = ">=19.2.0"
453 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
454 | iniconfig = "*"
455 | packaging = "*"
456 | pluggy = ">=0.12,<2.0"
457 | py = ">=1.8.2"
458 | tomli = ">=1.0.0"
459 |
460 | [package.extras]
461 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
462 |
463 | [[package]]
464 | name = "pytest-django"
465 | version = "4.5.2"
466 | description = "A Django plugin for pytest."
467 | category = "dev"
468 | optional = false
469 | python-versions = ">=3.5"
470 |
471 | [package.dependencies]
472 | pytest = ">=5.4.0"
473 |
474 | [package.extras]
475 | docs = ["sphinx", "sphinx-rtd-theme"]
476 | testing = ["django", "django-configurations (>=2.0)"]
477 |
478 | [[package]]
479 | name = "python-dateutil"
480 | version = "2.8.2"
481 | description = "Extensions to the standard Python datetime module"
482 | category = "dev"
483 | optional = false
484 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
485 |
486 | [package.dependencies]
487 | six = ">=1.5"
488 |
489 | [[package]]
490 | name = "pytz"
491 | version = "2022.2.1"
492 | description = "World timezone definitions, modern and historical"
493 | category = "main"
494 | optional = false
495 | python-versions = "*"
496 |
497 | [[package]]
498 | name = "pyyaml"
499 | version = "6.0"
500 | description = "YAML parser and emitter for Python"
501 | category = "dev"
502 | optional = false
503 | python-versions = ">=3.6"
504 |
505 | [[package]]
506 | name = "six"
507 | version = "1.16.0"
508 | description = "Python 2 and 3 compatibility utilities"
509 | category = "dev"
510 | optional = false
511 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
512 |
513 | [[package]]
514 | name = "sqlparse"
515 | version = "0.4.2"
516 | description = "A non-validating SQL parser."
517 | category = "main"
518 | optional = false
519 | python-versions = ">=3.5"
520 |
521 | [[package]]
522 | name = "stack-data"
523 | version = "0.5.0"
524 | description = "Extract data from python stack frames and tracebacks for informative displays"
525 | category = "dev"
526 | optional = false
527 | python-versions = "*"
528 |
529 | [package.dependencies]
530 | asttokens = "*"
531 | executing = "*"
532 | pure-eval = "*"
533 |
534 | [package.extras]
535 | tests = ["cython", "littleutils", "pygments", "typeguard", "pytest"]
536 |
537 | [[package]]
538 | name = "toml"
539 | version = "0.10.2"
540 | description = "Python Library for Tom's Obvious, Minimal Language"
541 | category = "dev"
542 | optional = false
543 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
544 |
545 | [[package]]
546 | name = "tomli"
547 | version = "2.0.1"
548 | description = "A lil' TOML parser"
549 | category = "dev"
550 | optional = false
551 | python-versions = ">=3.7"
552 |
553 | [[package]]
554 | name = "traitlets"
555 | version = "5.3.0"
556 | description = ""
557 | category = "dev"
558 | optional = false
559 | python-versions = ">=3.7"
560 |
561 | [package.extras]
562 | test = ["pre-commit", "pytest"]
563 |
564 | [[package]]
565 | name = "typing-extensions"
566 | version = "4.3.0"
567 | description = "Backported and Experimental Type Hints for Python 3.7+"
568 | category = "dev"
569 | optional = false
570 | python-versions = ">=3.7"
571 |
572 | [[package]]
573 | name = "virtualenv"
574 | version = "20.16.4"
575 | description = "Virtual Python Environment builder"
576 | category = "dev"
577 | optional = false
578 | python-versions = ">=3.6"
579 |
580 | [package.dependencies]
581 | distlib = ">=0.3.5,<1"
582 | filelock = ">=3.4.1,<4"
583 | platformdirs = ">=2.4,<3"
584 |
585 | [package.extras]
586 | docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
587 | testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
588 |
589 | [[package]]
590 | name = "wcwidth"
591 | version = "0.2.5"
592 | description = "Measures the displayed width of unicode strings in a terminal"
593 | category = "dev"
594 | optional = false
595 | python-versions = "*"
596 |
597 | [metadata]
598 | lock-version = "1.1"
599 | python-versions = "^3.9"
600 | content-hash = "04095c23876db6300ca8bd60bdcb507b0c7c5590aa5f032692cb68da16e11f49"
601 |
602 | [metadata.files]
603 | appnope = []
604 | asgiref = []
605 | asttokens = []
606 | attrs = []
607 | backcall = []
608 | black = []
609 | cfgv = []
610 | click = []
611 | colorama = []
612 | decorator = []
613 | distlib = []
614 | django = []
615 | executing = []
616 | factory-boy = []
617 | faker = []
618 | filelock = []
619 | identify = []
620 | iniconfig = [
621 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
622 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
623 | ]
624 | ipdb = []
625 | ipython = []
626 | jedi = []
627 | matplotlib-inline = []
628 | mypy-extensions = [
629 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
630 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
631 | ]
632 | nodeenv = []
633 | packaging = []
634 | parso = []
635 | pathspec = []
636 | pexpect = []
637 | pickleshare = []
638 | platformdirs = []
639 | pluggy = []
640 | pre-commit = []
641 | prompt-toolkit = []
642 | ptyprocess = []
643 | pure-eval = []
644 | py = []
645 | pygments = []
646 | pyparsing = []
647 | pytest = []
648 | pytest-django = []
649 | python-dateutil = [
650 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
651 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
652 | ]
653 | pytz = []
654 | pyyaml = []
655 | six = [
656 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
657 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
658 | ]
659 | sqlparse = []
660 | stack-data = []
661 | toml = [
662 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
663 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
664 | ]
665 | tomli = []
666 | traitlets = []
667 | typing-extensions = []
668 | virtualenv = []
669 | wcwidth = []
670 |
--------------------------------------------------------------------------------