diff --git a/conftest.py b/conftest.py index d64f0b52..0d71e85c 100644 --- a/conftest.py +++ b/conftest.py @@ -17,8 +17,8 @@ def client_with_user(client, logged_user): return client -_all_roles = set('data_scientist lead client webdev bootcamper member'.split()) -_advanced_roles = {'member'} +_all_roles = set('data_scientist lead client webdev bootcamper pythonista member'.split()) +_advanced_roles = set('member pythonista'.split()) _level_one_roles = set('client webdev bootcamper member'.split()) _level_two_roles = set('webdev bootcamper member'.split()) _level_three_roles = set('bootcamper member'.split()) diff --git a/pythonpro/core/admin.py b/pythonpro/core/admin.py index 742e144c..6f2aa792 100644 --- a/pythonpro/core/admin.py +++ b/pythonpro/core/admin.py @@ -53,7 +53,7 @@ class UserAdmin(RolePermissionsUserAdminMixin, admin.ModelAdmin): search_fields = ('first_name', 'email') ordering = ('first_name',) filter_horizontal = ('groups', 'user_permissions',) - actions = ['make_bootcamper', 'make_webdev', 'make_member', 'make_data_scientist'] + actions = ['make_bootcamper', 'make_webdev', 'make_member', 'make_data_scientist', 'make_pythonista'] def make_webdev(self, request, queryset): from pythonpro.domain import user_facade @@ -71,6 +71,14 @@ def make_bootcamper(self, request, queryset): except UserRoleException: pass # No need to handle on admin + def make_pythonista(self, request, queryset): + from pythonpro.domain import user_facade + for user in queryset: + try: + user_facade.promote_pythonista(user, 'django_admin') + except UserRoleException: + pass # No need to handle on admin + def make_data_scientist(self, request, queryset): from pythonpro.domain import user_facade for user in queryset: diff --git a/pythonpro/core/facade.py b/pythonpro/core/facade.py index 2082a546..2f899211 100644 --- a/pythonpro/core/facade.py +++ b/pythonpro/core/facade.py @@ -141,6 +141,11 @@ def promote_to_data_scientist(user, source): assign_role(user, 'data_scientist') +def promote_to_pythonista(user, source): + UserInteraction(category=UserInteraction.BECOME_PYTHONISTA, source=source, user=user).save() + assign_role(user, 'pythonista') + + def visit_launch_landing_page(user: User, source: str): return UserInteraction(category=UserInteraction.LAUNCH_LP, source=source, user=user).save() diff --git a/pythonpro/core/migrations/0016_become_pythonista.py b/pythonpro/core/migrations/0016_become_pythonista.py new file mode 100644 index 00000000..fd72ccb6 --- /dev/null +++ b/pythonpro/core/migrations/0016_become_pythonista.py @@ -0,0 +1,34 @@ +# Generated by Django 3.0.8 on 2020-07-23 01:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('core', '0015_become_bootcamper'), + ] + + operations = [ + migrations.AlterField( + model_name='userinteraction', + name='category', + field=models.CharField( + choices=[('BECOME_LEAD', 'User become Lead'), ('ACTIVATED', 'User Watched first video class'), + ('CLIENT_LP', 'User visited Client Landing Page'), + ('CLIENT_CHECKOUT', 'User clicked on Client checkout button'), + ('CLIENT_CHECKOUT_FORM', 'User Filled Client Checkout form'), + ('CLIENT_BOLETO', 'User generated a Client Boleto'), ('BECOME_CLIENT', 'User become Client'), + ('MEMBER_LP', 'User visited Member Landing Page'), + ('MEMBER_CHECKOUT', 'User clicked on Member checkout Button'), + ('MEMBER_CHECKOUT_FORM', 'User Filled Member Checkout form'), + ('WEBDEV_CHECKOUT_FORM', 'User Filled Webdev Checkout form'), + ('MEMBER_BOLETO', 'User generate Member Boleto'), + ('WAITING_LIST', 'User subscribed to Waiting List'), ('BECOME_MEMBER', 'User Become Member'), + ('BECOME_BOOTCAMPER', 'User Become Bootcamper'), + ('BECOME_PYTHONISTA', 'User Become Pythonista'), ('BECOME_WEBDEV', 'User Become Webdev'), + ('LAUNCH_LP', 'User visited Launch Landing Page'), + ('LAUNCH_SUBSCRIPTION', 'User subscribed to launch'), ('CPL1', 'User Visited CPL1'), + ('CPL2', 'User Visited CPL2'), ('CPL3', 'User Visited CPL3'), + ('BECOME_DATA_SCIENTIST', 'User Become Data Scientist')], max_length=32), + ), + ] diff --git a/pythonpro/core/models.py b/pythonpro/core/models.py index 1cf559ea..e3fce2d2 100644 --- a/pythonpro/core/models.py +++ b/pythonpro/core/models.py @@ -92,6 +92,7 @@ class Meta: WAITING_LIST = 'WAITING_LIST' BECOME_MEMBER = 'BECOME_MEMBER' BECOME_BOOTCAMPER = 'BECOME_BOOTCAMPER' + BECOME_PYTHONISTA = 'BECOME_PYTHONISTA' WEBDEV_CHECKOUT_FORM = 'WEBDEV_CHECKOUT_FORM' BECOME_WEBDEV = 'BECOME_WEBDEV' LAUNCH_LP = 'LAUNCH_LP' @@ -121,6 +122,7 @@ class Meta: (WAITING_LIST, 'User subscribed to Waiting List'), (BECOME_MEMBER, 'User Become Member'), (BECOME_BOOTCAMPER, 'User Become Bootcamper'), + (BECOME_PYTHONISTA, 'User Become Pythonista'), (BECOME_WEBDEV, 'User Become Webdev'), (LAUNCH_LP, 'User visited Launch Landing Page'), (LAUNCH_SUBSCRIPTION, 'User subscribed to launch'), diff --git a/pythonpro/core/roles.py b/pythonpro/core/roles.py index c5e2bda3..453d83e7 100644 --- a/pythonpro/core/roles.py +++ b/pythonpro/core/roles.py @@ -60,11 +60,22 @@ class Bootcamper(AbstractUserRole): } +watch_pythonista_modules = 'watch_pythonista_modules' + + +class Pythonista(AbstractUserRole): + available_permissions = { + watch_pythonista_modules: True, + access_forum: True, + } + + watch_member_modules = 'watch_member_modules' class Member(AbstractUserRole): available_permissions = { + watch_pythonista_modules: True, watch_webdev_modules: True, watch_lead_modules: True, watch_client_modules: True, diff --git a/pythonpro/domain/user_facade.py b/pythonpro/domain/user_facade.py index 61a0d92c..c8ecdf15 100644 --- a/pythonpro/domain/user_facade.py +++ b/pythonpro/domain/user_facade.py @@ -145,6 +145,21 @@ def promote_data_scientist(user: _User, source: str) -> _User: return user +def promote_pythonista(user: _User, source: str) -> _User: + """ + Promote a user to Pythonista role and change it's role on Email Marketing. Will not fail in case API call fails. + Email welcome email is sent to user + :param source: source of traffic + :param user: + :return: + """ + _core_facade.promote_to_pythonista(user, source) + sync_user_on_discourse.delay(user.id) + _email_marketing_facade.create_or_update_pythonista.delay( + user.first_name, user.email, id=user.id) + return user + + def find_user_by_email(user_email: str) -> _User: """ Find user by her email diff --git a/pythonpro/email_marketing/facade.py b/pythonpro/email_marketing/facade.py index c89de495..14446dbc 100644 --- a/pythonpro/email_marketing/facade.py +++ b/pythonpro/email_marketing/facade.py @@ -14,11 +14,13 @@ WEBDEV = 'webdev' BOOTCAMPER = 'bootcamper' DATA_SCIENTIST = 'data-scientist' +PYTHONISTA = 'pythonista' _PYTHON_PRO_ROLES = {LEAD, CLIENT, WEBDEV, BOOTCAMPER, MEMBER} _ALL_ROLES = set(_PYTHON_PRO_ROLES) _ALL_ROLES.add(DATA_SCIENTIST) +_ALL_ROLES.add(PYTHONISTA) run_until_available = shared_task(autoretry_for=(JSONDecodeError,), retry_backoff=True, max_retries=None) @@ -43,6 +45,11 @@ def create_or_update_data_scientist(name: str, email: str, *tags, id='0', phone= return create_or_update_user(name, email, DATA_SCIENTIST, *tags, id=id, phone=phone) +@run_until_available +def create_or_update_pythonista(name: str, email: str, *tags, id='0', phone=None): + return create_or_update_user(name, email, PYTHONISTA, *tags, id=id, phone=phone) + + @run_until_available def create_or_update_client(name: str, email: str, *tags, id='0', phone=None): return create_or_update_user(name, email, CLIENT, *tags, id=id, phone=phone) @@ -120,8 +127,8 @@ def grant_role(email, id, role: str): role = role.lower() if role not in _ALL_ROLES: - raise ValueError(f'Role {role} must be one of {_PYTHON_PRO_ROLES}') - if role == DATA_SCIENTIST: + raise ValueError(f'Role {role} must be one of {_ALL_ROLES}') + if role in {DATA_SCIENTIST, PYTHONISTA}: roles_to_remove = set() role_to_grant = role.capitalize() elif role in _PYTHON_PRO_ROLES: diff --git a/pythonpro/modules/permissions.py b/pythonpro/modules/permissions.py index 3484ff2c..62e45542 100644 --- a/pythonpro/modules/permissions.py +++ b/pythonpro/modules/permissions.py @@ -5,7 +5,7 @@ Member, watch_client_modules, watch_lead_modules, - watch_webdev_modules, watch_bootcamp_modules + watch_webdev_modules, watch_bootcamp_modules, watch_pythonista_modules ) from pythonpro.modules.models import Content @@ -13,6 +13,7 @@ _CLIENT_MODULES = {'python-birds', 'pytools'} _WEBDEV_MODULES = {'python-birds', 'pytools', 'django'} _BOOTCAMPER_MODULES = {'python-birds', 'pytools', 'django', 'entrevistas-tecnicas'} +_PYTHONISTA_MODULES = {'python-birds', 'objetos-pythonicos', 'python-para-pythonistas', 'python-patterns'} @register_object_checker() @@ -24,6 +25,8 @@ def access_content(role, user, content: Content) -> bool: return True if module_slug in _WEBDEV_MODULES and has_permission(user, watch_webdev_modules): return True + if module_slug in _PYTHONISTA_MODULES and has_permission(user, watch_pythonista_modules): + return True if module_slug in _CLIENT_MODULES and has_permission(user, watch_client_modules): return True if module_slug in _LEAD_MODULES and has_permission(user, watch_lead_modules):