Django: The value of the id field is coming None

I want to create a unique id for all my groups in my django project which will help us to easily detect the user action.

I want to create a unique id for all my groups in my django project which will help us to easily detect the user action.

I have tried the following:

Models:

class Group1(models.Model):
    urlhash = models.CharField(max_length=6, null=True, blank=True, unique=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=True,blank=True)
    group_name = models.CharField(max_length=32)

This is the functionality I have given:

def save(self):
    if not self.urlhash:
       if self.user.profile.user_type == 'Bussiness User':
           self.urlhash = 'B' + str(self.user.id) + ('00') + str(self.id)
       else:
           self.urlhash = 'P' + str(self.user.id) + ('00') + str(self.id)
super(Group1, self).save()

But str(self.id) is coming None.

Can anyone plz explain me why this error is happening?

Thank you

According to the solution given by @JPG and @sandip I finally got the solution

def save(self):
   super(Group1, self).save()
   if not self.urlhash:
       if self.user.profile.user_type == 'Bussiness User':
           self.urlhash = 'B' + str(self.user.id) + ('00') + str(self.id)
           Group1.objects.filter(pk=self.pk).update(urlhash=self.urlhash)
       else:
           self.urlhash = 'P' + str(self.user.id) + ('00') + str(self.id)
           Group1.objects.filter(pk=self.pk).update(urlhash=self.urlhash)

This works for me...Thank you everyone

The Ultimate VueJs and Django CRUD Tutorial

The Ultimate VueJs and Django CRUD Tutorial

The Ultimate VueJs and Django CRUD Tutorial - In this tutorial, We show you how to build an API with Django REST Framework and a SPA with Vue.js.

Originally published by  Junior Gantin at dunebook.com

As a developer, CRUD (Create-Read-Update-Delete) is one of the basic operations to know. In this tutorial, I’ll show you how to build an API with Django REST Framework and a SPA with Vue.js. I am going to show you step by step from scratch.

Take a look at the CRUD app we will build:


CRUD API with Django REST Framework

Django REST framework is a powerful and flexible toolkit for building Web APIs. Open your favorite command-line interface and make sure to install Pipenv. Pipenv is one of the best tools to manage Python project dependencies. alternatively, you can use CodeMix IDE for fast and smooth Vue and Python app development.

mkdir subscription-api
cd subscription-api
pipenv install --three
pipenv shell
pipenv install django
pipenv install django-rest-framework

BashCopy

Right now, we’ve installed Django and Django REST Framework. Let’s create a Django project and a Django app:

./manage.py startproject subscription-api .
./manage.py startapp subscriptions

So make sure to add subscriptions and restframework to our list of INSTALLEDAPPS in the settings.py file.

INSTALLED_APPS = [
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  'rest_framework',
  'subscriptions',
]

Our database model will contain Subscription model only. Let’s create 6 fields:

# subscriptions/models.py
from django.db import models

class Subscription(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField()
    currency = models.CharField(max_length=255)
    amount = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def str(self):
        return self.name

Now let’s update our database by first creating a new migration file and then applying it.

./manage.py makemigrations
./manage.py migrate

Create a serializer to convert our data into JSON format:

# subscriptions/serializers.py
from rest_framework import serializers
from .models import Subscription

class SubscriptionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subscription
        fields = ('name', 'description', 'currency',
            'amount', 'created_at', 'updated_at'
        )

Django REST Framework provides class-based generic API views. Update the views.py file:

# subscriptions/views.py
from .models import Subscription
from .serializers import SubscriptionSerializer
from rest_framework import generics

class SubscriptionList(generics.ListCreateAPIView):
    queryset = Subscription.objects.all()
    serializer_class = SubscriptionSerializer

class SubscriptionDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Subscription.objects.all()
    serializer_class = SubscriptionSerializer

Let’s add our API endpoints.

# subscription_api/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('api', include('subscriptions.urls')),
    path('admin/', admin.site.urls),
]


# subscriptions/urls.py
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from subscriptions import views

urlpatterns = [
    path('subscriptions/', views.SubscriptionList.as_view()),
    path('subscriptions/<int:pk>/', views.SubscriptionDetail.as_view()),
]

Start the server.

./manage.py runserver

Your browsable API is ready.

 

Let’s add Cross-Origin Resource Sharing (CORS) headers to responses with django-cors-headers:

pipenv install django-cors-headers

And then add it to your installed apps:

# subscription_api/settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]

You will also need to add a middleware class to listen in on responses:

# subscription_api/settings.py
MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]

Just allow all origins to make cross-site HTTP requests:

# subscription_api/settings.py
CORS_ORIGIN_ALLOW_ALL = True
Vue.js CRUD REST API Consumption

Make sure you have the latest version of vue-cli installed.

vue create subscription-app
cd subscription-app
yarn add axios bootstrap bootstrap-vue vee-validate

We just create a new Vue.js project and installed:

  • axios: a great HTTP client library;
  • bootstrap and bootstrap-vue: a library to quickly integrate Bootstrap 4 components with Vue.js;
  • vue-validate: validate HTML inputs and Vue components the easy way.

Inside src/components folder, create the following Vue components:

  • Index.vue
  • Create.vue
  • Edit.vue

Make sure to import bootstrap and vee-validate in your main.js:

// src/main.js
...
import BootstrapVue from "bootstrap-vue";
import VeeValidate from "vee-validate";

import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap-vue/dist/bootstrap-vue.css";

Vue.use(BootstrapVue);
Vue.use(VeeValidate);
...

Now, we need to define our routes.

import Vue from "vue";
import Router from "vue-router";

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: "/",
      redirect: '/index'
    },
    {
      path: "/create",
      name: "create",
      component: () => import("./components/Create.vue")
    },
    {
      path: "/edit/:id",
      name: "edit",
      component: () => import("./components/Edit.vue")
    },
    {
      path: "/index",
      name: "index",
      component: () => import("./components/Index.vue")
    },
  ]
});

Each route should map to a component we created. We created a redirection to redirect from /to /index.

The next step should be to define a router view in App.vue file.

Index.vue

So the first file called will be Index.vue. This component will display all subscriptions.

Let’s explain this deeper. We need to get all subscriptions from our API. Create a property called susbscriptions:

...
    data() {
        return {
            subscriptions: []
        }
    },
...

Create a method which gets all subscriptions from server with axios:

...
all: function () {
        axios.get('http://127.0.0.1:8000/api/subscriptions/')
            .then( response => {
                this.subscriptions = response.data
            });
    }
