YouTube API Integration: Uploading Videos with Django
Tags: django, python, showdev, tutorial
Published: 2018-09-30 02:31:37

---
title: YouTube API Integration: Uploading Videos with Django
published: true
tags: django,python,showdev,tutorial
canonical_url: https://www.toptal.com/django/youtube-api-integration-uploading-videos#book-tested-programmers
---

A short while ago, I was working for a client, integrating video reviews in their website. Like any motivated developer solving a novel problem, the first thing I did was Google it, and I found a plethora of unhelpful or misguided answers on how to achieve something entirely different, or outdated and unmaintained Python packages. Eventually, I bit the bullet and the team and I built everything from scratch: we created the views, learned about Google’s API, created the API client, and eventually succeeded in programmatically uploading videos from Django.

In this post, I’ll try to guide you step by step in how to post YouTube videos from your Django app. This will require a bit of playing around with Google API credentials—first with the web interface, then with the code. The YouTube part itself is very straightforward. We need to understand how Google stuff works because sometimes it’s tricky and the information is spread through many places.

## Prerequisites

I recommend familiarizing yourself with the following before we begin working:

* [YouTube Data API: Python Quickstart](https://developers.google.com/youtube/v3/quickstart/python)
* [YouTube Data API: API Reference](https://developers.google.com/youtube/v3/docs/)
* [YouTube Data API: Code Samples](https://developers.google.com/youtube/v3/code_samples/)
* [Google Python API Client Library](https://developers.google.com/api-client-library/python/)
* [Google Python API Client Library: Reference Doc](https://developers.google.com/api-client-library/python/reference/pydoc)
* [Google Python API Client Library: Code Samples](https://github.com/google/google-api-python-client/blob/master/samples/README.md)
* [YouTube API: Python Code Samples](https://github.com/youtube/api-samples/tree/master/python)

An interesting bit of code to note is the following Python Snippet from the [Google YouTube API Docs](https://developers.google.com/youtube/v3/docs/videos/insert):

``` python

# Sample python code for videos.insert

def videos_insert(client, properties, media_file, **kwargs):
  resource = build_resource(properties) # See full sample for function
  kwargs = remove_empty_kwargs(**kwargs) # See full sample for function
  request = client.videos().insert(
    body=resource,
    media_body=MediaFileUpload(media_file, chunksize=-1,
                               resumable=True),
    **kwargs
  )

  # See full sample for function
  return resumable_upload(request, 'video', 'insert')

media_file = 'sample_video.flv'
  if not os.path.exists(media_file):
    exit('Please specify a valid file location.')
videos_insert(client, 
    {'snippet.categoryId': '22',
     'snippet.defaultLanguage': '',
     'snippet.description': 'Description of uploaded video.',
     'snippet.tags[]': '',
     'snippet.title': 'Test video upload',
     'status.embeddable': '',
     'status.license': '',
     'status.privacyStatus': 'private',
     'status.publicStatsViewable': ''},
    media_file,
    part='snippet,status')

```

## Getting Started

After you’ve read the prerequisites, it’s time to get started. Let’s see what we need.

### Toolbelt

Basically, let’s create a virtual environment. I personally prefer [pyenv](https://github.com/pyenv/pyenv). Setting up both is out of the scope of this post, so I’m going to post some pyenv commands below and, if your preference is `virtualenv`, feel free to replace the commands accordingly.

I’m going to use Python 3.7 and Django 2.1 in this post.


``` bash
➜  ~/projects $ mkdir django-youtube
➜  ~/projects $ cd django-youtube
➜  ~/projects/django-youtube $ pyenv virtualenv 3.7.0 djangoyt
➜  ~/projects/django-youtube $ vim .python-version
```
```
        djangoyt
```

Installing dependencies:

``` bash
➜  ~/projects/django-youtube $ pip install google-api-python-client google-auth\
 google-auth-oauthlib google-auth-httplib2 oauth2client Django unipath jsonpickle
```

Now time to start our django project:

``` bash
➜  ~/projects/django-youtube $ django-admin startproject django_youtube .
```

## Pause for some Google Config.

Let’s config our project credentials now so we are able to use the Google APIs.

Step 1. Go to the following URL: https://console.developers.google.com/apis/library/youtube.googleapis.com

Step 2. Create a New Project.

![](https://thepracticaldev.s3.amazonaws.com/i/qia5363b26v9tqgyz09e.png)

Step3. Click "Enable APIs and Services."

![](https://thepracticaldev.s3.amazonaws.com/i/2nw39v3h767r1t8ch76q.png)

Step4. Look for Youtube Data API v3, click "Enable."

![](https://thepracticaldev.s3.amazonaws.com/i/uol2oo9s5gqpuufn8uf3.png)

Step 5. You should get a message about credentials.

![](https://thepracticaldev.s3.amazonaws.com/i/c3ghsorvh53ej95kfgv0.png)


Step 6. Click on the "Create credentials" blue button on the right side, and you should get the following screen:

![](https://thepracticaldev.s3.amazonaws.com/i/ketgq0ubfrijjx0tgq2v.png)

Step 7. Choose Web server, User Data

![](https://thepracticaldev.s3.amazonaws.com/i/xrjljcb8caj2gsa2u4ka.png)

Step 8. Add authorized JS origins and redirect URIs. Continue to the end.

![](https://thepracticaldev.s3.amazonaws.com/i/e6hpsl8jd6jqznbvo66m.png)

OK we are done with our credentials set up. You can either download the credentials in a JSON format or copy the *Client ID* and *Client Secret*.

## Back to Django

Let’s start our very first Django app. I usually name it “core”:

``` bash
(djangoyt) ➜  ~/projects/django-youtube $ python manage.py startapp core

```
Now, let’s add the following to our root urls.py file to route the homepage requests to our core app:

``` python
# urls.py

from django.urls import path, include


    path('', include(('core.urls', 'core'), namespace='core')),
```
In the core app, let’s have another urls.py file, with some config also:

``` python
# core/urls.py

from django.conf import settings
from django.conf.urls.static import static
from django.urls import path

from .views import HomePageView

urlpatterns = [
    path('', HomePageView.as_view(), name='home')
]

if settings.DEBUG:
    urlpatterns += static(
        settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(
        settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```
See there is an empty path pointing to `HomePageView`. Time to add some code.

Let’s do now a simple `TemplateView` just to see it running.
``` python
# core/views.py
from django.shortcuts import render
from django.views.generic import TemplateView


class HomePageView(TemplateView):
    template_name = 'core/home.html'
```
And of course we need a basic template:

``` django
# core/templates/core/home.html
<!DOCTYPE html>
<html>
<body>

<h1>My First Heading</h1>
<p>My first paragraph.</p>

</body>
</html>
```
We need to do some settings tweaks:

``` python
# settings.py


from unipath import Path

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


INSTALLED_APPS = [
    # [...]
    'core',
]


STATIC_ROOT = BASE_DIR.parent.child('staticfiles')

STATIC_URL = '/static/'


MEDIA_ROOT = BASE_DIR.parent.child('uploads')

MEDIA_URL = '/media/'
```

Let's create now a `YoutubeForm` and add as `form_class` to the view:

``` python
from django import forms
from django.views.generic.edit import FormView


class YouTubeForm(forms.Form):
    pass


class HomePageView(FormView):
    template_name = 'core/home.html'
    form_class = YouTubeForm
```
Try to run your application now, and the page will look like this:


![](https://thepracticaldev.s3.amazonaws.com/i/djpzc0k4joac4vb9lrcs.png)


### Pause to do Authorization


First of all, you have to create a model to store your credentials. You could to through a file, cache system, or any other storage solution, but a database seems reasonable and scalable, and also you can store credentials per users if you want.

Before proceeding, an adjustment needs to be made—there is fork of oauth2client that supports Django 2.1 that we have to use. Soon, we’ll have official support, but in the meantime, you can inspect the fork changes. They are very simple.

``` bash
pip install -e git://github.com/Schweigi/oauth2client.git@v4.1.3#egg=oauth2client
# Because of compatibility with Django 2.0
```

Go to your `settings.py` and place the *Client ID* and *Client Secret* you got from Google in previous steps.

``` python
# settings.py

GOOGLE_OAUTH2_CLIENT_ID = '<your client id>'
GOOGLE_OAUTH2_CLIENT_SECRET = '<your client secret>'
```

*Caution:* storing secrets in your code is not recommended. I’m doing this simply as a demonstration. I recommend using environment variables in your production app, and not hardcoding secrets in application files. Alternatively, if you downloaded the JSON from Google, you can also specify its path instead of the settings above:

``` python
GOOGLE_OAUTH2_CLIENT_SECRETS_JSON = '/path/to/client_id.json'
```

The oauth2client package already provides plenty of functionality, with a CredentialsField already done that we can use. It’s possible to add more fields, like a foreign key and created/modified dates so we get more robust, but let’s stay simple.

Simple model to store credentials:

``` python
# core/models.py
from django.db import models
from oauth2client.contrib.django_util.models import CredentialsField


class CredentialsModel(models.Model):
    credential = CredentialsField()
```

Time to create migrations and migrate:

``` bash
(djangoyt) ➜  ~/projects/django-youtube $ ./manage.py makemigrations core
(djangoyt) ➜  ~/projects/django-youtube $ ./manage.py migrate
```

Now let’s change our API views to be able to authorize our application:

In our `core/urls.py` file, let’s add another entry for the first authorization view:

``` python
# core/urls.py
from .views import AuthorizeView, HomePageView


urlpatterns = [
    # [...]
    path('authorize/', AuthorizeView.as_view(), name='authorize'),
]
```

So first part of the AuthorizeView will be:

``` python
# core/views.py

from django.conf import settings
from django.shortcuts import render, redirect
from django.views.generic.base import View

from oauth2client.client import flow_from_clientsecrets, OAuth2WebServerFlow
from oauth2client.contrib import xsrfutil
from oauth2client.contrib.django_util.storage import DjangoORMStorage
from .models import CredentialsModel

# [...]

class AuthorizeView(View):

    def get(self, request, *args, **kwargs):
        storage = DjangoORMStorage(
            CredentialsModel, 'id', request.user.id, 'credential')
        credential = storage.get()
        flow = OAuth2WebServerFlow(
            client_id=settings.GOOGLE_OAUTH2_CLIENT_ID,
            client_secret=settings.GOOGLE_OAUTH2_CLIENT_SECRET,
            scope='https://www.googleapis.com/auth/youtube',
            redirect_uri='http://localhost:8888/oauth2callback/')

        # or if you downloaded the client_secrets file
        '''flow = flow_from_clientsecrets(
            settings.GOOGLE_OAUTH2_CLIENT_SECRETS_JSON,
            scope='https://www.googleapis.com/auth/youtube',
            redirect_uri='http://localhost:8888/oauth2callback/')'''
```

And then second part:

``` python
        if credential is None or credential.invalid == True:
            flow.params['state'] = xsrfutil.generate_token(
                settings.SECRET_KEY, request.user)
            authorize_url = flow.step1_get_authorize_url()
            return redirect(authorize_url)
        return redirect('/')
```

So if there is no credential or the credential is invalid, generate one and then redirect it to the authorize URL. Otherwise, just go to the homepage so we can upload a video!

Let’s access the view now and see what happens:


![](https://thepracticaldev.s3.amazonaws.com/i/1pfxeq50chhxh3a0hebh.png)

Let’s create a user then, before going to that page.

``` bash
(djangoyt) ➜  ~/projects/django-youtube $ python manage.py createsuperuser

Username (leave blank to use 'ivan'): ivan
Email address: ivan***@mail.com
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
```

Let’s also log in with it via `/admin`. After, let’s access our `/authorize/` view again.


![](https://thepracticaldev.s3.amazonaws.com/i/38by08tj2pngg47d00tu.png)



![](https://thepracticaldev.s3.amazonaws.com/i/2hftmrnqwcyr8ryjpi1u.png)


Then,


![](https://thepracticaldev.s3.amazonaws.com/i/0fguhm74sffrebd81nmf.png)


OK, it tried to redirect to the callback URL we configured long ago with Google. Now we need to implement the callback view.

Let’s add one more entry to our `core/urls.py`:

``` python
# core/urls.py

from .views import AuthorizeView, HomePageView, Oauth2CallbackView

urlpatterns = [
    # [...]
    path('oauth2callback/', Oauth2CallbackView.as_view(),
         name='oauth2callback')
]

```
And another view:

``` python
# core/views.py


# the following variable stays as global for now
flow = OAuth2WebServerFlow(
    client_id=settings.GOOGLE_OAUTH2_CLIENT_ID,
    client_secret=settings.GOOGLE_OAUTH2_CLIENT_SECRET,
    scope='https://www.googleapis.com/auth/youtube',
    redirect_uri='http://localhost:8888/oauth2callback/')
# or if you downloaded the client_secrets file
'''flow = flow_from_clientsecrets(
    settings.GOOGLE_OAUTH2_CLIENT_SECRETS_JSON,
    scope='https://www.googleapis.com/auth/youtube',
    redirect_uri='http://localhost:8888/oauth2callback/')'''


# [...]


class Oauth2CallbackView(View):

    def get(self, request, *args, **kwargs):
        if not xsrfutil.validate_token(
            settings.SECRET_KEY, request.GET.get('state').encode(),
            request.user):
                return HttpResponseBadRequest()
        credential = flow.step2_exchange(request.GET)
        storage = DjangoORMStorage(
            CredentialsModel, 'id', request.user.id, 'credential')
        storage.put(credential)
        return redirect('/')
```

_Note: The flow was moved to outside of the `AuthorizeView`, becoming global. Ideally, you should create it under the `AuthorizeView` and save in a cache, then retrieve it in the callback. But that is out of the scope of this post._

The get method of AuthorizeView is now:

``` python
    def get(self, request, *args, **kwargs):
        storage = DjangoORMStorage(
            CredentialsModel, 'id', request.user.id, 'credential')
        credential = storage.get()

        if credential is None or credential.invalid == True:
            flow.params['state'] = xsrfutil.generate_token(
                settings.SECRET_KEY, request.user)
            authorize_url = flow.step1_get_authorize_url()
            return redirect(authorize_url)
        return redirect('/')
```

You can take a look at similar implementations here. The `oauth2clien` package itself provides views but I particularly prefer to implement my custom Oauth view.

* https://github.com/google/google-api-python-client/blob/master/samples/django_sample/plus/views.py
* https://github.com/google/oauth2client/blob/master/oauth2client/contrib/django_util/views.py

Now if you try the `/authorize/` URL again, the OAuth flow should work. Time to see if this work is worth it and upload our video! The HomePageView will first check for credentials and if it’s all good, we are ready for uploading our video.

Let’s check how our new code for the `HomePageView` will look:

``` python
import tempfile
from django.http import HttpResponse, HttpResponseBadRequest
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload


class HomePageView(FormView):
    template_name = 'core/home.html'
    form_class = YouTubeForm

    def form_valid(self, form):
        fname = form.cleaned_data['video'].temporary_file_path()

        storage = DjangoORMStorage(
            CredentialsModel, 'id', self.request.user.id, 'credential')
        credentials = storage.get()

        client = build('youtube', 'v3', credentials=credentials)

        body = {
            'snippet': {
                'title': 'My Django Youtube Video',
                'description': 'My Django Youtube Video Description',
                'tags': 'django,howto,video,api',
                'categoryId': '27'
            },
            'status': {
                'privacyStatus': 'unlisted'
            }
        }

        with tempfile.NamedTemporaryFile('wb', suffix='yt-django') as tmpfile:
            with open(fname, 'rb') as fileobj:
                tmpfile.write(fileobj.read())
                insert_request = client.videos().insert(
                    part=','.join(body.keys()),
                    body=body,
                    media_body=MediaFileUpload(
                        tmpfile.name, chunksize=-1, resumable=True)
                )
                insert_request.execute()

        return HttpResponse('It worked!')
```

And the template:

``` django
# core/templates/core/home.html

        <!DOCTYPE html>
        <html>
        <body>

        <h1>Upload your video</h1>
        <p>Here is the form:</p>
        <form action="." method="post" enctype="multipart/form-data">
        { csrf_token }
        \{\{ form.as_p \}\}
        <input type="submit" value="Submit">
        </form>

        </body>
        </html>
```

Don't forget to add the video field to YouTubeForm:

```
class YouTubeForm(forms.Form):
    video = forms.FileField()
```

Here we go!

![](https://thepracticaldev.s3.amazonaws.com/i/9w7nlkyfmawiy64oe7a8.png)




![](https://thepracticaldev.s3.amazonaws.com/i/hjm9q0z1gmnybcc0kyyg.png)

Voila!


### Closing Notes

The code needs some improvement, but it’s a good starting point. I hope it helped with most of the Google’s YouTube API Integration problems. Here are a few more important things to note:
* For authorization, it’s important to require login and extra permissions for the user that will authorize your application to be uploading videos.
* The flow variable needs to be moved out from being global. It isn’t safe in a production environment. It’s better to cache based on the user ID or session who accessed the first view, for instance.
* Google only provides a refresh token when you do the first authorization. So after some time, mostly one hour, your token will expire and if you didn’t interact with their API you will start receiving invalid_grant responses. Reauthorizing the same user who already authorized a client will not guarantee your refresh token. * You have to revoke the application in your Google Accounts page and then do the authorization process again. In some cases, you might need to run a task to keep refreshing the token.
* We need to require login in our view since we are using a user credential directly related to the request.
* There is a work in progress based on this experience and this post. Of course there is a lot to be added, but it’s a good starting point: https://github.com/ivancrneto/youtube-django

![](https://thepracticaldev.s3.amazonaws.com/i/s1r6istovdehyhzn2hd2.png)

Uploading takes a lot of time, and doing it in your main application process can cause the entire application to block while the upload happens. The right way would be to move it into its own process and handle uploads asynchronously.

The code needs some improvement, but it's a good starting point. I hope it helped with most of the Google's API handling and of course uploading your video to YouTube!

Cheers!