使用django框架搭建个人相册网站

写于

搭建开发环境

配置虚拟环境

虚拟环境(virtualenv,或venv )是 Python 多版本管理的工具,可以使每个项目环境与其他项目独立开来,保持环境的干净,解决包冲突问题。你可以将虚拟环境理解为一个隔绝的小系统。

首先在合适的位置新建一个文件夹,比如 django_album_tutorial 。

在命令行中进入此文件夹:

# 输入指令
D:\> cd Developer\Py\django_album_tutorial
# 进到目录
D:\Developer\Py\django_album_tutorial> 

输入如下指令,创建名为 env 的虚拟环境:

> python -m venv env

为阅读方便,后续将省略盘符前的路径,以 > 符替代。(不影响阅读下)

创建完成后,输入env\Scripts\activate.bat,即可进入虚拟环境:

> env\Scripts\activate.bat

# Linux 或 Mac 用户改为 source env/bin/activate
# 看到盘符前有 (env) 标识说明进入虚拟环境成功了
(env)>

可以用 pip list 观察下虚拟环境内的包:

(env)> pip list
Package    Version
---------- -------
pip        21.1.3
setuptools 56.0.0

非常的干净,与全局的包是完全隔离的。

安装Django

在虚拟环境下,输入如下命令:

(env)> pip install django==3.2.5 -i https://pypi.tuna.tsinghua.edu.cn/simple

# 显示下面的文字则表明安装django成功
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting django==3.2.5
  Downloading ...
Successfully installed asgiref-3.4.1 django-3.2.5 pytz-2021.1 sqlparse-0.4.1

由于国内复杂的网络环境,命令中的 -i ... 指定了安装所采用的国内镜像源。(这里用的清华源)

安装大概会花费几分钟时间。完成后再观察下环境中的包:

(env)> pip list

# 已经有django了
Package    Version
---------- -------
asgiref    3.4.1
Django     3.2.5
pip        21.1.3
pytz       2021.1
setuptools 56.0.0
sqlparse   0.4.1

创建Django项目

还是在虚拟环境下,在命令行中中创建 Django 项目:

(env)> django-admin startproject album

运行此命令后将新创建名叫 album 的路径。进入此路径。

# 进入新创建的 album 目录
(env)> cd album

目录结构像下面这样:

album/
  manage.py
  album/
    __init__.py
    settings.py
    urls.py
    asgi.py
    wsgi.py

这就是我们刚创建出来的 album 相册项目了。

运行Django服务器

Django 自带一个轻量的Web开发服务器,也被叫做 runserver 。

开发服务器可以避开配置生产环境的繁琐环节,快速开发 Web 程序。它会自动的检测代码的改变并加载它,因此在修改代码后不需要手动去重启服务器,非常的方便。

要运行此服务器,首先进入album文件夹,即含有 manage.py 文件的路径。(上面的指令已经进入了)

然后输入如下命令:

(env)> python manage.py runserver

# 下面的文字表示启动服务器成功
Watching for file changes with StatReloader
Performing system checks...
...
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

打开chrome浏览器,输入http://127.0.0.1:8000/ ,即倒数第二排文字的地址。

创建 App

上一章在虚拟环境中创建好了 Django 项目:

(env)> django-admin startproject album

接下来,进入 album 路径(有 manage.py 文件的路径),运行下面的指令创建 App:

(env)> python manage.py startapp photo

Django 用 App 来组织功能独立的模块。比如个人博客中,文章功能可以是一个 App,评论功能是另一个 App。

运行完成后,看看目前的目录结构:

album/
  manage.py
  album/
    __init__.py
    settings.py
    urls.py
    asgi.py
    wsgi.py
    db.sqlite3  # runserver 时自动创建的
  photo/  # 刚创建的 photo app
    __init__.py
    admin.py
    apps.py
    models.py
    tests.py
    views.py
    migrations/
      __init__.py

