Skip to content

Commit

Permalink
Merge branch 'development' of github.com:breatheco-de/apiv2 into deve…
Browse files Browse the repository at this point in the history
…lopment
  • Loading branch information
jefer94 committed Oct 12, 2023
2 parents 614f7c5 + 7208299 commit 1dbc306
Show file tree
Hide file tree
Showing 16 changed files with 537 additions and 328 deletions.
2 changes: 1 addition & 1 deletion breathecode/activity/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def add_activity(user_id: int,
related_id: Optional[str | int] = None,
related_slug: Optional[str] = None,
**_):
logger.info('Executing add_activity')
logger.info(f'Executing add_activity related to {str(kind)}')

if related_type and not (bool(related_id) ^ bool(related_slug)):
raise AbortTask(
Expand Down
3 changes: 2 additions & 1 deletion breathecode/assignments/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ def validate(self, data):
return data

def update(self, instance, validated_data):
if 'opened_at' in validated_data and validated_data['opened_at'] > instance.opened_at:
if 'opened_at' in validated_data and (instance.opened_at is None
or validated_data['opened_at'] > instance.opened_at):
tasks_activity.add_activity.delay(self.context['request'].user.id,
'read_assignment',
related_type='assignments.Task',
Expand Down
12 changes: 7 additions & 5 deletions breathecode/assignments/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,12 +639,14 @@ def post(self, request, user_id=None):
},
many=True)
if serializer.is_valid():
serializer.save()
tasks = serializer.save()
# tasks.teacher_task_notification.delay(serializer.data['id'])
tasks_activity.add_activity.delay(request.user.id,
'open_syllabus_module',
related_type='assignments.Task',
related_id=serializer.id)
for t in tasks:
tasks_activity.add_activity.delay(request.user.id,
'open_syllabus_module',
related_type='assignments.Task',
related_id=t.id)

return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Expand Down
114 changes: 114 additions & 0 deletions breathecode/authenticate/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -902,3 +902,117 @@ def get_app(pk: str | int) -> App:
raise Exception('App not found')

return app


def accept_invite_action(data={}, token=None, lang='en'):
from breathecode.payments.models import Invoice, Bag, Plan

password1 = data.get('password', None)
password2 = data.get('repeat_password', None)

invite = UserInvite.objects.filter(token=str(token), status='PENDING', email__isnull=False).first()
if invite is None:
raise Exception(
translation(lang,
en='Invalid or expired invitation ' + str(token),
es='Invitación inválida o expirada ' + str(token)))

first_name = data.get('first_name', None)
last_name = data.get('last_name', None)
if first_name is None or first_name == '' or last_name is None or last_name == '':
raise Exception(translation(lang, en='Invalid first or last name', es='Nombre o apellido inválido'))

if password1 != password2:
raise Exception(translation(lang, en='Passwords don\'t match', es='Las contraseñas no coinciden'))

if not password1:
raise Exception(translation(lang, en='Password is empty', es='La contraseña está vacía'))

user = User.objects.filter(email=invite.email).first()
if user is None:
user = User(email=invite.email, first_name=first_name, last_name=last_name, username=invite.email)
user.save()
user.set_password(password1)
user.save()

if invite.academy is not None:
profile = ProfileAcademy.objects.filter(email=invite.email, academy=invite.academy).first()
if profile is None:
role = invite.role
if not role:
role = Role.objects.filter(slug='student').first()

if not role:
raise Exception(
translation(lang,
en='Unexpected error occurred with invite, please contact the '
'staff of 4geeks',
es='Ocurrió un error inesperado con la invitación, por favor '
'contacta al staff de 4geeks'))

profile = ProfileAcademy(email=invite.email,
academy=invite.academy,
role=role,
first_name=first_name,
last_name=last_name)

if invite.first_name is not None and invite.first_name != '':
profile.first_name = invite.first_name
if invite.last_name is not None and invite.last_name != '':
profile.last_name = invite.last_name

profile.user = user
profile.status = 'ACTIVE'
profile.save()

if invite.cohort is not None:
role = 'student'
if invite.role is not None and invite.role.slug != 'student':
role = invite.role.slug.upper()

cu = CohortUser.objects.filter(user=user, cohort=invite.cohort).first()
if cu is None:
cu = CohortUser(user=user, cohort=invite.cohort, role=role.upper())
cu.save()

plan = Plan.objects.filter(cohort_set__cohorts=invite.cohort, invites=invite).first()

if plan and invite.user and invite.cohort.academy.main_currency and (
invite.cohort.available_as_saas == True or
(invite.cohort.available_as_saas == None and invite.cohort.academy.available_as_saas == True)):
utc_now = timezone.now()

bag = Bag()
bag.chosen_period = 'NO_SET'
bag.status = 'PAID'
bag.type = 'INVITED'
bag.how_many_installments = 1
bag.academy = invite.cohort.academy
bag.user = user
bag.is_recurrent = False
bag.was_delivered = False
bag.token = None
bag.currency = invite.cohort.academy.main_currency
bag.expires_at = None

bag.save()

bag.plans.add(plan)
bag.selected_cohorts.add(invite.cohort)

