From dcbab36ef0c5d13cfe699946f168c150803fb3e0 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 7 Sep 2020 12:14:34 -0700
Subject: [PATCH 01/32] Added osprojects to admin interface.

---
 project/osprojects/admin.py | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/project/osprojects/admin.py b/project/osprojects/admin.py
index 8c38f3f3..4f391a5c 100644
--- a/project/osprojects/admin.py
+++ b/project/osprojects/admin.py
@@ -1,3 +1,17 @@
 from django.contrib import admin
+from .models import OSProjects
+
 
 # Register your models here.
+class OSProjectAdmin(admin.ModelAdmin):
+
+    list_display = ['tag_list']
+
+    def get_queryset(self, request):
+        return super().get_queryset(request).prefetch_related('tags')
+
+    def tag_list(self, obj):
+        return u", ".join(o.name for o in obj.tags.all())
+
+
+admin.site.register(OSProjects)

From 31edf0fb1a1a1bc731042500608554693651e771 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 7 Sep 2020 13:24:44 -0700
Subject: [PATCH 02/32] Added osprojects to admin interface.

---
 project/osprojects/admin.py | 14 --------------
 1 file changed, 14 deletions(-)

diff --git a/project/osprojects/admin.py b/project/osprojects/admin.py
index 4f391a5c..8c38f3f3 100644
--- a/project/osprojects/admin.py
+++ b/project/osprojects/admin.py
@@ -1,17 +1,3 @@
 from django.contrib import admin
-from .models import OSProjects
-
 
 # Register your models here.
-class OSProjectAdmin(admin.ModelAdmin):
-
-    list_display = ['tag_list']
-
-    def get_queryset(self, request):
-        return super().get_queryset(request).prefetch_related('tags')
-
-    def tag_list(self, obj):
-        return u", ".join(o.name for o in obj.tags.all())
-
-
-admin.site.register(OSProjects)

From d585cddb58feaf3ced429a04815a67f0fc93a585 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 7 Sep 2020 13:33:24 -0700
Subject: [PATCH 03/32] Added OSProjects to admin interface.

---
 project/osprojects/admin.py | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/project/osprojects/admin.py b/project/osprojects/admin.py
index 8c38f3f3..4f391a5c 100644
--- a/project/osprojects/admin.py
+++ b/project/osprojects/admin.py
@@ -1,3 +1,17 @@
 from django.contrib import admin
+from .models import OSProjects
+
 
 # Register your models here.
+class OSProjectAdmin(admin.ModelAdmin):
+
+    list_display = ['tag_list']
+
+    def get_queryset(self, request):
+        return super().get_queryset(request).prefetch_related('tags')
+
+    def tag_list(self, obj):
+        return u", ".join(o.name for o in obj.tags.all())
+
+
+admin.site.register(OSProjects)

From 606c871b96d3806a907cb0a5ebfa14a7fe1237f6 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 7 Sep 2020 18:22:23 -0700
Subject: [PATCH 04/32] added django-res-auth and associated basic settings to
 test DRF and allauth together.

---
 project/config/settings/base.py | 6 +++++-
 project/config/urls.py          | 2 ++
 project/requirements/base.txt   | 2 ++
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/project/config/settings/base.py b/project/config/settings/base.py
index 387b145a..c645303c 100644
--- a/project/config/settings/base.py
+++ b/project/config/settings/base.py
@@ -77,10 +77,13 @@
     "allauth.account",
     "allauth.socialaccount",
     "rest_framework",
+    "rest_framework.authtoken",
     "corsheaders",
     "taggit",
     "django_celery_beat",
-    "taggit_serializer"
+    "taggit_serializer",
+    "rest_auth",
+    "rest_auth.registration"
 ]
 
 LOCAL_APPS = [
@@ -327,6 +330,7 @@
     'PAGE_SIZE': 10,
 }
 
+REST_USE_JWT = True
 
 JWT_AUTH = {
     'JWT_AUTH_HEADER_PREFIX': 'Bearer',
diff --git a/project/config/urls.py b/project/config/urls.py
index a757dc3c..4391a52d 100644
--- a/project/config/urls.py
+++ b/project/config/urls.py
@@ -21,6 +21,8 @@
     # Your stuff: custom urls includes go here
     path('api/v1/', include('resources.urls')),
     path('auth/', include('userauth.urls', namespace="userauth")),
+    path('rest-auth/', include('rest_auth.urls')),
+    path('rest-auth/registration/', include('rest_auth.registration.urls'))
 
 ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 
diff --git a/project/requirements/base.txt b/project/requirements/base.txt
index bf8c4ab6..3d62143e 100644
--- a/project/requirements/base.txt
+++ b/project/requirements/base.txt
@@ -33,3 +33,5 @@ djangorestframework==3.10.2   # https://github.com/encode/django-rest-framework
 coreapi==2.3.3                # https://github.com/core-api/python-client
 django_taggit_serializer==0.1.7 #https://github.com/glemmaPaul/django-taggit-serializer
 drf-jwt==1.13.4               # https://github.com/Styria-Digital/django-rest-framework-jwt
+django-rest-auth==0.9.5 #https://github.com/Tivix/django-rest-auth
+django-rest-authtoken==2.1.3 #https://pypi.org/project/django-rest-authtoken/

From 30936fe475f384a6ddf0204027ccce7e15fcb844 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 14 Sep 2020 05:49:44 -0700
Subject: [PATCH 05/32] Working state Mon Sept 14 - dj-rest-auth and
 django-allauth

---
 project/config/settings/base.py               | 61 +++++++++++--------
 project/config/urls.py                        | 22 ++++---
 .../email/email _confirmation_subject.txt     |  1 +
 .../email/email_confirmation_message.txt      |  9 +++
 project/requirements/base.txt                 |  2 +
 project/resources/serializers.py              |  9 +--
 project/resources/urls.py                     |  4 +-
 project/userauth/adapter.py                   | 10 +++
 project/userauth/serializers.py               | 39 +++++++++++-
 project/userauth/urls.py                      | 41 +++++++++----
 project/userauth/views.py                     | 38 +++++++++++-
 project/users/urls.py                         |  3 +-
 12 files changed, 182 insertions(+), 57 deletions(-)
 create mode 100644 project/core/templates/account/email/email _confirmation_subject.txt
 create mode 100644 project/core/templates/account/email/email_confirmation_message.txt
 create mode 100644 project/userauth/adapter.py

diff --git a/project/config/settings/base.py b/project/config/settings/base.py
index c645303c..6105773a 100644
--- a/project/config/settings/base.py
+++ b/project/config/settings/base.py
@@ -82,8 +82,8 @@
     "taggit",
     "django_celery_beat",
     "taggit_serializer",
-    "rest_auth",
-    "rest_auth.registration"
+    "dj_rest_auth",
+    "dj_rest_auth.registration",
 ]
 
 LOCAL_APPS = [
@@ -290,21 +290,43 @@
 # CELERY_TASK_SOFT_TIME_LIMIT = 60
 # # http://docs.celeryproject.org/en/latest/userguide/configuration.html#beat-scheduler
 # CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
-# # django-allauth
-# # ------------------------------------------------------------------------------
-# ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
-# # https://django-allauth.readthedocs.io/en/latest/configuration.html
-# ACCOUNT_AUTHENTICATION_METHOD = "username"
-# # https://django-allauth.readthedocs.io/en/latest/configuration.html
-# ACCOUNT_EMAIL_REQUIRED = True
-# # https://django-allauth.readthedocs.io/en/latest/configuration.html
-# ACCOUNT_EMAIL_VERIFICATION = "mandatory"
-# # https://django-allauth.readthedocs.io/en/latest/configuration.html
-# ACCOUNT_ADAPTER = "users.adapters.AccountAdapter"
+
+
+# # django-allauth config
 # # https://django-allauth.readthedocs.io/en/latest/configuration.html
+# # ------------------------------------------------------------------------------
+ACCOUNT_ADAPTER = "userauth.adapter.CustomAccountAdapter"
+CUSTOM_ACCOUNT_CONFIRM_EMAIL_URL = "verify-email/?key={0}"
+CUSTOM_ACCOUNT_PASSWORD_RESET_CONFIRM_URL = "password/reset/confirm/"
+#URL_FRONT = "http://localhost:8000/"
+#ACCOUNT_ADAPTER = "users.adapters.AccountAdapter"
+ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
+ACCOUNT_EMAIL_REQUIRED = True
+ACCOUNT_EMAIL_VERIFICATION = "mandatory"
+ACCOUNT_AUTHENTICATION_METHOD = "username_email"
+ACCOUNT_CONFIRM_EMAIL_ON_GET = False
+#ACCOUNT_USER_MODEL_USERNAME_FIELD = None
+ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = None
+#ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = reverse_lazy('account_confirm_complete')
+ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 3
+ACCOUNT_EMAIL_CONFIRMATION_HMAC = True
+ACCOUNT_EMAIL_SUBJECT_PREFIX = "Codebuddies: "
+ACCOUNT_DEFAULT_HTTP_PROTOCOL = "http"
+ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
+ACCOUNT_LOGOUT_ON_GET = False
+
 # SOCIALACCOUNT_ADAPTER = "users.adapters.SocialAccountAdapter"
 
 
+# #dj-rest-auth config
+# #https://dj-rest-auth.readthedocs.io/en/latest/configuration.html
+# # ---------------------------------------------------------------------------------
+REST_USE_JWT = True
+JWT_AUTH_COOKIE = 'cb-auth'
+OLD_PASSWORD_FIELD_ENABLED = True
+LOGOUT_ON_PASSWORD_CHANGE = True
+
+
 REST_FRAMEWORK = {
     'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
 
@@ -323,23 +345,14 @@
         'rest_framework.renderers.BrowsableAPIRenderer',
     ],
     'DEFAULT_AUTHENTICATION_CLASSES': [
+        'rest_framework_simplejwt.authentication.JWTAuthentication',
         'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
-        'rest_framework.authentication.SessionAuthentication',
+       # 'rest_framework.authentication.SessionAuthentication',
         'rest_framework.authentication.BasicAuthentication', ],
     'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
     'PAGE_SIZE': 10,
 }
 
