Docker Compose - The ImageField field in Django cannot upload images when I migrate the database from sqlite3 to MySQL in Docker Compose
I have the Article
model in Django blog app
File /backend/blog/models.py
class Article(models.Model):
class Status(models.TextChoices):
DRAFT = 'DF', 'Draft'
PUBLISHED = 'PB', 'Published'
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=100, blank=True, unique=True)
content = MDTextField(null=True, blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
comments = models.IntegerField(default=0)
reactions = models.IntegerField(default=0)
updated = models.DateTimeField(auto_now=True)
category = models.ForeignKey(Category, related_name='articles', on_delete=models.SET_NULL, null=True)
tags = models.ManyToManyField(Tag)
image_background = models.ImageField(upload_to='articles_images/', blank=True, null=True)
image_url = models.URLField(max_length=500, blank=True, null=True) # Updated field to store image URL from Firebase
intro = models.TextField()
estimate_time = models.CharField(max_length=50)
type = models.ForeignKey(Type, on_delete=models.SET_NULL, null=True)
publish = models.DateTimeField(default=timezone.now)
status = models.CharField(max_length=2,
choices=Status.choices,
default=Status.DRAFT)
Next, I porting the default database SQLITE3 to MYSQL for Django project
File /backend/settings.py
# ! Config database for mysql
DATABASES = {
"default": {
"ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"),
"NAME": os.environ.get("SQL_DATABASE", os.path.join(BASE_DIR, 'db.sqlite3')),
"USER": os.environ.get("SQL_USER", "myuser"),
"PASSWORD": os.environ.get("SQL_PASSWORD", "myuserpassword"),
"HOST": os.environ.get("SQL_HOST", "localhost"),
"PORT": os.environ.get("SQL_PORT", "3306"),
'OPTIONS': {
'charset': 'utf8mb4',
}
}
}
I also config the path media to upload image /backend/settings.py
STATIC_URL = 'static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATICFILES_DIRS = (os.path.join(BASE_DIR,"static"), )
I used the docker-compose.yml
to build image and run container for backend (django), frontend (nextjs) and database (mysql) to develop my project
version: '3.8'
services:
database:
image: mysql:9.1.0
container_name: mysql-container
volumes:
# mysql_data: data stogare managed by Docker: /var/lib/docker/volumes/
# See info: docker volume inspect mysql_data
# /var/lib/mysql: is the default path in container MySQL
- mysql_data:/var/lib/mysql
restart: always
env_file:
- .env
environment:
- MYSQL_ROOT_PASSWORD=${SQL_ROOT_PASSWORD}
- MYSQL_USER=${SQL_USER}
- MYSQL_PASSWORD=${SQL_PASSWORD}
- MYSQL_DATABASE=${SQL_DATABASE}
- MYSQL_TCP_PORT=${MYSQL_TCP_PORT}
ports:
- "3306:3306"
networks:
- default
backend:
build:
dockerfile: Dockerfile
context: ./backend # Find location the Dockerfile of backend in the `backend` directory.
image: django
container_name: django-container
restart: always
env_file:
- .env
volumes:
# - <path_in_host>:<path_in_container>
- ./backend:/app # Mount all contents of folder backend into container
- ./backend/media:/app/media
ports:
- "3001:3001"
command: python manage.py runserver 0.0.0.0:3001
depends_on:
- database
networks:
- default
frontend:
build:
dockerfile: Dockerfile
context: ./frontend
image: nextjs
container_name: nextjs-container
restart: always
volumes:
- ./frontend:/app
- /app/node_modules
ports:
- "3000:3000"
command: sh -c "export PATH=$$PATH:/app/node_modules/.bin && npm run dev"
depends_on:
- backend
networks:
- default
networks:
default:
driver: bridge
volumes:
mysql_data:
However, I CANNOT upload any images from
image_background = models.ImageField(upload_to='articles_images/', blank=True, null=True)
even though everything WORKED FINE with SQLITE3 before. I have checked the access permissions of the /media
directory in the container, and all directories have read, write, and execute permissions.
Please help me fix this issue. Thank you so much
-------------------------------------------
These are my entire the /backend/blog/models.py
from django.db import models
from django.utils import timezone
import shutil, os, re
from django.contrib.auth.models import User
from .firebase import upload_image_to_firebase
from ckeditor.fields import RichTextField
from ckeditor_uploader.fields import RichTextUploadingField
import os
from mdeditor.fields import MDTextField
from django.conf import settings
from slugify import slugify
from .logger import Logger
from .github_discussions_api import get_comment_react
LOGGER = Logger("Create model Article")
class Type(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Category(models.Model):
name = models.CharField(max_length=100)
hex_color = models.CharField(max_length=7, default='#FFFFFF')
emoji = models.CharField(max_length=10, blank=True, null=True)
def __str__(self):
return self.name
class Tag(models.Model):
name = models.CharField(max_length=100)
hex_color = models.CharField(max_length=7, default='#FFFFFF')
def __str__(self):
return self.name
def find_markdown_images(text):
pattern = r'!\[(.*?)\]\(((?:.*?\s*?)+?)\s*(?:\"(.*?)\")?\)'
matches = re.findall(pattern, text)
filtered_matches = [(match[0], match[1]) for match in matches if not match[1].startswith(('http:', 'https:'))]
return filtered_matches
def copy_image_background_to_article_folder(app_path, slug_article, image_name):
#image_name = articles_images/describle_project.png
image_name = image_name.split('/')[1]
new_path = f"/app/media/articles_images/{slug_article}/{image_name}"
os.makedirs(os.path.dirname(new_path), exist_ok=True)
shutil.copy(app_path, new_path)
return new_path
class Article(models.Model):
class Status(models.TextChoices):
DRAFT = 'DF', 'Draft'
PUBLISHED = 'PB', 'Published'
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=100, blank=True, unique=True)
content = MDTextField(null=True, blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
comments = models.IntegerField(default=0)
reactions = models.IntegerField(default=0)
updated = models.DateTimeField(auto_now=True)
category = models.ForeignKey(Category, related_name='articles', on_delete=models.SET_NULL, null=True)
tags = models.ManyToManyField(Tag)
image_background = models.ImageField(upload_to='articles_images/', blank=True, null=True)
image_url = models.URLField(max_length=500, blank=True, null=True) # Updated field to store image URL from Firebase
intro = models.TextField()
estimate_time = models.CharField(max_length=50)
type = models.ForeignKey(Type, on_delete=models.SET_NULL, null=True)
publish = models.DateTimeField(default=timezone.now)
status = models.CharField(max_length=2,
choices=Status.choices,
default=Status.DRAFT)
class Meta:
ordering = ['-publish']
indexes = [
models.Index(fields=['-publish']),
]
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title, max_length=100)
update_fields = []
if self.pk is None:
super().save(*args, **kwargs)
else:
update_fields.append('slug')
if self.image_background:
full_path = f"/app/media/{self.image_background.name}"
full_path_article = copy_image_background_to_article_folder(full_path, self.slug, self.image_background.name)
self.image_url = upload_image_to_firebase(self.title, full_path_article, os.path.basename(full_path_article))
update_fields.append('image_url')
image_tuples = find_markdown_images(self.content)
for alt_text, image_path in image_tuples:
full_path = os.path.join(settings.BASE_DIR, image_path.strip('/'))
if os.path.exists(full_path):
url_image = upload_image_to_firebase(self.title, full_path, os.path.basename(image_path))
pattern = r'\!\[' + re.escape(alt_text) + r'\]\(' + re.escape(image_path) + r'(\s+".*?")?\)'
self.content = re.sub(pattern, f'', self.content)
else:
LOGGER.error(f"File not found: {full_path} when upload image into content")
update_fields.append('content')
comments, reactions = get_comment_react(self.slug)
self.comments = comments
self.reactions = reactions
update_fields.extend(['comments', 'reactions'])
super().save(*args, **kwargs, update_fields=update_fields if update_fields else None)
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=50, blank=True, null=True)
about_me = MDTextField(null=True, blank=True)
short_intro = models.CharField(max_length=255, blank=True, null=True)
occupation = models.CharField(max_length=255, blank=True, null=True)
github = models.URLField(blank=True, null=True)
github_display_name = models.CharField(max_length=255, blank=True, null=True)
facebook = models.URLField(blank=True, null=True)
facebook_display_name = models.CharField(max_length=255, blank=True, null=True)
linkedin = models.URLField(blank=True, null=True)
linkedin_display_name = models.CharField(max_length=255, blank=True, null=True)
leetcode = models.URLField(blank=True, null=True)
leetcode_display_name = models.CharField(max_length=255, blank=True, null=True)
email = models.EmailField(blank=True, null=True)
email_display_name = models.CharField(max_length=255, blank=True, null=True)
def __str__(self):
return self.user.username
class TimeLine(models.Model):
date = models.CharField(max_length=50)
title = models.CharField(max_length=255)
corporation = models.CharField(max_length=255)
description = models.TextField()
iconPath = models.CharField(max_length=255)
toolAndService = models.JSONField()
type = models.CharField(max_length=50)
index = models.IntegerField()
def __str__(self):
return self.title
I tried debug the permission my folder /app/media