Skip to content

Latest commit

 

History

History

backend

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

BACKEND README

Software Requirements

Install Python and Pip before working on the Backend. Python 3.8.8 64-bit, pip 20.2.3 was used while creating this Backend Project.

Installation

Follow the steps mentioned below after Python and Pip Installation.

NOTE

If you are reading this documentation. It is assumed that you are already inside the backend directory of the project and has already cloned the repository. If not goto home directory of this repo and read the readme documentation.

Inside backend directory ,run

pip install virtualenv

for installing virtualenv package which will be used for isolating the packages used for this project. We recommend that you should create a virtual environment before working on this backend project.

virtualenv venv

'venv' can be replaced according to your preference.

You can activate the python environment by running the following command: Mac OS / Linux

source venv/bin/activate

Windows

venv\Scripts\activate

You should activate the venv again if you comeback later or open new tab on the terminal.

After activating the virtual-environment, please run the following commands.

pip install -r requirements.txt

'requirements.txt' contains all the pip packages required for this project to work properly. This file contains

django
djangorestframework
django-cors-headers
djangorestframework_simplejwt
drf-yasg

Once successful, run the migration commands to create migrations files and migrate the Models to the database.

python manage.py makemigrations
python manage.py migrate

You must run the above commands every time you do any changes in the models like adding a property, and needs to be updated in the database.

This project may contain database file already and If you want a fresh install delete migrations folder inside api module and db.sqlite3 file and run (optional)

python manage.py makemigrations api
python manage.py migrate

If you completely remove the folder related to migrations then you have to mention the app name as shown above to install the migrations properly otherwise simply remove the files only not the migrations module folder.

Starting the Server

python manage.py runserver

To create a new-app

python manage.py startapp

Running Tests

python manage.py test

runs all the test cases from tests module and functions with test_ as a prefix

Documentation

Backend

All the autogenerated html files containing function/class definitions can be found inside backend/docs/_build/html/ and the entry point is index.html. (Recently Introduced, may not cover all the function definitions but should be enough to get the general idea)

If you would like to contribute to this documentation method, Install sphinx tool. (optional)

pip install -U sphinx
cd docs
make html

Reference : (https://www.sphinx-doc.org/en/master/)

API Documentation

Everything is automated using djangorestframework yet another swagger generator. To view all available endpoints , Gotohttp://localhost:8000/swagger/ or http://localhost:8000/redoc/ after starting the backend server.

If you want to add a description to what your endpoints does simply add

from drf_yasg.utils import swagger_auto_schema

@swagger_auto_schema(operation_description="partial_update description override", responses={404: 'slug not found'})
def partial_update(self, request, *args, **kwargs):
   """partial_update method docstring"""
   ...

just before your function as shown above.

Reference: (https://drf-yasg.readthedocs.io/en/stable/)

PROJECT FILE STRUCTURE

    beergame
    ├── api
    │   ├── __init__.py
    │   ├── admin.py          # To register models on admin panel.
    │   ├── apps.py
    │   ├── migrations        # Migration files for database(automated) 
    │   ├── models.py         # Contains all Models User,Game,Role,Week.
    │   ├── serializers.py   # Serializers for models ( for validation/model output)
    │   ├── tests             # Contains all the testing codes
    │   │   ├── __init__.py
    │   │   ├── tests_api_game.py
    │   │   ├── tests_api_roles.py
    │   │   ├── tests_api_user.py
    │   │   └── tests_model.py
    │   └── views                # All endpoints related codes
    │       ├── __init__.py
    │       ├── game.py
    │       ├── role.py
    │       └── user.py
    ├── beergameapi.  #Project Module 
    │   ├── __init__.py
    │   ├── asgi.py
    │   ├── settings.py  #Config files for tokens, CORS, registering apps.
    │   ├── urls.py    #Contains ulrs + router for handling routes
    │   └── wsgi.py
    ├── db.sqlite3        #Database File
    ├── manage.py      # Use this to run commands like runserver, makemigrations
    ├── readme.md
    └── requirements.txt  #Contains requiredpackages

Project Structure

Settings

Settings.py contains all the config files related to the projects. New apps and modules can be added in this array inside settings.py file

INSTALLED_APPS = [
...
...
'api',
'rest_framework',
'corsheaders',
'rest_framework_simplejwt',
'rest_framework_simplejwt.token_blacklist',
'drf_yasg',
'django.contrib.admindocs',
]

If you want to customize how the token is generated you can modify these lines of code.

#BASIC JWT FUNCTION
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(days=5),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=10),
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUTH_HEADER_TYPES': ('JWT',),
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',
}

On this setup the token will expire in 5 days.

Change Default Authentication to the API by modifying the code mentioned below.

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny'
    ],

    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ],
}

URLs

urls.py inside beergameapi contains all the routers and url-patterns for basic handling of routes in the backend.

router = routers.DefaultRouter()
router.register('game', gameview,'Game')
router.register('role', roleview)

urlpatterns = [
    path("api/",include(router.urls)),
    path("api/user/",userview.as_view()),
    path("api/user/changepassword/",ChangePasswordView.as_view()),
    path("api/register/",registerview.as_view()),
    re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
   re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
   re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
    path('admin/doc/', include('django.contrib.admindocs.urls')),
    path('admin/', admin.site.urls),
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenObtainPairView.as_view(), name='token_refresh'),
]

