A Simple Shoutbox with Automated Translations in Django
Tags: python, django, tutorial, azure
Published: 2022-04-24 12:45:03

In this tutorial we will create a shoutbox with automated translations. Users can enter a message and this message can be read by other users in different languages. We use Django for the web app and the [Translator API](https://azure.microsoft.com/en-us/services/cognitive-services/translator/#overview) of the Azure Cognitive Services.

We will send the messages directly to the translation services and store the result in our database. In contrast to website translation services, the API is therefore not hit on every page request. This enables us to protect visitor's information as much as possible.

## Signing up for Azure

Before we start, we need an active Azure Account and create a translation service. If you don't already have an Azure account, you can find all the information about free tiers i my [Getting Started with Azure](https://bas.surf/start-azure) post.

Once you're logged in in the [Azure Portal](https://portal.azure.com/), you can easily create a Translation Service like so:


![Creating an Azure Translation Service](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqtbw1wqjbrvegql3ra9.png)


## Setup

We start with an empty Django project which we call `babelbox_project` and an empty app called `babelbox`. We use the [`django-crispy-forms`](https://github.com/django-crispy-forms/django-crispy-forms) package to style our forms with Bootstrap.


In our settings, we have to define three things:
1. The Cripsy Forms Template pack (in our case bootstrap5)
2. The languages we want to target
3. The access configuration for the Azure Translation Service

To our `settings.py` file we append these lines (remember to replace the AZURE_KEY variables with the access key provided by Azure):

```python
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"

TRANSLATED_LANGUAGES = (
    ('🇬🇧', 'EN', 'English'),
    ('🇪🇸', 'ES', 'Español'),
    ('🇫🇷', 'FR', 'Français')
)


AZURE_KEY_1 = "<>"
AZURE_LOCATION = "global"
AZURE_ENDPOINT = "https://api.cognitive.microsofttranslator.com/"
```

## Creating the Models

A message in our case consists of only three attributes:

1. The name of the user submiting the message
2. The message itself
3. The language the message is written in

Let's create a simple model for this in our `models.py` file:

```python
class Message(models.Model):
    """This model represents the original user input."""
    class Meta:
        ordering =['-created_at']
    created_at = models.DateTimeField(auto_now_add=True)

    name = models.CharField(max_length=255)
    message_language = models.CharField(max_length=2, choices=((lang_id, flag) for flag,lang_id,_ in settings.TRANSLATED_LANGUAGES))
    message = models.TextField()
```

In addition to the original messages we need a model to store its translations. We can create a `TranslatedMessage` model like so:

```python
class TranslatedMessage(models.Model):
    """This model contains the translated message."""
    class Meta:
        """Model Meta options"""
        unique_together = ['message', 'language']
    message = models.ForeignKey(Message, on_delete=models.CASCADE, related_name="translated_messages")
    language = models.CharField(max_length=2, choices=((lang_id, flag) for flag,lang_id,_ in settings.TRANSLATED_LANGUAGES))
    translated_message = models.TextField()
```


## Signals

Signals are a useful feature provided by the Django framework. Signals allow senders to notify a set of receivers that some action has taken place.
One of such actions could be saving a model instance to the database. In our case we can use a `receiver` to request a translation from the Azure Translation service whenever a new message has been saved.

Our `signals.py` looks like this:

```python
@receiver(post_save, sender=Message)
def my_handler(sender, instance, **kwargs):
    target_languages = [lang_id.lower() for _,lang_id,_ in settings.TRANSLATED_LANGUAGES]
    request = requests.post(settings.AZURE_ENDPOINT + '/translate',
                            params={
                                'api-version': '3.0',
                                'from': instance.message_language.lower(),
                                'to': target_languages
                            },
                            headers={'Ocp-Apim-Subscription-Key': settings.AZURE_KEY_1},
                            json=[{
                                'text': instance.message
                            }])
    translations = request.json().pop().get('translations', [])
    for translation in translations:
        translation_object = TranslatedMessage(
            message=instance,
            language=translation.get('to').upper(),
            translated_message=translation.get('text')
        )
        translation_object.save()
```

In order for this code to be excuted automatically on every save event, we need to import the signals.py file on our app startup.
We can achieve this by adding a `ready` method to our `AppConfig` located in `babelbox/app.py`. This ensures that the file is loaded and the receiver is registered. Everything else is handled by Django automatically.

```python
from django.apps import AppConfig


class BabelboxConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'babelbox'

    def ready(self):
        from . import signals
```


## Views and Templates

Now that we have created a database design for our shoutbox app, we need to create the views and templates for it. For our simple case we just need two views:

1. The index view which displays a list of all messages (and their translations)
2. The add view which displays a form to enter a new message.


Inside our `views.py` we first define the `index` method. This method gets a list of all message objects and passes that list to our template.

```python
def index(request):
    messages = Message.objects.all()
    languages = settings.TRANSLATED_LANGUAGES
    return render(request, 'babelbox/index.html', dict(messages=messages, languages=languages))
```


Next, we create the `add` message that handles our `MessageForm`:

```python
def add(request):
    form = MessageForm(request.POST or None)
    if request.method=="POST":
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("/")
    return render(request, 'babelbox/add.html', dict(form=form))
```

Of course, we need to create the `MessageForm` class itself, which is as easy as it can get:

```python
class MessageForm(forms.ModelForm):
    class Meta:
        model = Message
        fields = ['name', 'message', 'message_language']
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_id = 'id-exampleForm'
        self.helper.form_method = 'post'
        self.helper.form_action = '/add'

        self.helper.add_input(Submit('submit', 'Submit'))
```

## Final Result



![Screen Shot of the final Shoutbox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h36ethjv96r6mgwt31im.png)


## GitHub Repository

The whole source code can be retrieved from the GitHub repository [codewithbas/django-babelbox](https://github.com/codewithbas/django-babelbox).