Add product model and api tests

 - added product model
 - adds basic api and api tests for product
diff --git a/bh_core/settings.py b/bh_core/settings.py
index f0db01a..37ac64b 100644
--- a/bh_core/settings.py
+++ b/bh_core/settings.py
@@ -145,6 +145,5 @@
 
 REST_FRAMEWORK = {
     'DEFAULT_PERMISSION_CLASSES': [
-        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
-    ]
+    ],
 }
diff --git a/bh_core/urls.py b/bh_core/urls.py
index 408612b..a285126 100644
--- a/bh_core/urls.py
+++ b/bh_core/urls.py
@@ -36,5 +36,6 @@
 
 urlpatterns = [
     path('', include('trackers.urls')),
+    path('api-auth/', include('rest_framework.urls')),
     path('admin/', admin.site.urls),
 ]
diff --git a/functional_tests.py b/functional_tests.py
index df49437..50e22fb 100644
--- a/functional_tests.py
+++ b/functional_tests.py
@@ -21,7 +21,7 @@
 import unittest
 
 
-class TicketViewTest(unittest.TestCase):
+class HomePageViewTest(unittest.TestCase):
     def setUp(self):
         self.browser = webdriver.Firefox()
         self.browser.implicitly_wait(3)
@@ -29,11 +29,25 @@
     def tearDown(self):
         self.browser.quit()
 
-    def test_user_can_add_view_and_delete_ticket(self):
+    def test_user_can_see_homepage(self):
         self.browser.get('http://localhost:8000')
 
         self.assertIn('Bloodhound', self.browser.title)
 
 
+class ApiHomePageViewTest(unittest.TestCase):
+    def setUp(self):
+        self.browser = webdriver.Firefox()
+        self.browser.implicitly_wait(3)
+
+    def tearDown(self):
+        self.browser.quit()
+
+    def test_user_can_see_api_homepage(self):
+        self.browser.get('http://localhost:8000/api')
+
+        self.assertIn('Api Root', self.browser.title)
+
+
 if __name__ == '__main__':
     unittest.main(warnings='ignore')
diff --git a/trackers/api/__init__.py b/trackers/api/__init__.py
new file mode 100644
index 0000000..084b296
--- /dev/null
+++ b/trackers/api/__init__.py
@@ -0,0 +1,16 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
diff --git a/trackers/serializers.py b/trackers/api/serializers.py
similarity index 91%
rename from trackers/serializers.py
rename to trackers/api/serializers.py
index 2616ff1..92db9c6 100644
--- a/trackers/serializers.py
+++ b/trackers/api/serializers.py
@@ -1,6 +1,7 @@
 from django.contrib.auth.models import User, Group
 from rest_framework import serializers
 from trackers import models
+from ..models import Product
 
 
 class UserSerializer(serializers.HyperlinkedModelSerializer):
@@ -15,6 +16,12 @@
         fields = ('url', 'name')
 
 
+class ProductSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Product
+        fields = '__all__'
+
+
 class TicketSerializer(serializers.ModelSerializer):
     api_url = serializers.SerializerMethodField()
     api_events_url = serializers.SerializerMethodField()