invoice = Invoice(amount=0,
paid_at=utc_now,
user=invite.user,
bag=bag,
academy=bag.academy,
status='FULFILLED',
currency=bag.academy.main_currency)
invoice.save()

payment_tasks.build_plan_financing.delay(bag.id, invoice.id, is_free=True)

invite.status = 'ACCEPTED'
invite.is_email_validated = True
invite.save()

return invite
2 changes: 1 addition & 1 deletion breathecode/authenticate/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,7 @@ def validate(self, data: dict[str, str]):
now = str(timezone.now())

if not self.instance:
data['token'] = hashlib.sha1((now + data['email']).encode('UTF-8')).hexdigest()
data['token'] = hashlib.sha512((data['email']).encode('UTF-8') + os.urandom(64)).hexdigest()

return data

Expand Down
108 changes: 108 additions & 0 deletions breathecode/authenticate/tests/urls/tests_member_invite_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from unittest.mock import MagicMock, patch
from django.template import loader
from django.urls.base import reverse_lazy
import pytest
from rest_framework import status
from django.http.request import HttpRequest
from random import randint
Expand Down Expand Up @@ -139,6 +140,28 @@ def salt(self):
return 'salt'


def post_serializer(data={}):
return {
'created_at': ...,
'email': None,
'id': 0,
'sent_at': None,
'status': 'PENDING',
**data,
}


created_at = None


@pytest.fixture(autouse=True)
def setup(monkeypatch, utc_now):
global created_at
created_at = utc_now
monkeypatch.setattr('django.utils.timezone.now', MagicMock(return_value=utc_now))
yield


class AuthenticateTestSuite(AuthTestCase):
"""Authentication test suite"""
"""
Expand Down Expand Up @@ -1020,3 +1043,88 @@ def test_member_invite_token__post__with_cohort__without_role(self):
])

self.assertEqual(self.bc.database.list_of('admissions.CohortUser'), [])

"""
🔽🔽🔽 POST JSON password is empty, UserInvite with email
"""

@patch('django.template.loader.render_to_string', MagicMock(side_effect=render_to_string_mock))
@patch('django.contrib.auth.hashers.get_hasher', MagicMock(side_effect=GetHasherMock))
@patch('django.db.models.signals.pre_delete.send', MagicMock(return_value=None))
@patch('breathecode.admissions.signals.student_edu_status_updated.send', MagicMock(return_value=None))
def test__post__json__password_is_empty(self):
user_invite = {'email': '[email protected]'}
model = self.bc.database.create(user_invite=user_invite)
url = reverse_lazy('authenticate:member_invite_token', kwargs={'token': model.user_invite.token})

data = {'first_name': 'abc', 'last_name': 'xyz'}
response = self.client.post(url, data, format='json')

json = response.json()
expected = {'detail': 'Password is empty', 'status_code': 400}

self.assertEqual(json, expected)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(self.bc.database.list_of('authenticate.UserInvite'), [
self.bc.format.to_dict(model.user_invite),
])

self.assertEqual(self.bc.database.list_of('auth.User'), [])
self.assertEqual(self.bc.database.list_of('authenticate.ProfileAcademy'), [])
self.assertEqual(self.bc.database.list_of('admissions.CohortUser'), [])

"""
🔽🔽🔽 POST JSON with first name, last name and passwords, UserInvite with email
"""

@patch('django.template.loader.render_to_string', MagicMock(side_effect=render_to_string_mock))
@patch('django.contrib.auth.hashers.get_hasher', MagicMock(side_effect=GetHasherMock))
@patch('django.db.models.signals.pre_delete.send', MagicMock(return_value=None))
@patch('breathecode.admissions.signals.student_edu_status_updated.send', MagicMock(return_value=None))
def test__post__json__with_first_name_last_name_and_passwords(self):
user_invite = {'email': '[email protected]'}
model = self.bc.database.create(user_invite=user_invite)
url = reverse_lazy('authenticate:member_invite_token', kwargs={'token': model.user_invite.token})
data = {
'first_name': 'abc',
'last_name': 'xyz',
'password': '^3^3uUppppp',
'repeat_password': '^3^3uUppppp',
}
response = self.client.post(url, data, format='json')

json = response.json()
expected = post_serializer({
'id': 1,
'created_at': self.bc.datetime.to_iso_string(created_at),
'status': 'ACCEPTED',
'email': '[email protected]',
})

self.assertEqual(json, expected)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.bc.database.list_of('authenticate.UserInvite'),
[{
**self.bc.format.to_dict(model.user_invite),
'status': 'ACCEPTED',
'is_email_validated': True,
}])

user_db = [
x for x in self.bc.database.list_of('auth.User') if x['date_joined'] and x.pop('date_joined')
]
self.assertEqual(user_db, [{
'email': '[email protected]',
'first_name': 'abc',
'id': 1,
'is_active': True,
'is_staff': False,
'is_superuser': False,
'last_login': None,
'last_name': 'xyz',
'password': CSRF_TOKEN,
'username': '[email protected]'
}])

self.assertEqual(self.bc.database.list_of('authenticate.ProfileAcademy'), [])
self.assertEqual(self.bc.database.list_of('admissions.CohortUser'), [])
Loading

0 comments on commit 1dbc306

Please sign in to comment.