DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Lets Build An E-commerce Django REST API & GraphQL on the same App .
myk_okoth_ogodo
myk_okoth_ogodo

Posted on

Lets Build An E-commerce Django REST API & GraphQL on the same App .

In my previous tutorial i wrote about building Go-lang micro-services,in this tutorial i want to us to build a single application to serve GraphQL and REST at the same time. It will be an e-commerce API and depending on the reception we might get to building a front-end store based on React or React-Native(vote below in the comments).

We will be using the python framework called Django and the tool DRF(Django Rest Framework). To authenticate our API we will be implementing one scheme that comes out of the box with Django-rest and also one more third-party scheme just for demonstration purposes.
In the front-end i intend to use react-native, to build a simple mobile front-end store or react for a simple we-app, it will entirely depend on what the readers will prefer.

I intend to break this application into three small parts:

  • Build a simple Django REST API
  • Build a simple Django Graphql API based on the models we define for the REST API above.
  • Build a simple front-end app using React-Native/ React

You can refer to this respository if you get stuck.
Get yourself a warm cup of coffee and Lets begin.....

REST API BACK-END.

setting up our development Environment.
You either have Pipenv or Virtualenv installed in your system(if not i advice you to get those installed because they help us create an isolated development environment for our application).
if you have virtualenv installed in your system,open your terminal create a directory graphRestAPI and inside that directory create a development environment as below.

mykmyk@skynet:~/graphRestAPI$ virtualenv venv
Using base prefix '/usr'
New python executable in /home/mykmyk/graphRestAPI/venv/bin/python
Installing setuptools, pip, wheel...done.

Enter fullscreen mode Exit fullscreen mode

To activate our newly created environment:

mykmyk@skynet:~/graphRestAPI$ source venv/bin/activate
(venv) mykmyk@skynet:~/graphRestAPI$ 

Enter fullscreen mode Exit fullscreen mode

Now in this environment we install all the packages that we will be using in this application, we start with our framework Django and the django-rest tool.

(venv) mykmyk@skynet:~/graphRestAPI$ pip install Django djangorestframework
Enter fullscreen mode Exit fullscreen mode

we then create a project using Django admin to house our apps, this structure while being a bit more code verbose, allows for better scaling of our applications.
We type in the following commands in our terminal.

(venv) mykmyk@skynet:~/graphRestAPI$ django-admin startproject CoreAPI
(venv) mykmyk@skynet:~/graphRestAPI$ ls
CoreAPI  venv
Enter fullscreen mode Exit fullscreen mode

enter into the newly created 'CoreAPI' project folder and create an app that will hold all the project specific apps.