多出来 photo 路径,里面的文件都从属于这个 photo App 了。

此外在根目录还多了个 db.sqlite3 ,这是数据库文件,暂时先不管它。

视图函数

App 中最重要的文件可能就是 views.py 了,它负责获取数据、处理数据,并将数据传递给用户观看的页面上。

让我们先试试打印个 Hello World 到浏览器中。

将 views.py 改成如下:

# /photo/views.py

from django.http import HttpResponse

def home(request):
    title = '<h1>Hello World</h1>'
    return HttpResponse(title)

home() 就是这个非常重要的获取、展现数据的函数了,称为视图函数。

视图函数接收的第一个参数 request ,被称为请求体,它包含了从用户端(如浏览器)传递过来的数据等信息。

视图函数必须要做的只有一件事:要么返回一个响应体(给用户端),要么抛出一个异常(raise)。至于其他还要做什么,都随便你。

所以你可以看到,home() 函数甚至都没有用到 request 请求体,直接返回了 HttpResponse 响应体。

你可以简单理解为直接返回给前端了。(虽然中间还有其他的处理)

url路径

视图函数是有了,但是程序并不知道这个函数对应哪个 url 路径。

是 www.a.com/home ?还是 www.a.com/ ?

为了告诉程序视图函数和 url 路径的对应关系,因此创建 photo/urls.py ,写入:

# /photo/urls.py

from django.urls import path
from photo.views import home

# App名称
# 用于Django幕后的url查询
app_name = 'photo'

# url列表
urlpatterns = [
    path('', home, name='home'),
]

urlpatterns 收集当前 App 下的所有 url 路径,其中的 path() 接收三个参数,分别是:

  • 路径。空字符串表示没有下一级的路径了。
  • 视图函数。
  • 路径名。

光有 /photo/urls.py 还不行,因为这个路径文件实际上是可以在任意位置或有任意名称的,程序并不知道。因此 Django 有一个 url 路径的集中入口,也就是 /album/urls.py 这个文件了。

将 /album/urls.py 修改成这样:

# /album/urls.py

from django.contrib import admin
from django.urls import path, include
from photo.views import home

urlpatterns = [
    path('admin/', admin.site.urls),
    # 下面在配置路径
    path('photo/', include('photo.urls', namespace='photo')),
    path('', home, name='home'),
]

在这个文件里新配置了两个 path()。

path('photo/', ...) 指定了 photo App 的根路径,里面的 include() 函数指定了子路径文件的位置,以及子路径的命名空间。举个栗子,photo App 的路径就变成 /photo/a/ 或者 /photo/b/ 了;在某些函数中,你也可以用 photo:home 很方便的指代 /photo/home/ 这个路径。

由于 /photo/home/ 就是网站的首页,因此额外配置了一条 path('', home, ...) ,就可以通过根路径直接访问了。

Django 允许多路径指向同一视图函数。

也就是说,www.ds.com/ 和 www.ds.com/photo/home/ 是相同的。

数据库与模型

数据库是存储信息的场所。数据库由多个数据表构成。

啥意思?举个栗子,三年级二班中同学名册就是数据表。有的名册记录每位同学的考试成绩、有的记录身高体重、还有的记录兴趣爱好...所有的这些名册都放在老师的柜子里,这个柜子就是“数据库”了。

默认情况下,数据库就是db.sqlite3这个文件了。

在网站上线后你可能想换别的数据库,不过目前还不需要讨论这个内容。

操作数据库使用的是古老的 SQL 语句,它是完全不同于 Python 的另一种语言,这对新手来说无疑是困难的。

幸运的是,在 Django 里写小型 Web 应用并不需要你直接去操作数据库。你只需要用 Python 语言定义好模型,而模型会自动生成操作数据库所必要的一切。

这叫对象关系映射(Object Relational Mapping,简称ORM),用于实现编程语言里不同类型系统的数据之间的转换。

光讲理论有点枯燥,接下来通过实践理解。

