Skip to content

Commit

Permalink
merging all data
Browse files Browse the repository at this point in the history
  • Loading branch information
Sascha Dobbelaere committed Feb 20, 2024
2 parents 5610a85 + dfcaeb7 commit db45898
Show file tree
Hide file tree
Showing 43 changed files with 476 additions and 117 deletions.
4 changes: 2 additions & 2 deletions OneSila/contacts/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ class CompanyQueryset(MultiTenantQuerySet):
def filter_supplier(self, multi_tenant_company):
return self.filter(is_supplier=True, multi_tenant_company=multi_tenant_company)

def filter_customer(self):
def filter_customer(self, multi_tenant_company):
return self.filter(is_customer=True, multi_tenant_company=multi_tenant_company)

def filter_influencer(self):
def filter_influencer(self, multi_tenant_company):
return self.filter(is_influencer=True, multi_tenant_company=multi_tenant_company)


Expand Down
3 changes: 3 additions & 0 deletions OneSila/contacts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ class Address(models.Model):
city = models.CharField(max_length=100)
country = models.CharField(max_length=2, choices=COUNTRY_CHOICES)

# FIXME: Add constraint that there can be only invoice address for a company
# If a customer has multiple invoice addresses, they are multiple comnpanies
# So a new company should be added, and related to the current one.
is_invoice_address = models.BooleanField(default=False)
is_shipping_address = models.BooleanField(default=False)

Expand Down
33 changes: 22 additions & 11 deletions OneSila/core/factories/multi_tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from django.core.exceptions import ValidationError
from django.contrib.auth.password_validation import validate_password

from core.signals import registered, invite_sent, invite_accepted, \
disabled, enabled, login_token_created, recovery_token_created, \
from core.signals import registered, invited, invite_accepted, \
disabled, enabled, login_token_requested, recovery_token_created, \
password_changed


Expand All @@ -23,22 +23,25 @@ def run(self):
self.set_user()


class LoginTokenFactory:
class RequestLoginTokenFactory:
def __init__(self, user):
self.user = user

def create_token(self):
self.token = MultiTenantUserLoginToken.objects.create(
multi_tenant_user=self.user)
self.token = MultiTenantUserLoginToken.objects.create(multi_tenant_user=self.user)
# The save method will add the expiry-date after the save(). Let's refresh
# to ensure the frontend can use that date.
self.token.refresh_from_db()

def send_signal(self):
login_token_created.send(sender=self.token.__class__, instance=self.token)
login_token_requested.send(sender=self.token.__class__, instance=self.token)

def run(self):
self.create_token()
self.send_signal()


class RecoveryTokenFactory(LoginTokenFactory):
class RecoveryTokenFactory(RequestLoginTokenFactory):
def send_signal(self):
recovery_token_created.send(sender=self.token.__class__, instance=self.token)

Expand Down Expand Up @@ -102,10 +105,13 @@ def run(self):
self.send_signal()


class InviteUserFactory:
class InviteUserFactory(RequestLoginTokenFactory):
model = MultiTenantUser
invitation_accepted = False
is_active = False

# Setting is_active to false will stop the user to accept the invite.
# as a non-active user cannot be logged in.
is_active = True

