From 8656fc7903cf453b91264e7d188a114016e2244d Mon Sep 17 00:00:00 2001 From: Dmitriy Shesterkin Date: Mon, 26 Jun 2017 15:40:26 +0300 Subject: [PATCH] refactor project, add task delete not activated users --- Makefile | 34 ++++++------ README.md | 17 +++--- conf/docker/entrypoint.sh | 6 +-- conf/docker/entrypoint_stage.sh | 9 ++-- conf/env.local | 2 +- conf/env.stage | 2 +- conf/env.template | 2 +- conf/supervisor.conf | 10 ++-- src/manage.py => manage.py | 4 +- pytest.ini | 19 +++++++ requirements/base.txt | 9 ++++ src/customer/tasks.py | 7 ++- src/dokumentor/settings/__init__.py | 1 - src/dokumentor/settings/local.py | 2 +- src/dokumentor/settings/production.py | 2 +- src/dokumentor/settings/stage.py | 2 +- src/dokumentor/settings/testing.py | 26 +++++++++ src/factories/__init__.py | 0 src/factories/models.py | 76 +++++++++++++++++++++++++++ src/tests/conftest.py | 4 ++ src/tests/fixtures/models.py | 13 +++++ src/tests/test_tasks.py | 61 +++++++++++++++++++++ tox.ini | 4 -- 23 files changed, 261 insertions(+), 51 deletions(-) rename src/manage.py => manage.py (89%) create mode 100644 pytest.ini create mode 100644 src/dokumentor/settings/testing.py create mode 100644 src/factories/__init__.py create mode 100755 src/factories/models.py create mode 100644 src/tests/conftest.py create mode 100644 src/tests/fixtures/models.py create mode 100644 src/tests/test_tasks.py delete mode 100755 tox.ini diff --git a/Makefile b/Makefile index 8949670..1f1c4a8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all help build virtualenv requirements-local loaddata run migrate shell collectstatic clean worker +.PHONY: all help build virtualenv requirements-local loaddata run migrate shell collectstatic clean worker qa # target: all - Default target. Does nothing. all: @@ -25,31 +25,31 @@ requirements-local: # target: loaddata - Load fixtures loaddata: - python src/manage.py loaddata src/myauth/fixtures/myauth.json - python src/manage.py loaddata src/commons/fixtures/cms.json - python src/manage.py loaddata src/commons/fixtures/djangocms_text_ckeditor.json - python src/manage.py loaddata src/commons/fixtures/sites.json - python src/manage.py loaddata src/customer/fixtures/price.json - python src/manage.py loaddata src/docs/fixtures/country.json - python src/manage.py loaddata src/docs/fixtures/currency.json - python src/manage.py loaddata src/docs/fixtures/measure.json + python manage.py loaddata src/myauth/fixtures/myauth.json + python manage.py loaddata src/commons/fixtures/cms.json + python manage.py loaddata src/commons/fixtures/djangocms_text_ckeditor.json + python manage.py loaddata src/commons/fixtures/sites.json + python manage.py loaddata src/customer/fixtures/price.json + python manage.py loaddata src/docs/fixtures/country.json + python manage.py loaddata src/docs/fixtures/currency.json + python manage.py loaddata src/docs/fixtures/measure.json # target: run - Runserver run: - python src/manage.py runserver 0.0.0.0:8000 + python manage.py runserver 0.0.0.0:8000 # target: migrate - Build all docker containers, defined in docker-compose.stage.yml migrate: - python src/manage.py migrate myauth --noinput - python src/manage.py migrate --noinput + python manage.py migrate myauth --noinput + python manage.py migrate --noinput # target: shell - Run python shell shell: - python src/manage.py shell + python manage.py shell # target: collectstatic - Run collectstatic collectstatic: - python src/manage.py collectstatic --noinput + python manage.py collectstatic --noinput clean_temp: @@ -68,4 +68,8 @@ clean: clean_temp clean_venv clean_db # target: worker - Run celery worker worker: - cd src && celery -A dokumentor worker -l info -E -B + celery -A src.dokumentor worker -l info -E -B + +# target: qa - Run pytest +qa: + pytest diff --git a/README.md b/README.md index 503b380..aea27c4 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ git checkout develop ```bash virtualenv --python=python3 env source env/bin/activate -pip install -r requirements.txt +pip install -r requirements/local.txt ``` ### Настройка окружения @@ -36,23 +36,26 @@ cp conf/env.template conf/env Накатываем миграции и заполняем базу данных ```bash -bin/migrate.sh +make migrate ``` Загружаем фикстуры ```bash -bin/loaddata.sh +make loaddata ``` -### Установка проекта для разработки командой `make` +### Установка проекта для разработки Для разработки проект можно развернуть выполив ```bash -make make run ``` ###Запуск сервера Celery ```bash -cd src -celery -A dokumentor worker -l info -E +make worker +``` + +###Запуск Тестов +```bash +make qa ``` diff --git a/conf/docker/entrypoint.sh b/conf/docker/entrypoint.sh index bb5612f..e2803d9 100755 --- a/conf/docker/entrypoint.sh +++ b/conf/docker/entrypoint.sh @@ -2,12 +2,12 @@ # Make database migrations echo "Make database migrations" -python src/manage.py migrate makemigrations +python manage.py migrate makemigrations # Apply database migrations echo "Apply database migrations" -python src/manage.py migrate myauth --noinput -python src/manage.py migrate --noinput +python manage.py migrate myauth --noinput +python manage.py migrate --noinput cd /opt/app diff --git a/conf/docker/entrypoint_stage.sh b/conf/docker/entrypoint_stage.sh index 89eb5b1..f355593 100755 --- a/conf/docker/entrypoint_stage.sh +++ b/conf/docker/entrypoint_stage.sh @@ -11,16 +11,15 @@ bower install --allow-root # Collect static files echo "Collect static files" -python src/manage.py collectstatic --noinput +python manage.py collectstatic --noinput # Make database migrations echo "Make database migrations" -python src/manage.py makemigrations +python manage.py makemigrations # Apply database migrations echo "Apply database migrations" -python src/manage.py migrate myauth --noinput -python src/manage.py migrate --noinput +python manage.py migrate myauth --noinput +python manage.py migrate --noinput supervisord -c /opt/app/conf/supervisor.conf - diff --git a/conf/env.local b/conf/env.local index 34b7fc2..7ea75c6 100644 --- a/conf/env.local +++ b/conf/env.local @@ -1,4 +1,4 @@ -DJANGO_SETTINGS_MODULE='dokumentor.settings.local' +DJANGO_SETTINGS_MODULE='src.dokumentor.settings.local' DJANGO_SECRET='CHANGE_ME_IN_PRODUCTION' diff --git a/conf/env.stage b/conf/env.stage index baab671..9f28abc 100644 --- a/conf/env.stage +++ b/conf/env.stage @@ -1,4 +1,4 @@ -DJANGO_SETTINGS_MODULE=dokumentor.settings.stage +DJANGO_SETTINGS_MODULE=src.dokumentor.settings.stage SSL=False diff --git a/conf/env.template b/conf/env.template index 41b9593..3157f72 100644 --- a/conf/env.template +++ b/conf/env.template @@ -1,4 +1,4 @@ -DJANGO_SETTINGS_MODULE=dokumentor.settings.{{env}} +DJANGO_SETTINGS_MODULE=src.dokumentor.settings.{{env}} DJANGO_SECRET='CHANGE_ME_IN_PRODUCTION' diff --git a/conf/supervisor.conf b/conf/supervisor.conf index 1ee2d8c..f44a56d 100644 --- a/conf/supervisor.conf +++ b/conf/supervisor.conf @@ -17,8 +17,8 @@ stdout_events_enabled=true stderr_events_enabled=true [program:gunicorn] -command=gunicorn dokumentor.wsgi:application -c /opt/app/conf/gunicorn_prod.py -directory=/opt/app/src +command=gunicorn src.dokumentor.wsgi:application -c /opt/app/conf/gunicorn_prod.py +directory=/opt/app priority=2 stdout_logfile=/var/log/gunicorn.log redirect_stderr=true @@ -27,10 +27,10 @@ stderr_events_enabled=true autorestart=true [program:celeryd] -command=celery -A dokumentor worker -l info -E -B -directory=/opt/app/src +command=celery -A src.dokumentor worker -l info -E -B +directory=/opt/app stdout_logfile=/var/log/celery-worker.log -stderr_logfile=/var/log/celery-worker.log +stderr_logfile=/var/log/celery-worker-error.log autostart=true autorestart=true startsecs=10 diff --git a/src/manage.py b/manage.py similarity index 89% rename from src/manage.py rename to manage.py index 14a5ba5..4fc15af 100755 --- a/src/manage.py +++ b/manage.py @@ -6,9 +6,11 @@ import sys import envvars as env if __name__ == "__main__": - env_file = p.normpath(p.join(p.abspath(p.dirname(__file__)), "../conf/env")) + env_file = p.normpath(p.join(p.abspath(p.dirname(__file__)), "./conf/env")) env.load(env_file) + print(env_file) + from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..cc7e682 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,19 @@ +[pytest] +DJANGO_SETTINGS_MODULE = src.dokumentor.settings.testing +norecursedirs = env/* docs/* misc/* static/* mysql/* + + +addopts = --flake8 -vv -s + +python_files = + test_*.py + +flake8-max-line-length = 99 + +# E731 - do not assign a lambda expression, use a def +# F405 - name may be undefined, or defined from star imports: module +flake8-ignore = + *.py E731 F405 + **/conf/** ALL + **/migrations/** ALL + **/templates/** ALL diff --git a/requirements/base.txt b/requirements/base.txt index 03dd5b1..02e86c9 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -73,3 +73,12 @@ trans==2.1.0 python-decouple==3.0 flake8==3.2.1 numpy==1.13.0 +mock==2.0.0 +mockredispy==2.9.3 +pytest-django==3.1.2 +pytest-sugar==0.8.0 +factory-boy==2.8.1 +django-test-plus==1.0.17 +Faker==0.7.15 +coverage==4.4.1 +pytest-flake8==0.8.1 diff --git a/src/customer/tasks.py b/src/customer/tasks.py index cf885f1..d2bf776 100644 --- a/src/customer/tasks.py +++ b/src/customer/tasks.py @@ -3,11 +3,11 @@ from __future__ import absolute_import import traceback +from django.utils import timezone from datetime import datetime, timedelta from celery import shared_task -from django.utils import timezone from django.core.mail import mail_admins from myauth.models import DokUser, ConfirmEmail @@ -43,13 +43,12 @@ def check_license(): @shared_task def delete_not_activated_users(): - now = timezone.now() users = DokUser.objects.filter(profile__active=False, profile__confirmed=False).\ - filter(profile__created_at__lte=now - timedelta(5)) + filter(profile__created_at__lte=timezone.now() - timezone.timedelta(days=5)) if users: for user in users: profile = user.profile - confirm = ConfirmEmail.objects.filter(user=user, is_confirmed=False) + confirm = ConfirmEmail.objects.filter(user=user, is_confirmed=False).first() if profile: profile.delete() if confirm: diff --git a/src/dokumentor/settings/__init__.py b/src/dokumentor/settings/__init__.py index 7c68785..e69de29 100644 --- a/src/dokumentor/settings/__init__.py +++ b/src/dokumentor/settings/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/src/dokumentor/settings/local.py b/src/dokumentor/settings/local.py index 44a4fd3..494bcbb 100644 --- a/src/dokumentor/settings/local.py +++ b/src/dokumentor/settings/local.py @@ -2,7 +2,7 @@ import dj_database_url -from dokumentor.settings.common import * +from src.dokumentor.settings.common import * # noqa def custom_show_toolbar(self): diff --git a/src/dokumentor/settings/production.py b/src/dokumentor/settings/production.py index 493f192..aac19d3 100644 --- a/src/dokumentor/settings/production.py +++ b/src/dokumentor/settings/production.py @@ -2,7 +2,7 @@ import dj_database_url -from dokumentor.settings.common import * +from src.dokumentor.settings.common import * # noqa DEBUG = False TEMPLATES[0]['OPTIONS']['debug'] = DEBUG diff --git a/src/dokumentor/settings/stage.py b/src/dokumentor/settings/stage.py index 95d6964..c67c516 100644 --- a/src/dokumentor/settings/stage.py +++ b/src/dokumentor/settings/stage.py @@ -2,7 +2,7 @@ import dj_database_url -from dokumentor.settings.common import * +from src.dokumentor.settings.common import * # noqa DEBUG = False TEMPLATES[0]['OPTIONS']['debug'] = DEBUG diff --git a/src/dokumentor/settings/testing.py b/src/dokumentor/settings/testing.py new file mode 100644 index 0000000..17d4c3c --- /dev/null +++ b/src/dokumentor/settings/testing.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +import logging +import dj_database_url + +from src.dokumentor.settings.common import * # noqa + +DEBUG = True +TEMPLATES[0]['OPTIONS']['debug'] = DEBUG # noqa + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +PASSWORD_HASHERS = ('django.contrib.auth.hashers.MD5PasswordHasher', ) + +DATABASES = { + 'default': dj_database_url.parse(e.get('TEST_DB')), +} + + +CELERY_TASK_ALWAYS_EAGER = True + +# Disable cache during testing +CACHE_DISABLED = True + +LOGGING = {} +LOCAL_APPS_LOGGERS = {} +# Disable all logging calls with levels less severe than or equal to CRITICAL +logging.disable(logging.CRITICAL) diff --git a/src/factories/__init__.py b/src/factories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/factories/models.py b/src/factories/models.py new file mode 100755 index 0000000..65cf958 --- /dev/null +++ b/src/factories/models.py @@ -0,0 +1,76 @@ +import pytz + +import factory +import factory.fuzzy + +from django.contrib.auth import get_user_model + +from myauth.models import ConfirmEmail +from customer.models import UserProfile +from functools import partial + +USER_PASSWORD = 'test' + +Faker = partial(factory.Faker, locale='ru_RU') + + +class ProfileFactory(factory.django.DjangoModelFactory): + profile_type = factory.Iterator([1, 2]) + boss_surname = Faker('last_name') + boss_name = Faker('first_name') + boss_midname = Faker('middle_name') + inn = factory.Faker('isbn10') + ogrn = factory.Faker('isbn10') + okpo = factory.Faker('isbn10') + glavbuh_surname = Faker('last_name') + glavbuh_name = Faker('first_name') + glavbuh_midname = Faker('middle_name') + address = Faker('address') + jur_address = Faker('address') + real_address = Faker('address') + phone = Faker('phone_number') + fax = Faker('phone_number') + email = Faker('email') + site = Faker('url') + svid_gos_reg = factory.Faker('isbn10') + ip_reg_date = factory.Faker('date') + name = Faker('company') + full_name = f'{Faker("company_prefix")} {Faker("company")}' + kpp = factory.Faker('ean8') + boss_title = Faker('job') + na_osnovanii = factory.fuzzy.FuzzyChoice(['устава', 'положения']) + active = True + confirmed = True + user_session_key = Faker('sha1') + + class Meta: + model = UserProfile + + +class UserFactory(factory.django.DjangoModelFactory): + + first_name = Faker('first_name') + last_name = Faker('last_name') + username = Faker('user_name') + email = Faker('email') + password = factory.PostGenerationMethodCall('set_password', USER_PASSWORD) + is_active = True + is_staff = False + date_joined = Faker( + 'past_datetime', + start_date='-30d', + tzinfo=pytz.UTC + ) + profile = factory.SubFactory(ProfileFactory) + + class Meta: + model = get_user_model() + django_get_or_create = ('username',) + + +class ConfirmEmailFactory(factory.django.DjangoModelFactory): + user = factory.SubFactory(UserFactory) + is_confirmed = True + + class Meta: + model = ConfirmEmail diff --git a/src/tests/conftest.py b/src/tests/conftest.py new file mode 100644 index 0000000..9fbd962 --- /dev/null +++ b/src/tests/conftest.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +pytest_plugins = [ + 'src.tests.fixtures.models' +] diff --git a/src/tests/fixtures/models.py b/src/tests/fixtures/models.py new file mode 100644 index 0000000..ae52297 --- /dev/null +++ b/src/tests/fixtures/models.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +import pytest +from factories.models import UserFactory, ConfirmEmail + + +@pytest.fixture +def user(): + return UserFactory() + + +@pytest.fixture +def confirm_email(): + return ConfirmEmail() diff --git a/src/tests/test_tasks.py b/src/tests/test_tasks.py new file mode 100644 index 0000000..8be26ac --- /dev/null +++ b/src/tests/test_tasks.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +import pytest +from django.utils import timezone +from myauth.models import DokUser, ConfirmEmail + +from customer.models import UserProfile +from customer.tasks import delete_not_activated_users + + +@pytest.mark.django_db +def test_delete_not_activated_users_more_than_five_day(user): + user.is_active = False + profile = user.profile + profile.active = False + profile.confirmed = False + profile.created_at = timezone.now() - timezone.timedelta(days=15) + profile.save() + user.save() + ConfirmEmail.objects.get_or_create(user=user) + + delete_not_activated_users() + + assert DokUser.objects.count() == 0 + assert UserProfile.objects.count() == 0 + assert ConfirmEmail.objects.count() == 0 + + +@pytest.mark.django_db +def test_delete_not_activated_users_equal_five_day(user): + user.is_active = False + profile = user.profile + profile.active = False + profile.confirmed = False + profile.created_at = timezone.now() - timezone.timedelta(days=5) + profile.save() + user.save() + ConfirmEmail.objects.get_or_create(user=user) + + delete_not_activated_users() + + assert DokUser.objects.count() == 0 + assert UserProfile.objects.count() == 0 + assert ConfirmEmail.objects.count() == 0 + + +@pytest.mark.django_db +def test_delete_not_activated_users_less_five_day(user): + user.is_active = False + profile = user.profile + profile.active = False + profile.confirmed = False + profile.created_at = timezone.now() - timezone.timedelta(days=4) + profile.save() + user.save() + ConfirmEmail.objects.get_or_create(user=user) + + delete_not_activated_users() + + assert DokUser.objects.count() == 1 + assert UserProfile.objects.count() == 1 + assert ConfirmEmail.objects.count() == 1 diff --git a/tox.ini b/tox.ini deleted file mode 100755 index f2946b3..0000000 --- a/tox.ini +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -ignore = F403 -max-line-length = 120 -exclude = ./venv/*,*/migrations/*, ./node_modules/* \ No newline at end of file