编写模型

Photo App 中的 models.py 就是编写模型的地方。

将其修改如下:

# /photo/models.py

from django.db import models
from django.utils.timezone import now
from django.core.files.storage import DefaultStorage

# Create your models here.


class Photo(models.Model):
    title = models.CharField(max_length=200, verbose_name='标题')
    description = models.TextField(verbose_name='描述', blank=True)
    image = models.ImageField(upload_to='photos/%Y%m%d', verbose_name='图片', storage=DefaultStorage())
    created = models.DateTimeField(default=now, verbose_name='创建时间')

    class Meta:
        verbose_name = '照片'
        verbose_name_plural = verbose_name
        ordering = ['-created']

    def __str__(self):
        return self.title

继承自 models.Model 的对象被称为模型类,它对应了数据库中的数据表。模型类中可以定义很多字段,字段对应数据表中的不同信息。

比如名册,里面每个同学都有姓名、年龄,在数据库中就是姓名字段、年龄字段等。

Photo 模型中仅有两个字段。

ImageField 字段用于存储图片信息。通常来说,字段中会存储对应数据,比如 CharField 会将字符数据保存在数据库中。但 ImageField 有点特殊,因为图片作为一种文件,直接保存在硬盘中就足够了,并不需要真正放进数据库中。因此 ImageField 实际上只在数据库中保存了图片的名称、存储路径、索引等元数据,真正的图片文件被保存在 upload_to 参数所指定的路径中。

'photo/%Y%m%d/' 是动态格式化当前日期的特殊书写方式。比如今天是2021年8月5日,那么图片将被保存在项目路径下的 /photo/20210805/ 文件夹中。

另外一个 DateTimeField 用于记录图片创建的时间,默认值为当前时间。

方法 __str__ 用于美化模型在后台、命令行中的输出信息。

接下来要修改 /album/settings.py 文件,这是 Django 的全局配置文件:

# /album/settings.py

...

INSTALLED_APPS = [
    'django.contrib.admin',
    '...

    # 注册App
    'photo',
]

...

# 指定媒体文件路径
MEDIA_URL  = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

# Aliyun OSS configurations
DEFAULT_FILE_STORAGE = 'django_oss_storage.backends.OssMediaStorage'
OSS_ACCESS_KEY_ID = '***'  # 替换为你的 Access Key ID
OSS_ACCESS_KEY_SECRET = '***'  # 替换为你的 Access Key Secret
OSS_BUCKET_NAME = 'fei-photos'  # 替换为你的 Bucket 名称
OSS_ENDPOINT = 'oss-cn-heyuan.aliyuncs.com'  # 替换为你的 Endpoint,例如: oss-cn-beijing.aliyuncs.com

将 photo 添加到 INSTALLED_APPS 列表中,让 Django 程序加载这个自定义的 App。

后续进行模型迁移时,程序会在此列表中进行搜索。

数据迁移

数据迁移这个词听起来很玄乎,其实它有点类似于将刚才写的模型同步到数据库里。要记得数据库和 Python 是完全不同的东西,你在 Python 里写的模型长啥样,数据库并不知道。因此需要数据迁移这个步骤,Django 框架将在幕后自动为你处理好 Python 对象和数据库数据之间的对应关系。

张三(Python)有一本名册(模型类),拿给了李四(数据库)。李四将名册的结构(需要记录哪些信息)原封不动抄到自己的名册中(数据迁移)。

由于 ImageField 的数据迁移依赖 Pillow 库,因此首先安装它:(一定要记得在虚拟环境中!)

(env)> pip install Pillow==8.3.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
...
Successfully installed Pillow-8.3.1

接着输入指令创建迁移文件:

(env)> python manage.py makemigrations
# 以下为输出
Migrations for 'photo':
  photo\migrations\0001_initial.py
    - Create model Photo

成功后执行迁移:

