Руководство по загрузке файлов (и изображений) в Django
Настройка
Если вы работаете на Mac, Desktop
- это удобное место для размещения нашего кода. Место не имеет значения; оно просто должно быть легко доступно.
В командной строке перейдите туда и создайте каталог insta
для наших файлов. Мы будем использовать Pipenv для установки Django и pillow, которая является библиотекой Python для обработки изображений, на которую Django полагается для файлов изображений. Для загрузки файлов, не являющихся изображениями, pillow
не требуется. Наконец, активируем нашу новую виртуальную среду командой shell
.
$ cd ~/Desktop $ mkdir insta && cd insta $ pipenv install django==3.0.3 pillow==7.0.0 $ pipenv shell (insta) $
Вы должны увидеть (insta)
, идущий вперед, чтобы показать, что мы находимся в активной виртуальной среде. Вы можете набрать exit
в любое время, чтобы выйти из нее, и pipenv shell
, чтобы войти снова.
Проект и приложение
Теперь создайте наш новый проект Django под названием insta_project
и новое приложение под названием posts
.
(insta) $ django-admin startproject insta_project . (insta) $ python manage.py startapp posts
Поскольку мы добавили новое приложение, нам нужно сообщить об этом Django в нижней части конфигурации INSTALLED_APPS
в settings.py
.
# insta_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'posts.apps.PostsConfig', # new ]
Теперь запустите python manage.py migrate
, чтобы установить новую базу данных для нашего проекта.
(insta) $ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying sessions.0001_initial... OK
Модели
Начать с модели базы данных - хороший выбор. В нашем случае наша модель Post
будет иметь только два поля: title
и cover
. Мы также включим метод __str__
ниже, чтобы title
появился в нашем Django admin позже.
# posts/models.py from django.db import models class Post(models.Model): title = models.TextField() cover = models.ImageField(upload_to='images/') def __str__(self): return self.title
Местонахождение загруженного image
будет находиться в MEDIA_ROOT/images
. В Django параметр MEDIA_ROOT
является местом, где мы определяем местоположение всех загруженных пользователем элементов. Мы установим это сейчас.
Если бы мы хотели использовать здесь обычный файл, единственным отличием было бы изменение
ImageField
наFileField
.
MEDIA_ROOT
Откройте insta_project/settings.py
в вашем текстовом редакторе. Мы добавим две новые конфигурации. По умолчанию MEDIA_URL
и MEDIA_ROOT
пустые и не отображаются, поэтому нам нужно их настроить:
MEDIA_ROOT
- абсолютный путь файловой системы к директории для загружаемых пользователем файловMEDIA_URL
- это URL, который мы можем использовать в наших шаблонах для файлов
# insta_project/settings.py MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Мы могли бы выбрать другое имя, кроме media
, но таково соглашение Django. Мы также создадим папку images
внутри нее, чтобы использовать ее в ближайшее время.
(insta) $ mkdir media (insta) $ mkdir media/images
Admin
Теперь обновите файл posts/admin.py
, чтобы мы могли видеть наше приложение Post
в админке Django.
# posts/admin.py from django.contrib import admin from .models import Post admin.site.register(Post)
И все готово! Создайте новый файл миграций.
(insta) $ python manage.py makemigrations Migrations for 'posts': posts/migrations/0001_initial.py - Create model Post
Затем запустите migrate
для обновления базы данных.
(insta) $ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, posts, session s Running migrations: Applying posts.0001_initial... OK
Теперь мы можем создать учетную запись superuser
для доступа к администратору, а затем выполнить runserver
для первого запуска локального веб-сервера.
(insta) $ python manage.py createsuperuser (insta) $ python manage.py runserver
Если вы перейдете по адресу http://127.0.0.1:8000/admin, вы сможете войти на сайт администратора Django. Он должен перенаправить вас на эту страницу:
Нажмите на ссылку "+ Добавить" рядом с Posts
. Вы можете добавить все, что хотите, но для этого урока я делаю пост о талисмане Django Pony. Вы можете скачать его здесь сами, если хотите.
После "Сохранить" вы будете перенаправлены на страницу Posts, где мы можем увидеть все наши сообщения.
Если вы посмотрите в локальную папку media
в вашем проекте, вы увидите, что в разделе images
теперь находится файл изображения djangopony.png
. Видите! Я говорил вам, что это то, что будет делать MEDIA_URL
.
Ок, итак, на данный момент мы закончили с основами. Но давайте сделаем шаг дальше и отобразим наши посты, которые означают urls.py
, views.py
, и файлы шаблонов.
URLs
Смущает в Django то, что для одной веб-страницы часто требуется 4 разных, но взаимосвязанных файла: models.py
, urls.py
, views.py
и файл шаблона html
. Мне проще всего рассуждать об этом, двигаясь в порядке от моделей -> урлов -> представлений -> файлов шаблонов. Наша модель уже готова, так что это означает погружение в маршруты URL.
Нам потребуется два обновления файлов urls.py
. Сначала в файлах insta_project/urls.py
на уровне проекта нужно добавить импорты для settings
, include
и static
. Затем определите маршрут для приложения posts
. Обратите внимание, что нам также нужно добавить MEDIA_URL
, если настройки находятся в режиме DEBUG
, иначе мы не сможем просматривать загруженные изображения локально.
# insta_project/urls.py from django.contrib import admin from django.conf import settings # new from django.urls import path, include # new from django.conf.urls.static import static # new urlpatterns = [ path('admin/', admin.site.urls), path('', include('posts.urls')), # new ] if settings.DEBUG: # new urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Далее нам нужно разобраться с маршрутами URL в приложении posts
. Сначала создайте этот файл.
(insta) $ touch posts/urls.py
Затем мы поместим все посты на главную страницу, поэтому снова используем пустую строку ''
в качестве пути маршрута.
# posts/urls.py from django.urls import path from .views import HomePageView urlpatterns = [ path('', HomePageView.as_view(), name='home'), ]
Это ссылается на представление под названием HomePageView
, которое мы создадим следующим.
Виды
Здесь мы можем использовать общий класс на основе ListView
, импортировать нашу модель Post
, а затем создать HomePageView
, который использует модель и шаблон под названием home.html
.
# posts/views.py from django.views.generic import ListView from .models import Post class HomePageView(ListView): model = Post template_name = 'home.html'
Boom! Переходим к последнему шагу - это файл шаблона под названием home.html
.
Шаблоны
У нас есть два варианта расположения нашего шаблона. Мы могли бы поместить его в приложение posts
по адресу posts/templates/posts/home.html
, но я считаю такую структуру избыточной. Кроме того, сложнее рассуждать о шаблонах, когда все они похоронены в соответствующих приложениях. Поэтому обычно я создаю каталог templates
на уровне проекта.
$ mkdir templates $ touch templates/home.html
Мы говорим Django, чтобы also искал здесь любые шаблоны, обновляя TEMPLATES
конфигурацию внутри insta_project/settings.py
.
# insta_project/settings.py TEMPLATES = [ { ... 'DIRS': [os.path.join(BASE_DIR, 'templates')], # new ... }, ]
В нашем файле шаблона home.html
будут отображаться title
и image
для всех постов. Точно так же, как это делает Instagram :)
.
<!-- templates/home.html --> <h1>Django Image Uploading</h1> <ul> {% for post in object_list %} <h2>{{ post.title }}</h2> <img src="{{ post.cover.url}}" alt="{{ post.title }}"> {% endfor %} </ul>
Ок, вот и все! Убедитесь, что сервер запущен с помощью команды python manage.py runserver
и перейдите на нашу домашнюю страницу по адресу http://127.0.0.1:8000. При необходимости обновите страницу.
И вуаля! Если вы добавите дополнительные посты с названием и изображением через админку, они появятся на главной странице.
Форма
Теперь мы можем добавить форму, чтобы обычные пользователи, у которых нет доступа к админке, также могли добавлять сообщения. Это означает создание новой страницы с формой.
Начнем с файла views.py
. Мы назовем наш новый вид CreatePostView
, который будет расширять встроенный Django CreateView. Мы также импортируем reverse_lazy для обработки перенаправления обратно на нашу домашнюю страницу после отправки формы.
В представлении мы указываем model
, form_class
, который мы создадим следующим, template_name
и, наконец, success_url
, который является тем, что мы хотим, чтобы произошло после представления.
# posts/views.py from django.views.generic import ListView, CreateView # new from django.urls import reverse_lazy # new from .forms import PostForm # new from .models import Post class HomePageView(ListView): model = Post template_name = 'home.html' class CreatePostView(CreateView): # new model = Post form_class = PostForm template_name = 'post.html' success_url = reverse_lazy('home')
Следующая форма. Сначала создайте ее.
(insta) $ touch posts/forms.py
Мы можем расширить встроенную в Django ModelForm. Все, что нам нужно для нашей базовой формы, это указать правильную модель Post
и поля, которые мы хотим отобразить, а именно title
и cover
.
# posts/forms.py from django import forms from .models import Post class PostForm(forms.ModelForm): class Meta: model = Post fields = ['title', 'cover']
Мы сделаем специальную страницу для этой формы по пути post/
.
# posts/urls.py from django.urls import path from .views import HomePageView, CreatePostView # new urlpatterns = [ path('', HomePageView.as_view(), name='home'), path('post/', CreatePostView.as_view(), name='add_post') # new ]
Затем создайте новый шаблон.
(insta) $ touch templates/post.html
И заполните его заголовком и формой. Важно всегда добавлять csrf_token
для защиты. Мы указываем form.as_p
, что означает, что Django будет выводить каждое поле как тег параграфа.
<!-- templates/post.html --> <h1>Create Post Page</h1> <form method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} <button type="submit">Submit New Post</button> </form>
Вот и все! Убедитесь, что ваш сервер работает, и перейдите на страницу по адресу http://127.0.0.1:8000/post/.
После отправки нового сообщения вы будете перенаправлены на главную страницу и увидите все сообщения.
Следующие шаги
Размещение этого сайта в продакшене потребует нескольких дополнительных шагов. В частности, вполне вероятно, что вы будете использовать WhiteNoise на сервере для статических файлов, однако WhiteNoise явно не поддерживает медиафайлы. Общепринятой практикой является использование django-storages для этой цели и подключение к чему-то вроде S3.
Что еще? Возможно, вы хотите установить ограничения на размер изображения, что можно сделать изначально в файле models.py
или с помощью CSS. Возможно, вы захотите добавить опции редактирования и удаления для сообщения. И, скорее всего, вам также понадобится уменьшенная версия изображений, что можно сделать с помощью sorl-thumbnail.