...

Call method when Index component is created:

...
created() {
    this.all();
},
...

The Index template part is just a Bootstrap card component with a for-loop.

We create a delete button into the template.

<button
    class="btn btn-danger btn-sm ml-1"
    v-on:click="deleteSubscription(subscription)">
    Delete
</button>

deleteSubscription is called when the button is clicked.

deleteSubscription: function(subscr) {
    if (confirm('Delete ' + subscr.name)) {
        axios.delete(http://127.0.0.1:8000/api/subscriptions/${subscr.id})
            .then( response => {
                this.all();
            });
    }
},

This method will call your API after you confirm the deletion.

Create.vue

This file will create and store a subscription. We’ll use vee-validate which is a template based validation library.

We create a property called subscription and a boolean called submitted:

...
subscription: {
    name: '',
    currency: '',
    amount: '',
    description: '',
},
submitted: false
...

All we need is to add the v-validate directive to the input we wish to validate. Like below:

<input
    type="text"
    class="form-control"
    id="name"
    v-model="subscription.name"
    v-validate="'required'"
    name="name"
    placeholder="Enter name">

The CSS class we bind is just a Bootstrap 4 HTML5 form validation hint wich display invalid-feedback block.

<input
...
    :class="{
        'is-invalid':
            errors.has('subscription.name')
        && submitted}">

So let’s store our subscription.

create: function (e) {
    this.$validator.validate().then(result => {
        this.submitted = true;
        if (!result) {
            return;
        }
        axios
            .post('http://127.0.0.1:8000/api/subscriptions/',
                this.subscription
            )
            .then(response => {
                this.$router.push('/');
            })
    });
}

We make a HTTP request and return to / path when everything works.

Edit.vue

When the Edit.vue component is loaded, then we fetch the subscription data from the database and then display it inside the same form we used in Create.vue.

I hope this is helpful to you. You can check out the subscription-app repo.

Originally published by  Junior Gantin at dunebook.com

==================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ Nuxt.js - Vue.js on Steroids

☞ Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

☞ Python and Django Full Stack Web Developer Bootcamp

☞ Django 2.1 & Python | The Ultimate Web Development Bootcamp

☞ Master Vuejs from scratch (incl Vuex, Vue Router)

☞ Vue JS 2.0 - Mastering Web Apps

☞ Vue.js Essentials - 3 Course Bundle

☞ MEVP Stack Vue JS 2 Course: MySQL + Express.js + Vue.js +PHP

☞ Python eCommerce | Build a Django eCommerce Web Application

☞ Python Django Dev To Deployment

☞ The Complete JavaScript Course 2019: Build Real Projects!

☞ JavaScript: Understanding the Weird Parts

☞ The Modern JavaScript Bootcamp (2019)

Serving React and Django together

Serving React and Django together

In this article, we’ll create an API using Django REST Framework and a React project (frontend) which will consume the API. The idea is very simple, React will fetch some book names from the backend (Django) and render them.

In this article, we’ll create an API using Django REST Framework and a React project (frontend) which will consume the API. The idea is very simple, React will fetch some book names from the backend (Django) and render them.

Reactjs is a nice framework for frontend and Django REST framework (DRF) is another great framework for API development. I wonder how to serve React and Django projects in the same server and same port! Finally, I’ve reached a solution and today I’ll discuss it.

Backend (Django) project

I’m using Django 2 for this project. At first, create a Django project named book.

Install django-rest-framework using pip install django-rest-framework and add rest_framework to INSTALLED_APPS list in settings.py .

Create two apps named api and core using python manage.py startapp apiand python manage.py startapp core , then add the app names to INSTALLED_APPS list in settings.py .

This is our INSTALLED_APPS list in settings.py :

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    "rest_framework",
    
    "api",
    "core",
]

Edit /api/views.py and add the following codes :

from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import api_view


# This will return a list of books
@api_view(["GET"])
def book(request):
    books = ["Pro Python", "Fluent Python", "Speaking javascript", "The Go programming language"]
return Response(status=status.HTTP_200_OK, data={"data": books})

Configure the url in book/urls.py (url configuration can be done in more elegant approach but I’m skipping it for brevity) :

from django.urls import path

from api.views import book
from core.views import index

urlpatterns = [
    path("book/", book, name="book"),
]

Start the server using python manage.py runserver , (it will run the server at default port 8000).

Test our API

Postman is a great tool for testing APIs. Open Postman and navigate to “http://127.0.0.1:8000/book/” :

Our API is working fine! Now we’ll develop the frontend using React.

Frontend (React) Project

We’ll use create-react-app package to create React project. At first, install “create-react-app” package using npm install create-react-app .

Now create the react app named “book-frontend” create-react-app book-frontend inside the project directory (book).

Change the current directory to “book-frontend” and run npm start .

It will run our frontend server at default port 3000.

Navigate to localhost:3000 in your favorite browser (I’m using google chrome) :

We’ll use two more packages in the frontend. Let’s install them first:

axios : npm install axios

react-router-dom : npm install react-router-dom

Create a folder named Component inside src folder, then inside Component folder create a folder named Book. Inside book create a javascript file name index.js (That means: /src/Component/Book/index.js).

Put the following code into index.js (this code will fetch data from backend and render them to frontend).

Our index.js :

import React, { Component } from "react";

import axios from "axios";

export default class Book extends Component {
  constructor(props) {
    super(props);
    this.state = {
    books:[],
    };
    this.loadBooks = this.loadBooks.bind(this);
  }

  componentWillMount() {
    this.loadBooks();
  }

  async loadBooks()
  {
    const promise = await axios.get("http://localhost:3000/book");
    const status = promise.status;
    if(status===200)
    {
      const data = promise.data.data;
      this.setState({books:data});
    }
  }

  render() {
    return(
      <div>
        <h1>Books</h1>
            {this.state.books.map((value,index)=>{return <h4 key={index}>{value}</h4>})}
      </div>
    )
  }
}

And modify App.js like this :

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

import { BrowserRouter as Router, Route, Link } from "react-router-dom";

import Book from "./Component/Book/index";

class App extends Component {
  render() {
    return (
      <Router>
        <Route path="/" exact component={Book} />
    </Router>
    );
  }
}

export default App;

All the features of our million dollar book app are complete now!

Navigate to localhost:3000 to see the output :

OPS! Nothing is showing without the word “books”, right?

Open the console of your browser :

We have to solve the CORS issue.

We can solve this issue using django-cors-headers library.

My settings.py solving CORS issue :