(env)> python manage.py migrate
# 以下为输出
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, photo, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  ...
  Applying photo.0001_initial... OK
  Applying sessions.0001_initial... OK

没报错就表示迁移成功了。

接下来我们存储些数据看看效果。

后台操作

Django 开发之所以高效,原因之一就是自带很多通用功能的默认实现,比如后台管理功能。

首先在命令行里创建管理员账号:

(env)> python manage.py createsuperuser

Username (leave blank to use 'dusai'): dusai
Email address:
Password: *** (again):
Superuser created successfully.

注意在命令行里填写密码是不会显示任何字符的。

username: fei , email:i***@qq.com ,password: ***

接着还需要将 Photo 模型注册到后台中。修改 /photo/admin.py 文件如下:

# /photo/admin.py

from django.contrib import admin
from photo.models import Photo

admin.site.register(Photo)

这就Ok了。

重新启动服务器,输入 127.0.0.1:8000/admin 路径:

出现了管理员登录页面。

输入刚才的账号密码登录后台:

可以看到后台中已经有了 Photo 的管理入口。

点击 Add 添加新的图片数据:

随便选择一张本地图片,并点击 Save:

保存好之后,就可以在后台中看到已经上传的图片信息了。

接着到 /media 路径查看,图片文件确实也岁月静好的躺在那里:

总结

将图片上传至阿里云OSS并在网站中显示

photo/models.py的代码如下

from django.db import models
from django.utils.timezone import now
import os
from album.oss_utils import OssStorage
from datetime import datetime

# Create your models here.


class Photo(models.Model):
    title = models.CharField(max_length=200, verbose_name='标题')
    description = models.TextField(verbose_name='描述', blank=True)
    image = models.ImageField(
        upload_to='', 
        verbose_name='图片'
    )
    created = models.DateTimeField(default=now, verbose_name='创建时间')

    class Meta:
        verbose_name = '照片'
        verbose_name_plural = verbose_name
        ordering = ['-created']

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if self.image:
            try:
                # 获取当前年份
                current_year = str(datetime.now().year)

                # 确保文件名是唯一的
                file_name = os.path.basename(self.image.name)
                object_name = f'{current_year}/{now().strftime("%m%d_%H%M%S")}_{file_name}'

                # 上传文件到 OSS
                oss_storage = OssStorage()
                url = oss_storage.upload_file(self.image.file, object_name)
                print(f"File uploaded to OSS: {url}")

                # 更新图片字段
                self.image.name = object_name

            except Exception as e:
                print(f"Error uploading to OSS: {str(e)}")
                raise

        super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        # 删除 OSS 上的文件
        if self.image:
            try:
                oss_storage = OssStorage()
                oss_storage.delete_file(self.image.name)
            except Exception as e:
                print(f"Error deleting from OSS: {str(e)}")

        super().delete(*args, **kwargs)

album/settings.py代码如下