def __init__(self, *, multi_tenant_company, language, username, first_name="", last_name=""):
self.multi_tenant_company = multi_tenant_company
Expand All @@ -114,7 +120,6 @@ def __init__(self, *, multi_tenant_company, language, username, first_name="", l
self.first_name = first_name
self.last_name = last_name

@transaction.atomic
def create_user(self):
user = self.model(
multi_tenant_company=self.multi_tenant_company,
Expand All @@ -136,11 +141,12 @@ def create_user(self):
self.user = user

def send_signal(self):
invite_sent.send(sender=self.user.__class__, instance=self.user)
invited.send(sender=self.token.__class__, instance=self.token)

@transaction.atomic
def run(self):
self.create_user()
self.create_token()
self.send_signal()


Expand All @@ -151,6 +157,10 @@ def __init__(self, user, password, language):
self.password = password
self.language = language

def sanity_check(self):
if self.user.is_anonymous:
raise ValidationError(f"Permission denied. Only logged in users can accept invitations.")

def update_user(self):
self.user.invitation_accepted = True
self.user.language = self.language
Expand All @@ -164,6 +174,7 @@ def send_signal(self):

@transaction.atomic
def run(self):
self.sanity_check()
self.update_user()
self.send_signal()

Expand Down
5 changes: 3 additions & 2 deletions OneSila/core/managers/multi_tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@
from django.db import IntegrityError
from core.managers.search import SearchQuerySetMixin, SearchManagerMixin
from core.managers.decorators import multi_tenant_company_required
from django.utils.translation import gettext_lazy as _


class MultiTenantUserLoginTokenQuerySet(DjangoQueryset):
def get_by_token(self, token):
try:
instance = self.get(token=token)
except self.model.DoesNotExist:
raise ValidationError('Unknown token')
raise ValidationError(_('Unknown token'))

if not instance.is_valid:
raise ValidationError('Token is no longer valid')
raise ValidationError(_('Token is no longer valid'))

return instance

Expand Down
19 changes: 19 additions & 0 deletions OneSila/core/migrations/0015_alter_multitenantuser_timezone.py

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions OneSila/core/migrations/0016_alter_multitenantuser_timezone.py

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions OneSila/core/migrations/0017_alter_multitenantuser_timezone.py

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions OneSila/core/models/multi_tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,13 @@ def set_token(self):
if not self.token:
self.token = shake_256(shortuuid.uuid().encode('utf-8')).hexdigest(10)

def set_expires_at(self, save=False):
def set_expires_at(self, expires_at=None):
# This strange construction is to avoid a second save signal.
expires_at = self.created_at + timezone.timedelta(minutes=self.EXPIRES_AFTER_MIN)
# if expires_at is not None:
if expires_at is None:
expires_at = self.created_at + timezone.timedelta(minutes=self.EXPIRES_AFTER_MIN)

self.__class__.objects.filter(id=self.id).update(expires_at=expires_at)

def is_valid(self, now=timezone.now()):
if self.expires_at > now:
return False

return True
return self.expires_at >= now
13 changes: 13 additions & 0 deletions OneSila/core/schema/core/mixins.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from strawberry_django.auth.utils import get_current_user

from .helpers import get_multi_tenant_company


Expand All @@ -16,3 +18,14 @@ class GetQuerysetMultiTenantMixin:
def get_queryset(cls, queryset, info, **kwargs):
multi_tenant_company = get_multi_tenant_company(info)
return queryset.filter(multi_tenant_company=multi_tenant_company)


class GetCurrentUserMixin:
@classmethod
def get_current_user(self, info, fail_silently=False):
current_user = get_current_user(info)

if not fail_silently and not current_user:
raise ValueError("Unable to identify the current user.")

return current_user
2 changes: 1 addition & 1 deletion OneSila/core/schema/core/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from typing import List

from .decorators import multi_tenant_owner_protection
from .mixins import GetMultiTenantCompanyMixin
from .mixins import GetMultiTenantCompanyMixin, GetCurrentUserMixin
from .extensions import default_extensions


Expand Down
6 changes: 3 additions & 3 deletions OneSila/core/schema/multi_tenant/mutations/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .mutation_classes import MyMultiTenantCompanyCreateMutation, \
MyMultiTentantCompanyUpdateMutation, UpdateMeMutation, \
InviteUserMutation, AcceptInvitationMutation, EnableUserMutation, \
DisableUserMutation, LoginTokenMutation, RecoveryTokenMutation, \
DisableUserMutation, RequestLoginTokenMutation, RecoveryTokenMutation, \
UpdateMyPasswordMutation

import functools
Expand Down Expand Up @@ -62,9 +62,9 @@ def recovery_token():
return RecoveryTokenMutation(MultiTenantLoginLinkInput, extensions=extensions)


def login_token():
def request_login_token():
extensions = []
return RecoveryTokenMutation(MultiTenantLoginLinkInput, extensions=extensions)
return RequestLoginTokenMutation(MultiTenantLoginLinkInput, extensions=extensions)


register_user = functools.partial(strawberry.mutation, resolver=resolve_register_user)
Expand Down
45 changes: 24 additions & 21 deletions OneSila/core/schema/multi_tenant/mutations/mutation_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from strawberry.relay.utils import from_base64
from strawberry_django.resolvers import django_resolver
from strawberry_django.mutations import resolvers
from strawberry_django.auth.utils import get_current_user
from strawberry_django.optimizer import DjangoOptimizerExtension
from strawberry_django.utils.requests import get_request

Expand All @@ -18,11 +17,12 @@

from channels import auth as channels_auth

from core.schema.core.mixins import GetCurrentUserMixin, GetMultiTenantCompanyMixin
from core.schema.core.mutations import create, type, DjangoUpdateMutation, \
DjangoCreateMutation, GetMultiTenantCompanyMixin, default_extensions, \
DjangoCreateMutation, default_extensions, \
update, Info, models, Iterable, Any, IsAuthenticated
from core.factories.multi_tenant import InviteUserFactory, RegisterUserFactory, \
AcceptUserInviteFactory, EnableUserFactory, DisableUserFactory, LoginTokenFactory, \
AcceptUserInviteFactory, EnableUserFactory, DisableUserFactory, RequestLoginTokenFactory, \
RecoveryTokenFactory, AuthenticateTokenFactory, ChangePasswordFactory
from core.models.multi_tenant import MultiTenantUser

Expand All @@ -36,9 +36,9 @@ def cleanup_data(self, data):
return data


class LoginTokenMutation(CleanupDataMixin, DjangoCreateMutation):
class RequestLoginTokenMutation(CleanupDataMixin, DjangoCreateMutation):
def create_token(self, *, user):
fac = LoginTokenFactory(user)
fac = RequestLoginTokenFactory(user)
fac.run()
return fac.token

Expand All @@ -49,13 +49,6 @@ def create(self, data: dict[str, Any], *, info: Info):
return self.create_token(user=user)


class RecoveryTokenMutation(LoginTokenMutation):
def create_token(self, *, user):
fac = RecoveryTokenFactory(user)
fac.run()
return fac.token


class InviteUserMutation(CleanupDataMixin, GetMultiTenantCompanyMixin, DjangoCreateMutation):
def create(self, data: dict[str, Any], *, info: Info):
multi_tenant_company = self.get_multi_tenant_company(info)
Expand All @@ -69,25 +62,35 @@ def create(self, data: dict[str, Any], *, info: Info):
return fac.user


class AcceptInvitationMutation(DjangoUpdateMutation):
class RecoveryTokenMutation(RequestLoginTokenMutation):
def create_token(self, *, user):
fac = RecoveryTokenFactory(user)
fac.run()
return fac.token


class AcceptInvitationMutation(GetCurrentUserMixin, DjangoUpdateMutation):
def update(self, info: Info, instance: models.Model, data: dict[str, Any]):
# Do not optimize anything while retrieving the object to update
user = self.get_current_user(info)
language = data['language']
password = data['password']

with DjangoOptimizerExtension.disabled():
fac = AcceptUserInviteFactory(
user=instance,
password=data['password'],
language=data['language'])
user=user,
password=password,
language=language)
fac.run()

return fac.user


class MyMultiTenantCompanyCreateMutation(GetMultiTenantCompanyMixin, DjangoCreateMutation):
class MyMultiTenantCompanyCreateMutation(GetCurrentUserMixin, DjangoCreateMutation):
def create(self, data: dict[str, Any], *, info: Info):
model = self.django_model
assert model is not None

user = get_current_user(info)
user = self.get_current_user(info)

with DjangoOptimizerExtension.disabled():
obj = resolvers.create(
Expand Down Expand Up @@ -120,7 +123,7 @@ def resolver(self, source: Any, info: Info, args: list[Any], kwargs: dict[str, A
return self.update(info, instance, resolvers.parse_input(info, vdata))


class UpdateMeMutation(DjangoUpdateMutation):
class UpdateMeMutation(GetCurrentUserMixin, DjangoUpdateMutation):
@django_resolver
@transaction.atomic
def resolver(self, source: Any, info: Info, args: list[Any], kwargs: dict[str, Any],) -> Any:
Expand All @@ -129,7 +132,7 @@ def resolver(self, source: Any, info: Info, args: list[Any], kwargs: dict[str, A

data: Any = kwargs.get(self.argument_name)
vdata = vars(data).copy() if data is not None else {}
instance = get_current_user(info)
instance = self.get_current_user(info)

return self.update(info, instance, resolvers.parse_input(info, vdata))

Expand Down
4 changes: 2 additions & 2 deletions OneSila/core/schema/multi_tenant/mutations/mutation_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from strawberry_django import auth as strawberry_auth
from .fields import register_my_multi_tenant_company, \
update_me, update_my_multi_tenant_company, invite_user, \
accept_user_invitation, disable_user, enable_user, login_token, \
accept_user_invitation, disable_user, enable_user, request_login_token, \
recovery_token, authenticate_token, register_user, update_my_password

from core.schema.multi_tenant.types.types import MultiTenantUserType, \
Expand All @@ -22,7 +22,7 @@ class MultiTenantMutation:
update_my_multi_tenant_company: MultiTenantCompanyType = update_my_multi_tenant_company()

recovery_token: MultiTenantUserLoginTokenType = recovery_token()
login_token: MultiTenantUserLoginTokenType = login_token()
request_login_token: MultiTenantUserLoginTokenType = request_login_token()
authenticate_token: MultiTenantUserType = authenticate_token()

invite_user: MultiTenantUserType = invite_user()
Expand Down
17 changes: 7 additions & 10 deletions OneSila/core/schema/multi_tenant/types/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ class MultiTenantCompanyPartialInput:
pass


@input(MultiTenantUser)
@input(MultiTenantUser, fields=['password', 'language'])
class MultiTenantUserAcceptInviteInput:
id: auto
username: auto
password: auto
language: auto
pass


@partial(MultiTenantUser, fields=['password'])
class MultiTenantUserPasswordInput:
pass


@input(MultiTenantUser)
Expand All @@ -50,11 +52,6 @@ class MultiTenantUserPartialInput:
pass


@partial(MultiTenantUser, fields=['password'])
class MultiTenantUserPasswordInput:
pass


@input(MultiTenantUser)
class MultiTenantInviteUserInput:
username: auto
Expand Down
4 changes: 2 additions & 2 deletions OneSila/core/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from django.db.models.signals import ModelSignal

registered = ModelSignal(use_caching=True)
invite_sent = ModelSignal(use_caching=True)
invited = ModelSignal(use_caching=True)
invite_accepted = ModelSignal(use_caching=True)
disabled = ModelSignal(use_caching=True)
enabled = ModelSignal(use_caching=True)
login_token_created = ModelSignal(use_caching=True)
login_token_requested = ModelSignal(use_caching=True)
recovery_token_created = ModelSignal(use_caching=True)
password_changed = ModelSignal(use_caching=True)
Loading

0 comments on commit db45898

Please sign in to comment.