"""
Django settings for book project.
Generated by 'django-admin startproject' using Django 2.2.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '[email protected]@1op&r-xle13)k2x0vv([email protected](ep&4'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    "corsheaders",  # added to solve CORS

    "rest_framework",

    "api",
    "core",
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # added to solve CORS
    'django.middleware.common.CommonMiddleware',  # added to solve CORS
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',



]

ROOT_URLCONF = 'book.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'book-frontend')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'book.wsgi.application'

# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/

STATIC_URL = '/static/'


CORS_ORIGIN_ALLOW_ALL = True # added to solve CORS

Now, navigate to localhost:3000 and see our desired output!

Serving React and Django together

This is the key point of this article, we’ll serve these two apps together in the same server.

Create the “build” version of our frontend app

Navigate to the book-frontend directory and run npm run build . This will create a build directory.

Then go to setting.pyand add the following lines :

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'book-frontend')]  #Look, we have added the root folder of frontend here
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'book-frontend', "build", "static"),  # update the STATICFILES_DIRS
)

Goto cors/view.py now, we will serve our frontend view from here. Update it like :

from django.shortcuts import render


def index(request):
return render(request, "build/index.html")

Update /book/urls.py :

from django.urls import path

from api.views import book

from core.views import index

urlpatterns = [
    path("book/", book, name="book"),
    path("", index, name="index")
]

Now close all the previous servers (if they’re active until now). Run the Django server using python manage.py runserver. Go to your browser and navigate to http://127.0.0.1:8000/ and BOOM! We’re serving our Django and React app in the same server!

How to Build an E-commerce Website with Django and Python

How to Build an E-commerce Website with Django and Python

Learn how to build an E-commerce website with Django and Python.

How to Build an E-commerce Website with Django and Python

💻 Project code: https://github.com/justdjango/django-ecommerce

💻 Original code: https://mdbootstrap.com/freebies/jquery/e-commerce/


⭐️ Course Contents ⭐️

⌨️ (0:00:00) Setup and project configuration

⌨️ (0:16:55) Adding items to a cart

⌨️ (0:54:09) Improving the UI

⌨️ (1:10:34) Creating an order summary

⌨️ (1:38:03) The checkout process

⌨️ (2:10:46) Handling payments with Stripe

⌨️ (2:37:33) Discount codes

⌨️ (3:07:41) Managing refunds

⌨️ (3:31:31) Default addresses

⌨️ (4:12:30) Saving credit card information

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading about Python

Complete Python Bootcamp: Go from zero to hero in Python 3

Machine Learning A-Z™: Hands-On Python & R In Data Science

Python and Django Full Stack Web Developer Bootcamp

Complete Python Masterclass

Python Tutorial - Python GUI Programming - Python GUI Examples (Tkinter Tutorial)

Computer Vision Using OpenCV

OpenCV Python Tutorial - Computer Vision With OpenCV In Python

Python Tutorial: Image processing with Python (Using OpenCV)

A guide to Golang e-commerce

Building an e-commerce site with Gatsby, Netlify, and Stripe

Building a BigCommerce App Using Laravel and React


Python Django with Docker and Gitlab CI

Python Django with Docker and Gitlab CI

Python Django with Docker and Gitlab CI - In this article i will describe how we set up Gitlab CI to run tests for Django project. But first couple of words about what tools we were using...✈️✈️✈️✈️✈️

Python Django with Docker and Gitlab CI - In this article i will describe how we set up Gitlab CI to run tests for Django project. But first couple of words about what tools we were using..

For a project I was specifically asked to build an API using Python Django. So, my first starting point was to google “django cookiecutter” which immediately brought me to this amazing cookiecutter project. What I am going to demonstrate here is how to quickly setup the project (for the sake of completeness) and use Gitlab Continuous Integration to automatically unit test, run linters, generate documentation, build a container and release it.

Setup the project

We start with initiating the project using the mentioned cookiecutter project,although you can also use another cookiecutter or build on your existing project; you probably need to make some alterations here and there. Here is a small list of prerequisites:

  • you have docker installed locally
  • you have Python installed locally
  • you have a Gitlab account and you can push using ssh keys

Now, install cookiecutter and generate the project:

pip install "cookiecutter>=1.4.0"
cookiecutter https://github.com/pydanny/cookiecutter-django

Provide the options in any way you like, so the Django project will be created. Type y when asked to include Docker (because that is why we are here!!!).

Walk trough the options for the Django Cookiecutter

Enter the project, create a git repo and push it there:

cd my_django_api
git init
git add .
git commit -m "first awesome commit"
git remote add origin [email protected]:jwdobken/my-django-api.git
git push -u origin master

Obviously replace my-django-api with your project name and jwdobken with your own Gitlab account name.

You can read here how to develop running docker locally. It is something I do with all my projects of any type; the dev and production environments are more alike and it has been years since I worked with something like virtual environments and I am not missing it!

Add a test environment

Make a test environment by copying the local environment:

cp local.yml test.yml
cp requirements/local.txt requirements/test.txt
cp -r compose/local compose/test

In compose/test/django/Dockerfile change requirements/local.txt to requirements/test.txt . You can make more alterations to the test environment later.

The Gitlab-CI file

Finally we get to the meat. Here is the .gitlab-ci.yml file:

image: docker:latest
	services:
	  - docker:dind
	

	variables:
	  DOCKER_HOST: tcp://docker:2375
	  DOCKER_DRIVER: overlay2
	  CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_BUILD_REF_SLUG
	  CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
	

	stages:
	  - test
	  - build
	  - release
	

	test:
	  stage: test
	  image: tiangolo/docker-with-compose
	  script:
	    - docker-compose -f test.yml build
	    # - docker-compose -f test.yml run --rm django pydocstyle
	    - docker-compose -f test.yml run --rm django flake8
	    - docker-compose -f test.yml run django coverage run -m pytest
	    - docker-compose -f local.yml run --rm django coverage html
	    - docker-compose -f local.yml run --rm django /bin/sh -c "cd docs && apk add make && make html"
	    - docker-compose -f local.yml run django coverage report
	  coverage: "/TOTAL.+ ([0-9]{1,3}%)/"
	  artifacts:
	    paths:
	      - htmlcov
	      - docs/_build
	    expire_in: 5 days
	

	build:
	  stage: build
	  script:
	    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
	    - docker build -t $CONTAINER_TEST_IMAGE -f compose/production/django/Dockerfile .
	    - docker push $CONTAINER_TEST_IMAGE
	

	release:
	  stage: release
	  script:
	    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $CI_REGISTRY
	    - docker pull $CONTAINER_TEST_IMAGE
	    - docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE
	    - docker push $CONTAINER_RELEASE_IMAGE
	  only:
	    - master
	

	pages:
	  stage: release
	  script:
	    - mkdir -p public/coverage
	    - mv htmlcov/* public/coverage
	    - mkdir -p public/docs
	    - mv -v docs/_build/html/* public/docs
	  artifacts:
	    paths:
	      - public
	    expire_in: 30 days
	  only:
	    - master

The test stage builds the container stack in the test environment, runs the unit tests with flake8, copies the html coverage report and catches the total coverage. Also, we misuse the test build to generate the sphinxdocumentation for which we need to install Make.

The build stage builds the production container and pushes it to the Gitlab container registry.

The release stage pulls the build container and tags it as the latest release before pushing it to the container registry.

The page part publishes the test and documentation artifacts with Gitlab Pages.

Push your code to Gitlab where you should find a running pipeline.

the pipeline is running

the pipeline has succesfully finished

In the container registry of the project you can find two images: the latest master image and the latest release image. The page itself explains how to pull images from here to anywhere.

Badges

Gitlab enables badges on the repo page to give any specific information. On the Gitlab project page, go to Settings, go to Badges. Here you can add the following badges:

Pipeline status of the master branch:

  • Link: https://gitlab.com/%{project_path}/pipelines

  • Badge image URL: https://gitlab.com/%{project_path}/badges/%{default_branch}/pipeline.svg

Test coverage and report:

  • Link: https://.gitlab.io/my-django-api/coverage/

  • Badge image URL: https://gitlab.com/%{project_path}/badges/%{default_branch}/coverage.svg?job=test

Documentation:

  • Link:https://.gitlab.io/my-django-api/docs/

  • Badge image URL: https://img.shields.io/static/v1.svg?label=sphinx&message=documentation&color=blue

Note that the URL link of Gitlab Pages, for the test coverage report and documentation, is not straightforward. Replace your username with a groupname if you work in a group. In the case of a subgroup, provide the full path. Usually I end up with a bit of trial-and-error; this article explains most of it.

status badges shown on the repo page

Pydocstyle

Finally, I highly recommend to check the existence and quality of your docstrings using pydocstyle. Add the following line to requirements/test.txtand requirements/local.txt in the Code quality section:

pydocstyle==3.0.0  # https://github.com/PyCQA/pydocstyle

Add the following lines to setup.cfg to configure pydocstyle:

[pydocstyle]
match = (?!\d{4}_).*\.py

And finally add the following line to .gitlab-ci.yml in the script section of the test stage (just after the build):

- docker-compose -f test.yml run — rm django pydocstyle

Be warned that the project does not comply with pydocstyle by default, so you will have to complete the code with docstrings to pass the test again.

Finally

We now have a fresh Django project with a neat CI pipeline on Gitlab for automated unit tests, documentation and container image release. You can later include Continuous Deployment to the pipeline; I left it out of the scope, because it depends too much on your production environment. You can read more about Gitlab CI here.

Currently the pipeline is quite slow mainy caused by the build of the images. The running time can be accelerated by caching dependencies.

There is a soft (10GB) size restriction for registry on GitLab.com, as part of the repository size limit. Therefore, when the number of images increases, you probably need to archive old images manually.

===================================================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Building a Universal Application with Nuxt.js and Django

Building a Universal Application with Nuxt.js and Django

In this article, we will see how to create a Universal application using Django and Nuxt.js. Django will handle the backend operations and provide the APIs using the (DRF) Django Rest Framework, while Nuxt.js will create the frontend.

In this article, we will see how to create a Universal application using Django and Nuxt.js. Django will handle the backend operations and provide the APIs using the (DRF) Django Rest Framework, while Nuxt.js will create the frontend.

Introduction

Table of Contents

The advent of modern JavaScript libraries such as React.js and Vue.js has transmogrified Front-end web development for the better. These libraries ship with wonderful features including SPA (Single Page Applications) which is basically the dynamic loading of the content in web pages without a full reload to the browser.

The concept behind most Single Page Applications is Client-Side Rendering. In Client-Side Rendering, the majority of content is rendered in a browser using JavaScript; on page load, the content doesn’t load initially until the JavaScript has fully downloaded and renders the rest of the site.

Client-Side Rendering is a relatively recent concept and there are trade-offs associated with its use. A notable negative side is that, since the content is not exactly rendered until the page is updated using JavaScript, SEO for the website will suffer as there will hardly be any data for search engines to crawl.

Server-Side Rendering, on the other hand, is the conventional way of getting HTML pages rendered on a browser. In the older Server-Side Rendered applications, the web application is built using a Server-Side language such as PHP. When a web page is requested by a browser, the remote server adds the (dynamic) content and delivers a populated HTML page.

Just as there are downsides to Client-Side Rendering, Server-Side rendering makes the browser send server requests too frequently and perform repetitions of full page reloads for similar data.

At this point, you must be thinking: “what if we could initially load the web page with an SSR (Server-Side Rendering) solution, then use a framework to handle the further dynamic routing and fetch only necessary data?”

Great thinking! There are JavaScript frameworks that already implement this solution and the resulting applications are called Universal Applications.

It is correct to say: Universal app = SSR + SPA.
In summary, a universal application is used to describe JavaScript code that can execute on the client and the server side. In this article, we will build a Universal Recipe application using Nuxt.js.

Nuxt.js is a higher-level framework for developing Universal Vue.js applications. Its creation was inspired by React’s Next.js and it helps to abstract the difficulties (server configuration and client code distribution) that arise in setting up Server-Side Rendered Vue.js applications. Nuxt.js also ships with lots of features that aid development between client side and server side such as async data, middleware, layouts etc.

It is correct to say: Universal app = SSR + SPA.
In this article, we will see how to create a Universal application using Django and Nuxt.js. Django will handle the backend operations and provide the APIs using the (DRF) Django Rest Framework, while Nuxt.js will create the frontend.

Here’s a demo of the final application:

From the image above, we see that the final application is a basic Recipes application that performs CRUD operations.

The source code for this tutorial is available here on GitHub.

Prerequisites

To follow along with this tutorial, you will need the following installed on your machine:

  1. Python3.
  2. Pip.
  3. Npm.

It is correct to say: Universal app = SSR + SPA.
The tutorial assumes that the reader has possession of the following:

  1. Python3.
  2. Pip.
  3. Npm.

Let’s dive right in!

Setting up the Backend

In this section, we will set up the backend and create all the folders that we need to get things up and running, so launch a new instance of a terminal and create the project’s directory by running this command:

$ mkdir recipes_app

Next, we will navigate into the directory:

cd recipes_app

Now we will install Pipenv using Pip and activate a new virtual environment:

$ pip install pipenv
$ pipenv shell

It is correct to say: Universal app = SSR + SPA.
Let’s install Django and other dependencies using Pipenv:

(recipes_app) $ pipenv install django django-rest-framework django-cors-headers

It is correct to say: Universal app = SSR + SPA.
Now, we will create a new Django project called api and a Django application called core:

(recipes_app) $ django-admin startproject api
(recipes_app) $ cd api
(recipes_app) $ python manage.py startapp core

Let’s register the core application, together with rest_framework and cors-headers, so that the Django project recognises it. Open the api/settings.py file and update it accordingly:

# api/settings.py

# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # add this
    'corsheaders', # add this
    'core' # add this 
  ] 

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # add this
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

 # add this block below MIDDLEWARE
CORS_ORIGIN_WHITELIST = (
    'localhost:3000',
)

# add the following just below STATIC_URL
MEDIA_URL = '/media/' # add this
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # add this

It is correct to say: Universal app = SSR + SPA.### Defining the Recipe model

Let’s create a model to define how the Recipe items should be stored in the database, open the core/models.py file and completely replace it with the snippet below:

# core/models.py

from django.db import models
# Create your models here.

class Recipe(models.Model):
    DIFFICULTY_LEVELS = (
        ('Easy', 'Easy'),
        ('Medium', 'Medium'),
        ('Hard', 'Hard'),
    )
    name = models.CharField(maxlength=120)
    ingredients = models.CharField(max_length=400)
    picture = models.FileField()
    difficulty = models.CharField(choices=DIFFICULTY_LEVELS, max_length=10)
    prep_time = models.PositiveIntegerField()
    prep_guide = models.TextField()

    def __str_(self):
        return "Recipe for {}".format(self.name)

The code snippet above describes six properties on the Recipe model:

Creating serializers for the Recipe model

We need serializers to convert model instances to JSON so that the frontend can easily work with the received data. We will create a core/serializers.py file and update it with the following:

# core/serializers.py

from rest_framework import serializers
from .models import Recipe 
class RecipeSerializer(serializers.ModelSerializer):

    class Meta:
        model = Recipe
        fields = ("id", "name", "ingredients", "picture", "difficulty", "prep_time", "prep_guide")

In the code snippet above, we specified the model to work with and the fields we want to be converted to JSON.

Setup the Admin panel

Django provides us with an admin interface out of the box; the interface will make it easy to test CRUD operations on the Recipe model we just created, but first, we will do a little configuration.

Open the core/admin.py file and completely replace it with the snippet below:

# core/admin.py

from django.contrib import admin
from .models import Recipe  # add this
# Register your models here.

admin.site.register(Recipe) # add this

Creating the Views

Let’s create a RecipeViewSet class in the core/views.py file, completely replace it with the snippet below:

# core/views.py

from rest_framework import viewsets
from .serializers import RecipeSerializer
from .models import Recipe

class RecipeViewSet(viewsets.ModelViewSet):
    serializer_class = RecipeSerializer
    queryset = Recipe.objects.all()

It is correct to say: Universal app = SSR + SPA.### Setting up the URLs

Head over to the api/urls.py file and completely replace it with the code below. This code specifies the URL path for the API:

# api/urls.py
from django.contrib import admin
from django.urls import path, include        # add this
from django.conf import settings             # add this
from django.conf.urls.static import static   # add this

urlpatterns = [
    path('admin/', admin.site.urls),
    path("api/", include('core.urls'))       # add this
]

# add this
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Now create a urls.py file in the core directory and paste in the snippet below:

# core/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import RecipeViewSet

router = DefaultRouter()
router.register(r'recipes', RecipeViewSet)

urlpatterns = [
    path("", include(router.urls))
]

In the code above, the router class generates the following URL patterns:

Running Migrations

Because we recently created a Recipe model and defined its structure, we need to make a Migration file and apply the changes on the model to the database, so let’s run the following commands:

(recipes_app) $ python manage.py makemigrations
(recipes_app) $ python manage.py migrate

Now, we will create a superuser account to access the admin interface:

(recipes_app) $ python manage.py createsuperuser

It is correct to say: Universal app = SSR + SPA.
Hooray! That’s all the configuration that needs to be done on the backend. We can now test the APIs we created, so let’s start the Django server:

(recipes_app) $ python manage.py runserver

Once the server is running, head over to http://localhost:8000/api/recipes/ to ensure it works:

We can create a new Recipe item using the interface:

We can also perform DELETE, PUT, and PATCH operations on specific Recipe items using their id primary keys. To do this, we will visit an address with this structure /api/recipe/{id}. Let’s try with this address — http://localhost:8000/api/recipes/1:

That’s all for the backend of the application, now we can move on to fleshing out the frontend.

Setting up the frontend

In this section of the tutorial, we will build the front-end of the application. We want to place the folder for the front-end code in the root of the recipes_app directory. So, navigate out of the api directory (or spin up a fresh terminal to run alongside the previous one) before running the commands in this section.

Let’s create a nuxt application called client with this command:

$ npx create-nuxt-app client

It is correct to say: Universal app = SSR + SPA.
Once the installation is complete, create-nuxt-app will ask a few questions about extra tools to be added. We will answer them as follows:

This will trigger the installation of dependencies using the selected package manager and finally, you will be presented with a screen like this:

Let’s run the following commands to start the application in development mode:

$ cd client
$ npm run dev

Once the development server has started, head over to http://localhost:3000 to see the application:

Awesome! Now let’s take a look at the directory structure of the client folder:

├── client
  ├── assets/
  ├── components/
  ├── layouts/
  ├── middleware/
  ├── node_modules/
  ├── pages/
  ├── plugins/
  ├── static/
  └── store/

Here’s a breakdown of what these directories are for:

There is also a nuxt.config.js file in the client folder, this file contains custom configuration for the Nuxt.js app. Before we continue, download this zip file, extract it and put the images/ folder inside the static/ directory.

Structure of the pages

Remember how we said that Nuxt.js reads all the .vue files in the pages/ directory and uses the information to create the application’s router? In this section, we will add some .vue files to the pages/ directory so that our application will have five pages as so:

Let’s add the following .vue files and folders to the pages/ directory so we have this exact structure:

├── pages/
   ├── recipes/
     ├── _id/
       └── edit.vue
       └── index.vue
     └── add.vue
     └── index.vue
  └── index.vue

The file structure above will generate the following routes:

It is correct to say: Universal app = SSR + SPA.### Creating the homepage

In Nuxt.js, Layouts are a great help when you want to change the look and feel of your application. Now, each instance of a Nuxt.js application ship with a default Layout, we want to remove all the styles so they do not interfere with our application.

Open the layouts/default.vue file and replace it with the following snippet below:

<template>
  <div>
    <nuxt/>
  </div>
</template>

<style>
</style>

Let’s update the pages/index.vue file with the code below:

<template>
  <header>
    <div class="text-box">
      <h1>La Recipes 😋</h1>
      <p class="mt-3">Recipes for the meals we love ❤️ ️</p>
      <nuxt-link class="btn btn-outline btn-large btn-info" to="/recipes">
        View Recipes <span class="ml-2">&rarr;</span>
      </nuxt-link>
    </div>
  </header>
</template>
<script>
export default {
  head() {
    return {
      title: "Home page"
    };
  },
};
</script>
<style>
header {
  min-height: 100vh;
  background-image: linear-gradient(
      to right,
      rgba(0, 0, 0, 0.9),
      rgba(0, 0, 0, 0.4)
    ),
    url("/images/banner.jpg");
  background-position: center;
  background-size: cover;
  position: relative;
}
.text-box {
  position: absolute;
  top: 50%;
  left: 10%;
  transform: translateY(-50%);
  color: #fff;
}
.text-box h1 {
  font-family: cursive;
  font-size: 5rem;
}
.text-box p {
  font-size: 2rem;
  font-weight: lighter;
}
</style>

From the code above, <nuxt-link> is a Nuxt.js component which can be used to navigate between pages. It is very similar to the <router-link> component from Vue Router.

Let’s start the front-end development server (if it isn’t already running), visit http://localhost:3000/, and see what the Homepage looks like:

npm run dev

It is correct to say: Universal app = SSR + SPA.
Every page in this application will be a Vue Component and Nuxt.js provides special attributes and functions to make the development of applications seamless. You can find documentation on all these special attributes here.

For the sake of this tutorial, we will make use of two of these functions:

Creating the Recipes List page

Let’s create a Vue.js component called RecipeCard.vue in the components/ directory and update it with the snippet below:

<template>
  <div class="card recipe-card">
    <img :src="recipe.picture" class="card-img-top" >
    <div class="card-body">
      <h5 class="card-title">{{ recipe.name }}</h5>
      <p class="card-text">
        <strong>Ingredients:</strong> {{ recipe.ingredients }}
      </p>
      <div class="action-buttons">
        <nuxt-link :to="`/recipes/${recipe.id}/`" class="btn btn-sm btn-success"> View </nuxt-link>
        <nuxt-link :to="`/recipes/${recipe.id}/edit/`" class="btn btn-sm btn-primary"> Edit </nuxt-link>
        <button @click="onDelete(recipe.id)"  class="btn btn-sm btn-danger">Delete</button>
      </div>
    </div>
  </div>
</template>
<script>
export default {
    props: ["recipe", "onDelete"]
};
</script>
<style>
.recipe-card {
    box-shadow: 0 1rem 1.5rem rgba(0,0,0,.6);
}
</style>

The component above accepts two props:

  1. Python3.
  2. Pip.
  3. Npm.

Next, open the pages/recipes/index.vue and update it with the snippet below:

<template>
  <main class="container mt-5">
    <div class="row">
      <div class="col-12 text-right mb-4">
        <div class="d-flex justify-content-between">
          <h3>La Recipes</h3>
          <nuxt-link to="/recipes/add" class="btn btn-info">Add Recipe</nuxt-link>
        </div>
      </div>
      <template v-for="recipe in recipes">
        <div :key="recipe.id" class="col-lg-3 col-md-4 col-sm-6 mb-4">
          <recipe-card :onDelete="deleteRecipe" :recipe="recipe"></recipe-card>
        </div>
      </template>
    </div>
  </main>
</template>
<script>
import RecipeCard from "~/components/RecipeCard.vue";

const sampleData = [
  {
    id: 1,
    name: "Jollof Rice",
    picture: "/images/food-1.jpeg",
    ingredients: "Beef, Tomato, Spinach",
    difficulty: "easy",
    prep_time: 15,
    prep_guide:
      "Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis, porro. Dignissimos ducimus ratione totam fugit officiis blanditiis exercitationem, nisi vero architecto quibusdam impedit, earum "
  },
  {
    id: 2,
    name: "Macaroni",
    picture: "/images/food-2.jpeg",
    ingredients: "Beef, Tomato, Spinach",
    difficulty: "easy",
    prep_time: 15,
    prep_guide:
      "Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis, porro. Dignissimos ducimus ratione totam fugit officiis blanditiis exercitationem, nisi vero architecto quibusdam impedit, earum "
  },
  {
    id: 3,
    name: "Fried Rice",
    picture: "/images/banner.jpg",
    ingredients: "Beef, Tomato, Spinach",
    difficulty: "easy",
    prep_time: 15,
    prep_guide:
      "Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis, porro. Dignissimos ducimus ratione totam fugit officiis blanditiis exercitationem, nisi vero architecto quibusdam impedit, earum "
  }
];

export default {
  head() {
    return {
      title: "Recipes list"
    };
  },
  components: {
    RecipeCard
  },
  asyncData(context) {
    let data = sampleData;
    return {
      recipes: data
    };
  },
  data() {
    return {
      recipes: []
    };
  },
  methods: {
    deleteRecipe(recipe_id) {
      console.log(deleted `${recipe.id}`) 
    }
  }
};
</script>
<style scoped>
</style>

Let’s start the front-end development server (if it isn’t already running), visit http://localhost:3000/recipes, and see this recipes listing page:

npm run dev

From the image above, we see that three recipe cards appear even though we set recipes to an empty array in the component’s data section. The explanation for this is that the method asyncData is executed before the page loads and it returns an object which updates the component’s data.

Now, all we need to do is modify the asyncData method to make an api request to the Django backend and update the component’s data with the result.

Before we do that, we have to install and configure Axios:

npm install -s @nuxtjs/axios

Once Axios is installed, open the nuxt.config.js file and update it accordingly:

// client/nuxt.config.js

  /_
  ** Nuxt.js modules
  _/
  modules: [,
    // Doc: https://bootstrap-vue.js.org/docs/
    'bootstrap-vue/nuxt',
    '@nuxtjs/axios' // add this
  ],

  // add this Axios object
  axios: {
    baseURL: "http://localhost:8000/api"
  },

Now, open the pages/recipes/index.vue file and replace the <script> section with the one below:

[...]

<script>
import RecipeCard from "~/components/RecipeCard.vue";

export default {
  head() {
    return {
      title: "Recipes list"
    };
  },
  components: {
    RecipeCard
  },
  async asyncData({ $axios, params }) {
    try {
      let recipes = await $axios.$get(`/recipes/`);
      return { recipes };
    } catch (e) {
      return { recipes: [] };
    }
  },
  data() {
    return {
      recipes: []
    };
  },
  methods: {
    async deleteRecipe(recipe_id) {
      try {
        await this.$axios.$delete(`/recipes/${recipe_id}/`); // delete recipe
        let newRecipes = await this.$axios.$get("/recipes/"); // get new list of recipes
        this.recipes = newRecipes; // update list of recipes
      } catch (e) {
        console.log(e);
      }
    }
  }
};
</script>

[...]

In the code above, asyncData()receives an object called context, which we destructure to get $axios . You can check out all the attributes of context here.

It is correct to say: Universal app = SSR + SPA.
This line of code — let recipes = await $axios.$get("/recipes/") — is a shorter version of:

let response = await $axios.get("/recipes")
let recipes = response.data

The deleteRecipe() method deletes a particular recipe, fetches the most recent list of recipes from the Django backend and finally updates the component’s data.

We can start the front-end development server (if it isn’t already running) now and we will see that the recipe cards are now being populated with data from the Django backend.

It is correct to say: Universal app = SSR + SPA.
Let’s visit http://localhost:3000/recipes, and take a peek:

npm run dev

You can also try deleting recipe items and watch them update accordingly.

Adding new Recipes

As we already discussed, we want to be able to add new recipes from the front-end of the application so open the pages/recipes/add/ file and update it with the following snippet:

<template>
  <main class="container my-5">
    <div class="row">
      <div class="col-12 text-center my-3">
        <h2 class="mb-3 display-4 text-uppercase">{{ recipe.name }}</h2>
      </div>
      <div class="col-md-6 mb-4">
        <img
          v-if="preview"
          class="img-fluid"
          style="width: 400px; border-radius: 10px; box-shadow: 0 1rem 1rem rgba(0,0,0,.7);"
          :src="preview"
          alt
        >
        <img
          v-else
          class="img-fluid"
          style="width: 400px; border-radius: 10px; box-shadow: 0 1rem 1rem rgba(0,0,0,.7);"
          src="@/static/images/placeholder.png"
        >
      </div>
      <div class="col-md-4">
        <form @submit.prevent="submitRecipe">
          <div class="form-group">
            <label for>Recipe Name</label>
            <input type="text" class="form-control" v-model="recipe.name">
          </div>
          <div class="form-group">
            <label for>Ingredients</label>
            <input v-model="recipe.ingredients" type="text" class="form-control">
          </div>
          <div class="form-group">
            <label for>Food picture</label>
            <input type="file" name="file" @change="onFileChange">
          </div>
          <div class="row">
            <div class="col-md-6">
              <div class="form-group">
                <label for>Difficulty</label>
                <select v-model="recipe.difficulty" class="form-control">
                  <option value="Easy">Easy</option>
                  <option value="Medium">Medium</option>
                  <option value="Hard">Hard</option>
                </select>
              </div>
            </div>
            <div class="col-md-6">
              <div class="form-group">
                <label for>
                  Prep time
                  <small>(minutes)</small>
                </label>
                <input v-model="recipe.prep_time" type="number" class="form-control">
              </div>
            </div>
          </div>
          <div class="form-group mb-3">
            <label for>Preparation guide</label>
            <textarea v-model="recipe.prep_guide" class="form-control" rows="8"></textarea>
          </div>
          <button type="submit" class="btn btn-primary">Submit</button>
        </form>
      </div>
    </div>
  </main>
</template>
<script>
export default {
  head() {
    return {
      title: "Add Recipe"
    };
  },
  data() {
    return {
      recipe: {
        name: "",
        picture: "",
        ingredients: "",
        difficulty: "",
        prep_time: null,
        prep_guide: ""
      },
      preview: ""
    };
  },
  methods: {
    onFileChange(e) {
      let files = e.target.files || e.dataTransfer.files;
      if (!files.length) {
        return;
      }
      this.recipe.picture = files[0];
      this.createImage(files[0]);
    },
    createImage(file) {
      // let image = new Image();
      let reader = new FileReader();
      let vm = this;
      reader.onload = e => {
        vm.preview = e.target.result;
      };
      reader.readAsDataURL(file);
    },
    async submitRecipe() {
      const config = {
        headers: { "content-type": "multipart/form-data" }
      };
      let formData = new FormData();
      for (let data in this.recipe) {
        formData.append(data, this.recipe[data]);
      }
      try {
        let response = await this.$axios.$post("/recipes/", formData, config);
        this.$router.push("/recipes/");
      } catch (e) {
        console.log(e);
      }
    }
  }
};
</script>
<style scoped>
</style>

In submitRecipe(), once the form data has been posted and the recipe is created successfully, the app is redirected to /recipes/ using this.$router.

Create the Single Recipe View page

Let’s create the view that allows a user to view a single Recipe item, open the /pages/recipes/_id/index.vue file and paste in the snippet below:

<template>
  <main class="container my-5">
    <div class="row">
      <div class="col-12 text-center my-3">
        <h2 class="mb-3 display-4 text-uppercase">{{ recipe.name }}</h2>
      </div>
      <div class="col-md-6 mb-4">
        <img
          class="img-fluid"
          style="width: 400px; border-radius: 10px; box-shadow: 0 1rem 1rem rgba(0,0,0,.7);"
          :src="recipe.picture"
          alt
        >
      </div>
      <div class="col-md-6">
        <div class="recipe-details">
          <h4>Ingredients</h4>
          <p>{{ recipe.ingredients }}</p>
          <h4>Preparation time ⏱</h4>
          <p>{{ recipe.prep_time }} mins</p>
          <h4>Difficulty</h4>
          <p>{{ recipe.difficulty }}</p>
          <h4>Preparation guide</h4>
          <textarea class="form-control" rows="10" v-html="recipe.prep_guide" disabled />
        </div>
      </div>
    </div>
  </main>
</template>
<script>
export default {
  head() {
    return {
      title: "View Recipe"
    };
  },
  async asyncData({ $axios, params }) {
    try {
      let recipe = await $axios.$get(`/recipes/${params.id}`);
      return { recipe };
    } catch (e) {
      return { recipe: [] };
    }
  },
  data() {
    return {
      recipe: {
        name: "",
        picture: "",
        ingredients: "",
        difficulty: "",
        prep_time: null,
        prep_guide: ""
      }
    };
  }
};
</script>
<style scoped>
</style>

The code here is pretty straight forward. The new thing we introduce here is the params key seen in the asyncData() method. In this case we are using params to get the ID of the recipe we want to view. We extract params from the URL and prefetch its data before displaying it on the page.

We can view a single Recipe item on the web browser now and see a similar screen:

Create the Single Recipe Edit Page

We need to create the view that allows the user to edit and update a single Recipe item, so open the /pages/recipes/_id/edit.vue file and paste in the snippet below:

<template>
  <main class="container my-5">
    <div class="row">
      <div class="col-12 text-center my-3">
        <h2 class="mb-3 display-4 text-uppercase">{{ recipe.name }}</h2>
      </div>
      <div class="col-md-6 mb-4">
        <img v-if="!preview" class="img-fluid" style="width: 400px; border-radius: 10px; box-shadow: 0 1rem 1rem rgba(0,0,0,.7);"  :src="recipe.picture">
        <img v-else class="img-fluid" style="width: 400px; border-radius: 10px; box-shadow: 0 1rem 1rem rgba(0,0,0,.7);"  :src="preview">
      </div>
      <div class="col-md-4">
        <form @submit.prevent="submitRecipe">
          <div class="form-group">
            <label for>Recipe Name</label>
            <input type="text" class="form-control" v-model="recipe.name" >
          </div>
          <div class="form-group">
            <label for>Ingredients</label>
            <input type="text" v-model="recipe.ingredients" class="form-control" name="Ingredients" >
          </div>
          <div class="form-group">
            <label for>Food picture</label>
            <input type="file" @change="onFileChange">
          </div>
          <div class="row">
            <div class="col-md-6">
              <div class="form-group">
                <label for>Difficulty</label>
                <select v-model="recipe.difficulty" class="form-control" >
                  <option value="Easy">Easy</option>
                  <option value="Medium">Medium</option>
                  <option value="Hard">Hard</option>
                </select>
              </div>
            </div>
            <div class="col-md-6">
              <div class="form-group">
                <label for>
                  Prep time
                  <small>(minutes)</small>
                </label>
                <input type="text" v-model="recipe.prep_time" class="form-control" name="Ingredients" >
              </div>
            </div>
          </div>
          <div class="form-group mb-3">
            <label for>Preparation guide</label>
            <textarea v-model="recipe.prep_guide" class="form-control" rows="8"></textarea>
          </div>
          <button type="submit" class="btn btn-success">Save</button>
        </form>
      </div>
    </div>
  </main>
</template>
<script>
export default {
  head(){
      return {
        title: "Edit Recipe"
      }
    },
  async asyncData({ $axios, params }) {
    try {
      let recipe = await $axios.$get(`/recipes/${params.id}`);
      return { recipe };
    } catch (e) {
      return { recipe: [] };
    }
  },
  data() {
    return {
      recipe: {
        name: "",
        picture: "",
        ingredients: "",
        difficulty: "",
        prep_time: null,
        prep_guide: ""
      },
      preview: ""
    };
  },
  methods: {
    onFileChange(e) {
      let files = e.target.files || e.dataTransfer.files;
      if (!files.length) {
        return;
      }
      this.recipe.picture = files[0]
      this.createImage(files[0]);
    },
    createImage(file) {
      let reader = new FileReader();
      let vm = this;
      reader.onload = e => {
        vm.preview = e.target.result;
      };
      reader.readAsDataURL(file);
    },
    async submitRecipe() {
      let editedRecipe = this.recipe
      if (editedRecipe.picture.indexOf("http://") != -1){
        delete editedRecipe["picture"]
      }
      const config = {
        headers: { "content-type": "multipart/form-data" }
      };
      let formData = new FormData();
      for (let data in editedRecipe) {
        formData.append(data, editedRecipe[data]);
      }
      try {
        let response = await this.$axios.$patch(`/recipes/${editedRecipe.id}/`, formData, config);
        this.$router.push("/recipes/");
      } catch (e) {
        console.log(e);
      }
    }
  }
};
</script>

<style>
</style>

In the code above, the submitRecipe() method has a conditional statement whose purpose is to remove the picture of an edited Recipe item from the data to be submitted if the picture was not changed.

Once the Recipe item has been updated, the application is redirected to the Recipes list page — /recipes/.

Setting up Transitions

Congratulations for making it this far! The application is fully functional and that’s great, however, we can give it a smoother look by adding transitions.

It is correct to say: Universal app = SSR + SPA.
We will set up transitions in the nuxt.config.js file. By default, the transition name is set to page, which simply means that the transitions we define will be active on all pages.

Let’s include the styling for the transition. Create a folder called css/ in the assets/ directory and add a transitions.css file within. Now open the transitions.css file and paste in the snippet below:

.page-enter-active,
.page-leave-active {
  transition: opacity .3s ease;
}
.page-enter,
.page-leave-to {
  opacity: 0;
}

Open the nuxt.config.js file and update it accordingly to load the CSS file we just created:

// nuxt.config.js

module.exports = {   /_
  ** Global CSS
  _/
  css: ['~/assets/css/transitions.css'], // update this
}

Voila! See how easy it is to add transitions to the application? Now our application will change frames on each navigation in a sleek way 😋:

Conclusion

In this article, we started out by learning the differences between Client-Side and Server-Side rendered applications. We went on to learn what a Universal application is and finally, we saw how to build a Universal application using Nuxt.js and Django.

The source code for this tutorial is available here on GitHub.

Learn More

10 Node Frameworks to Use in 2019

Machine Learning In Node.js With TensorFlow.js

Full Stack Developers: Everything You Need to Know

Building a mobile chat app with Nest.js and Ionic 4

Python Tutorial for Beginners (2019) - Learn Python for Machine Learning and Web Development

How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 16.04

Python and Django Full Stack Web Developer Bootcamp

Django 2.1 & Python | The Ultimate Web Development Bootcamp

Python Django Dev To Deployment

Build a Backend REST API with Python & Django - Advanced