django-storages + S3: lidando com arquivos de mesmo nome
Tags: django, s3, aws, braziliandevs
Published: 2022-12-10 23:41:01

O *django* é um *framework* para desenvolvimento web na linguagem Python e seu funcionamento é, majoritariamente, baseado em alguns padrões de design, como, por exemplo, os *decorators* e *adapters*. O sistema de *storage* de arquivos de mídia e de arquivos estáticos funciona, de modo simples, seguindo o padrão *adapter*.

> Arquivos de Mídia são os arquivos enviados pelos usuários por meio dos campos de upload de arquivo (`ImageField` e `FileField`).
>
> Arquivos Estáticos são os arquivos para serem usados nos layouts (como, por exemplo, *scripts*, *styles* e *assets*).

O `django-storages` é uma biblioteca que fornece alguns *adapters* para o sistema de armazenamento do *django*, enviando arquivos para provedores de nuvem, como a **AWS** (*Amazon Web Service*) e a **GCP** (*Google Cloud Platform*).

---

## O problema

Utilizando o sistema original de armazenamento, em disco, do *django* para arquivos estáticos e para arquivos de mídia ao mesmo tempo existem alguns comportamentos que são padrão e que acabam passando sem serem notados:

Durante o upload de arquivos (`FileField` ou `ImageField`) quando enviamos dois arquivos com o mesmo nome, por padrão, o *adapter* de storage adiciona uma parte aleatória no fim do nome do arquivo, permitindo, dessa forma, o upload de ambos sem conflitarem.

Já os arquivos estáticos, aqueles que são "coletados" utilizando o comando `python manage.py collectstatic`, os conflitos de nomes são resolvidos de uma forma diferente: "o arquivo que for localizado primeiro em uma das localizações especificadas será utilizado" (Ver Referência [[1](#ref-1)]). 

Essa diferença entre os comportamentos, configurações padrões  para ambos os *adapters*, é de suma importância pois, não faz sentido adicionar um `jquery_xyz.js`, por exemplo, a cada vez que o comando `python manage.py collectstatic` for executado.

## django-storages e a unificação do comportamento

O django-storages, mais especificamente seus *adapters* `S3Boto3Storage` e `S3StaticStorage`, unifica esses dois comportamentos em uma configuração `AWS_S3_FILE_OVERWRITE` que por padrão é `True` e de acordo com [[2](#ref-2)], "por padrão, arquivos com o mesmo nome serão sobrescritos um ao outro. Mude para `False` para adicionar caracteres extra adicionados".

O problema ainda persiste, pois o comportamento precisa ser diferente para cada um dos *adapter*. A solução é não alterar a propriedade `AWS_S3_FILE_OVERWRITE` e sobrescrever o *adapter* de upload de arquivos de mídia definindo essa configuração localmente:

```python
# core/storages.py
from storages.backends.s3boto3 import S3Boto3Storage

class MediaS3Boto3Storage(S3Boto3Storage):
    file_overwrite = False
```

Assim, alterando no `settings.py` a configuração principal:

```python
DEFAULT_FILE_STORAGE = 'core.storages.MediaS3Boto3Storage'
```

No exemplo, abaixo, o mesmo arquivo foi enviado duas vezes para o bucket S3 e o segundo aparece já com os caracteres acrescentados ao fim do nome:

![Exemplo do Bucket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j23ywo6y6e2vpi4asg2y.png)


---

## Observações

Segundo o comentário em [[3](#ref-3)], não existe a necessidade em, de fato, substituir o *adapter* e a configuração `AWS_S3_FILE_OVERWRITE = False` poderia ser utilizada devido ao fato de que o django remove o arquivo antes de criar o novo ao executar o comando `python manage.py collectstatic`, porém essa é uma questão não confirmada e que, além disso, deixa preocupações, por exemplo, em futuras atualizações do próprio *django*.

---

## Referências

<a name="ref-1"></a>[1] [The staticfiles app](https://docs.djangoproject.com/en/4.1/ref/contrib/staticfiles/#collectstatic) - Django Documentation.

<a name="ref-2"></a>[2] [Amazon S3](https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html) - Django-storages Documentation.

<a name="ref-3"></a>[3] [jschneier on GitHub](https://github.com/jschneier/django-storages/issues/50#issuecomment-319615823).