使用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 应用
- 复制目录:将整个
photo目录从当前项目(d:/Code/Python/0101photo_site/album/photo)复制到你想要集成的 Django 项目中。可以将其放在新 Django 项目的根目录或合适的应用文件夹中。
第二步:更新设置
- 添加到已安装应用
:打开你现有 Django 项目的settings.py文件,将photo应用添加到INSTALLED_APPS列表中:
CodeCopyInsertINSTALLED_APPS = [
...
'photo',
...
]
第三步:更新 URL
- 包含 URLs:在现有 Django 项目的主urls.py文件中,包含photo应用的 URL:
``` CodeCopyInsertfrom django.urls import include, path
urlpatterns = [ ... path('photo/', include('photo.urls')), ... ] ```
第四步:处理静态文件和模板文件
- 静态文件和模板文件:确保
photo应用使用的任何静态文件(CSS、JavaScript、图片)和模板文件都是可访问的。你可能需要在settings.py文件中相应地配置STATICFILES_DIRS和TEMPLATES设置。
第五步:数据库迁移
- 迁移数据库:如果photo 应用包含模型,运行以下命令以创建必要的数据库表:
``` python manage.py makemigrations photo python manage.py migrate
### 第六步:测试集成
1. 运行服务器:启动你的 Django 服务器并导航到 /photo/ URL 以测试集成:
Code CopyInsert
python manage.py runserver ```
第七步:检查和调整
- 检查功能:测试
photo应用的所有功能,以确保它们在新环境中按预期工作。根据现有项目的结构,你可能需要调整一些设置、路径或导入语句。
总结
通过以上步骤,你将成功将 photo 应用集成到你的现有 Django 网站中。如果在过程中遇到任何问题或需要进一步的帮助,请随时告诉我!