-REST_USE_JWT = True
-
-JWT_AUTH = {
-    'JWT_AUTH_HEADER_PREFIX': 'Bearer',
-    'JWT_RESPONSE_PAYLOAD_HANDLER': 'core.utils.my_jwt_response_handler',
-    'JWT_ALLOW_REFRESH': True,
-    'JWT_EXPIRATION_DELTA': timedelta(hours=1),
-    'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=3),
-}
-
 CORS_ORIGIN_WHITELIST = (
     'https://127.0.0.1:3000',
     'http://localhost:3000',
diff --git a/project/config/urls.py b/project/config/urls.py
index 4391a52d..ad2802a7 100644
--- a/project/config/urls.py
+++ b/project/config/urls.py
@@ -4,25 +4,31 @@
 from django.contrib import admin
 from django.views.generic import TemplateView
 from django.views import defaults as default_views
-from rest_framework.exceptions import server_error
+from rest_framework import routers, serializers, viewsets
+from resources.urls import router as resources_router
+from userauth.urls import router as userauth_router
 
+router = routers.DefaultRouter()
+router.registry.extend(resources_router.registry)
+router.registry.extend(userauth_router.registry)
 
 urlpatterns = [
     path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
-    path(
-        "about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
-    ),
+    path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"),
+
     # Django Admin, use {% url 'admin:index' %}
     path(settings.ADMIN_URL, admin.site.urls),
+
     # User management
     path("users/", include("users.urls", namespace="users")),
     path("accounts/", include("allauth.urls")),
 
     # Your stuff: custom urls includes go here
-    path('api/v1/', include('resources.urls')),
-    path('auth/', include('userauth.urls', namespace="userauth")),
-    path('rest-auth/', include('rest_auth.urls')),
-    path('rest-auth/registration/', include('rest_auth.registration.urls'))
+    #this is a route for logging into the "browsable api"  if not needed for testing, it should be omitted.
+    path('api/v1/', include('rest_framework.urls', namespace='rest_framework')),
+    path('api/v1/auth/', include('userauth.urls', namespace="userauth")),
+    path('api/v1/', include('resources.urls', namespace='resources')),
+
 
 ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 
diff --git a/project/core/templates/account/email/email _confirmation_subject.txt b/project/core/templates/account/email/email _confirmation_subject.txt
new file mode 100644
index 00000000..4c85ebb9
--- /dev/null
+++ b/project/core/templates/account/email/email _confirmation_subject.txt	
@@ -0,0 +1 @@
+{% include "account/email/email_confirmation_subject.txt" %}
diff --git a/project/core/templates/account/email/email_confirmation_message.txt b/project/core/templates/account/email/email_confirmation_message.txt
new file mode 100644
index 00000000..9714bf4d
--- /dev/null
+++ b/project/core/templates/account/email/email_confirmation_message.txt
@@ -0,0 +1,9 @@
+{% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site_name }}!
+
+You're receiving this e-mail because user {{ user_display }} has used this e-mail address to register an account on {{ site_domain }}.
+
+To confirm this is correct, go to {{ activate_url }}
+{% endblocktrans %}
+{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you from {{ site_name }}!
+{{ site_domain }}{% endblocktrans %}
+{% endautoescape %}
diff --git a/project/requirements/base.txt b/project/requirements/base.txt
index 3d62143e..f1c15b54 100644
--- a/project/requirements/base.txt
+++ b/project/requirements/base.txt
@@ -33,5 +33,7 @@ djangorestframework==3.10.2   # https://github.com/encode/django-rest-framework
 coreapi==2.3.3                # https://github.com/core-api/python-client
 django_taggit_serializer==0.1.7 #https://github.com/glemmaPaul/django-taggit-serializer
 drf-jwt==1.13.4               # https://github.com/Styria-Digital/django-rest-framework-jwt
+djangorestframework-simplejwt #https://github.com/SimpleJWT/django-rest-framework-simplejwt
 django-rest-auth==0.9.5 #https://github.com/Tivix/django-rest-auth
+dj-rest-auth==1.1.1                       #https://github.com/jazzband/dj-rest-auth
 django-rest-authtoken==2.1.3 #https://pypi.org/project/django-rest-authtoken/
diff --git a/project/resources/serializers.py b/project/resources/serializers.py
index 2d7329bb..7d72cf4a 100644
--- a/project/resources/serializers.py
+++ b/project/resources/serializers.py
@@ -7,19 +7,14 @@
 class MediaTypeSerializerField(serializers.ChoiceField):
 
     def to_representation(self, value):
-
         valid_media_types = ', '.join(item for item in self.choices)
-
         if not value:
             return ''
-
         else:
             try:
                 media_type = self.choices[value]
-
-            except KeyError as err:
-                raise KeyError(f'Invalid media type.  The media type should be one of the following: {valid_media_types}') from err
-
+            except KeyError:
+                raise serializers.ValidationError( f'Invalid media type.  The media type should be one of the following:  {valid_media_types}')
             return media_type
 
     def to_internal_value(self,  value):
diff --git a/project/resources/urls.py b/project/resources/urls.py
index 37d35615..5440e3c7 100644
--- a/project/resources/urls.py
+++ b/project/resources/urls.py
@@ -2,10 +2,10 @@
 from rest_framework import routers
 from . import views
 
-router = routers.DefaultRouter()
+app_name = 'resources'
+router = routers.SimpleRouter()
 router.register(r'resources', views.ResourceView, basename='resources')
 
 urlpatterns = [
     path('', include(router.urls)),
-    path('resource/', include('rest_framework.urls', namespace='rest_framework')),
 ]
diff --git a/project/userauth/adapter.py b/project/userauth/adapter.py
new file mode 100644
index 00000000..0f6c97df
--- /dev/null
+++ b/project/userauth/adapter.py
@@ -0,0 +1,10 @@
+from allauth.account.adapter import DefaultAccountAdapter
+from allauth.utils import build_absolute_uri
+from django.conf import settings
+
+class CustomAccountAdapter(DefaultAccountAdapter):
+
+    def get_email_confirmation_url(self, request, emailconfirmation):
+        url = settings.CUSTOM_ACCOUNT_CONFIRM_EMAIL_URL.format(emailconfirmation.key)
+        result = build_absolute_uri(request, url)
+        return result
diff --git a/project/userauth/serializers.py b/project/userauth/serializers.py
index 25a7e069..a41e3511 100644
--- a/project/userauth/serializers.py
+++ b/project/userauth/serializers.py
@@ -1,5 +1,8 @@
 from rest_framework import serializers
 from rest_framework_jwt.settings import api_settings
+from dj_rest_auth.serializers import LoginSerializer
+from django.conf import settings
+from allauth.account.models import EmailAddress
 from django.contrib.auth import get_user_model
 
 
@@ -11,7 +14,7 @@ class UserSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = get_user_model()
-        fields = ('id', 'username', 'first_name', 'last_name', 'is_superuser',)
+        fields = ('id', 'username', 'email', 'first_name', 'last_name', 'is_superuser')
         lookup_field = 'username'
 
 class UserSerializerWithToken(serializers.ModelSerializer):
@@ -43,3 +46,37 @@ def create(self, validated_data):
         instance.save()
 
         return instance
+
+class VerifyEmailSerializer(serializers.ModelSerializer):
+
+    def post(self, request):
+        serializer = self.get_serializer(data=request.DATA)
+
+        if serializer.is_valid():
+            user = UserSerializer(read_only=True)
+
+            # check if settings swith is on / then check validity
+            if settings.ACCOUNT_EMAIL_VERIFICATION == settings.ACCOUNT_EMAIL_VERIFICATION_MANDATORY:
+                email_address = user.emailaddress_set.get(email=user.email)
+
+                if not email_address.verified:
+                    raise serializers.ValidationError(f'Your email is not verified.  Please verify your email before continuing.')
+
+        token = serializer.object.get('token')
+        response_data = jwt_response_payload_handler(token, user, request)
+
+        return response_data
+
+
+class UserLoginSerializer(LoginSerializer):
+
+    user = get_user_model()
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+    def post(self):
+
+        if settings.ACCOUNT_EMAIL_VERIFICATION == 'mandatory':
+            if not EmailAddress.objects.get(email=user.get('email').verified()):
+                raise serializers.ValidationError(f'Your email is not verified.  Please verify your email before continuing.')
diff --git a/project/userauth/urls.py b/project/userauth/urls.py
index fc27d1f9..5993f906 100644
--- a/project/userauth/urls.py
+++ b/project/userauth/urls.py
@@ -1,15 +1,32 @@
-from django.urls import path
-from rest_framework_jwt.views import obtain_jwt_token
-from rest_framework_jwt.views import refresh_jwt_token
-from rest_framework_jwt.views import verify_jwt_token
-from .views import current_user, UserList
+from django.urls import include, path, re_path
+from django.views.generic import RedirectView, TemplateView
+from rest_framework import routers
+from dj_rest_auth.views import PasswordResetView, PasswordResetConfirmView
+from rest_framework_simplejwt.views import (
+    TokenObtainPairView,
+    TokenRefreshView,
+    TokenVerifyView,
+)
+
+from . import views
 
 app_name = "userauth"
+router = routers.SimpleRouter(r'userauth')
+
+urlpatterns = (
+    path('', include(router.urls)),
+    path('', include('dj_rest_auth.urls')),
+    path('registration/', include('dj_rest_auth.registration.urls')),
+    path('registration/verify-email/', views.VerifyEmailView.as_view(), name='email_confirm'),
+    path('password/reset/', PasswordResetView.as_view(), name='password_reset'),
+    path('password/reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', TemplateView.as_view(template_name="password_reset_confirm.html"), name='password_reset_confirm'),
+    path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
+    path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
+    path('token/verify/', TokenVerifyView.as_view(), name='token_verify'),
+    path('current_user/', views.current_user),
+    path('users/', views.UserList.as_view()),
+ )
+
+#EmailAddress.objects.filter(user=self.request.user, verified=True).exists()
 
-urlpatterns = [
-    path('obtain_token/', obtain_jwt_token),
-    path('refresh_token/', refresh_jwt_token),
-    path('validate_token/', verify_jwt_token),
-    path('current_user/', current_user),
-    path('users/', UserList.as_view()),
-]
+#(?P<key>[-:\w]+)/$'
diff --git a/project/userauth/views.py b/project/userauth/views.py
index 4df48c58..54a4bbe5 100644
--- a/project/userauth/views.py
+++ b/project/userauth/views.py
@@ -1,8 +1,14 @@
+from django.contrib.auth import get_user_model
+from django.http import HttpResponseRedirect
 from rest_framework import permissions, status
 from rest_framework.decorators import api_view
 from rest_framework.response import Response
 from rest_framework.views import APIView
-from .serializers import UserSerializer, UserSerializerWithToken
+from dj_rest_auth.registration.views import VerifyEmailView
+from dj_rest_auth.views import LoginView
+from allauth.account.views import ConfirmEmailView
+from allauth.account.models import EmailConfirmation, EmailConfirmationHMAC
+from .serializers import UserSerializer, VerifyEmailSerializer, UserLoginSerializer
 
 
 @api_view(['GET'])
@@ -24,8 +30,36 @@ class UserList(APIView):
     permission_classes = (permissions.AllowAny,)
 
     def post(self, request, format=None):
-        serializer = UserSerializerWithToken(data=request.data)
+        serializer = UserSerializer(data=request.data)
         if serializer.is_valid():
             serializer.save()
             return Response(serializer.data, status=status.HTTP_201_CREATED)
         return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+class VerifyEmailView(APIView, ConfirmEmailView):
+    permission_classes = (permissions.AllowAny,)
+    allowed_methods = ('POST', 'OPTIONS', 'HEAD')
+
+    def get_serializer(self, *args, **kwargs):
+        return VerifyEmailSerializer(*args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        serializer = self.get_serializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+        self.kwargs['key'] = serializer.validated_data['key']
+        confirmation = self.get_object()
+        confirmation.confirm(self.request)
+        return Response({'detail': ('HiHi!!')}, status=status.HTTP_200_OK)
+
+class CustomLoginView(LoginView):
+
+    permission_classes = (permissions.AllowAny,)
+    allowed_methods = ('POST', 'OPTIONS', 'HEAD')
+
+    def get_serializer(self, *args, **kwargs):
+        return UserLoginSerializer(*args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        serializer = self.get_serializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
diff --git a/project/users/urls.py b/project/users/urls.py
index e2b75707..cf22b1a2 100644
--- a/project/users/urls.py
+++ b/project/users/urls.py
@@ -1,4 +1,4 @@
-from django.urls import path
+from django.urls import include, path
 
 from users.views import (
     user_redirect_view,
@@ -7,6 +7,7 @@
 )
 
 app_name = "users"
+
 urlpatterns = [
     path("~redirect/", view=user_redirect_view, name="redirect"),
     path("~update/", view=user_update_view, name="update"),

From cdadba1b19f6c542806609ad9378fdd2650cc9ea Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 21 Sep 2020 20:48:44 -0700
Subject: [PATCH 06/32] Removed django-rest-jwt and django-rest-auth added
 simplejwt and dj-rest-auth settings.

---
 project/config/settings/base.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/project/config/settings/base.py b/project/config/settings/base.py
index 6105773a..4018957e 100644
--- a/project/config/settings/base.py
+++ b/project/config/settings/base.py
@@ -297,7 +297,7 @@
 # # ------------------------------------------------------------------------------
 ACCOUNT_ADAPTER = "userauth.adapter.CustomAccountAdapter"
 CUSTOM_ACCOUNT_CONFIRM_EMAIL_URL = "verify-email/?key={0}"
-CUSTOM_ACCOUNT_PASSWORD_RESET_CONFIRM_URL = "password/reset/confirm/"
+CUSTOM_ACCOUNT_PASSWORD_RESET_CONFIRM_URL = "password/reset/<uidb64>/<token>/"
 #URL_FRONT = "http://localhost:8000/"
 #ACCOUNT_ADAPTER = "users.adapters.AccountAdapter"
 ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
@@ -326,6 +326,12 @@
 OLD_PASSWORD_FIELD_ENABLED = True
 LOGOUT_ON_PASSWORD_CHANGE = True
 
+REST_AUTH_SERIALIZERS = {
+    'USER_DETAILS_SERIALIZER': 'userauth.serializers.CustomUserDetailSerializer',
+    'PASSWORD_RESET_SERIALIZER' : 'userauth.serializers.CustomPasswordResetSerializer',
+    'PASSWORD_RESET_CONFIRM_SERIALIZER': 'userauth.serializers.CustomPasswordResetConfirmSerializer',
+}
+
 
 REST_FRAMEWORK = {
     'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
@@ -346,9 +352,9 @@
     ],
     'DEFAULT_AUTHENTICATION_CLASSES': [
         'rest_framework_simplejwt.authentication.JWTAuthentication',
-        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
-       # 'rest_framework.authentication.SessionAuthentication',
+        'rest_framework.authentication.SessionAuthentication',
         'rest_framework.authentication.BasicAuthentication', ],
+
     'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
     'PAGE_SIZE': 10,
 }

From 782e05b35f4453359b21aad57326d343db127e22 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 21 Sep 2020 20:49:34 -0700
Subject: [PATCH 07/32] Added routes for registration and allauth.

---
 project/config/urls.py | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/project/config/urls.py b/project/config/urls.py
index ad2802a7..c52fd57b 100644
--- a/project/config/urls.py
+++ b/project/config/urls.py
@@ -20,13 +20,16 @@
     path(settings.ADMIN_URL, admin.site.urls),
 
     # User management
-    path("users/", include("users.urls", namespace="users")),
+    #currently inactive endpoint, but can re-activate if needed
+    #path("users/", include("users.urls", namespace="users")),
+
+    #we have to include these for registration email validation, but otherwise these paths are NOT used
     path("accounts/", include("allauth.urls")),
 
     # Your stuff: custom urls includes go here
     #this is a route for logging into the "browsable api"  if not needed for testing, it should be omitted.
     path('api/v1/', include('rest_framework.urls', namespace='rest_framework')),
-    path('api/v1/auth/', include('userauth.urls', namespace="userauth")),
+    path('api/v1/auth/', include(('userauth.urls', 'userauth'), namespace="userauth")),
     path('api/v1/', include('resources.urls', namespace='resources')),
 
 

From 040c6f3bda8dfc677b496ebc157684ae19add331 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 21 Sep 2020 20:50:11 -0700
Subject: [PATCH 08/32] Added password reset email template.

---
 .../registration/password_reset_email.html         | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
 create mode 100644 project/core/templates/registration/password_reset_email.html

diff --git a/project/core/templates/registration/password_reset_email.html b/project/core/templates/registration/password_reset_email.html
new file mode 100644
index 00000000..2d258766
--- /dev/null
+++ b/project/core/templates/registration/password_reset_email.html
@@ -0,0 +1,14 @@
+{% load i18n %}{% autoescape off %}
+2 	{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
+3
+4 	{% trans "Please go to the following page and choose a new password:" %}
+5 	{% block reset_link %}
+6 	{{ protocol }}://{{ domain }}{% url 'userauth:password_reset_confirm' uidb64=uid token=token %}
+7 	{% endblock %}
+8 	{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
+9
+10 	{% trans "Thanks for using our site!" %}
+11
+12 	{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
+13
+14 	{% endautoescape %}

From ef3a5ad08e3d18471c462603bfadeef07253f5fd Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 21 Sep 2020 20:51:18 -0700
Subject: [PATCH 09/32] Added confirm_email method.  Will probably remove it as
 uneeded later.

---
 project/userauth/adapter.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/project/userauth/adapter.py b/project/userauth/adapter.py
index 0f6c97df..55dbbbf8 100644
--- a/project/userauth/adapter.py
+++ b/project/userauth/adapter.py
@@ -1,4 +1,5 @@
 from allauth.account.adapter import DefaultAccountAdapter
+from django.contrib.sites.models import Site
 from allauth.utils import build_absolute_uri
 from django.conf import settings
 
@@ -8,3 +9,11 @@ def get_email_confirmation_url(self, request, emailconfirmation):
         url = settings.CUSTOM_ACCOUNT_CONFIRM_EMAIL_URL.format(emailconfirmation.key)
         result = build_absolute_uri(request, url)
         return result
+
+    def confirm_email(self, request, email_address):
+        """
+        Marks the email address as confirmed on the db
+        """
+        email_address.verified = True
+        email_address.set_as_primary(conditional=True)
+        email_address.save()

From 7e6ff72b7b48061ccaaf882225a1fcb58898b8e8 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 21 Sep 2020 20:52:17 -0700
Subject: [PATCH 10/32] Added CustomUserDetailsSerializer.  Stubbed other
 serializers for confrm emails and password reset.

---
 project/userauth/serializers.py | 83 +++++++++++----------------------
 1 file changed, 27 insertions(+), 56 deletions(-)

diff --git a/project/userauth/serializers.py b/project/userauth/serializers.py
index a41e3511..6d2b8d25 100644
--- a/project/userauth/serializers.py
+++ b/project/userauth/serializers.py
@@ -1,11 +1,12 @@
 from rest_framework import serializers
 from rest_framework_jwt.settings import api_settings
-from dj_rest_auth.serializers import LoginSerializer
-from django.conf import settings
-from allauth.account.models import EmailAddress
+from dj_rest_auth.serializers import JWTSerializer, UserDetailsSerializer
+from dj_rest_auth.registration.serializers import VerifyEmailSerializer
+from dj_rest_auth.serializers import PasswordResetSerializer, PasswordResetConfirmSerializer
 from django.contrib.auth import get_user_model
 
 
+#customize this for a user profile view
 class UserSerializer(serializers.ModelSerializer):
     """
     We use get_user_model here because of the custom User model.
@@ -14,69 +15,39 @@ class UserSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = get_user_model()
-        fields = ('id', 'username', 'email', 'first_name', 'last_name', 'is_superuser')
+        fields = ('id', 'username', 'first_name', 'last_name', 'is_superuser',)
         lookup_field = 'username'
 
-class UserSerializerWithToken(serializers.ModelSerializer):
 
-    password = serializers.CharField(write_only=True)
-    token = serializers.SerializerMethodField()
+#customize this for a user profile change
+class CustomUserDetailSerializer(UserDetailsSerializer):
+    """
+    We use get_user_model here because of the custom User model.
+    See: https://wsvincent.com/django-referencing-the-user-model/.
+    """
 
     class Meta:
         model = get_user_model()
-        fields = ('username', 'token', 'password', 'first_name', 'last_name', 'email')
-        extra_kwargs = {'password': {'write_only': True}}
-
-    def get_token(self, obj):
-        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
-        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
-
-        payload = jwt_payload_handler(obj)
-        token = jwt_encode_handler(payload)
-
-        return token
-
-
-    def create(self, validated_data):
-        password = validated_data.pop('password', None)
-        instance = self.Meta.model(**validated_data)
-
-        if password is not None:
-            instance.set_password(password)
-        instance.save()
-
-        return instance
-
-class VerifyEmailSerializer(serializers.ModelSerializer):
-
-    def post(self, request):
-        serializer = self.get_serializer(data=request.DATA)
-
-        if serializer.is_valid():
-            user = UserSerializer(read_only=True)
-
-            # check if settings swith is on / then check validity
-            if settings.ACCOUNT_EMAIL_VERIFICATION == settings.ACCOUNT_EMAIL_VERIFICATION_MANDATORY:
-                email_address = user.emailaddress_set.get(email=user.email)
-
-                if not email_address.verified:
-                    raise serializers.ValidationError(f'Your email is not verified.  Please verify your email before continuing.')
-
-        token = serializer.object.get('token')
-        response_data = jwt_response_payload_handler(token, user, request)
+        fields = ('id', 'username', 'email', 'first_name', 'last_name', 'is_superuser')
+        lookup_field = 'username'
 
-        return response_data
 
+#customize this for a user email validation link
+class CustomVerifyEmailSerializer(VerifyEmailSerializer):
+    key = serializers.CharField()
 
-class UserLoginSerializer(LoginSerializer):
 
-    user = get_user_model()
+#customize this for the password reset email.
+class CustomPasswordResetSerializer(PasswordResetSerializer):
+    """
+    Serializer for requesting a password reset e-mail.
+    """
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+    def get_email_options(self):
+        """Override this method to change default e-mail options"""
+        return {}
 
-    def post(self):
 
-        if settings.ACCOUNT_EMAIL_VERIFICATION == 'mandatory':
-            if not EmailAddress.objects.get(email=user.get('email').verified()):
-                raise serializers.ValidationError(f'Your email is not verified.  Please verify your email before continuing.')
+#customize this for the passowrd reset confirmation
+class CustomPasswordResetConfirmSerializer(PasswordResetConfirmSerializer):
+    pass

From e97153b4f0c66fd88c95caa6532c29d805b4f849 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 21 Sep 2020 20:53:05 -0700
Subject: [PATCH 11/32] Added routes for registration, email confirmation,
 password reset, login, logout, and user details.  Reorganized.

---
 project/userauth/urls.py | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/project/userauth/urls.py b/project/userauth/urls.py
index 5993f906..19f71ae8 100644
--- a/project/userauth/urls.py
+++ b/project/userauth/urls.py
@@ -1,5 +1,4 @@
-from django.urls import include, path, re_path
-from django.views.generic import RedirectView, TemplateView
+from django.urls import include, path, re_path, reverse_lazy
 from rest_framework import routers
 from dj_rest_auth.views import PasswordResetView, PasswordResetConfirmView
 from rest_framework_simplejwt.views import (
@@ -14,19 +13,14 @@
 router = routers.SimpleRouter(r'userauth')
 
 urlpatterns = (
-    path('', include(router.urls)),
     path('', include('dj_rest_auth.urls')),
+    path('', include(router.urls)),
     path('registration/', include('dj_rest_auth.registration.urls')),
-    path('registration/verify-email/', views.VerifyEmailView.as_view(), name='email_confirm'),
+    path('registration/verify-email/', views.CustomVerifyEmailView.as_view(), name='account_email_verification_sent'),
     path('password/reset/', PasswordResetView.as_view(), name='password_reset'),
-    path('password/reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', TemplateView.as_view(template_name="password_reset_confirm.html"), name='password_reset_confirm'),
+    path('password/reset/confirm/<uidb64>/<token>/', PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
     path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
     path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
     path('token/verify/', TokenVerifyView.as_view(), name='token_verify'),
     path('current_user/', views.current_user),
-    path('users/', views.UserList.as_view()),
  )
-
-#EmailAddress.objects.filter(user=self.request.user, verified=True).exists()
-
-#(?P<key>[-:\w]+)/$'

From b6c4ac215faa5b9d72c5e562cb46fdbfa39e1717 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 21 Sep 2020 20:54:14 -0700
Subject: [PATCH 12/32] Added CustomVerifyEmailView to enable email verify
 emails from the backend.

---
 project/userauth/views.py | 47 ++++++++++-----------------------------
 1 file changed, 12 insertions(+), 35 deletions(-)

diff --git a/project/userauth/views.py b/project/userauth/views.py
index 54a4bbe5..79c57a05 100644
--- a/project/userauth/views.py
+++ b/project/userauth/views.py
@@ -1,14 +1,15 @@
 from django.contrib.auth import get_user_model
-from django.http import HttpResponseRedirect
+from django.contrib.sites.models import Site
+from django.contrib.auth import views as auth_views
+from django.utils.translation import ugettext_lazy as _
 from rest_framework import permissions, status
 from rest_framework.decorators import api_view
 from rest_framework.response import Response
+from rest_framework.exceptions import NotFound, MethodNotAllowed
 from rest_framework.views import APIView
 from dj_rest_auth.registration.views import VerifyEmailView
-from dj_rest_auth.views import LoginView
-from allauth.account.views import ConfirmEmailView
-from allauth.account.models import EmailConfirmation, EmailConfirmationHMAC
-from .serializers import UserSerializer, VerifyEmailSerializer, UserLoginSerializer
+from dj_rest_auth.views import PasswordResetConfirmView as dj_PasswordResetConfirmView
+from .serializers import UserSerializer, CustomVerifyEmailSerializer
 
 
 @api_view(['GET'])
@@ -21,45 +22,21 @@ def current_user(request):
     return Response(serializer.data)
 
 
-class UserList(APIView):
-    """
-    Create a new user. It's called 'UserList' because normally we'd have a get
-    method here too, for retrieving a list of all User objects.
-    """
-
-    permission_classes = (permissions.AllowAny,)
-
-    def post(self, request, format=None):
-        serializer = UserSerializer(data=request.data)
-        if serializer.is_valid():
-            serializer.save()
-            return Response(serializer.data, status=status.HTTP_201_CREATED)
-        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
-
-
-class VerifyEmailView(APIView, ConfirmEmailView):
+#this is required for the allauth "validate your email address" email to work.
+class CustomVerifyEmailView(VerifyEmailView):
     permission_classes = (permissions.AllowAny,)
     allowed_methods = ('POST', 'OPTIONS', 'HEAD')
 
     def get_serializer(self, *args, **kwargs):
         return VerifyEmailSerializer(*args, **kwargs)
 
+    def get(self, *args, **kwargs):
+        raise MethodNotAllowed('GET')
+
     def post(self, request, *args, **kwargs):
         serializer = self.get_serializer(data=request.data)
         serializer.is_valid(raise_exception=True)
         self.kwargs['key'] = serializer.validated_data['key']
         confirmation = self.get_object()
         confirmation.confirm(self.request)
-        return Response({'detail': ('HiHi!!')}, status=status.HTTP_200_OK)
-
-class CustomLoginView(LoginView):
-
-    permission_classes = (permissions.AllowAny,)
-    allowed_methods = ('POST', 'OPTIONS', 'HEAD')
-
-    def get_serializer(self, *args, **kwargs):
-        return UserLoginSerializer(*args, **kwargs)
-
-    def post(self, request, *args, **kwargs):
-        serializer = self.get_serializer(data=request.data)
-        serializer.is_valid(raise_exception=True)
+        return Response({'detail': _('ok')}, status=status.HTTP_200_OK)

From f06a74c444f57f651b46f649557b53d9babd8af6 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Mon, 21 Sep 2020 20:54:47 -0700
Subject: [PATCH 13/32] Added tests for registration flow, token authorization,
 email verification, etc.

---
 project/userauth/tests.py | 849 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 846 insertions(+), 3 deletions(-)

diff --git a/project/userauth/tests.py b/project/userauth/tests.py
index 7b499d1e..1193d5d2 100644
--- a/project/userauth/tests.py
+++ b/project/userauth/tests.py
@@ -1,15 +1,857 @@
+import re
+import pytest, pytest_django
+import datetime
 from unittest.mock import patch
+from allauth.account.models import EmailAddress
 from rest_framework import status, serializers
-from rest_framework.test import APITestCase
+from rest_framework.test import APITestCase, URLPatternsTestCase
 from rest_framework_jwt.settings import api_settings
+from rest_framework_simplejwt.tokens import Token, AccessToken, RefreshToken
+from rest_framework_simplejwt.utils import (aware_utcnow, datetime_from_epoch, datetime_to_epoch, format_lazy)
 from users.factories import UserFactory
 from factory import PostGenerationMethodCall
+from django.core import mail
 from django.contrib.auth import get_user_model
+from django.utils.translation import gettext_lazy as _
+from django.db import models
 
 
-jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
-jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
 
+class UserauthTests(APITestCase):
+
+
+    def setUp(self):
+        #since the factories haven't been re-written, we're hardcoding here
+        self.user = {
+                "username": 'PetuniaPiglet',
+                "email": 'Petunia@thepiggyfarm.net',
+                "password1":  'codebuddies',
+                "password2": 'codebuddies'
+                }
+
+    def test_registration_get(self):
+        """Ensure that a GET request is not accepted for the endpoint."""
+
+        url = '/api/v1/auth/registration/'
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+        self.assertEqual(response.data['detail'], "Method \"GET\" not allowed.")
+
+    def test_registration_post(self):
+        """Ensure that a user is created  in the db and a validation email message is returned upon user registering."""
+
+        url = '/api/v1/auth/registration/'
+        data = {
+                "username": self.user['username'],
+                "email": self.user['email'],
+                "password1":  self.user['password1'],
+                "password2": self.user['password2']
+                }
+
+        response = self.client.post(url, data, format='json')
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.data['detail'], "Verification e-mail sent.")
+
+    def test_registered_user_creation(self):
+        """Test that a registration POST creates a user in the DB."""
+
+        url = '/api/v1/auth/registration/'
+        data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        response = self.client.post(url, data, format='json')
+        model = get_user_model()
+        new_user = model.objects.get(username=self.user['username'])
+
+        assert new_user.username == self.user['username']
+        assert new_user.email == self.user['email']
+
+    def test_registration_emailaddress_validation_email(self):
+        """Ensure that a validation email is sent upon user registering."""
+
+        url = '/api/v1/auth/registration/'
+        data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+        response = self.client.post(url, data, format='json')
+
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.data['detail'], "Verification e-mail sent.")
+
+        # did Django actually send an email?
+        assert len(mail.outbox) == 1, "Inbox is not empty"
+
+    def test_validation_email_content(self):
+        # start by registering a user
+        url = '/api/v1/auth/registration/'
+        data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+        response = self.client.post(url, data, format='json')
+
+        #is the email subject what we expect it to be?
+        verify_email_message = mail.outbox[0]
+        self.assertEqual(verify_email_message.subject, 'Codebuddies: Please Confirm Your E-mail Address')
+
+        #extracting what we need for the verification link
+        uri_regex = re.compile(r"(\/api\/v1\/auth\/registration\/verify-email\/)(\?key=)([\w:-]+)")
+        confirmation_uri = re.search(uri_regex, verify_email_message.body)
+
+        # is the uri for the verification link correct?
+        verification_path = "/api/v1/auth/registration/verify-email/"
+        self.assertEqual(confirmation_uri[1], verification_path)
+
+    def test_verify_email_path_get(self):
+        """Ensure that a GET request is not accepted for the endpoint."""
+
+        url = '/api/v1/auth/registration/verify-email/'
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+        self.assertEqual(response.data['detail'], "Method \"GET\" not allowed.")
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_verify_email_path_post(self):
+
+        # start by registering a user
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+        response = self.client.post(reg_url, reg_data, format='json')
+
+        # grab email from outbox so we can extract the verification link
+        email_message = mail.outbox[0]
+        verify_email_message = email_message.body
+
+        # extracting what we need for the verification post action
+        uri_regex = re.compile(r"(\/api\/v1\/auth\/registration\/verify-email\/)(\?key=)([\w:]+)")
+        confirmation_uri = re.search(uri_regex, verify_email_message)
+
+        # now, let's post the key to trigger validation
+        validate_email_url = f'/api/v1/auth/registration/verify-email/'
+        validate_key_data = {"key": confirmation_uri[3]}
+        validation_response = self.client.post(validate_email_url, validate_key_data, format='json')
+        print(validation_response)
+
+        # did the post result in the correct status messages?
+        self.assertEqual(validation_response.status_code, status.HTTP_200_OK)
+        self.assertEqual(validation_response.data["detail"], "ok")
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_verify_email_marked_valid_after_post(self):
+
+        #start by registering a user
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+        response = self.client.post(reg_url, reg_data, format='json')
+
+        #grab email from outbox so we can extract the verification link
+        verify_email_message = mail.outbox[0]
+
+        #extracting what we need for the verification post action
+        uri_regex = re.compile(r"(\/api\/v1\/auth\/registration\/verify-email\/)(\?key=)([\w:-]+)")
+        confirmation_uri = re.search(uri_regex, verify_email_message.body)
+
+
+        #now, let's post the key to trigger validation
+        email_url = '/api/v1/auth/registration/verify-email/'
+        key_data = {"key": confirmation_uri[3]}
+        response = self.client.post(email_url, key_data, format='json')
+
+        #did the post succeed in marking the email as valid in the DB?
+        model = get_user_model()
+        email_to_verify = EmailAddress.objects.get(email=reg_data['email'])
+        user = model.objects.get(pk=email_to_verify.user_id)
+
+        self.assertEqual(email_to_verify.verified, True)
+
+    def test_login_with_unverified_email(self):
+
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        reg_response = self.client.post(reg_url, reg_data, format='json')
+
+        login_url = '/api/v1/auth/login/'
+        login_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password": self.user['password1']
+        }
+
+        response = self.client.post(login_url, login_data, format='json')
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.data['non_field_errors'], ["E-mail is not verified."])
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_login_with_verified_email(self):
+
+        #first we register through the registration endpoint
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        self.client.post(reg_url, reg_data, format='json')
+
+        #next, we retrieve the newly created user model and their corresponding account_emailaddress
+        model = get_user_model()
+        email_to_verify = EmailAddress.objects.get(email=reg_data['email'])
+
+        #we set the verified flag to True for the account_emailaddress and save
+        email_to_verify.verified = 1
+        email_to_verify.save()
+
+        #we grab the user object based on the updated email for use in logging in
+        user = model.objects.get(pk=email_to_verify.user_id)
+
+        #we login via the login endpoint
+        login_url = '/api/v1/auth/login/'
+        login_data = {
+            "username": user.username,
+            "email": user.email,
+            "password": "codebuddies",
+        }
+        response = self.client.post(login_url, login_data, format='json')
+
+        #we validate that the user we posted is logged in, and an access_token and refresh_token are returned
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(response.data['user']['email'], user.email)
+        self.assertEqual(response.data['user']['username'], user.username)
+        self.assertContains(response, "access_token")
+        self.assertContains(response, "refresh_token")
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_login_bad_password(self):
+
+        # first we register through the registration endpoint
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        self.client.post(reg_url, reg_data, format='json')
+
+        # next, we retrieve the newly created user model and their corresponding account_emailaddress
+        model = get_user_model()
+        email_to_verify = EmailAddress.objects.get(email=reg_data['email'])
+
+        # we set the verified flag to True for the account_emailaddress and save
+        email_to_verify.verified = 1
+        email_to_verify.save()
+
+        # we grab the user object based on the updated email for use in logging in
+        user = model.objects.get(pk=email_to_verify.user_id)
+
+        #we attempt a login via the login endpoint, but with a bad password
+        login_url = '/api/v1/auth/login/'
+        login_data = {
+            "username": user.username,
+            "email": user.email,
+            "password": "bad_password",
+        }
+        response = self.client.post(login_url, login_data, format='json')
+
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.data['non_field_errors'], ["Unable to log in with provided credentials."])
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_logout_get(self):
+
+        # first we register through the registration endpoint
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        self.client.post(reg_url, reg_data, format='json')
+
+        # next, we retrieve the newly created user model and their corresponding account_emailaddress
+        model = get_user_model()
+        email_to_verify = EmailAddress.objects.get(email=reg_data['email'])
+
+        # we set the verified flag to True for the account_emailaddress and save
+        email_to_verify.verified = 1
+        email_to_verify.save()
+
+        # we grab the user object based on the updated email for use in logging in
+        user = model.objects.get(pk=email_to_verify.user_id)
+
+        #we login via the login endpoint
+        login_url = '/api/v1/auth/login/'
+        login_data = {
+            "username": user.username,
+            "email": user.email,
+            "password": "codebuddies",
+        }
+        response = self.client.post(login_url, login_data, format='json')
+
+        #next, we try to logout using GET
+        logout_url = '/api/v1/auth/logout/'
+
+        response = self.client.get(logout_url, format='json')
+        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+        self.assertEqual(response.data['detail'], "Method \"GET\" not allowed.")
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_logout_post(self):
+
+        # first we register through the registration endpoint
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        self.client.post(reg_url, reg_data, format='json')
+
+        # next, we retrieve the newly created user model and their corresponding account_emailaddress
+        model = get_user_model()
+        email_to_verify = EmailAddress.objects.get(email=reg_data['email'])
+
+        # we set the verified flag to True for the account_emailaddress and save
+        email_to_verify.verified = 1
+        email_to_verify.save()
+
+        # we grab the user object based on the updated email for use in logging in
+        user = model.objects.get(pk=email_to_verify.user_id)
+
+        #we login via the login endpoint
+        login_url = '/api/v1/auth/login/'
+        login_data = {
+            "username": user.username,
+            "email": user.email,
+            "password": "codebuddies",
+        }
+        response = self.client.post(login_url, login_data, format='json')
+
+        #next, we trigger logout and verify
+        logout_url = '/api/v1/auth/logout/'
+
+        response = self.client.post(logout_url, format='json')
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(response.data['detail'], "Successfully logged out.")
+
+    def test_password_reset_request_get(self):
+        """Ensure that a GET request is not accepted for the endpoint."""
+
+        url = '/api/v1/auth/password/reset/'
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+        self.assertEqual(response.data['detail'], "Method \"GET\" not allowed.")
+
+
+    def test_password_reset_request_post(self):
+        """Ensure that a password reset email is sent upon POST to reset endpoint."""
+
+        reset_url = '/api/v1/auth/password/reset/'
+        reset_data = {"email": self.user['email']}
+
+        response = self.client.post(reset_url, reset_data, format='json')
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(response.data['detail'], "Password reset e-mail has been sent.")
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_password_reset_email_sent(self):
+
+        # first we register through the registration endpoint
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        self.client.post(reg_url, reg_data, format='json')
+
+        #next, we clean out the email outbox
+        mail.outbox.clear()
+
+        #next, we retrieve the newly created user model and their corresponding account_emailaddress
+        model = get_user_model()
+        user_to_reset = model.objects.get(username=reg_data['username'])
+        email_to_verify = EmailAddress.objects.get(email=user_to_reset.email)
+
+        # we set the verified flag to True for the reset_user_account_emailaddress and save
+        email_to_verify.verified = 1
+        email_to_verify.save()
+
+        reset_url = '/api/v1/auth/password/reset/'
+        reset_data = {"email": email_to_verify.email}
+        response = self.client.post(reset_url, reset_data, format='json')
+
+        # did Django actually send an email?
+        assert len(mail.outbox) == 1, "Inbox is not empty"
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_passowrd_reset_email_content(self):
+
+        # first we register through the registration endpoint
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        self.client.post(reg_url, reg_data, format='json')
+
+        #next, we clean out the email outbox
+        mail.outbox.clear()
+
+        #next, we retrieve the newly created user model and their corresponding account_emailaddress
+        model = get_user_model()
+        user_to_reset = model.objects.get(username=reg_data['username'])
+        email_to_verify = EmailAddress.objects.get(email=user_to_reset.email)
+
+        # we set the verified flag to True for the reset_user_account_emailaddress and save
+        email_to_verify.verified = 1
+        email_to_verify.save()
+
+        reset_url = '/api/v1/auth/password/reset/'
+        reset_data = {"email": email_to_verify.email}
+        response = self.client.post(reset_url, reset_data, format='json')
+
+        # is the email subject and addressee what we expect them to be?
+        reset_email_message = mail.outbox[0]
+        self.assertEqual(reset_email_message.subject, 'Password reset on CBV3 Django Prototype')
+        self.assertEqual(reset_email_message.to[0], user_to_reset.email)
+
+        # extracting what we need for the reset link
+        reset_uri_regex = re.compile(r"(\/api\/v1\/auth\/password\/reset\/confirm\/)([A-Z]+)([\w:-]+)")
+        reset_uri = re.search(reset_uri_regex, reset_email_message.body)
+
+        # is the uri for the verification link correct?
+        reset_path = "/api/v1/auth/password/reset/confirm/"
+        self.assertEqual(reset_uri[1], reset_path)
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_password_reset_path_get(self):
+
+        # first we register through the registration endpoint
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        self.client.post(reg_url, reg_data, format='json')
+
+        #next, we clean out the email outbox
+        mail.outbox.clear()
+
+        #next, we retrieve the newly created user model and their corresponding account_emailaddress
+        model = get_user_model()
+        user_to_reset = model.objects.get(username=reg_data['username'])
+        email_to_verify = EmailAddress.objects.get(email=user_to_reset.email)
+
+        # we set the verified flag to True for the reset_user_account_emailaddress and save
+        email_to_verify.verified = 1
+        email_to_verify.save()
+
+        reset_url = '/api/v1/auth/password/reset/'
+        reset_data = {"email": email_to_verify.email}
+        response = self.client.post(reset_url, reset_data, format='json')
+
+        # extracting what we need for the reset link
+        reset_email_message = mail.outbox[0]
+        reset_uri_regex = re.compile(r"(\/api\/v1\/auth\/password\/reset\/confirm\/)([\w]+)\/([\w:-]+)\/")
+        reset_uri = re.search(reset_uri_regex, reset_email_message.body)
+
+        # now we hit the uri with the info, but as a GET
+        password_reset_confirm_uri = f"/api/v1/auth/password/reset/confirm/{reset_uri[2]}/{reset_uri[3]}/"
+        reset_response = self.client.get(password_reset_confirm_uri)
+
+        self.assertEqual(reset_response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+        self.assertEqual(reset_response.data['detail'], "Method \"GET\" not allowed.")
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_password_reset_post(self):
+
+        # first we register through the registration endpoint
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        self.client.post(reg_url, reg_data, format='json')
+
+        #next, we clean out the email outbox
+        mail.outbox.clear()
+
+        #next, we retrieve the newly created user model and their corresponding account_emailaddress
+        model = get_user_model()
+        user_to_reset = model.objects.get(username=reg_data['username'])
+        email_to_verify = EmailAddress.objects.get(email=user_to_reset.email)
+
+        # we set the verified flag to True for the reset_user_account_emailaddress and save
+        email_to_verify.verified = 1
+        email_to_verify.save()
+
+        reset_url = '/api/v1/auth/password/reset/'
+        reset_email_address = {"email": email_to_verify.email}
+        response = self.client.post(reset_url, reset_email_address, format='json')
+
+        # extracting what we need for the reset link
+        reset_email_message = mail.outbox[0]
+        reset_uri_regex = re.compile(r"(\/api\/v1\/auth\/password\/reset\/confirm\/)([\w]+)\/([\w:-]+)\/")
+        reset_uri = re.search(reset_uri_regex, reset_email_message.body)
+        reset_uid = reset_uri[2]
+        reset_token = reset_uri[3]
+
+        # now we POST to the uri with the UID, TOKEN and the reset data
+        password_reset_confirm_uri = f"/api/v1/auth/password/reset/confirm/{reset_uid}/{reset_token}/"
+        password_reset_confirm_data = {
+            "new_password1": "codebuddies_II",
+            "new_password2": "codebuddies_II",
+            "uid": reset_uid,
+            "token": reset_token
+        }
+
+        password_reset_confirm_response = self.client.post(password_reset_confirm_uri, password_reset_confirm_data, format='json')
+        self.assertEqual(password_reset_confirm_response.status_code, status.HTTP_200_OK)
+        self.assertEqual(password_reset_confirm_response.data['detail'], "Password has been reset with the new password.")
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_password_reset_db_change(self):
+
+        # first we register through the registration endpoint
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        self.client.post(reg_url, reg_data, format='json')
+
+        #next, we clean out the email outbox
+        mail.outbox.clear()
+
+        #next, we retrieve the newly created user model and their corresponding account_emailaddress
+        model = get_user_model()
+        user_to_reset = model.objects.get(username=reg_data['username'])
+        email_to_verify = EmailAddress.objects.get(email=user_to_reset.email)
+
+        # we set the verified flag to True for the reset_user_account_emailaddress and save
+        email_to_verify.verified = 1
+        email_to_verify.save()
+
+        reset_url = '/api/v1/auth/password/reset/'
+        reset_email_address = {"email": email_to_verify.email}
+        response = self.client.post(reset_url, reset_email_address, format='json')
+
+        # extracting what we need for the reset link
+        reset_email_message = mail.outbox[0]
+        reset_uri_regex = re.compile(r"(\/api\/v1\/auth\/password\/reset\/confirm\/)([\w]+)\/([\w:-]+)\/")
+        reset_uri = re.search(reset_uri_regex, reset_email_message.body)
+        reset_uid = reset_uri[2]
+        reset_token = reset_uri[3]
+
+        # now we POST to the uri with the UID, TOKEN and the reset data
+        password_reset_confirm_uri = f"/api/v1/auth/password/reset/confirm/{reset_uid}/{reset_token}/"
+        password_reset_confirm_data = {
+            "new_password1": "codebuddies_II",
+            "new_password2": "codebuddies_II",
+            "uid": reset_uid,
+            "token": reset_token
+        }
+
+        password_reset_confirm_response = self.client.post(password_reset_confirm_uri, password_reset_confirm_data, format='json')
+        self.assertEqual(password_reset_confirm_response.status_code, status.HTTP_200_OK)
+        self.assertEqual(password_reset_confirm_response.data['detail'], "Password has been reset with the new password.")
+
+
+    @pytest.mark.django_db(transaction=True)
+    def test_login_with_reset_password(self):
+
+        # first we register through the registration endpoint
+        reg_url = '/api/v1/auth/registration/'
+        reg_data = {
+            "username": self.user['username'],
+            "email": self.user['email'],
+            "password1": self.user['password1'],
+            "password2": self.user['password2']
+        }
+
+        self.client.post(reg_url, reg_data, format='json')
+
+        #next, we clean out the email outbox
+        mail.outbox.clear()
+
+        #next, we retrieve the newly created user model and their corresponding account_emailaddress
+        model = get_user_model()
+        user_to_reset = model.objects.get(username=reg_data['username'])
+        email_to_verify = EmailAddress.objects.get(email=user_to_reset.email)
+
+        # we set the verified flag to True for the reset_user_account_emailaddress and save
+        email_to_verify.verified = 1
+        email_to_verify.save()
+
+        reset_url = '/api/v1/auth/password/reset/'
+        reset_email_address = {"email": email_to_verify.email}
+        response = self.client.post(reset_url, reset_email_address, format='json')
+
+        # extracting what we need for the reset link
+        reset_email_message = mail.outbox[0]
+        reset_uri_regex = re.compile(r"(\/api\/v1\/auth\/password\/reset\/confirm\/)([\w]+)\/([\w:-]+)\/")
+        reset_uri = re.search(reset_uri_regex, reset_email_message.body)
+        reset_uid = reset_uri[2]
+        reset_token = reset_uri[3]
+
+        # now we POST to the uri with the UID, TOKEN and the reset data
+        password_reset_confirm_uri = f"/api/v1/auth/password/reset/confirm/{reset_uid}/{reset_token}/"
+        password_reset_confirm_data = {
+            "new_password1": "codebuddies_II",
+            "new_password2": "codebuddies_II",
+            "uid": reset_uid,
+            "token": reset_token
+        }
+
+        self.client.post(password_reset_confirm_uri, password_reset_confirm_data, format='json')
+
+
+        #finally, we attempt a login with the newly reset password
+        new_login_uri = '/api/v1/auth/login/'
+        new_login_data = {
+            "username": user_to_reset.username,
+            "email": user_to_reset.email,
+            "password": "codebuddies_II",
+        }
+
+        new_login_response = self.client.post(new_login_uri, new_login_data, format='json')
+
+        # finally, we validate that the user we posted is
+        # logged in with the new password and an access_token and refresh_token are returned
+        self.assertEqual(new_login_response.status_code, status.HTTP_200_OK)
+        self.assertEqual(new_login_response.data['user']['email'], user_to_reset.email)
+        self.assertEqual(new_login_response.data['user']['username'], user_to_reset.username)
+        self.assertContains(new_login_response, "access_token")
+        self.assertContains(new_login_response, "refresh_token")
+
+    @pytest.mark.django_db(transaction=True)
+    def test_view_user_details_authed(self):
+
+        token_uri = '/api/v1/auth/token/'
+        user_to_view = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+        user_auth_data = {
+            "username": user_to_view.username,
+            "password": 'codebuddies'
+        }
+
+        authed_user_tokens = self.client.post(token_uri, user_auth_data, format='json')
+        authed_user_access_token = authed_user_tokens.data['access']
+        authed_user_refresh_token = authed_user_tokens.data['refresh']
+
+        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + authed_user_access_token)
+
+        user_details_uri = '/api/v1/auth/user/'
+        user_details_response = self.client.get(user_details_uri)
+
+        self.assertEqual(user_details_response.status_code, status.HTTP_200_OK)
+        self.assertEqual(user_details_response.data['username'], user_to_view.username)
+
+
+    def test_view_user_details_unauthed(self):
+
+        details_uri = '/api/v1/auth/user/'
+
+        details_response = self.client.get(details_uri)
+        self.assertEqual(details_response.status_code, status.HTTP_401_UNAUTHORIZED)
+        self.assertEqual(details_response.data['detail'], "Authentication credentials were not provided.")
+
+    def test_current_user_method_authed(self):
+        pass
+
+    def test_current_user_method_unauthed(self):
+        pass
+
+    def test_JWTtoken_obtain_pair(self):
+        token_uri = '/api/v1/auth/token/'
+        user_to_auth = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+        user_auth_data = {
+            "username": user_to_auth.username,
+            "password": 'codebuddies'
+        }
+
+        JWT_user_tokens = self.client.post(token_uri, user_auth_data, format='json')
+        self.assertEqual(JWT_user_tokens.status_code, status.HTTP_200_OK)
+        self.assertContains(JWT_user_tokens,  "access")
+        self.assertContains(JWT_user_tokens, "refresh")
+
+    def test_JWTtoken_refresh(self):
+        token_uri = '/api/v1/auth/token/'
+        user_to_refresh = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+        user_refresh_data = {
+            "username": user_to_refresh.username,
+            "password": 'codebuddies'
+        }
+
+        JWT_user_obtain_tokens = self.client.post(token_uri, user_refresh_data, format='json')
+
+        #now we refresh the token
+        refresh_uri = '/api/v1/auth/token/refresh/'
+        data_to_refresh = {
+            "refresh": JWT_user_obtain_tokens.data['refresh'],
+        }
+
+        renewed_token = self.client.post(refresh_uri, data_to_refresh, format='json')
+
+        self.assertEqual(renewed_token.status_code, status.HTTP_200_OK)
+        self.assertContains(renewed_token, "access")
+
+    def test_JWTtoken_validate_access(self):
+
+        token_uri = '/api/v1/auth/token/'
+        user_to_validate = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+        user_validate_data = {
+            "username": user_to_validate.username,
+            "password": 'codebuddies'
+        }
+
+        JWT_user_validate_tokens = self.client.post(token_uri, user_validate_data, format='json')
+
+        # now we validate the token
+        validation_uri = '/api/v1/auth/token/verify/'
+        data_to_validate = {
+            "token": JWT_user_validate_tokens.data['access'],
+        }
+
+        validated_token = self.client.post(validation_uri, data_to_validate, format='json')
+        self.assertEqual(validated_token.status_code, status.HTTP_200_OK)
+
+    def test_JWTtoken_validate_refresh(self):
+
+        token_uri = '/api/v1/auth/token/'
+        user_to_validate = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+        user_validate_data = {
+            "username": user_to_validate.username,
+            "password": 'codebuddies'
+        }
+
+        JWT_user_validate_tokens = self.client.post(token_uri, user_validate_data, format='json')
+
+        # now we validate the token
+        validation_uri = '/api/v1/auth/token/verify/'
+        data_to_validate = {
+            "token": JWT_user_validate_tokens.data['refresh'],
+        }
+
+        validated_token = self.client.post(validation_uri, data_to_validate, format='json')
+        self.assertEqual(validated_token.status_code, status.HTTP_200_OK)
+
+    def test_JWTtoken_expired_access(self):
+
+        #make a user to auth
+        user_to_expire = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+
+        #make a date that's yesterday
+        today = datetime.datetime.now()
+        diff = datetime.timedelta(days=1)
+        start_time = today - diff
+
+        #now we manually create a token with our user and a token that expired yesterday
+        expired_token = AccessToken.for_user(user_to_expire)
+        expired_token.set_exp(from_time=start_time)
+
+        # now we attempt to validate the expired token
+        expiration_validation_uri = '/api/v1/auth/token/verify/'
+        data_to_validate_expired = {"token": str(expired_token),}
+        expired_token_response = self.client.post(expiration_validation_uri, data_to_validate_expired, format='json')
+
+
+        #did we get rejected by the API?
+        self.assertEqual(expired_token_response.status_code, status.HTTP_401_UNAUTHORIZED)
+        self.assertEqual(expired_token_response.data['detail'], "Token is invalid or expired")
+        self.assertEqual(expired_token_response.data['code'], "token_not_valid")
+
+
+    # self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
+    # self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
+
+    def test_JWTtoken_refresh_expired_access(self):
+
+        # make a user to auth
+        user_to_expire_refresh = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+
+        # make a date that's yesterday
+        today = datetime.datetime.now()
+        diff = datetime.timedelta(days=1)
+        start_time = today - diff
+
+        # now we manually create a token with our user and a token that expired yesterday
+        expired_token = AccessToken.for_user(user_to_expire_refresh)
+        expired_token.set_exp(from_time=start_time)
+
+        # now we attempt to validate the expired token
+        expiration_refresh_uri = '/api/v1/auth/token/refresh/'
+        data_to_refresh_expired = {"refresh": str(expired_token), }
+        expired_refresh_response = self.client.post(expiration_refresh_uri, data_to_refresh_expired, format='json')
+
+        # did we get rejected by the API?
+        self.assertEqual(expired_refresh_response.status_code, status.HTTP_401_UNAUTHORIZED)
+        self.assertEqual(expired_refresh_response.data['detail'], "Token is invalid or expired")
+        self.assertEqual(expired_refresh_response.data['code'], "token_not_valid")
+
+
+    @patch('rest_framework_simplejwt.tokens.Token.check_exp')
+    def test_JWTtoken_expired_after_refresh(self, check_exp):
+        pass
+
+'''
 class UserauthTests(APITestCase):
 
     def setUp(self):
@@ -178,3 +1020,4 @@ def test_create_new_user(self):
         self.assertEqual((response.data['first_name'], response.data['last_name']),('Cali', 'French'))
         self.assertEqual(response.data['email'], 'asificare@mailme.net')
         self.assertContains(response, 'token', status_code=201)
+'''

From 221b7b6a142e7412c534e0aa0b46d8eb4980a9de Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Wed, 23 Sep 2020 21:13:40 -0700
Subject: [PATCH 14/32] Fixed tests that were failing due to auth change.

---
 project/resources/tests.py | 58 ++++++++++++++++++--------------------
 1 file changed, 28 insertions(+), 30 deletions(-)

diff --git a/project/resources/tests.py b/project/resources/tests.py
index 827a070e..09aefc71 100644
--- a/project/resources/tests.py
+++ b/project/resources/tests.py
@@ -1,18 +1,11 @@
-from unittest import skip
-from pytest import raises
 from random import randint
-from rest_framework import status
-from rest_framework.test import APITestCase
-from rest_framework_jwt.settings import api_settings
+from rest_framework import status, serializers
+from rest_framework.test import APITestCase, URLPatternsTestCase
 from users.factories import UserFactory
 from resources.factories import ResourceFactory
 from factory import PostGenerationMethodCall, LazyAttribute, create, create_batch
 
 
-jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
-jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
-
-
 class PublicResourcesTests(APITestCase):
     # Viewing resources, viewing a single resource, and search don't require user authentication
 
@@ -73,13 +66,16 @@ def test_create_a_resource(self):
 class AuthedResourcesTests(APITestCase):
 
     def setUp(self):
-        self.user = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+        token_uri = '/api/v1/auth/token/'
+        user_to_auth = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+        user_auth_data = {
+            "username": user_to_auth.username,
+            "password": 'codebuddies'
+        }
 
-        url = '/auth/obtain_token/'
-        data = {"username": self.user.username, "password": "codebuddies"}
-        token_response = self.client.post(url, data, format='json')
+        JWT_user_tokens = self.client.post(token_uri, user_auth_data, format='json')
 
-        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
+        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + JWT_user_tokens.data['access'])
 
     def test_patch_one_resource(self):
         new_resource = create(ResourceFactory)
@@ -189,20 +185,22 @@ def test_create_one_resource_without_media_type(self):
         self.assertEqual(response.data['media_type'], '')
 
     def test_create_one_resource_with_invalid_media_type(self):
-        with raises(KeyError, match=r"The media type should be one of the following:"):
-            url = '/api/v1/resources/'
-            data = {"title": "The Best Medium-Hard Data Analyst SQL Interview Questions",
-                    "author": "Zachary Thomas",
-                    "description": "The first 70% of SQL is pretty straightforward but the remaining 30% can be pretty tricky.  These are good practice problems for that tricky 30% part.",
-                    "url": "https://quip.com/2gwZArKuWk7W",
-                    "referring_url": "https://quip.com",
-                    "other_referring_source": "twitter.com/lpnotes",
-                    "date_published": "2020-04-19T03:27:06Z",
-                    "created": "2020-05-02T03:27:06.485Z",
-                    "modified":  "2020-05-02T03:27:06.485Z",
-                    "media_type": "DOP",
-                    "tags": ["SQLt", "BackEnd", "Databases"]
-                    }
-
-            response = self.client.post(url, data, format='json')
+        url = '/api/v1/resources/'
+        data = {"title": "The Best Medium-Hard Data Analyst SQL Interview Questions",
+                "author": "Zachary Thomas",
+                "description": "The first 70% of SQL is pretty straightforward but the remaining 30% can be pretty tricky.  These are good practice problems for that tricky 30% part.",
+                "url": "https://quip.com/2gwZArKuWk7W",
+                "referring_url": "https://quip.com",
+                "other_referring_source": "twitter.com/lpnotes",
+                "date_published": "2020-04-19T03:27:06Z",
+                "created": "2020-05-02T03:27:06.485Z",
+                "modified":  "2020-05-02T03:27:06.485Z",
+                "media_type": "DOP",
+                "tags": ["SQLt", "BackEnd", "Databases"]
+                }
+
+        response = self.client.post(url, data, format='json')
+
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.data[0], "Invalid media type.  The media type should be one of the following:  VID, POD, PODEP, TALK, TUTOR, COURSE, BOOK, BLOG, GAME, EVENT, TOOL, LIB, WEB")
 

From e8197e7d1f2e99ab9696f518844ae008c5db35c7 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Wed, 23 Sep 2020 21:14:05 -0700
Subject: [PATCH 15/32] Added final simpleJWT tests for auth.

---
 project/userauth/tests.py | 342 ++++++++++++++++----------------------
 1 file changed, 143 insertions(+), 199 deletions(-)

diff --git a/project/userauth/tests.py b/project/userauth/tests.py
index 1193d5d2..be1ecb7b 100644
--- a/project/userauth/tests.py
+++ b/project/userauth/tests.py
@@ -1,20 +1,14 @@
 import re
 import pytest, pytest_django
 import datetime
-from unittest.mock import patch
 from allauth.account.models import EmailAddress
 from rest_framework import status, serializers
 from rest_framework.test import APITestCase, URLPatternsTestCase
-from rest_framework_jwt.settings import api_settings
 from rest_framework_simplejwt.tokens import Token, AccessToken, RefreshToken
-from rest_framework_simplejwt.utils import (aware_utcnow, datetime_from_epoch, datetime_to_epoch, format_lazy)
 from users.factories import UserFactory
 from factory import PostGenerationMethodCall
 from django.core import mail
 from django.contrib.auth import get_user_model
-from django.utils.translation import gettext_lazy as _
-from django.db import models
-
 
 
 class UserauthTests(APITestCase):
@@ -704,7 +698,6 @@ def test_view_user_details_authed(self):
         self.assertEqual(user_details_response.status_code, status.HTTP_200_OK)
         self.assertEqual(user_details_response.data['username'], user_to_view.username)
 
-
     def test_view_user_details_unauthed(self):
 
         details_uri = '/api/v1/auth/user/'
@@ -714,10 +707,37 @@ def test_view_user_details_unauthed(self):
         self.assertEqual(details_response.data['detail'], "Authentication credentials were not provided.")
 
     def test_current_user_method_authed(self):
-        pass
+
+        token_uri = '/api/v1/auth/token/'
+        user_to_auth = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+        user_to_auth_data = {
+            "username": user_to_auth.username,
+            "password": 'codebuddies'
+        }
+
+        authed_user_tokens = self.client.post(token_uri, user_to_auth_data, format='json')
+        authed_user_access_token = authed_user_tokens.data['access']
+        authed_user_refresh_token = authed_user_tokens.data['refresh']
+        authed_user_uri = '/api/v1/auth/current_user/'
+
+        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + authed_user_access_token)
+        current_user_request = self.client.get(authed_user_uri)
+
+        #do we get back the "current users" view info?
+        self.assertEqual(current_user_request.status_code, status.HTTP_200_OK)
+        self.assertEqual(current_user_request.data['username'], user_to_auth.username)
+        self.assertContains(current_user_request, 'is_superuser')
+        self.assertContains(current_user_request, 'last_name')
+        self.assertContains(current_user_request, 'id')
 
     def test_current_user_method_unauthed(self):
-        pass
+
+        unauthed_user_uri = '/api/v1/auth/current_user/'
+
+        unauthed_current_user_request = self.client.get(unauthed_user_uri)
+
+        #do we get rejcted by the api?
+        self.assertEqual(unauthed_current_user_request.status_code, status.HTTP_401_UNAUTHORIZED)
 
     def test_JWTtoken_obtain_pair(self):
         token_uri = '/api/v1/auth/token/'
@@ -732,28 +752,7 @@ def test_JWTtoken_obtain_pair(self):
         self.assertContains(JWT_user_tokens,  "access")
         self.assertContains(JWT_user_tokens, "refresh")
 
-    def test_JWTtoken_refresh(self):
-        token_uri = '/api/v1/auth/token/'
-        user_to_refresh = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
-        user_refresh_data = {
-            "username": user_to_refresh.username,
-            "password": 'codebuddies'
-        }
-
-        JWT_user_obtain_tokens = self.client.post(token_uri, user_refresh_data, format='json')
-
-        #now we refresh the token
-        refresh_uri = '/api/v1/auth/token/refresh/'
-        data_to_refresh = {
-            "refresh": JWT_user_obtain_tokens.data['refresh'],
-        }
-
-        renewed_token = self.client.post(refresh_uri, data_to_refresh, format='json')
-
-        self.assertEqual(renewed_token.status_code, status.HTTP_200_OK)
-        self.assertContains(renewed_token, "access")
-
-    def test_JWTtoken_validate_access(self):
+    def test_JWTtoken_verify_active_access_token(self):
 
         token_uri = '/api/v1/auth/token/'
         user_to_validate = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
@@ -773,7 +772,7 @@ def test_JWTtoken_validate_access(self):
         validated_token = self.client.post(validation_uri, data_to_validate, format='json')
         self.assertEqual(validated_token.status_code, status.HTTP_200_OK)
 
-    def test_JWTtoken_validate_refresh(self):
+    def test_JWTtoken_verify_active_refresh_token(self):
 
         token_uri = '/api/v1/auth/token/'
         user_to_validate = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
@@ -793,7 +792,7 @@ def test_JWTtoken_validate_refresh(self):
         validated_token = self.client.post(validation_uri, data_to_validate, format='json')
         self.assertEqual(validated_token.status_code, status.HTTP_200_OK)
 
-    def test_JWTtoken_expired_access(self):
+    def test_JWTtoken_verify_with_expired_access_token(self):
 
         #make a user to auth
         user_to_expire = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
@@ -818,11 +817,75 @@ def test_JWTtoken_expired_access(self):
         self.assertEqual(expired_token_response.data['detail'], "Token is invalid or expired")
         self.assertEqual(expired_token_response.data['code'], "token_not_valid")
 
+    def test_JWTtoken_verify_with_expired_refresh_token(self):
+
+        #make a user to auth
+        user_to_expire_refresh = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+
+        #make a date that's yesterday
+        today = datetime.datetime.now()
+        diff = datetime.timedelta(days=1)
+        start_time = today - diff
+
+        #now we manually create a token with our user and a token that expired yesterday
+        expired_refresh_token = RefreshToken.for_user(user_to_expire_refresh)
+        expired_refresh_token.set_exp(from_time=start_time)
+
+        # now we attempt to validate the expired token
+        expiration_validation_uri = '/api/v1/auth/token/verify/'
+        data_to_validate_expired_refresh = {"token": str(expired_refresh_token),}
+        expired_token_response = self.client.post(expiration_validation_uri, data_to_validate_expired_refresh, format='json')
+
+
+        #did we get rejected by the API?
+        self.assertEqual(expired_token_response.status_code, status.HTTP_401_UNAUTHORIZED)
+        self.assertEqual(expired_token_response.data['detail'], "Token is invalid or expired")
+        self.assertEqual(expired_token_response.data['code'], "token_not_valid")
+
+    def test_JWTtoken_refresh_access_token_with_active_refresh_token(self):
+        token_uri = '/api/v1/auth/token/'
+        user_to_refresh = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+        user_refresh_data = {
+            "username": user_to_refresh.username,
+            "password": 'codebuddies'
+        }
+
+        JWT_user_obtain_tokens = self.client.post(token_uri, user_refresh_data, format='json')
+
+        #now we call refresh with the active refresh token
+        refresh_uri = '/api/v1/auth/token/refresh/'
+        data_to_refresh = {
+            "refresh": JWT_user_obtain_tokens.data['refresh'],
+        }
+
+        renewed_token = self.client.post(refresh_uri, data_to_refresh, format='json')
+
+        # Did we get a new access token in exchange for the refresh request?
+        self.assertEqual(renewed_token.status_code, status.HTTP_200_OK)
+        self.assertContains(renewed_token, "access")
 
-    # self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
-    # self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
+    def test_JWTtoken_refresh_access_token_with_active_access_token(self):
+        token_uri = '/api/v1/auth/token/'
+        user_to_refresh_access = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
+        user_refresh_access_data = {
+            "username": user_to_refresh_access.username,
+            "password": 'codebuddies'
+        }
 
-    def test_JWTtoken_refresh_expired_access(self):
+        JWT_user_obtain_tokens_to_refresh = self.client.post(token_uri, user_refresh_access_data, format='json')
+
+        #now we call refresh with the active refresh token
+        refresh_uri = '/api/v1/auth/token/refresh/'
+        access_data_to_refresh = {
+            "refresh": JWT_user_obtain_tokens_to_refresh.data['access'],
+        }
+
+        renewed_access_token = self.client.post(refresh_uri, access_data_to_refresh, format='json')
+
+        # Did we get rejected from refresh request due to it being an access token?
+        self.assertEqual(renewed_access_token.status_code, status.HTTP_401_UNAUTHORIZED)
+
+    def test_JWTtoken_refresh_expired_access_token_with_expired_access_token(self):
 
         # make a user to auth
         user_to_expire_refresh = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
@@ -846,178 +909,59 @@ def test_JWTtoken_refresh_expired_access(self):
         self.assertEqual(expired_refresh_response.data['detail'], "Token is invalid or expired")
         self.assertEqual(expired_refresh_response.data['code'], "token_not_valid")
 
+    def test_JWTtoken_refresh_expired_access_token_with_valid_refresh_token(self):
+        # make a user to auth
+        user_to_refresh = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
 
-    @patch('rest_framework_simplejwt.tokens.Token.check_exp')
-    def test_JWTtoken_expired_after_refresh(self, check_exp):
-        pass
-
-'''
-class UserauthTests(APITestCase):
-
-    def setUp(self):
-        self.user = UserFactory(
-            password=PostGenerationMethodCall('set_password', 'codebuddies')
-        )
-
-    def test_jwt_not_authed(self):
-        """
-        Ensure that if we aren't authed with a token, we don't get to view the
-        current_user
-        """
-
-        url = '/auth/current_user/'
-        response = self.client.get(url)
-        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+        # make a date that's yesterday
+        today = datetime.datetime.now()
+        diff = datetime.timedelta(days=1)
+        diff_refresh = datetime.timedelta(hours=1)
+        start_time = today - diff
+        start_time_refresh = today + diff_refresh
 
 
-    def test_jwt_auth(self):
-        """
-        Ensure we can obtain a token with a valid UN and PW combo.
-        """
+        # now we manually create a access token with our user and a token that expired yesterday
+        expired_token = AccessToken.for_user(user_to_refresh)
+        expired_token.set_exp(from_time=start_time)
 
-        url = '/auth/obtain_token/'
-        data = {"username": self.user.username, "password": "codebuddies"}
-        response = self.client.post(url, data, format='json')
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertContains(response, 'token')
-
-
-    def test_jwt_validate(self):
-        """
-        Ensure we can validate a previously acquired token.
-        """
-        token_response = self.client.post(
-            '/auth/obtain_token/',
-            {"username": self.user.username, "password": "codebuddies"},
-            format='json'
-        )
-        token = token_response.data['token']
-        url = '/auth/validate_token/'
-        data = {"token": token}
-        response = self.client.post(url, data, format='json')
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertContains(response, token)
-        self.assertContains(response, self.user.username)
-
-
-    def test_jwt_current_user(self):
-        """
-        Ensure that if we obtain a token in the 'browser',
-        we can retrieve the current_user based on the browser token
-        """
-
-        token_response = self.client.post(
-            '/auth/obtain_token/',
-            {"username": self.user.username, "password": "codebuddies"},
-            format='json'
-        )
-        url = '/auth/current_user/'
-        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
-        response = self.client.get(url)
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertContains(response, self.user.username)
-        self.assertContains(response, 'is_superuser')
-
-
-    def test_jwt_refresh(self):
-        """
-        Ensure that if we ask for a token refresh based on our current token
-        we get a refreshed token in return.
-        """
-
-        token_response = self.client.post(
-            '/auth/obtain_token/',
-            {"username": self.user.username, "password": "codebuddies"},
-            format='json'
-        )
-        url = '/auth/refresh_token/'
-        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
-        data = {"token": token_response.data['token']}
-        response = self.client.post(url, data, format='json')
-        self.assertEqual(response.status_code, status.HTTP_200_OK)
-        self.assertEqual(token_response.data['token'], response.data['token'], msg=None)
-
-
-    @patch('rest_framework_jwt.serializers.RefreshAuthTokenSerializer.validate')
-    def test_jwt_expired_refresh(self, validate_mock):
-        """
-        Ensure that a request to refresh and expired token fails.
-        """
-        token_response = self.client.post(
-            '/auth/obtain_token/',
-            {"username": self.user.username, "password": "codebuddies"},
-            format='json'
-        )
-        url = '/auth/refresh_token/'
-        data = {"token": token_response.data['token']}
-        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
-        validate_mock.side_effect = serializers.ValidationError('Refresh has expired.')
-        response = self.client.post(url, data, format='json')
-        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        # next, we manually crate a valid refresh token with our user and a now() date
+        refresh_token = RefreshToken.for_user(user_to_refresh)
+        refresh_token.set_exp(from_time=start_time_refresh)
 
+        # now we attempt to refresh the expired access token with the refresh token
+        expiration_refresh_uri = '/api/v1/auth/token/refresh/'
+        data_to_refresh_expired = {"refresh": str(refresh_token), }
+        refresh_response = self.client.post(expiration_refresh_uri, data_to_refresh_expired, format='json')
 
-    @patch('rest_framework_jwt.serializers._check_payload')
-    def test_jwt_expired_token_validate(self, validate_mock):
-        """
-        Ensure that a request to validate an expired token fails.
-        """
-        token_response = self.client.post(
-            '/auth/obtain_token/',
-            {"username": self.user.username, "password": "codebuddies"},
-            format='json'
-        )
-        url = '/auth/validate_token/'
-        data = {"token": token_response.data['token']}
-        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
-        validate_mock.side_effect = serializers.ValidationError('Token has expired.')
-        response = self.client.post(url, data, format='json')
-        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        # did we get a new, valid access token from the API?
+        self.assertEqual(refresh_response.status_code, status.HTTP_200_OK)
+        self.assertContains(refresh_response, 'access')
 
+    def test_JWTtoken_refresh_expired_access_token_with_expired_refresh_token(self):
+        # make a user to auth
+        user_to_refresh = UserFactory(password=PostGenerationMethodCall('set_password', 'codebuddies'))
 
-    @patch('rest_framework_jwt.serializers._check_payload')
-    def test_jwt_expired_token_access(self, validate_mock):
-        """
-        Ensure that a request to a protected api endpoint fails with an
-        expired token.
-        """
-        token_response = self.client.post(
-            '/auth/obtain_token/',
-            {"username": self.user.username, "password": "codebuddies"},
-            format='json'
-        )
-        url = '/api/v1/resources/'
-        data = {"token": token_response.data['token']}
-        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
-        validate_mock.side_effect = serializers.ValidationError('Token has expired.')
-        response = self.client.post(url, data, format='json')
-        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        # make a date that's yesterday
+        today = datetime.datetime.now()
+        diff = datetime.timedelta(days=1)
+        start_time = today - diff
+        start_time_refresh_past = today - diff
 
+        # now we manually create a access token with our user and a token that expired yesterday
+        expired_token = AccessToken.for_user(user_to_refresh)
+        expired_token.set_exp(from_time=start_time)
 
-    def test_create_new_user(self):
-        """
-        Ensure that a new user is created in the DB and a token for that user
-        is returned with valid confirmation data.
-        """
+        # next, we manually crate a refresh token with our user and a now() date
+        past_refresh_token = RefreshToken.for_user(user_to_refresh)
+        past_refresh_token.set_exp(from_time=start_time_refresh_past)
 
-        url = '/auth/users/'
-        data = {
-                "username": "claudette",
-                "password": "codebuddies",
-                "first_name": "Cali",
-                "last_name": "French",
-                "email": "asificare@mailme.net"
-                }
-        token_response = self.client.post(
-            '/auth/obtain_token/',
-            {"username": self.user.username, "password": "codebuddies"},
-            format='json'
-        )
-        self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token_response.data['token'])
-        response = self.client.post(url, data, format='json')
+        # now we attempt to refresh the expired access token with the refresh token
+        expiration_refresh_uri = '/api/v1/auth/token/refresh/'
+        data_to_refresh_past = {"refresh": str(past_refresh_token), }
+        past_refresh_response = self.client.post(expiration_refresh_uri, data_to_refresh_past, format='json')
 
-        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
-        self.assertEqual(response.data['username'], 'claudette')
-        self.assertEqual((response.data['first_name'], response.data['last_name']),('Cali', 'French'))
-        self.assertEqual(response.data['email'], 'asificare@mailme.net')
-        self.assertContains(response, 'token', status_code=201)
-'''
+        # did we get rejected because the refresh token has expired?
+        self.assertEqual(past_refresh_response.status_code, status.HTTP_401_UNAUTHORIZED)
+        self.assertEqual(past_refresh_response.data['detail'], "Token is invalid or expired")
+        self.assertEqual(past_refresh_response.data['code'], "token_not_valid")

From f2202246de186274cc99089ac2661b290f7f6caf Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Wed, 23 Sep 2020 21:14:30 -0700
Subject: [PATCH 16/32] Added skips for User tests broken due to auth changes.

---
 project/users/tests/test_models.py | 2 +-
 project/users/tests/test_urls.py   | 6 +++---
 project/users/tests/test_views.py  | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/project/users/tests/test_models.py b/project/users/tests/test_models.py
index 54863632..12efb61e 100644
--- a/project/users/tests/test_models.py
+++ b/project/users/tests/test_models.py
@@ -3,6 +3,6 @@
 
 pytestmark = pytest.mark.django_db
 
-
+@pytest.mark.skip(reason="App needs rewrite after auth change.")
 def test_user_get_absolute_url(user: settings.AUTH_USER_MODEL):
     assert user.get_absolute_url() == f"/users/{user.username}/"
diff --git a/project/users/tests/test_urls.py b/project/users/tests/test_urls.py
index c6361920..90842c11 100644
--- a/project/users/tests/test_urls.py
+++ b/project/users/tests/test_urls.py
@@ -4,7 +4,7 @@
 
 pytestmark = pytest.mark.django_db
 
-
+@pytest.mark.skip(reason="App needs rewrite after auth change.")
 def test_detail(user: settings.AUTH_USER_MODEL):
     assert (
         reverse("users:detail", kwargs={"username": user.username})
@@ -12,12 +12,12 @@ def test_detail(user: settings.AUTH_USER_MODEL):
     )
     assert resolve(f"/users/{user.username}/").view_name == "users:detail"
 
-
+@pytest.mark.skip(reason="App needs rewrite after auth change.")
 def test_update():
     assert reverse("users:update") == "/users/~update/"
     assert resolve("/users/~update/").view_name == "users:update"
 
-
+@pytest.mark.skip(reason="App needs rewrite after auth change.")
 def test_redirect():
     assert reverse("users:redirect") == "/users/~redirect/"
     assert resolve("/users/~redirect/").view_name == "users:redirect"
diff --git a/project/users/tests/test_views.py b/project/users/tests/test_views.py
index 6bf42cb6..9df7fc02 100644
--- a/project/users/tests/test_views.py
+++ b/project/users/tests/test_views.py
@@ -6,7 +6,7 @@
 
 pytestmark = pytest.mark.django_db
 
-
+@pytest.mark.skip(reason="App needs rewrite after auth change.")
 class TestUserUpdateView:
     """
     TODO:
@@ -38,7 +38,7 @@ def test_get_object(
 
         assert view.get_object() == user
 
-
+@pytest.mark.skip(reason="App needs rewrite after auth change.")
 class TestUserRedirectView:
     def test_get_redirect_url(
         self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory

From bb47eb0669c01eacb4e9f3a2204dec72d9696c45 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Wed, 23 Sep 2020 22:47:52 -0700
Subject: [PATCH 17/32] Removed django-rest_auth and django-rest-jwt from
 requirememts.

---
 project/requirements/base.txt | 2 --
 1 file changed, 2 deletions(-)

diff --git a/project/requirements/base.txt b/project/requirements/base.txt
index f1c15b54..9f2c0d57 100644
--- a/project/requirements/base.txt
+++ b/project/requirements/base.txt
@@ -32,8 +32,6 @@ django-taggit==1.2.0          # https://github.com/jazzband/django-taggit
 djangorestframework==3.10.2   # https://github.com/encode/django-rest-framework
 coreapi==2.3.3                # https://github.com/core-api/python-client
 django_taggit_serializer==0.1.7 #https://github.com/glemmaPaul/django-taggit-serializer
-drf-jwt==1.13.4               # https://github.com/Styria-Digital/django-rest-framework-jwt
 djangorestframework-simplejwt #https://github.com/SimpleJWT/django-rest-framework-simplejwt
-django-rest-auth==0.9.5 #https://github.com/Tivix/django-rest-auth
 dj-rest-auth==1.1.1                       #https://github.com/jazzband/dj-rest-auth
 django-rest-authtoken==2.1.3 #https://pypi.org/project/django-rest-authtoken/

From 9fd6eb7275ea3a1c1556e28f1fc4800b22e56e53 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Wed, 23 Sep 2020 22:54:11 -0700
Subject: [PATCH 18/32] Removed unused import that was causing an error.

---
 project/userauth/serializers.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/project/userauth/serializers.py b/project/userauth/serializers.py
index 6d2b8d25..1a556217 100644
--- a/project/userauth/serializers.py
+++ b/project/userauth/serializers.py
@@ -1,5 +1,4 @@
 from rest_framework import serializers
-from rest_framework_jwt.settings import api_settings
 from dj_rest_auth.serializers import JWTSerializer, UserDetailsSerializer
 from dj_rest_auth.registration.serializers import VerifyEmailSerializer
 from dj_rest_auth.serializers import PasswordResetSerializer, PasswordResetConfirmSerializer

From 2670780aceb011c8250c1dcffc6d5ce9f56596d2 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Thu, 24 Sep 2020 13:50:52 -0700
Subject: [PATCH 19/32] Removed excess space from
 email_confirmation_subject.txt filename.

---
 ...l _confirmation_subject.txt => email_confirmation_subject.txt} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename project/core/templates/account/email/{email _confirmation_subject.txt => email_confirmation_subject.txt} (100%)

diff --git a/project/core/templates/account/email/email _confirmation_subject.txt b/project/core/templates/account/email/email_confirmation_subject.txt
similarity index 100%
rename from project/core/templates/account/email/email _confirmation_subject.txt
rename to project/core/templates/account/email/email_confirmation_subject.txt

From 9451986b49ae45161d4707ca5340c61a9112cdc2 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Thu, 24 Sep 2020 14:06:01 -0700
Subject: [PATCH 20/32] Revert "Removed excess space from
 email_confirmation_subject.txt filename."

This reverts commit 2670780aceb011c8250c1dcffc6d5ce9f56596d2.

This file rename breaks allauth and all auth test cases for some reason.  It also causes a python failure due to an endless import recursion.
Not sure why/how this came about, but the easiest thing is to leave it as-is, and revert the change.
---
 ...l_confirmation_subject.txt => email _confirmation_subject.txt} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename project/core/templates/account/email/{email_confirmation_subject.txt => email _confirmation_subject.txt} (100%)

diff --git a/project/core/templates/account/email/email_confirmation_subject.txt b/project/core/templates/account/email/email _confirmation_subject.txt
similarity index 100%
rename from project/core/templates/account/email/email_confirmation_subject.txt
rename to project/core/templates/account/email/email _confirmation_subject.txt

From aa6218ae62241284a4c8d14e85b3ee6839a0d133 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Thu, 24 Sep 2020 14:31:41 -0700
Subject: [PATCH 21/32] Attempted to fix intermittent email verification post
 test failure by updating settings and tests.

---
 project/config/urls.py    | 4 ++--
 project/userauth/tests.py | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/project/config/urls.py b/project/config/urls.py
index c52fd57b..ad3279e6 100644
--- a/project/config/urls.py
+++ b/project/config/urls.py
@@ -20,8 +20,8 @@
     path(settings.ADMIN_URL, admin.site.urls),
 
     # User management
-    #currently inactive endpoint, but can re-activate if needed
-    #path("users/", include("users.urls", namespace="users")),
+    #currently an unused endpoint, but can be used if needed for extended user profiles, etc.
+    path("users/", include("users.urls", namespace="users")),
 
     #we have to include these for registration email validation, but otherwise these paths are NOT used
     path("accounts/", include("allauth.urls")),
diff --git a/project/userauth/tests.py b/project/userauth/tests.py
index be1ecb7b..40aeecc2 100644
--- a/project/userauth/tests.py
+++ b/project/userauth/tests.py
@@ -136,7 +136,7 @@ def test_verify_email_path_post(self):
         confirmation_uri = re.search(uri_regex, verify_email_message)
 
         # now, let's post the key to trigger validation
-        validate_email_url = f'/api/v1/auth/registration/verify-email/'
+        validate_email_url = f'/api/v1/auth/registration/verify-email/?key={confirmation_uri[3]}/'
         validate_key_data = {"key": confirmation_uri[3]}
         validation_response = self.client.post(validate_email_url, validate_key_data, format='json')
         print(validation_response)

From f91a22d4fa0e53ec0f40e75a7bd747abdce9c7a2 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Thu, 24 Sep 2020 16:04:47 -0700
Subject: [PATCH 22/32] Changed order of included URLs.

---
 project/config/urls.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/project/config/urls.py b/project/config/urls.py
index ad3279e6..ce11e23b 100644
--- a/project/config/urls.py
+++ b/project/config/urls.py
@@ -6,6 +6,7 @@
 from django.views import defaults as default_views
 from rest_framework import routers, serializers, viewsets
 from resources.urls import router as resources_router
+from userauth.views import CustomVerifyEmailView
 from userauth.urls import router as userauth_router
 
 router = routers.DefaultRouter()
@@ -23,15 +24,15 @@
     #currently an unused endpoint, but can be used if needed for extended user profiles, etc.
     path("users/", include("users.urls", namespace="users")),
 
-    #we have to include these for registration email validation, but otherwise these paths are NOT used
-    path("accounts/", include("allauth.urls")),
-
     # Your stuff: custom urls includes go here
     #this is a route for logging into the "browsable api"  if not needed for testing, it should be omitted.
     path('api/v1/', include('rest_framework.urls', namespace='rest_framework')),
     path('api/v1/auth/', include(('userauth.urls', 'userauth'), namespace="userauth")),
     path('api/v1/', include('resources.urls', namespace='resources')),
 
+    #we have to include these for registration email validation, but otherwise these paths are NOT used
+    path("accounts/", include("allauth.urls")),
+
 
 ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 

From a2b2b4bd8e0c97131f4053de60e45991eab88990 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Thu, 24 Sep 2020 16:10:15 -0700
Subject: [PATCH 23/32] Added specific path for email link verification post
 and changed path regex.

---
 project/userauth/urls.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/project/userauth/urls.py b/project/userauth/urls.py
index 19f71ae8..c9b45a50 100644
--- a/project/userauth/urls.py
+++ b/project/userauth/urls.py
@@ -15,8 +15,9 @@
 urlpatterns = (
     path('', include('dj_rest_auth.urls')),
     path('', include(router.urls)),
-    path('registration/', include('dj_rest_auth.registration.urls')),
-    path('registration/verify-email/', views.CustomVerifyEmailView.as_view(), name='account_email_verification_sent'),
+    path('registration/', include('dj_rest_auth.registration.urls'), name='registration'),
+    path('registration/verify-email/$', views.CustomVerifyEmailView.as_view(), name='account_email_verification_sent'),
+    path('registration/verify-email/(?P<key>[-:\w]+)/$', views.CustomVerifyEmailView.as_view(), name='account_confirm_email'),
     path('password/reset/', PasswordResetView.as_view(), name='password_reset'),
     path('password/reset/confirm/<uidb64>/<token>/', PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
     path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),

From 569ee99ec033f2e0ddb338ba9f6623a6661a0925 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Thu, 24 Sep 2020 16:11:22 -0700
Subject: [PATCH 24/32] Fun trick: If your regex fails to capture all the key
 generations cases, it will fail intermittently - not consistantly.

---
 project/userauth/tests.py | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/project/userauth/tests.py b/project/userauth/tests.py
index 40aeecc2..83dcdaea 100644
--- a/project/userauth/tests.py
+++ b/project/userauth/tests.py
@@ -118,25 +118,25 @@ def test_verify_email_path_get(self):
     def test_verify_email_path_post(self):
 
         # start by registering a user
-        reg_url = '/api/v1/auth/registration/'
-        reg_data = {
+        new_user_reg_url = '/api/v1/auth/registration/'
+        new_user_reg_data = {
             "username": self.user['username'],
             "email": self.user['email'],
             "password1": self.user['password1'],
             "password2": self.user['password2']
         }
-        response = self.client.post(reg_url, reg_data, format='json')
+        response = self.client.post(new_user_reg_url, new_user_reg_data, format='json')
 
         # grab email from outbox so we can extract the verification link
         email_message = mail.outbox[0]
         verify_email_message = email_message.body
 
         # extracting what we need for the verification post action
-        uri_regex = re.compile(r"(\/api\/v1\/auth\/registration\/verify-email\/)(\?key=)([\w:]+)")
+        uri_regex = re.compile(r"(\/api\/v1\/auth\/registration\/verify-email\/)(\?key=)([\w:-]+)")
         confirmation_uri = re.search(uri_regex, verify_email_message)
 
         # now, let's post the key to trigger validation
-        validate_email_url = f'/api/v1/auth/registration/verify-email/?key={confirmation_uri[3]}/'
+        validate_email_url = f'{confirmation_uri[0]}'
         validate_key_data = {"key": confirmation_uri[3]}
         validation_response = self.client.post(validate_email_url, validate_key_data, format='json')
         print(validation_response)
@@ -172,6 +172,7 @@ def test_verify_email_marked_valid_after_post(self):
         key_data = {"key": confirmation_uri[3]}
         response = self.client.post(email_url, key_data, format='json')
 
+
         #did the post succeed in marking the email as valid in the DB?
         model = get_user_model()
         email_to_verify = EmailAddress.objects.get(email=reg_data['email'])

From 35c5610ef3161789b5bd51d9d9fd2f43d9d2d072 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Thu, 24 Sep 2020 16:12:29 -0700
Subject: [PATCH 25/32] Removed print statement from email post verificaton
 test case.

---
 project/userauth/tests.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/project/userauth/tests.py b/project/userauth/tests.py
index 83dcdaea..d3b7371f 100644
--- a/project/userauth/tests.py
+++ b/project/userauth/tests.py
@@ -139,7 +139,6 @@ def test_verify_email_path_post(self):
         validate_email_url = f'{confirmation_uri[0]}'
         validate_key_data = {"key": confirmation_uri[3]}
         validation_response = self.client.post(validate_email_url, validate_key_data, format='json')
-        print(validation_response)
 
         # did the post result in the correct status messages?
         self.assertEqual(validation_response.status_code, status.HTTP_200_OK)

From 75e9461c42a77aafc59fe806511135c6ca05c829 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Thu, 24 Sep 2020 16:46:51 -0700
Subject: [PATCH 26/32] Corrected circular reference that was breaking tests
 and renamed file to exclude extra space.

---
 .../templates/account/email/email _confirmation_subject.txt   | 1 -
 .../templates/account/email/email_confirmation_subject.txt    | 4 ++++
 2 files changed, 4 insertions(+), 1 deletion(-)
 delete mode 100644 project/core/templates/account/email/email _confirmation_subject.txt
 create mode 100644 project/core/templates/account/email/email_confirmation_subject.txt

diff --git a/project/core/templates/account/email/email _confirmation_subject.txt b/project/core/templates/account/email/email _confirmation_subject.txt
deleted file mode 100644
index 4c85ebb9..00000000
--- a/project/core/templates/account/email/email _confirmation_subject.txt	
+++ /dev/null
@@ -1 +0,0 @@
-{% include "account/email/email_confirmation_subject.txt" %}
diff --git a/project/core/templates/account/email/email_confirmation_subject.txt b/project/core/templates/account/email/email_confirmation_subject.txt
new file mode 100644
index 00000000..b0a876f5
--- /dev/null
+++ b/project/core/templates/account/email/email_confirmation_subject.txt
@@ -0,0 +1,4 @@
+{% load i18n %}
+{% autoescape off %}
+{% blocktrans %}Please Confirm Your E-mail Address{% endblocktrans %}
+{% endautoescape %}

From b2ba2c2f4715668b94174cef1b18544a5d01fc6b Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Thu, 24 Sep 2020 16:53:21 -0700
Subject: [PATCH 27/32] Removed regex from path url to avoid djang deprecation
 and migration warnings.

---
 project/userauth/urls.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/project/userauth/urls.py b/project/userauth/urls.py
index c9b45a50..aa25e3e0 100644
--- a/project/userauth/urls.py
+++ b/project/userauth/urls.py
@@ -16,8 +16,8 @@
     path('', include('dj_rest_auth.urls')),
     path('', include(router.urls)),
     path('registration/', include('dj_rest_auth.registration.urls'), name='registration'),
-    path('registration/verify-email/$', views.CustomVerifyEmailView.as_view(), name='account_email_verification_sent'),
-    path('registration/verify-email/(?P<key>[-:\w]+)/$', views.CustomVerifyEmailView.as_view(), name='account_confirm_email'),
+    path('registration/verify-email/', views.CustomVerifyEmailView.as_view(), name='account_email_verification_sent'),
+    path('registration/verify-email/<key>', views.CustomVerifyEmailView.as_view(), name='account_confirm_email'),
     path('password/reset/', PasswordResetView.as_view(), name='password_reset'),
     path('password/reset/confirm/<uidb64>/<token>/', PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
     path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),

From 69342912db3e0e8764fd4e794b724958d1fd6872 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Fri, 25 Sep 2020 22:55:09 -0700
Subject: [PATCH 28/32] New stub files after running create app script for
 django.

---
 project/hangouts/__init__.py            | 0
 project/hangouts/admin.py               | 3 +++
 project/hangouts/apps.py                | 5 +++++
 project/hangouts/migrations/__init__.py | 0
 project/hangouts/models.py              | 3 +++
 project/hangouts/tests.py               | 3 +++
 project/hangouts/views.py               | 3 +++
 7 files changed, 17 insertions(+)
 create mode 100644 project/hangouts/__init__.py
 create mode 100644 project/hangouts/admin.py
 create mode 100644 project/hangouts/apps.py
 create mode 100644 project/hangouts/migrations/__init__.py
 create mode 100644 project/hangouts/models.py
 create mode 100644 project/hangouts/tests.py
 create mode 100644 project/hangouts/views.py

diff --git a/project/hangouts/__init__.py b/project/hangouts/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/project/hangouts/admin.py b/project/hangouts/admin.py
new file mode 100644
index 00000000..8c38f3f3
--- /dev/null
+++ b/project/hangouts/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/project/hangouts/apps.py b/project/hangouts/apps.py
new file mode 100644
index 00000000..8c515da1
--- /dev/null
+++ b/project/hangouts/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class HangoutsConfig(AppConfig):
+    name = 'hangouts'
diff --git a/project/hangouts/migrations/__init__.py b/project/hangouts/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/project/hangouts/models.py b/project/hangouts/models.py
new file mode 100644
index 00000000..71a83623
--- /dev/null
+++ b/project/hangouts/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/project/hangouts/tests.py b/project/hangouts/tests.py
new file mode 100644
index 00000000..7ce503c2
--- /dev/null
+++ b/project/hangouts/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/project/hangouts/views.py b/project/hangouts/views.py
new file mode 100644
index 00000000..91ea44a2
--- /dev/null
+++ b/project/hangouts/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.

From 738f7c1d15a319af8d8a20bb90d850d9cfa949f2 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Sat, 26 Sep 2020 02:25:00 -0700
Subject: [PATCH 29/32] Registered Hangouts as an app.

---
 project/config/settings/base.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/project/config/settings/base.py b/project/config/settings/base.py
index 4018957e..4ebc0620 100644
--- a/project/config/settings/base.py
+++ b/project/config/settings/base.py
@@ -92,7 +92,8 @@
     "resources.apps.ResourcesConfig",
     "tagging.apps.TaggingConfig",
     'userauth.apps.UserauthConfig',
-    'osprojects.apps.OsprojectsConfig'
+    'osprojects.apps.OsprojectsConfig',
+    'hangouts.apps.HangoutsConfig'
     # Your stuff: custom apps go here
 ]
 # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps

From 11c40e2c88db236e8c7eedf27965b680e04b35ab Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Sat, 26 Sep 2020 02:25:30 -0700
Subject: [PATCH 30/32] WIP for registering Hangouts in admin.  Issues with
 properly inlining model.

---
 project/hangouts/admin.py | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/project/hangouts/admin.py b/project/hangouts/admin.py
index 8c38f3f3..4caa48ca 100644
--- a/project/hangouts/admin.py
+++ b/project/hangouts/admin.py
@@ -1,3 +1,30 @@
 from django.contrib import admin
+from .models import Hangout, HangoutSessions, HangoutResponses
 
 # Register your models here.
+class HangoutSessionsInline(admin.StackedInline):
+    model = HangoutSessions
+    readonly_fields = ('id', 'guid')
+
+
+class HangoutResponsesInline(admin.StackedInline):
+    model = HangoutResponses
+    readonly_fields = ('id', 'guid')
+
+
+class HangoutAdmin(admin.ModelAdmin):
+    model = Hangout
+    readonly_fields = ('id', 'guid')
+    list_display = ['tag_list']
+    inlines = ['HangoutSessionsInline', 'HangoutResponsesInline']
+
+    def get_queryset(self, request):
+        return super().get_queryset(request).prefetch_related('tags')
+
+    def tag_list(self, obj):
+        return u", ".join(o.name for o in obj.tags.all())
+
+
+admin.site.register(Hangout)
+admin.site.register(HangoutResponses)
+admin.site.register(HangoutSessions)

From 92b8944f7c3c1090e9dcdaca39a5d838183ecf57 Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Sat, 26 Sep 2020 02:26:08 -0700
Subject: [PATCH 31/32] Inital migration file for hangouts, hangoutsessions,
 and hangoutresponses.

---
 project/hangouts/migrations/0001_initial.py | 76 +++++++++++++++++++++
 1 file changed, 76 insertions(+)
 create mode 100644 project/hangouts/migrations/0001_initial.py

diff --git a/project/hangouts/migrations/0001_initial.py b/project/hangouts/migrations/0001_initial.py
new file mode 100644
index 00000000..d0645620
--- /dev/null
+++ b/project/hangouts/migrations/0001_initial.py
@@ -0,0 +1,76 @@
+# Generated by Django 2.2.4 on 2020-09-26 08:22
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+import hangouts.models
+import taggit.managers
+import uuid
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('tagging', '0003_auto_20200508_1230'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('resources', '0007_auto_20200303_1258'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Hangout',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('guid', models.UUIDField(default=uuid.uuid1, editable=False)),
+                ('status', models.CharField(blank=True, max_length=200)),
+                ('hangout_type', models.CharField(choices=[('WATCH', 'Watch Me Code'), ('PRES', 'Presentation'), ('COWRK', 'Co-work with Me'), ('STUDY', 'Study Group'), ('PAIR', 'Pairing'), ('ACNT', 'Keep Me Accountable'), ('DISC', 'Discussion'), ('TEACH', 'I have something to teach')], max_length=6)),
+                ('title', models.CharField(max_length=200)),
+                ('slug', models.SlugField(allow_unicode=True, max_length=100, verbose_name='Slug')),
+                ('short_description', models.TextField(max_length=300)),
+                ('long_description', models.TextField(blank=True, max_length=600, null=True)),
+                ('open_to_RSVP', models.BooleanField(default=False)),
+                ('start_time', models.DateTimeField(default=django.utils.timezone.now)),
+                ('end_time', models.DateTimeField()),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('modified', models.DateTimeField(default=django.utils.timezone.now)),
+                ('recurring', models.BooleanField(default=False)),
+                ('internal_platform', models.BooleanField(default=True)),
+                ('external_platform_link', models.URLField(blank=True, max_length=300, null=True)),
+                ('related_resources', models.ManyToManyField(blank=True, related_name='related_hangouts', to='resources.Resource')),
+                ('tags', taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='tagging.TaggedItems', to='tagging.CustomTag', verbose_name='Tags')),
+                ('user', models.ForeignKey(on_delete=models.SET(hangouts.models.get_sentinel_user), to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='HangoutSessions',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('guid', models.UUIDField(default=uuid.uuid1, editable=False)),
+                ('status', models.CharField(blank=True, max_length=200)),
+                ('start_time', models.DateTimeField(default=django.utils.timezone.now)),
+                ('end_time', models.DateTimeField()),
+                ('created', models.DateTimeField(auto_now_add=True)),
+                ('modified', models.DateTimeField(default=django.utils.timezone.now)),
+                ('hangout_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_sessions', to='hangouts.Hangout')),
+                ('related_resources', models.ManyToManyField(blank=True, related_name='related_hangout_sessions', to='resources.Resource')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='HangoutResponses',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('guid', models.UUIDField(default=uuid.uuid1, editable=False)),
+                ('express_interest', models.BooleanField(default=False)),
+                ('request_to_join', models.BooleanField(default=False)),
+                ('rsvp', models.BooleanField(default=False)),
+                ('response_comment', models.TextField(blank=True, max_length=300, null=True)),
+                ('status', models.TextField(max_length=10)),
+                ('hangout_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_responses', to='hangouts.Hangout')),
+                ('hangout_session_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='related_session_responses', to='hangouts.HangoutSessions')),
+                ('user_id', models.ForeignKey(on_delete=models.SET(hangouts.models.get_sentinel_user), to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+    ]

From afa7f2519503169cf25a842b9864bd801fbe126a Mon Sep 17 00:00:00 2001
From: BethanyG <bethanymgarcia@gmail.com>
Date: Sat, 26 Sep 2020 02:26:27 -0700
Subject: [PATCH 32/32] First pass at hangouts model.

---
 project/hangouts/models.py | 98 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 97 insertions(+), 1 deletion(-)

diff --git a/project/hangouts/models.py b/project/hangouts/models.py
index 71a83623..1db2a01b 100644
--- a/project/hangouts/models.py
+++ b/project/hangouts/models.py
@@ -1,3 +1,99 @@
+import uuid
+import datetime
+from taggit.managers import TaggableManager
+from django.conf import settings
 from django.db import models
+from django.utils import timezone
+from django.contrib.auth import get_user_model
+from django.utils.translation import ugettext_lazy as _
+from resources.models import Resource
+from tagging.managers import CustomTaggableManager
+from tagging.models import CustomTag, TaggedItems
+
+
+def get_sentinel_user():
+    return get_user_model().objects.get_or_create(username='deleted')[0]
+
+
+def get_tags_display(self):
+    return self.tags.values_list('name', flat=True)
+
+
+class Hangout(models.Model):
+    HANGOUT_TYPES = [
+        ('WATCH', 'Watch Me Code'),
+        ('PRES', 'Presentation'),
+        ('COWRK', 'Co-work with Me'),
+        ('STUDY', 'Study Group'),
+        ('PAIR', 'Pairing'),
+        ('ACNT', 'Keep Me Accountable'),
+        ('DISC', 'Discussion'),
+        ('TEACH', 'I have something to teach'),
+    ]
+
+    guid = models.UUIDField(default=uuid.uuid1, editable=False)
+
+    #One of scheduled, pending, rescheduled, stale, hold, closed, completed
+    status = models.CharField(blank=True, max_length=200)
+    hangout_type = models.CharField(max_length=6, choices=HANGOUT_TYPES)
+
+    #we are going to require a title
+    title = models.CharField(max_length=200, blank=False)
+    slug = models.SlugField(verbose_name=_("Slug"),  max_length=100, allow_unicode=True)
+    short_description = models.TextField(max_length=300, blank=False, null=False)
+    long_description = models.TextField(max_length=600, blank=True, null=True)
+
+    # user who "owns" the hangout we'll pull this from their TOKEN
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET(get_sentinel_user))
+
+    #sort of a public/private thing and confirmed/not confirmed thing
+    open_to_RSVP = models.BooleanField(blank=False, null=False, default=False)
+
+    #Calendar date + start time would be derived from the datetime object
+    start_time = models.DateTimeField(default=timezone.now)
+
+    #Calendar date + end time would be derived from the datetime object
+    end_time = models.DateTimeField(blank=False, null=False)
+
+    # creation date of hangout entry
+    created = models.DateTimeField(auto_now_add=True)
+
+    # modification date of hangout entry
+    modified = models.DateTimeField(default=timezone.now)
+
+    recurring = models.BooleanField(null=False, default=False)
+
+    internal_platform = models.BooleanField(null=False, default=True)
+    external_platform_link = models.URLField(max_length=300, blank=True, null=True)
+
+    related_resources = models.ManyToManyField(Resource, blank=True, related_name='related_hangouts')
+
+    # Allow tags to be used across entities
+    # E.g. so we can create composite views showing all entities sharing a common tag
+    tags = TaggableManager(through=TaggedItems, manager=CustomTaggableManager, blank=True)
+
+
+class HangoutSessions(models.Model):
+    guid = models.UUIDField(default=uuid.uuid1, editable=False)
+    hangout_id = models.ForeignKey(Hangout, on_delete=models.CASCADE, blank=True, null=True, related_name='related_sessions')
+    status = models.CharField(blank=True, max_length=200) #scheduled, pending, rescheduled, stale, hold, closed, completed
+    start_time = models.DateTimeField(default=timezone.now)
+    end_time = models.DateTimeField(blank=False, null=False)
+    related_resources = models.ManyToManyField(Resource, blank=True, related_name='related_hangout_sessions')
+    created = models.DateTimeField(auto_now_add=True)
+    modified = models.DateTimeField(default=timezone.now)
+
+
+class HangoutResponses(models.Model):
+    guid = models.UUIDField(default=uuid.uuid1, editable=False)
+    hangout_id = models.ForeignKey(Hangout, on_delete=models.CASCADE, blank=True, null=True,
+                                   related_name='related_responses')
+    hangout_session_id = models.ForeignKey(HangoutSessions, on_delete=models.CASCADE, blank=True, null=True,
+                                           related_name='related_session_responses')
+    user_id = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET(get_sentinel_user))
+    express_interest = models.BooleanField(blank=False, null=False, default=False)
+    request_to_join = models.BooleanField(blank=False, null=False, default=False)
+    rsvp = models.BooleanField(blank=False, null=False, default=False)
+    response_comment = models.TextField(max_length=300, blank=True, null=True)
+    status = models.TextField(max_length=10, blank=False, null=False)
 
-# Create your models here.