Image hosting in 30 minutes

by Valentin Golev

We have another guest post by Valentin Golev, CTO of Uploadcare . Check out his first blog post on image cropping for your web app in 30 minutes.

How to create an image hosting service in the easiest way?

It used to be fairly simply, but progress marches on, and today many factors need to be taken into account. Uploadcare makes working with files easier. Everything from uploading, storage, processing, to distribution to the end user. Let us use it as our primary block.

The examples are given in Python, because the
pyuploadcare library receives priority updates. Of course Uploadcare has libraries for an assortment of different languages, all of them opensource. If the module you’re working with is missing some functionality, you can either wait for it to appear, or simply add it yourself.

Let’s begin by creating a new project in Django:

$ pip install django pyuploadcare==0.19
$ django-admin.py startproject upload_test
$ cd upload_test/ && chmod u+x ./manage.py
$ ./manage.py startapp imageshare
$ ./manage.py syncdb —noinput

In settings.py, in addition to the usual settings for connecting to the database and INSTALLED_APPS, you need to specify the public and private keys:

UPLOADCARE = {
    'pub_key': 'demopublickey',
    'secret': 'demoprivatekey',
}

For purposes of demonstration, We’ll be using a demo account. The only limitation for this account is that the files are eventually automatically deleted.

As an example we add the upload form on the home page. And the image ID will be saved to the database after the file is sent. To do so we only need a model like:

import string
import random
from pyuploadcare.dj import ImageField
from django.db import models

class Image(models.Model):
    slug = models.SlugField(max_length=10, primary_key=True, blank=True)
    image = ImageField(manual_crop="")

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = ''.join(random.sample(string.ascii_lowercase, 6))
        super(Image, self).save(*args, **kwargs)

As you can probably notice, the ImageField we’re using isn’t from Django, but from the pyuploadcare library. We’ve only specified a single setting: it will allow the user to select the area of ​​the image that he wants to download. The save () method generates a slug for a short link.

Now the nifty part: the home page form for saving the image and the form that allows us to view the upload:

from django.views.generic import DetailView
from django.views.generic.edit import CreateView
from .models import Image

class UploadView(CreateView):
    model = Image

class ImageView(DetailView):
    model = Image

Class, in 2 lines. Django by itself is also a great building block for your projects. In order to display the form you will need a small template. Nothing out of the ordinary, but you have to specify the public key for the widget, and don’t forget to put a document tag . Often people forget about this attribute.




    <script>
    UPLOADCARE_PUBLIC_KEY = 'demopublickey';
    </script>
    



    <div class="center">
        <form action="." method="post">
            <p>Please, select an image:</p>
            <p></p>
            <p><input type="submit" value="send"></p>
            
        </form>
    </div>

Load it.

image

A widget for selecting files will appear on the page. But saving doesn’t seem to work, Django just spouts “No URL to redirect to”. Which is understandable, as you need to point where you’ll be getting the full url for the image. Let’s add one more method to the model.

    @models.permalink
    def get_absolute_url(self):
        return 'detail', (), {'pk': self.pk}

Now all we need to do is create a template for output, and mission accomplished!


    <div class="center">
        <form action="." method="post">
            <p>Please, select an image:</p>
            <p></p>
            <p><input type="submit" value="send"></p>
            
        </form>
    </div>

The fellows from our Instagram all say hello.


image
The attentive reader will notice that everything covered to this point took 15 minutes at most. Damn, how are we going to kill the other 15?

Upload page improvement

In its current version the user has to perform 2 extra clicks: to open the widget, and to submit the form. We can do without both. To do so you need to use the javascript api widget:

<script>
(function() {
    uploadcare.start();

    var widget = uploadcare.Widget('#id_image');
    widget.openDialog();
    widget.onChange(function(file) {
        if (file) {
            var form = document.getElementById('upload-form');
            form.submit();
            form.style.display = 'none';
        }
    });
})();
</script>

Here we initialize the widget by using the start (), so we’re not waiting for the page to load, and then we open the dialogue, without waiting for the user to click. And if a file is uploaded, we submit a form.

What else? You can also make the image view page a bit more informative, by showing a preview picture instead of the full picture, and outputting more information.


    <div class="center">
        <form action="." method="post">
            <p>Please, select an image:</p>
            <p></p>
            <p><input type="submit" value="send"></p>
            
        </form>
    </div>

The thumbnail size is obtained by specifying its settings directly in the url of the image. Information is obtained through the info method. Unfortunately, datetime_uploaded is given as a string, so we had to cheat and cut the first 10 characters. Parsing it would have been the proper thing to do. Here’s hoping someone fixes it by the year 10000 :)

image

Handling of an deleted image
How to provide a 404 instead of a 500? This is best done when retrieving an object from the database: you request file information, and if there is indication that the file is deleted, you delete the stored link. Additionally, if a file was deleted a long time ago, the api may provide no info at all. You’ll need to handle this occurence as well.

class ImageView(DetailView):
    model = Image

    def get_object(self):
        object = super(ImageView, self).get_object()
        try:
            if object.image.is_removed:
                raise ValueError('File was deleted.')
        except (InvalidRequestError, ValueError):
            object.delete()
            raise Http404

        return object


This seems like the place to stop.
All that remains to do is activate the last block - the free (up to a certain load) hosting service heroku - and see the results: iamshare.herokuapp.com. The source code is also available, in case anyone wants to see the whole thing together.