Storing Media Files on Bunny.net with Django
Tags: django
Published: 2021-02-16 00:29:24

[Bunny.net](https://bunny.net) is a fast and reliable CDN. I'm going to show you how to set up and start using Bunny.net in your next Django project.

### Bunny.net Setup

Go to [bunny.net](https://bunny.net) and login. Click on the Storage icon and then on Add Storage Zone. Give your storage zone a name and select the geolocations that best fit your need. In this guide I'll call my zone `myzone` in location New York.

Once created you'll be redirected to the File Manager for your zone. Click on the Connect Pull Zone button in the top right. Again, give it a name (I like to keep names of my zones consistent) and select that options that best fit your CDN needs.

### Django Setup

*We assume that you've already created and done a the bare minimum of setting up a new django project.*

We'll first edit our `settings.py` and include some new attributes. In bunny.net, under Storage, click FTP & API Access. Copy the Username and Password found on the page and save them as attributes in your settings.

I also went ahead and set the default file storage to a `BunnyStorage` class (that we'll create below) and changed the media url to that of the respective pull zone we created.

```python
# Media files
#

DEFAULT_FILE_STORAGE = 'myapp.storage.BunnyStorage'

MEDIA_URL = 'https://myzone.b-cdn.net/myzone/media/'


# Bunny.net CDN Access
#

BUNNY_ZONENAME = 'myzone'

BUNNY_PASSWORD = 'arandom-string-of-numbersand-letters'
```

As of Feb. 2021, Bunny.net does not have an official Python API. However, they do link [NiveditJain's](https://github.com/NiveditJain/BunnyCDNStorageLib) library as a stable library. It's just a single file so we'll just `wget` it into our django project folder (where manage.py is saved) and import it relatively to our custom storage module.

```bash
cd myproject

wget https://raw.githubusercontent.com/NiveditJain/BunnyCDNStorageLib/master/BunnyCDNStorage.py
```

#### Important!

You must edit the `BunnyCDNStorage.py` file before proceeding! Change the `upload_file` method to the one below.

```python
def upload_file(self,cdn_path,file_name,file_data):
        """
            uploads your files to cdn server \n
            cdn_path - directory to save in CDN \n
            filename - name to save with cdn \n
            if none it will look for file in present working directory
        """
        
        if(cdn_path[-1]=='/'):
            cdn_path=cdn_path[:-1]

        request_url=self.base_url+cdn_path+'/'+file_name

        response=requests.request("PUT",request_url,data=file_data,headers=self.headers)

        return(response.json())
```

Once done, change directory into the django app you'd like to associate the storage backend to and create a new file `storage.py`.

The django [docs](https://docs.djangoproject.com/en/3.1/howto/custom-file-storage/) provide in-depth documentation of writing custom storage backends. So, if you want to get a deeper understanding of what we're doing exactly, you should check those pages out.

Our storage backend consists of a single class and five methods.

In `myapp/storage.py`,

```python
from django.conf import settings
from django.core.files.images import ImageFile
from django.core.files.storage import Storage

import BunnyCDNStorage


class BunnyStorage(Storage):
    def __init__(self):
        self.conn = BunnyCDNStorage.CDNConnector(
            settings.BUNNY_PASSWORD,
            settings.BUNNY_ZONENAME,
            storage_zone_region='ny'
        )

    def _open(self, name, mode='rb'):
        f = self.conn.get_file(name)
        
        return ImageFile(f)

    def _save(self, name, content):
        self.conn.upload_file('/media/', name, content)

        return name

    def delete(self, name):
        self.conn.remove(name)

    def exists(self, name):
        try:
            self.conn.get_file(name)
        except ValueError:
            return False

        return True

    def listdir(self, path):
        pass

    def size(self, name):
        pass

    def url(self, name):
        return f'https://myzone.b-cdn.net/media/{name}'

```

And that's it! Until Bunny.net releases an official Python API and a standard Django package is released, this is kind of the best we got right now... Happy coding!