diff --git a/trackers/api/tests/__init__.py b/trackers/api/tests/__init__.py
new file mode 100644
index 0000000..084b296
--- /dev/null
+++ b/trackers/api/tests/__init__.py
@@ -0,0 +1,16 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
diff --git a/trackers/api/tests/test_product_api.py b/trackers/api/tests/test_product_api.py
new file mode 100644
index 0000000..39b9243
--- /dev/null
+++ b/trackers/api/tests/test_product_api.py
@@ -0,0 +1,112 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+from django.contrib.auth.models import User
+from django.urls import reverse
+from rest_framework.test import APITestCase
+from rest_framework import status
+
+from ...models import Product
+from ..serializers import ProductSerializer
+
+
+class ProductsApiTest(APITestCase):
+    """Test for GET all products API """
+    def setUp(self):
+        self.ally = Product.objects.create(prefix='ALY', name='Project Alice')
+        self.bob = Product.objects.create(prefix='BOB', name='Project Robert')
+
+        self.new_product_data = {
+            'prefix': 'CAR',
+            'name': 'Project Caroline',
+        }
+
+        self.product_data = {
+            'prefix': self.ally.prefix,
+            'name': 'Project Alan',
+        }
+
+        self.bad_product_data = {
+            'prefix': self.bob.prefix,
+            'name': '',
+        }
+
+    def test_get_all_products(self):
+        response = self.client.get(reverse('product-list'))
+        products = Product.objects.all()
+        serializer = ProductSerializer(products, many=True)
+        self.assertEqual(response.data, serializer.data)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+    def test_get_product(self):
+        response = self.client.get(
+            reverse('product-detail', args=[self.ally.prefix])
+        )
+        product = Product.objects.get(prefix=self.ally.prefix)
+        serializer = ProductSerializer(product)
+        self.assertEqual(response.data, serializer.data)
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+    def test_get_invalid_product(self):
+        response = self.client.get(
+            reverse('product-detail', args=['randomnonsense'])
+        )
+        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+    def test_create_product(self):
+        response = self.client.post(
+            reverse('product-list'),
+            self.new_product_data,
+        )
+        product = Product.objects.get(prefix=self.new_product_data['prefix'])
+        serializer = ProductSerializer(product)
+
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.data, serializer.data)
+
+    def test_create_bad_product(self):
+        response = self.client.post(
+            reverse('product-list'),
+            self.bad_product_data,
+        )
+
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+    def test_update_product(self):
+        response = self.client.put(
+            reverse('product-detail', args=[self.ally.prefix]),
+            self.product_data,
+        )
+        product = Product.objects.get(prefix=self.product_data['prefix'])
+        serializer = ProductSerializer(product)
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+        self.assertEqual(response.data, serializer.data)
+
+    def test_update_product_bad_data(self):
+        response = self.client.put(
+            reverse('product-detail', args=[self.bob.prefix]),
+            self.bad_product_data,
+        )
+
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+    def test_delete_product(self):
+        response = self.client.delete(
+            reverse('product-detail', args=[self.ally.prefix]),
+        )
+        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
diff --git a/trackers/api/urls.py b/trackers/api/urls.py
new file mode 100644
index 0000000..c9ccf2f
--- /dev/null
+++ b/trackers/api/urls.py
@@ -0,0 +1,38 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+from django.urls import path
+from django.conf.urls import include
+from rest_framework import routers
+from . import views
+
+router = routers.DefaultRouter()
+router.register('users', views.UserViewSet)
+router.register('groups', views.GroupViewSet)
+router.register('products', views.ProductViewSet)
+router.register('tickets', views.TicketViewSet)
+
+ticket_router = routers.DefaultRouter()
+ticket_router.register('ticketevents', views.ChangeEventViewSet)
+
+urlpatterns = [
+    path('', include(router.urls)),
+    path('tickets/<uuid:id>/', include(ticket_router.urls)),
+    path('swagger<str:format>', views.schema_view.without_ui(cache_timeout=0), name='schema-json'),
+    path('swagger/', views.schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
+    path('redoc/', views.schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
+]
diff --git a/trackers/api/views.py b/trackers/api/views.py
new file mode 100644
index 0000000..34f0d57
--- /dev/null
+++ b/trackers/api/views.py
@@ -0,0 +1,64 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+from django.contrib.auth.models import User, Group
+from drf_yasg.views import get_schema_view
+from drf_yasg import openapi
+from rest_framework import permissions, viewsets
+from . import serializers
+from ..models import Product
+from trackers import models
+
+
+schema_view = get_schema_view(
+    openapi.Info(
+        title='Bloodhound Core API',
+        default_version='v1',
+    ),
+    public=True,
+    permission_classes=(permissions.AllowAny,),
+)
+
+
+class UserViewSet(viewsets.ModelViewSet):
+    queryset = User.objects.all()
+    serializer_class = serializers.UserSerializer
+
+
+class GroupViewSet(viewsets.ModelViewSet):
+    queryset = Group.objects.all()
+    serializer_class = serializers.GroupSerializer
+
+
+class ProductViewSet(viewsets.ModelViewSet):
+    queryset = Product.objects.all()
+    serializer_class = serializers.ProductSerializer
+
+
+class TicketFieldViewSet(viewsets.ModelViewSet):
+    queryset = models.TicketField.objects.all()
+    serializer_class = serializers.TicketFieldSerializer
+
+
+class TicketViewSet(viewsets.ModelViewSet):
+    queryset = models.Ticket.objects.all()
+    serializer_class = serializers.TicketSerializer
+
+
+class ChangeEventViewSet(viewsets.ModelViewSet):
+    queryset = models.ChangeEvent.objects.all()
+    serializer_class = serializers.ChangeEventSerializer
diff --git a/trackers/apps.py b/trackers/apps.py
index 7b9d013..9615c59 100644
--- a/trackers/apps.py
+++ b/trackers/apps.py
@@ -19,4 +19,5 @@
 
 
 class TrackersConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
     name = 'trackers'
diff --git a/trackers/models.py b/trackers/models.py
index 7baab71..f7fb321 100644
--- a/trackers/models.py
+++ b/trackers/models.py
@@ -26,6 +26,38 @@
 logger = logging.getLogger(__name__)
 
 
+class Product(models.Model):
+    prefix = models.TextField(primary_key=True)
+    name = models.TextField()
+    description = models.TextField(blank=True, null=True)
+    owner = models.TextField(blank=True, null=True)
+
+    class Meta:
+        db_table = 'bloodhound_product'
+
+
+class ProductConfig(models.Model):
+    """Possibly legacy table - keeping for now"""
+    product = models.ForeignKey(Product, on_delete=models.CASCADE)
+    section = models.TextField()
+    option = models.TextField()
+    value = models.TextField(blank=True, null=True)
+
+    class Meta:
+        db_table = 'bloodhound_productconfig'
+        unique_together = (('product', 'section', 'option'),)
+
+
+class ProductResourceMap(models.Model):
+    """Possibly legacy model - keeping for now"""
+    product_id = models.ForeignKey(Product, on_delete=models.CASCADE)
+    resource_type = models.TextField(blank=True, null=True)
+    resource_id = models.TextField(blank=True, null=True)
+
+    class Meta:
+        db_table = 'bloodhound_productresourcemap'
+
+
 class ModelCommon(models.Model):
     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
     created = models.DateTimeField(auto_now_add=True, editable=False)
diff --git a/trackers/tests/__init__.py b/trackers/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trackers/tests/__init__.py
diff --git a/trackers/tests/test_models.py b/trackers/tests/test_models.py
new file mode 100644
index 0000000..13191fc
--- /dev/null
+++ b/trackers/tests/test_models.py
@@ -0,0 +1,41 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+from django.test import TestCase
+from ..models import Product
+
+
+class ProductTest(TestCase):
+    """Test modules for Product model"""
+    def setUp(self):
+        Product.objects.create(
+            prefix='BHD',
+            name='Bloodhound Legacy',
+            description='The original Apache Bloodhound',
+        )
+        Product.objects.create(
+            prefix='BH',
+            name='Bloodhound',
+            description='The future of Apache Bloodhound',
+        )
+
+    def test_product_name(self):
+        bhd = Product.objects.get(prefix='BHD')
+        bh = Product.objects.get(prefix='BH')
+
+        self.assertEqual(bhd.name, "Bloodhound Legacy")
+        self.assertEqual(bh.name, "Bloodhound")
diff --git a/trackers/tests.py b/trackers/tests/tests.py
similarity index 97%
rename from trackers/tests.py
rename to trackers/tests/tests.py
index cbc666b..ef6bcb4 100644
--- a/trackers/tests.py
+++ b/trackers/tests/tests.py
@@ -18,7 +18,7 @@
 from django.http import HttpRequest
 from django.test import TestCase
 from django.urls import resolve
-from trackers.views import home
+from ..views import home
 
 
 class HomePageTest(TestCase):
@@ -35,7 +35,7 @@
         self.assertTrue(response.content.endswith(b'</html>'))
 
 
-from trackers.models import Ticket
+from ..models import Ticket
 
 class TicketModelTest(TestCase):
     def test_last_update_on_create_returns_created_date(self):
diff --git a/trackers/urls.py b/trackers/urls.py
index ffcc408..c62892f 100644
--- a/trackers/urls.py
+++ b/trackers/urls.py
@@ -17,23 +17,9 @@
 
 from django.urls import path
 from django.conf.urls import include
-from rest_framework import routers
 from . import views
 
-router = routers.DefaultRouter()
-router.register('users', views.UserViewSet)
-router.register('groups', views.GroupViewSet)
-router.register('tickets', views.TicketViewSet)
-router.register('ticketfields', views.TicketFieldViewSet)
-
-ticket_router = routers.DefaultRouter()
-ticket_router.register('ticketevents', views.ChangeEventViewSet)
-
 urlpatterns = [
     path('', views.home, name='home'),
-    path('api/', include(router.urls)),
-    path('api/tickets/<uuid:id>/', include(ticket_router.urls)),
-    path('swagger<str:format>', views.schema_view.without_ui(cache_timeout=0), name='schema-json'),
-    path('swagger/', views.schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
-    path('redoc/', views.schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
+    path('api/', include('trackers.api.urls')),
 ]
diff --git a/trackers/views.py b/trackers/views.py
index f1a6999..0861c17 100644
--- a/trackers/views.py
+++ b/trackers/views.py
@@ -15,49 +15,8 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-from django.contrib.auth.models import User, Group
 from django.http import HttpResponse
-from drf_yasg.views import get_schema_view
-from drf_yasg import openapi
-from rest_framework import permissions, viewsets
-from . import serializers
-from . import models
-
-
-schema_view = get_schema_view(
-    openapi.Info(
-        title='Bloodhound Core API',
-        default_version='v1',
-    ),
-    public=True,
-    permission_classes=(permissions.AllowAny,),
-)
 
 
 def home(request):
     return HttpResponse('<html><title>Bloodhound Trackers</title></html>')
-
-
-class UserViewSet(viewsets.ModelViewSet):
-    queryset = User.objects.all()
-    serializer_class = serializers.UserSerializer
-
-
-class GroupViewSet(viewsets.ModelViewSet):
-    queryset = Group.objects.all()
-    serializer_class = serializers.GroupSerializer
-
-
-class TicketFieldViewSet(viewsets.ModelViewSet):
-    queryset = models.TicketField.objects.all()
-    serializer_class = serializers.TicketFieldSerializer
-
-
-class TicketViewSet(viewsets.ModelViewSet):
-    queryset = models.Ticket.objects.all()
-    serializer_class = serializers.TicketSerializer
-
-
-class ChangeEventViewSet(viewsets.ModelViewSet):
-    queryset = models.ChangeEvent.objects.all()
-    serializer_class = serializers.ChangeEventSerializer