diff --git a/apps/referral/__init__.py b/apps/referral/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/referral/apps.py b/apps/referral/apps.py new file mode 100644 index 00000000..031e3e05 --- /dev/null +++ b/apps/referral/apps.py @@ -0,0 +1,10 @@ +# HTK Imports +from htk.app_config import HtkAppConfig + + +# isort: off + + +class HtkReferralAppConfig(HtkAppConfig): + name = 'htk.apps.referral' + verbose_name = 'HTK Referral' diff --git a/apps/referral/constants/__init__.py b/apps/referral/constants/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/referral/constants/defaults.py b/apps/referral/constants/defaults.py new file mode 100644 index 00000000..e76de5cc --- /dev/null +++ b/apps/referral/constants/defaults.py @@ -0,0 +1,16 @@ +# Referral app settings + +# Allow hash referral codes +HTK_REFERRAL_ALLOW_HASH_CODE = True + +# Allow usernames as referral code +HTK_REFERRAL_ALLOW_USERNAME = True + +# Redirect URL name for the referral URL +HTK_REFERRAL_REDIRECT_URL_NAME = 'home' + +HTK_REFERRAL_QUERY_PARAM = 'ref' + +HTK_REFERRAL_MODEL = 'htk.Referral' + +HTK_REFERRAL_CODE_MODEL = 'htk.ReferralCode' diff --git a/apps/referral/middleware.py b/apps/referral/middleware.py new file mode 100644 index 00000000..c3b7f7d5 --- /dev/null +++ b/apps/referral/middleware.py @@ -0,0 +1,29 @@ +# Django Imports +from django.utils.deprecation import MiddlewareMixin + +# HTK Imports +from htk.utils import htk_setting + +# Local Imports +from .utils import get_referrer_from_code + + +# isort: off + + +class ReferralMiddleware(MiddlewareMixin): + def __init__(self, *args, **kwargs): + super(ReferralMiddleware, self).__init__(*args, **kwargs) + self.referrer = None + + def process_request(self, request): + key = htk_setting('HTK_REFERRAL_QUERY_PARAM_KEY') + referral_code = request.GET.get(key) + if referral_code: + self.referrer = get_referrer_from_code(referral_code) + + def process_response(self, request, response): + if self.referrer: + request.session['referrer_id'] = self.referrer.id + response.set_cookie('referrer_id', self.referrer.id) + return response diff --git a/apps/referral/models/__init__.py b/apps/referral/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/referral/models/referal_code.py b/apps/referral/models/referal_code.py new file mode 100644 index 00000000..52f5074c --- /dev/null +++ b/apps/referral/models/referal_code.py @@ -0,0 +1,20 @@ +# Django Imports +from django.conf import settings +from django.db import models + + +class ReferralCode(models.Model): + """Referral Code model""" + + code = models.CharField(max_length=56, unique=True) + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + related_name='referral_codes', + on_delete=models.CASCADE, + ) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + unique_together = ('code',) diff --git a/apps/referral/models/referral.py b/apps/referral/models/referral.py new file mode 100644 index 00000000..361fe790 --- /dev/null +++ b/apps/referral/models/referral.py @@ -0,0 +1,27 @@ +# Django Imports +from django.conf import settings +from django.db import models + + +# isort: off + + +class Referral(models.Model): + """Referral model""" + + referrer = models.ForeignKey( + settings.AUTH_USER_MODEL, + related_name='referrals', + on_delete=models.CASCADE, + ) + referred = models.OneToOneField( + settings.AUTH_USER_MODEL, + related_name='referred_by', + on_delete=models.CASCADE, + ) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True + unique_together = ('referrer', 'referred') diff --git a/apps/referral/resolvers.py b/apps/referral/resolvers.py new file mode 100644 index 00000000..f021c007 --- /dev/null +++ b/apps/referral/resolvers.py @@ -0,0 +1,17 @@ +# Django Imports +from django.core.exceptions import AppRegistryNotReady + +# HTK Imports +from htk.utils import ( + htk_setting, + resolve_model_dynamically, +) + + +try: + Referral = resolve_model_dynamically(htk_setting('HTK_REFERRAL_MODEL')) + ReferralCode = resolve_model_dynamically( + htk_setting('HTK_REFERRAL_CODE_MODEL') + ) +except (LookupError, AppRegistryNotReady): + pass diff --git a/apps/referral/urls.py b/apps/referral/urls.py new file mode 100644 index 00000000..a33f43e9 --- /dev/null +++ b/apps/referral/urls.py @@ -0,0 +1,18 @@ +# Django Imports +from django.urls import re_path + +# Local Imports +from . import views + + +# isort: off + + +urlpatterns = [ + re_path( + # if the referral code is prefixed with an '@', it is an username + r'^referrals/(?P@?[a-zA-Z0-9_-]+)$', + views.referral_view, + name='htk_referral', + ), +] diff --git a/apps/referral/utils.py b/apps/referral/utils.py new file mode 100644 index 00000000..860eb5bc --- /dev/null +++ b/apps/referral/utils.py @@ -0,0 +1,51 @@ +# Django Imports +from django.contrib.auth import get_user_model + +# HTK Imports +from htk.utils import htk_setting + +# Local Imports +from .resolvers import ReferralCode + + +# isort: off + + +UserModel = get_user_model() + + +def get_referrer_from_code(referral_code): + referrer = None + if htk_setting('HTK_REFERRAL_ALLOW_USERNAME') and referral_code.startswith( + '@' + ): + # referral code is an username + username = referral_code[1:] + try: + referrer = UserModel.objects.get(username=username) + except UserModel.DoesNotExist: + pass + elif htk_setting('HTK_REFERRAL_ALLOW_HASH_CODE'): + try: + referral_code = ReferralCode.objects.get(code=referral_code) + referrer = referral_code.user + except ReferralCode.DoesNotExist: + pass + else: + raise ValueError( + 'Either usernames or hash codes must be allowed in referral codes.' + ) + return referrer + + +def get_referrer(request): + referrer = None + key = 'referrer_id' + referrer_id = request.COOKIE.get(key) or request.session.get(key) + + if referrer_id: + try: + referrer = UserModel.objects.get(id=referrer_id) + except UserModel.DoesNotExist: + pass + return referrer diff --git a/apps/referral/views.py b/apps/referral/views.py new file mode 100644 index 00000000..2e244103 --- /dev/null +++ b/apps/referral/views.py @@ -0,0 +1,29 @@ +# Django Imports +from django.contrib.auth import get_user_model +from django.http import Http404 +from django.shortcuts import redirect + +# HTK Imports +from htk.utils import htk_setting + +# Local Imports +from .utils import get_referrer_from_code + + +# isort: off + + +UserModel = get_user_model() + + +def referral_view(request, referral_code: str, *args, **kwargs): + referrer = get_referrer_from_code(referral_code) + + if referrer: + request.session['referrer_id'] = referrer.id + request.set_cookie('referrer_id', referrer.id) + response = redirect(htk_setting('HTK_REFERRAL_REDIRECT_URL')) + else: + response = Http404 + + return response diff --git a/constants/defaults.py b/constants/defaults.py index c35aeb8c..eb46bb7d 100644 --- a/constants/defaults.py +++ b/constants/defaults.py @@ -102,6 +102,7 @@ from htk.apps.notifications.constants.defaults import * from htk.apps.organizations.constants.defaults import * from htk.apps.prelaunch.constants.defaults import * +from htk.apps.referral.constants.defaults import * from htk.cache.constants.defaults import * from htk.forms.constants.defaults import * from htk.lib.alexa.constants.defaults import *