Руководство по загрузке файлов (и изображений) в 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. Он должен перенаправить вас на эту страницу:

Admin Homepage

Нажмите на ссылку "+ Добавить" рядом с Posts. Вы можете добавить все, что хотите, но для этого урока я делаю пост о талисмане Django Pony. Вы можете скачать его здесь сами, если хотите.

Django Pony Post

После "Сохранить" вы будете перенаправлены на страницу Posts, где мы можем увидеть все наши сообщения.

Image 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. При необходимости обновите страницу.

Homepage

И вуаля! Если вы добавите дополнительные посты с названием и изображением через админку, они появятся на главной странице.

Форма

Теперь мы можем добавить форму, чтобы обычные пользователи, у которых нет доступа к админке, также могли добавлять сообщения. Это означает создание новой страницы с формой.

Начнем с файла 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/.

Create Post

После отправки нового сообщения вы будете перенаправлены на главную страницу и увидите все сообщения.

Следующие шаги

Размещение этого сайта в продакшене потребует нескольких дополнительных шагов. В частности, вполне вероятно, что вы будете использовать WhiteNoise на сервере для статических файлов, однако WhiteNoise явно не поддерживает медиафайлы. Общепринятой практикой является использование django-storages для этой цели и подключение к чему-то вроде S3.

Что еще? Возможно, вы хотите установить ограничения на размер изображения, что можно сделать изначально в файле models.py или с помощью CSS. Возможно, вы захотите добавить опции редактирования и удаления для сообщения. И, скорее всего, вам также понадобится уменьшенная версия изображений, что можно сделать с помощью sorl-thumbnail.

Вернуться на верх