Normally, If you have a simple view such as change password that does nothing other than changing password then you can add it as a single path in the url-patterns array. But, you can also register a router as shown above for gameview and roleview, which are viewsets from djangorestframework and allows us to handle multiple routes coming after /game or /role. For example,

/game/{gameid}/
/game/{gameid}/monitor
/role/{roleid}/
/role/{roleid}/register
/role/{roleid}/orderbeer
...
...

You can see all the full source code inside urls.py.

Models

This model.pyfile inside api contains all the models and automated codes related to this project. For example,

from django.dispatch import receiver
......
.......

class Role(models.Model):
    """
    Role model with with user, associated game, week as foreigin keys
    """
    roleName = models.CharField(max_length=30)
    associatedGame = models.ForeignKey(
        Game, on_delete=models.CASCADE, related_name='gameroles')
    ordered = models.BooleanField(default=False)
    downstreamPlayer = models.ForeignKey(
        "self", null=True, blank=True, on_delete=models.CASCADE, related_name='%(class)s_downstreamPlayer')
    upstreamPlayer = models.ForeignKey(
        "self", null=True, blank=True, on_delete=models.CASCADE, related_name='%(class)s_upstreamPlayer')
    playedBy = models.ForeignKey(User, null=True,  blank=True, limit_choices_to={
                                 'is_instructor': False}, on_delete=models.CASCADE, related_name="playerrole")

    class Meta:  # Can play as only one Role
        unique_together = ('playedBy', 'associatedGame',)

    def __str__(self):
        return (self.roleName + " of Game " + str(self.associatedGame.id))
        

.......
........
# On Role Creation CREATE DEFAULT Week 1
@receiver(post_save, sender=Role)
def onRoleCreation(sender, instance, created, **kwargs):
    """
    Handles automatic creation of Weeks on RoleCreation with default values. 
    """

    if created: #if new role created , generate a default week 1
        week = Week.objects.create(number=1, associatedRole=instance,
                                   inventory=instance.associatedGame.starting_inventory,
                                   cost=instance.associatedGame.starting_inventory*instance.associatedGame.holding_cost)
        week.save()

Views

We have separated all the views inside api app(folder) , functions according to the model. So, there are currently role.py, game.py, user.py handling different routes separately making it cleaner. Comments and description are given to each function so that it's easier to understand the code and the variable names are self explanatory. For example,

from rest_framework.views import APIView
.......
.......
class userview(APIView):
    """
    Displays User Info
    """
    permission_classes = [IsAuthenticated]
    serializer_class = UserSerializer

    @swagger_auto_schema(operation_description="Returns Logged in user info")
    def get(self, request, format="json"):
        serialized = UserSerializer(request.user)
        return Response(serialized.data, status=status.HTTP_200_OK)
 
class registerview(generics.CreateAPIView):
    """
    Register new user
    """
    serializer_class = UserSerializer

This is a basic class based view that extends APIView from djangorestframework and handles user request and returns user info using UserSerializer.

Serializer

This serializers.py contains all the Serializers imported and extended from base serializers contained djangorestframework.

And a UserSerializer mentioned above would look like.

from rest_framework import serializers
....

.....

class UserSerializer(serializers.ModelSerializer):
    """
    User Serializer for Registeration.
    """
    class Meta:
        model = User
        fields = ('email', 'name', 'is_instructor', 'password',)
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        password = validated_data.pop('password', None)
        # as long as the fields are the same, we can just use this
        instance = self.Meta.model(**validated_data)
        if password is not None:
            instance.set_password(password)
        instance.save()
        return instance

Tests

Contains all the test related functions. To create a new test for API

from rest_framework.test import APITestCase
class GameApiTest(APITestCase):
	 def setUp(self):
		#all your setup codes
		#creating test users,games,....
	def test_yourtestfunctionname(self):
		#testing setup,logic
		self.assertEqual(5, TOCHECKVARIABLE)

For example,

    def test_authentication_without_password(self):
        """
        Test to verify that a post call with missing fields ( password)
        """
        response = self.client.post(self.url, {"email": "[email protected]"})
        self.assertEqual(400, response.status_code)

TODO

  1. Maintain a connection between frontend and backend and notify frontend if there is any change like user has ordered or the week is complete and the user should reload the page.
  2. Optimize Responses by creating more Serialisers and improving data structuring specific for each endpoints.
  3. Some methods uses duplicate codes. Must refactor by creating functions for better code visibility.
  4. Implement demand Pattern Model and integrate it with Game.
  5. Introduce new endpoints providing data suitable for Graphs used in the frontend.
  6. If Useful , maintain a relationship between User and Instructor for limited availability of game.
  7. and many more ideas that you might have on your mind.

References

  1. Django Documentation Django was used in this project because it contains all the necessary features like Admin, Models, URL routing to get you started quickly.

  2. djangorestframework Quicstart Offers cleaner and efficient Class,Function based views with serialisers to convert your Models into API.

  3. django-cors-headers Cross-origin resource sharing. Necessary to allow frontend communicate and request data from the backend.

  4. djangorestframework_simplejwt - WebTokens Fits well with djangorestframeworks and useful for handling tokens as session is not a thing if you separate frontend and backend to different servers.

  5. drf-yasg Yet another Swagger generator - API Playground/ Generates automatic documentation for all views compatible with djangorestframework