"""
Django settings for album project.

Generated by 'django-admin startproject' using Django 5.1.4.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '***'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'photo',
    'storages',  # 添加 storages
    'django_oss_storage',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'album.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'album.wsgi.application'


# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'


# Aliyun OSS configurations
DEFAULT_FILE_STORAGE = 'django_oss_storage.backends.OssMediaStorage'

# 阿里云OSS配置
OSS_ACCESS_KEY_ID = '***'
OSS_ACCESS_KEY_SECRET = '***'
OSS_BUCKET_NAME = 'fei-photos'
OSS_ENDPOINT = 'oss-cn-heyuan.aliyuncs.com'

# OSS特定配置
OSS_FILE_OVERWRITE = False
OSS_AUTO_CLEAN = True
OSS_CONNECT_TIMEOUT = 30
OSS_MAX_RETRIES = 3
OSS_EXPIRE_TIME = 60 * 60  # URL签名过期时间,1小时

# 媒体文件配置
MEDIA_URL = f'https://{OSS_BUCKET_NAME}.{OSS_ENDPOINT}/'
MEDIA_ROOT = ''

在album下加了个oss_utils.py文件

import oss2
from django.conf import settings
import os
import time

class OssStorage:
    def __init__(self):
        # 创建 Bucket 实例
        self.auth = oss2.Auth(settings.OSS_ACCESS_KEY_ID, settings.OSS_ACCESS_KEY_SECRET)
        self.bucket = oss2.Bucket(self.auth, settings.OSS_ENDPOINT, settings.OSS_BUCKET_NAME)

    def upload_file(self, file_obj, object_name=None):
        """
        上传文件到 OSS
        :param file_obj: 文件对象
        :param object_name: OSS中的文件名
        :return: 文件的签名URL
        """
        if object_name is None:
            object_name = file_obj.name

        try:
            # 上传文件
            self.bucket.put_object(object_name, file_obj)

            # 生成签名URL
            url = self.bucket.sign_url('GET', object_name, settings.OSS_EXPIRE_TIME)
            print(f"File uploaded to OSS with signed URL: {url}")
            return url

        except Exception as e:
            print(f"Upload error: {str(e)}")
            raise

    def get_signed_url(self, object_name, expire=3600):
        """
        获取文件的签名URL
        :param object_name: OSS中的文件名
        :param expire: 过期时间(秒)
        :return: 签名URL
        """
        try:
            url = self.bucket.sign_url('GET', object_name, expire)
            return url
        except Exception as e:
            print(f"Error generating signed URL: {str(e)}")
            raise

    def delete_file(self, object_name):
        """
        从 OSS 删除文件
        :param object_name: OSS中的文件名
        """
        try:
            self.bucket.delete_object(object_name)
        except Exception as e:
            print(f"Delete error: {str(e)}")
            raise

图片列表显示合适大小的缩略图,而图片详情图能看到实际大小

@. 我想这个项目中的photo应用的所有功能都添加到我另外一个已有的django网站中,应该如何做?为了减少bug,请将本项目的代码尽可能集中到photo目录下

要将当前项目中的 photo 应用的所有功能添加到另一个已有的 Django 网站中,可以按照以下步骤操作:

第一步:复制 Photo 应用

  1. 复制目录:将整个 photo 目录从当前项目(d:/Code/Python/0101photo_site/album/photo)复制到你想要集成的 Django 项目中。可以将其放在新 Django 项目的根目录或合适的应用文件夹中。

第二步:更新设置

  1. 添加到已安装应用

:打开你现有 Django 项目的settings.py文件,将photo应用添加到INSTALLED_APPS列表中:

CodeCopyInsertINSTALLED_APPS = [ ... 'photo', ... ]

第三步:更新 URL

  1. 包含 URLs:在现有 Django 项目的主urls.py文件中,包含photo应用的 URL:

``` CodeCopyInsertfrom django.urls import include, path

urlpatterns = [ ... path('photo/', include('photo.urls')), ... ] ```

第四步:处理静态文件和模板文件

  1. 静态文件和模板文件:确保 photo 应用使用的任何静态文件(CSS、JavaScript、图片)和模板文件都是可访问的。你可能需要在 settings.py 文件中相应地配置 STATICFILES_DIRS 和 TEMPLATES 设置。

第五步:数据库迁移

  1. 迁移数据库:如果photo 应用包含模型,运行以下命令以创建必要的数据库表:

``` python manage.py makemigrations photo python manage.py migrate


### 第六步:测试集成

1. 运行服务器:启动你的 Django 服务器并导航到 /photo/ URL 以测试集成:

Code CopyInsert

python manage.py runserver ```

第七步:检查和调整

  1. 检查功能:测试 photo 应用的所有功能,以确保它们在新环境中按预期工作。根据现有项目的结构,你可能需要调整一些设置、路径或导入语句。

总结

通过以上步骤,你将成功将 photo 应用集成到你的现有 Django 网站中。如果在过程中遇到任何问题或需要进一步的帮助,请随时告诉我!