(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI$ django-admin startapp coreGraphRestAPI
(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI$ ls
CoreAPI  coreGraphRestAPI  manage.py
Enter fullscreen mode Exit fullscreen mode

move into the newly created 'coreGraphRestAPI' and delete the files , admin.py, models.py, tests.py,views.py

(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI/coreGraphRestAPI$ ll
total 32
drwxrwxr-x 3 mykmyk mykmyk 4096 Jun 19 09:55 ./
drwxrwxr-x 4 mykmyk mykmyk 4096 Jun 19 09:55 ../
-rw-rw-r-- 1 mykmyk mykmyk   63 Jun 19 09:55 admin.py
-rw-rw-r-- 1 mykmyk mykmyk  164 Jun 19 09:55 apps.py
-rw-rw-r-- 1 mykmyk mykmyk    0 Jun 19 09:55 __init__.py
drwxrwxr-x 2 mykmyk mykmyk 4096 Jun 19 09:55 migrations/
-rw-rw-r-- 1 mykmyk mykmyk   57 Jun 19 09:55 models.py
-rw-rw-r-- 1 mykmyk mykmyk   60 Jun 19 09:55 tests.py
-rw-rw-r-- 1 mykmyk mykmyk   63 Jun 19 09:55 views.py
(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI/coreGraphRestAPI$ rm admin.py models.py tests.py views.py
Enter fullscreen mode Exit fullscreen mode

We then move into the 'CoreAPI' folder containing our 'settings.py' that houses our Django configurations, we then add our main app and our django-rest to the INSTALLED_APPS:

 #CoreAPI/settings.py

 33 INSTALLED_APPS = [
 34     'django.contrib.admin',
 35     ....
 39     'django.contrib.staticfiles',
 40     'rest_framework',                                                                                                                                               
 41     'coreGraphRestAPI'
 42 ]
Enter fullscreen mode Exit fullscreen mode

Create User Application:

We will now create a user application for our project, To create a user application

(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI$ ls
CoreAPI  coreGraphRestAPI  manage.py
(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI$ cd coreGraphRestAPI/ && python ../manage.py startapp Users

Enter fullscreen mode Exit fullscreen mode

now if we move into the 'coreGraphRestAPI' folder we see:

(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI/coreGraphRestAPI$ ll
total 24
drwxrwxr-x 5 mykmyk mykmyk 4096 Jun 19 10:24 ./
drwxrwxr-x 4 mykmyk mykmyk 4096 Jun 19 09:55 ../
-rw-rw-r-- 1 mykmyk mykmyk  164 Jun 19 09:55 apps.py
-rw-rw-r-- 1 mykmyk mykmyk    0 Jun 19 09:55 __init__.py
drwxrwxr-x 2 mykmyk mykmyk 4096 Jun 19 09:55 migrations/
drwxrwxr-x 2 mykmyk mykmyk 4096 Jun 19 10:24 __pycache__/
drwxrwxr-x 3 mykmyk mykmyk 4096 Jun 19 10:24 Users/

Enter fullscreen mode Exit fullscreen mode

we now have to add this application to our settings.py

INSTALLED_APPS = [
 34     'django.contrib.admin',
        ...
 40     'rest_framework',
 41     'coreGraphRestAPI',
 42     'coreGraphRestAPI.Users',                                               
 43 ]
Enter fullscreen mode Exit fullscreen mode

we then move into the file 'coreGraphRestAPI/Users' and open the file apps.py and add in the following

 1 from django.apps import AppConfig
  2 
  3 
  4 class UsersConfig(AppConfig):
  5     default_auto_field = 'django.db.models.BigAutoField'
  6     name  = 'coreGraphRestAPI.Users'                                        
  7     label = 'coreGraphRestAPI_Users'

Enter fullscreen mode Exit fullscreen mode

And then open the file "init.py " in the same folder and add the following:

 1 default_app_config = 'coreGrapRestAPI.Users.apps.UsersConfig'

Enter fullscreen mode Exit fullscreen mode

Sewing Our User Logic Together.
Django Auth System
Django comes with a built-in user authentication system. It handles user accounts, groups, permissions and cookie-based sessions. Django's authentication system handles both authentication and authorization.
However, we usually find that this out of the box solution doesn't cover all our use cases and we need to extend it to fit our specific project needs

User Model
In this case we will be creating a Custom user model by wrapping the Django AbstractBaseUser class. Our custom user will extend AbstractBaseUser and we will rewrite the UserManager to customize creation of a user in our database.

Lets dive into it.....

open the file "graphRestAPI/CoreAPI/coreGraphRestAPI/Users/models.py" and add the following code to it.

1 from django.db import models                                                                                                                                        
  2 from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
  3 
  4 
  5 class UserManager(BaseUserManager):
  6     def create_user(self, email, password, **extra_fields):
  7         if not email:
  8             raise ValueError("Email is required")
  9 
 10         email = self.normalize_email(email)
 11         user = self.model(email=email, **extra_fields)
 12         user.set_password(password)
 13         user.save()
 14         return user
 15 
 16     def create_superuser(self, email, password, **extra_fields):
 17         extra_fields.setdefault('is_staff', True)
 18         extra_fields.setdefault('is_superuser', True)
 19         extra_fields.setdefault('is_active', True)
 20         extra_fields.setdefault('first_name', "admin")
 21         extra_fields.setdefault('last_name', "admin")
 22 
 23         if not extra_fields.get("is_staff", False):
 24             raise ValueError('Superuser must have is_staff=True.')
 25 
 26         if not extra_fields.get("is_superuser", False):
 27             raise ValueError('Superuser must have is_staff=True.')
 28 
 29         return self.create_user(email, password, **extra_fields)
 30 
 31 
 32 
 33 class ImageUpload(models.Model):
 34     image = models.ImageField(upload_to="images")
 35 
 36     def __str__(self):
 37         return str(self.image)
 38 
 39 
 40 class UserProfile(models.Model):
 41     #user = models.OneToOneField(User, related_name="user_profile", on_delete=models.CASCADE)
 42     profile_picture = models.ForeignKey(ImageUpload, related_name="user_images", on_delete=models.SET_NULL, null=True)
 43     dob = models.DateField()
 44     phone = models.PositiveIntegerField()
 45     country_code = models.CharField(default="+234", max_length=5)
 46     created_at = models.DateTimeField(auto_now_add=True)
 47     updated_at = models.DateTimeField(auto_now=True)
 48 
 49     def __str__(self):
 50         return self.user.email
 51 
 52 
 53 class UserAddress(models.Model):
 54     user_profile = models.ForeignKey(UserProfile, related_name="user_addresses", on_delete=models.CASCADE)
 55     street = models.TextField()
 56     city = models.CharField(max_length=100)
 57     state = models.CharField(max_length=100)
 58     country = models.CharField(max_length=100, default="Nigeria")
 59     is_default = models.BooleanField(default=False)
 60 
 61     created_at = models.DateTimeField(auto_now_add=True)
 62     updated_at = models.DateTimeField(auto_now=True)
 63 
 64     def __str__(self):
 65         return self.user_profile.user.email
 66                                                                                                                                                                     
 67 
 68 
 69 class User(AbstractBaseUser, PermissionsMixin):
 70     email = models.EmailField(unique=True)
 71     first_name = models.CharField(max_length=100, default="yourfirstname")
 72     last_name = models.CharField(max_length=100, default="yourlastname")
 73     user_address = models.OneToOneField(UserAddress, related_name="user_address",blank=True, null=True, on_delete=models.CASCADE)
 74     created_at = models.DateTimeField(auto_now_add=True)
 75     updated_at = models.DateTimeField(auto_now=True)
 76 
 77     is_staff = models.BooleanField(default=False)
 78     is_active = models.BooleanField(default=True)
 79 
 80     USERNAME_FIELD = "email"
 81     objects = UserManager()
 82 
 83     def __str__(self):
 84         return self.email
 85 
 86     def save(self, *args, **kwargs):
 87         super().full_clean()
 88         super().save(*args, **kwargs)

Enter fullscreen mode Exit fullscreen mode

Then head to your "settings.py" file in the folder "graphRestAPI/CoreAPI/CoreAPI" , in hat file add the following line , what it does is it now uses our extension of the Django User as the application user for things like authentication, cookie-based session management etc.

*settings.py *

AUTH_USER_MODEL = 'coreGraphRestAPI_Users.User'

Enter fullscreen mode Exit fullscreen mode

The next logical step is serialization of our data, i.e our recently defined models.Serialization refers to the process of converting a data object (e.g., Python objects, Tensorflow models) into a format that allows us to store or transmit the data and then recreate the object when needed using the reverse process of de-serialization. In this case we will be serializing our data from python native objects to JSON for transmission and then de-serializing from JSON format into python native data types for storage.

create a file "serializer.py" in the folder "graphRestAPI/CoreAPI/coreGraphRestAPI/Users", open that file and add in the following code:

serializer.py

  1 from django.contrib.auth import get_user_model
  2 from rest_framework import serializers                                                                                                                              
  3 from rest_framework.reverse import reverse
  4 from .models import UserProfile, UserAddress, ImageUpload
  5 from drf_writable_nested import WritableNestedModelSerializer
  6 
  7 
  8 
  9 User = get_user_model()
 10 
 11 
 12 class ImageUploadSerializer(serializers.ModelSerializer):
 13     class Meta:
 14         model = ImageUpload
 15         fields = ('image',)
 16 
 17 
 18 class UserProfileSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):
 19     profile_picture = ImageUploadSerializer(read_only = False)
 20     class Meta:
 21         model = UserProfile
 22         fields = ('profile_picture','dob','phone','country_code')
 23 
 24 
 25 class UserAddressSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):
 26     user_profile = UserProfileSerializer(read_only = False)
 27 
 28     class Meta:
 29         model = UserAddress
 30         fields = ('user_profile','street','city','state','country','is_default')
 31 
 32 
 33 
 34 class UserSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):
 35     links = serializers.SerializerMethodField('get_links')
 36     user_address = UserAddressSerializer(read_only=False)
 37     
 38     class Meta:
 39         model = User
 40         fields = ('id', User.USERNAME_FIELD,'password','first_name','last_name','user_address','is_active','links')
 41 
 42 
 43     def get_links(self, obj):
 44         request = self.context['request']
 45         username = obj.get_username()
 46 
 47         return {
 48                 #'self': reverse('user-detail',
 49                 #kwargs = {User.USERNAME_FIELD: username}, request=request),
 50                 }


Enter fullscreen mode Exit fullscreen mode

run the command below to install the drf_writable tool

pip install drf_writable_nested

Enter fullscreen mode Exit fullscreen mode

All we are doing in the above code is creating serializer and deserializer classes for our three models: User model, UserProfile model, UserAddress model.
We are using the tool "drf_writable_nested" to serialize our nested classes, because of the one_to_one and one_to_many relationship between our model classes.

Next on the table is to create viewsets. Now, there are many ways to go about this but to make the process simple , i will be using viewsets.ModelViewSet, The ModelViewSet class inherits from GenericAPIView and includes implementations for various actions, by mixing in the behavior of the various mixin classes.
The actions provided by the ModelViewSet class are .list(), .retrieve(), .create(), .update(), .partial_update(), and .destroy().

Also note that although this class provides the complete set of create/list/retrieve/update/destroy actions by default, you can restrict the available operations by using the standard permission classes.

open the file "views.py" in the folder "graphRestAPI/CoreAPI/coreGraphRestAPI/Users",

views.py

 1 from django.contrib.auth import get_user_model                                                                                                                      
  2 from rest_framework import authentication, permissions, viewsets, filters
  3 from .serializer import UserSerializer
  4 from django_filters.rest_framework import DjangoFilterBackend
  5 
  6 
  7 
  8 from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope
  9 
 10 
 11 User = get_user_model()
 12 
 13 class DefaultsMixin(object):
 14     """Default settings for view authentication, permissions, filtering and pagination."""
 15     authentication_classes = (
 16         authentication.BasicAuthentication,
 17         authentication.TokenAuthentication,
 18             )
 19     permission_classes = (
 20         permissions.IsAuthenticated,
 21         #TokenHasReadWriteScope,
 22         )
 23     paginate_by = 25
 24     paginate_by_param = 'page_size'
 25     max_paginate_by = 100
 26     filter_backends = (
 27         DjangoFilterBackend,
 28         filters.SearchFilter,
 29         filters.OrderingFilter
 30             )    
 31     
 32 class UserViewSet(DefaultsMixin, viewsets.ModelViewSet):
 33     """API endpoint for listing users."""
 34     
 35     lookup_field = User.USERNAME_FIELD
 36     lookup_url_kwarg = User.USERNAME_FIELD
 37     queryset = User.objects.order_by(User.USERNAME_FIELD) 
 38     serializer_class = UserSerializer 
Enter fullscreen mode Exit fullscreen mode

run the following in your terminal

pip install django-filter==21.1 django-oauth-toolkit==2.0.0

Enter fullscreen mode Exit fullscreen mode

Notice Our DefaultsMixin Class above.A mixin is a class that provides method implementations for reuse by multiple related child classes. However, the inheritance is not implying an is-a relationship. A mixin doesn't define a new type.

Connecting to the Router
At this point the Users app has the basic data model and view logic in place, but it has not been connected to the URL routing system. Django-rest-framework has its own URL routing extension for handling ViewSets, where each ViewSet is registered with the router for a given URL prefix. This will be added to a new file in graphRestAPI/CoreAPI/coreGraphRestAPI/urls.py.

So create a urls.py file in the folder "graphRestAPI/CoreAPI/coreGraphRestAPI" and add the following code:

  1 from rest_framework.routers import DefaultRouter                                                                                                                    
  2 from .Users.views import UserViewSet
  3 
  4 
  5 router = DefaultRouter()
  6 
  7 router.register(r'users', UserViewSet)
  8 
Enter fullscreen mode Exit fullscreen mode

Finally, this router needs to be included in the root URL configuration in /urls.py.

  1 from django.contrib import admin                                                                                                                                    
  2 from django.urls import path
  3 from django.conf.urls import include, url
  4 from rest_framework.authtoken.views import obtain_auth_token
  5 from coreGraphRestAPI.urls import router
  6 
  7 
  8 
  9 urlpatterns = [
 10     url(r'^admin/', admin.site.urls),
 11     url(r'^api/token/', obtain_auth_token, name='api-token'),
 12     url(r'^api/', include(router.urls)),
 13     
 14 ]

Enter fullscreen mode Exit fullscreen mode

Create Product application

Next we want to create the second application called Products, the first application was Users application.
We follow the same flow as we just did to create the user app:

Go to the folder "graphRestAPI/CoreAPI", then run the command below to create the app:

(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI$ cd coreGraphRestAPI/ && python ../manage.py startapp Products
Enter fullscreen mode Exit fullscreen mode

then to create Businesses app:

(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI$ cd coreGraphRestAPI/ && python ../manage.py startapp Businesses
Enter fullscreen mode Exit fullscreen mode

Then to create the Cart app

(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI$ cd coreGraphRestAPI/ && python ../manage.py startapp Cart
Enter fullscreen mode Exit fullscreen mode

Then to create the Wish app

(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI$ cd coreGraphRestAPI/ && python ../manage.py startapp Wish
Enter fullscreen mode Exit fullscreen mode

now if you cd into the folder "graphRestAPI/CoreAPI/coreGraphRestAPI", it looks like below:

(venv) mykmyk@skynet:~/graphRestAPI/CoreAPI/coreGraphRestAPI$ ll
total 40
drwxrwxr-x 8 mykmyk mykmyk 4096 Jun 20 12:03 ./
drwxrwxr-x 6 mykmyk mykmyk 4096 Jun 20 13:52 ../
-rw-rw-r-- 1 mykmyk mykmyk  164 Jun 19 09:55 apps.py
drwxrwxr-x 4 mykmyk mykmyk 4096 Jun 20 13:47 Businesses/
drwxrwxr-x 4 mykmyk mykmyk 4096 Jun 20 14:17 Cart/
-rw-rw-r-- 1 mykmyk mykmyk    0 Jun 19 09:55 __init__.py
drwxrwxr-x 4 mykmyk mykmyk 4096 Jun 20 12:39 Products/
drwxrwxr-x 2 mykmyk mykmyk 4096 Jun 20 12:07 __pycache__/
-rw-rw-r-- 1 mykmyk mykmyk  486 Jun 20 12:03 urls.py
drwxrwxr-x 4 mykmyk mykmyk 4096 Jun 20 13:52 Users/
drwxrwxr-x 4 mykmyk mykmyk 4096 Jun 20 14:17 Wish/


Enter fullscreen mode Exit fullscreen mode

proceed into the folder "graphRestAPI/CoreAPI/coreGraphRestAPI/Products", and the open the "apps.py" just as we did in the Users app above, then add the following code:

apps.py

  1 from django.apps import AppConfig
  2 
  3 
  4 class ProductsConfig(AppConfig):
  5     default_auto_field = 'django.db.models.BigAutoField'
  6     name  = 'coreGraphRestAPI.Products'                                                                                                                             
  7     label = 'coreGraphRestAPI_Products'


Enter fullscreen mode Exit fullscreen mode

Next, open the file "init.py" in the same folder and add the following code:

init.py

  1 default_app_config = 'coreGraphRestAPI.Products.apps.ProductsConfig'          
Enter fullscreen mode Exit fullscreen mode

Repeat the two processes above for the three new apps that we have created i.e Cart , Wish, Businesses

then we head to our settings.py file in the folder : graphRestAPI/CoreAPI/CoreAPI

settings.py

  33 INSTALLED_APPS = [
 34     'django.contrib.admin',
         ....
 40     'rest_framework',
 41     'django_filters',
 42     'coreGraphRestAPI',
 43     'coreGraphRestAPI.Users',
 44     'coreGraphRestAPI.Products',
 45     'coreGraphRestAPI.Businesses',
 46     'coreGraphRestAPI.Cart',                                                                                                                                        
 47     'coreGraphRestAPI.Wish',
 48 ]



Enter fullscreen mode Exit fullscreen mode

Notice above we have added the second application "coreGraphRestAPI.Products", the third Businesses , fourth Cart and finally Wish.

The next task is defining the models for this "Products" application, to do this we head to the folder: graphRestAPI/CoreAPI/coreGraphRestAPI/Products

open the file "models.py" and add the following code,

models.py

  1 from django.db import models                                                                                                                                        
  2 from coreGraphRestAPI.Users.models import ImageUpload
  3 from coreGraphRestAPI.Businesses.models import Business
  4 from django.contrib.auth import get_user_model
  5 
  6 User = get_user_model()
  7 
  8 class Category(models.Model):
  9     name = models.CharField(max_length=100, unique=True)
 10     created_at = models.DateTimeField(auto_now_add=True)
 11 
 12     def __str__(self):
 13         return self.name
 14 
 15 class ProductImage(models.Model):
 16     image = models.ForeignKey(ImageUpload, related_name="product_image", on_delete=models.CASCADE)
 17     is_cover = models.BooleanField(default=False)
 18     created_at = models.DateTimeField(auto_now_add=True)
 19     update_at = models.DateTimeField(auto_now=True)
 20 
 21     def __str__(self):
 22         return f"{self.product.business.name} - {self.product.name} - {self.image}"
 23 
 24 class ProductComment(models.Model):
 25     user = models.ForeignKey(User, related_name="user_comments_on_product", on_delete=models.CASCADE)
 26     comment = models.TextField()
 27     rate = models.IntegerField(default=3)
 28     created_at = models.DateTimeField(auto_now_add=True)
 29 
 30 class Product(models.Model):
 31     category = models.ForeignKey(Category, related_name="product_categories", on_delete=models.CASCADE)
 32     business = models.ForeignKey(Business, related_name="belongs_to_business", on_delete=models.CASCADE)
 33     name = models.CharField(max_length=100)
 34     price = models.FloatField()
 35     total_available = models.PositiveIntegerField()
 36     total_count = models.PositiveIntegerField()
 37     description = models.TextField()
 38     product_images = models.ForeignKey(ProductImage, related_name="product_images", on_delete=models.CASCADE)
 39     product_comments = models.ForeignKey(ProductComment, related_name="product_comments", on_delete=models.CASCADE)
 40     created_at = models.DateTimeField(auto_now_add=True)
 41     update_at = models.DateTimeField(auto_now=True)
 42 
 43     def __str__(self):
 44         return f"{self.business.name} - {self.name}"
 45 
 46     class Meta:
 47         ordering = ("-created_at",)

Enter fullscreen mode Exit fullscreen mode

After defining the model Product the next natural step is to define the serializer. Create a file "serializer.py" open it and add the following code:

serializer.py

  1 from django.contrib.auth import get_user_model                                                                                                                      
  2 from rest_framework import serializers
  3 from rest_framework.reverse import reverse
  4 from .models import Product, ProductImage, ProductComment, Category, ImageUpload
  5 from drf_writable_nested import WritableNestedModelSerializer
  6 
  7 User = get_user_model()
  8 
  9 class CategorySerializer(serializers.ModelSerializer):
 10     class Meta:
 11         model = Category
 12         fields = "__all__"
 13 
 14 class ImageUploadSerializer(serializers.ModelSerializer):
 15     class Meta:
 16         model = ImageUpload
 17         fields = "__all__"
 18 
 19 class ProductImageSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):
 20     image = ImageUploadSerializer(read_only = False)
 21     class Meta:
 22         model = ProductImage
 23         fields = "__all__"
 24 
 25 class ProductCommentSerializer(serializers.ModelSerializer):
 26     class Meta:
 27         model = ProductComment
 28         fields = "__all__"
 29 
 30 class ProductSerializer(WritableNestedModelSerializer, serializers.ModelSerializer):
 31     links = serializers.SerializerMethodField('get_links')
 32     category = CategorySerializer(read_only = False)
 33     product_images = ProductImageSerializer(read_only = False)
 34     product_comments = ProductCommentSerializer(read_only = False)
 35     class Meta:
 36         model = Product
 37         fields = ['id','category','business','name','price','total_available','total_count','description','product_images','product_comments','links']
 38 
 39     def get_links(self, obj):
 40         request = self.context['request']
 41         links = {
 42                 'self': reverse('product-detail',
 43                 kwargs = {'pk': obj.pk}, request=request),
 44                 'business': None,
 45                 }
 46 
 47         if obj.business:
 48             links['business'] = reverse('business-detail',
 49                 kwargs = {'pk': obj.business}, request=request)        
 50         return links

Enter fullscreen mode Exit fullscreen mode

After the serializing our data the next logical step is to piece together our Filters, create a file called "forms.py"

Open it and add the following code:

  1 from django_filters.rest_framework import BooleanFilter, FilterSet, DateFilter                                                                                      
  2 from django.contrib.auth import get_user_model
  3 from .models import Product
  4 
  5 
  6 class NullFilter(BooleanFilter):
  7     """ Filter on a field set as null or not."""
  8     def filter(self, qs, value):
  9         if value is not None:
 10             return qs.filter(**{'%s__isnull' % self.name: value})
 11         return qs
 12 
 13 
 14 class ProductFilter(FilterSet):
 15     class Meta:
 16         model = Product
 17         fields = ('business','total_count','price','category')

Enter fullscreen mode Exit fullscreen mode

The above file is uses to define our filters that will be used on our API data.

Next logical step is to create our viewsets, similar to the ones we defined when creating our Users.

open the "views.py" file and add the following code :

views.py

  1 from django.contrib.auth import get_user_model
  2 from rest_framework import authentication, permissions, viewsets, filters                                                                                           
  3 from .serializer import ProductSerializer
  4 from .models import Product
  5 from .forms import ProductFilter
  6 from django_filters.rest_framework import DjangoFilterBackend
  7 from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope
  8 
  9 
 10 class DefaultsMixin(object):
 11     """Default settings for view authentication, permissions, filtering and pagination."""
 12     authentication_classes = (
 13         authentication.BasicAuthentication,
 14         authentication.TokenAuthentication,
 15             )
 16     permission_classes = (
 17         permissions.IsAuthenticated,
 18         #TokenHasReadWriteScope,
 19         )
 20     paginate_by = 25
 21     paginate_by_param = 'page_size'
 22     max_paginate_by = 100
 23     filter_backends = (
 24         DjangoFilterBackend,
 25         filters.SearchFilter,
 26         filters.OrderingFilter
 27             )    
 28 
 29 
 30 class ProductViewSet(DefaultsMixin, viewsets.ModelViewSet):
 31     """API endpoint for listing Products."""
 32     
 33     queryset = Product.objects.order_by('name',) 
 34     serializer_class = ProductSerializer   
 35     filter_class = ProductFilter
 36     search_fields = ('name','price','business')                                                                                                                     
 37     ordering_fields = ('price','name','total_available')                 
Enter fullscreen mode Exit fullscreen mode

To finally add this view to our urls.py file in our root application "coreGraphRestAPI" folder , it will then look like this together with the other new applications:

CoreAPI/coreGraphRestAPI/urls.py

  1 from rest_framework.routers import DefaultRouter                                                                                                                    
  2 from .Users.views import UserViewSet
  3 from .Products.views import ProductViewSet
  4 from .Businesses.views import BusinessViewSet
  5 from .Cart.views import  CartViewSet
  6 from .Wish.views import  WishViewSet
  7 
  8 router = DefaultRouter()
  9 
 10 router.register(r'users', UserViewSet)
 11 router.register(r'products', ProductViewSet)
 12 router.register(r'businesses',BusinessViewSet)
 13 router.register(r'carts', CartViewSet)
 14 router.register(r'wishes', WishViewSet)
~

Enter fullscreen mode Exit fullscreen mode

Now to avoid repeating myself, the code for the models, serializers, views and Filters for the Businesses, Cart, Wishes applications can be found at the following repository:
e-commerce repo

In this part we will be using dbsqlite, but we will migrate to postgresql in the next chapter, so dont worry about that.

After you finish creating the above applications you can run the following command to migrate your data

python manage.py makemigrations
python manege.py  migrate
Enter fullscreen mode Exit fullscreen mode

Then create a superuser to log into your browsable API

python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

Then launch the application

python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Next, before we proceed to the next part of this series there are things we have to get out of the way

  • We need to move our data to postgresql or mysql (help me choose which one)
  • We need to wire up token authentication for our API
  • We need to write tests for our API application
  • Containerze our app using docker Till then, Keep Safe and stay curious.

Top comments (2)

Collapse
 
mcwolfmm profile image
mcwolfmm

rest is architecture!!! you cannot serve architecture.

Collapse
 
myk_okoth_ogodo profile image
myk_okoth_ogodo Author

duly adviced,i will change the title to fit closely with the topic.

πŸ— We built a 100% open source community software called Forem.

Β 
You can contribute to the codebase or host your own.