基于Django快速构建Web项目

本文介绍了如何基于Django快速构建Web项目,包括环境准备、后台搭建、数据库迁移、使用postgresql、REST API的实现,以及前端Angular6的集成和项目的编译打包。详细讲述了Django设置、数据库配置、权限验证、自定义分页和前端静态文件处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于Django快速构建Web项目

本章主要记录怎么用Django快速构建一个web项目,所有的内容也是本人亲自构建,整个项目完美运行。
项目地址见:https://github.com/blacksider/django-startup

1. 环境准备

  • 开发环境:
操作系统:Windows10 x64
python版本:3.7
VisualStudio版本:VS2017 Community
IDE: PyCharm 2017.1.2

2. 项目说明

项目搭建的方案选择前后端分离的构建方式,后端使用djangorestframework,前端使用Angular6

3. 搭建后台

  • 准备依赖环境
pip3 install django
  • 设置项目,执行 ‘django-admin startproject server .’,创建一个启动项目,执行后文件结构类似于:
...
server/
	__init__.py
	settings.py
	urls.py
	wsgi.py
manage.py
  • 此命令为 django 内置的命令,可以帮助开发者快速构建一个启动项目;
  • 注意命令行后面的 ‘.’,表示在当前目录下创建项目,否则会生成一个结构 ‘server/server/’;
  • 现在可以直接启动项目,测试项目是否可以运行,执行 ‘python manage.py runserver’,可以看到控制台中弹出了如下的信息:
Performing system checks...

System check identified no issues (0 silenced).

You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
October 15, 2018 - 11:08:12
Django version 2.1.1, using settings 'server.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
  • 现在访问地址 ‘http://127.0.0.1:8000/’ ,可以看到django的默认启动页面了。
  • 但是可以看到控制台信息中有一行提醒 ‘You have 15 unapplied migration(s)’,这是因为 django 本身内嵌了很多关于用户和权限的数据结构,并且 django 默认使用的数据库为 sqlite3,所以它会尝试初始化相关的 Model ,根据提示,先删除掉刚才启动时创建的文件 db.sqlite3,执行 ‘python manage.py migrate’,可以看到重新生成了一个 db.sqlite3 文件,通过 PyCharm 的 Database 视图连接此文件,可以看到项目中创建如下表:
auth_group
auth_group_permissions
auth_permission
auth_user
auth_user_groups
auth_user_user_permissions
django_admin_log
django_content_type
django_migrations
django_session
sqlite_master
sqlite_sequence
  • 这些即是 django 本身需要的数据库表了,再次执行 ‘python manage.py runserver’,可以看到提醒 migration 的信息消失。
  • 现在,已经拥有一个可以运行的后端项目了,下面需要将数据库替换为其他数据库,这里使用 postgresql 作为此项目的数据库;
  • 首先,删除掉刚才生成的 db.sqlite3 文件,然后打开文件 server/settings.py ,找到 DATABASES 配置项,这里默认的内容如下:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
  • 现在,需要将其替换为 postgresql 的配置,修改为:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'testdb',
        'USER': 'testdbuser',
        'PASSWORD': 'test',
        'HOST': 'localhost',
        'PORT': '',
    }
}
  • 在 postgresql 中创建对应的数据库和用户信息后,执行 migration 操作 ‘python manage.py migrate’,然后再启动项目,确定项目无异常。
  • 现在我们可以创建一个默认的超级用户,使用如下的命令
python manage.py createsuperuser --email admin@test.com --username admin
  • 这里我们创建一个用户名密码均为 admin 的用户,打开文件 server/urls.py,可以看到如下内容:
urlpatterns = [
    path('admin/', admin.site.urls),
]
  • 此处的配置为 django 的一个内置的页面链接,可以自行查看一下该内容,该链接会打开一个默认的admin登录页面,可以用刚才创建的用户登录此系统,这里我不需要使用该链接地址,直接删除掉,后面需要使用的是基于 RESTAPI 的 djangorestframework 提供的url地址。
  • 后面需要安装如下依赖:
pip3 install djangorestframework
pip3 install django-rest-auth
pip3 install djangorestframework-timed-auth-token
  • djangorestframework的官方文档位于https://www.django-rest-framework.org/tutorial/quickstart/
  • django-rest-auth 会整合 djangorestframework 提供一套基于 rest api 的登录退出以及重置密码等操作的 rest 服务;
  • 前端和后端的登录验证会以 token 模式处理,但是默认提供的 token 机制没有超时时间,即无法做到如30分钟无操作认为登录失效的效果,这里还需要使用 djangorestframework-timed-auth-token 来实现;
  • 在 server/settings.py 中在 INSTALLED_APPS 中添加如下内容:
INSTALLED_APPS = [
	...
    'rest_framework',
    'rest_framework.authtoken',
    'rest_auth',
    'timed_auth_token',
]

TIMED_AUTH_TOKEN = {
    'DEFAULT_VALIDITY_DURATION': timedelta(minutes=30)
}
  • TIMED_AUTH_TOKEN 配置中的 DEFAULT_VALIDITY_DURATION 配置表示超时时间为30分钟;
  • 修改 server/urls.py:
urlpatterns = [
    url(r'^api/', include(('timed_auth_token.urls', 'timed_auth_token'), namespace='auth'), name='login'),
    url(r'^api/logout/$', LogoutView.as_view(), name='logout'),
    url(r'^api/user/$', UserDetailsView.as_view(), name='user_details'),
]

  • 后面的内容不会涉及到用户的创建和删除问题,并且会添加相关请求权限,所以目前只会使用 timed_auth_token 的登录url,rest_auth 的退出url和查看当前用户信息的api;
  • 执行 ‘python manage.py migrate’,更新数据库表,然后启动项目,访问 ‘http://localhost:8000/api/login/’ ,可以看到提醒不支持 get 方法,并且下方会有一个默认的简单的登录表单,输入之前创建的用户信息后,可以看到返回信息为:
{
    "token": "wmX1HkBDIEIOwIYlXQ9JNVc5gXgSw3eH8V7-oYCw"
}
  • 此内容即为后面需要用到的登录验证的 token;
  • 查看数据库 timed_auth_token_timedauthtoken 表,可以看到一条超时时间为30分钟的记录。
  • 现在,再添加一个测试模块,用来测试当登录成功后,可以查看此模块中的信息:
  • 在 server 目录下添加目录 trees,执行 python manage.py startapp trees ./server/trees/,生成 trees 模块;
  • 在添加新代码之前,先将 logger 模块添加到项目中,在 server/settings.py 中添加如下内容:
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'
        }
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'file': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': 'log/server.log',
            'maxBytes': 1024 * 1024 * 5,
            'backupCount': 5,
            'formatter': 'standard',
        },
        'console': {
            'level': 'INFO',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'standard'
        },
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': 'log/error.log',
            'maxBytes': 1024 * 1024 * 5,
            'backupCount': 5,
            'formatter': 'standard',
        }
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'propagate': True,
        },
        'django.request': {
            'handlers': ['error'],
            'level': 'ERROR',
            'propagate': False,
        },
        'custom': {
            'handlers': ['console', 'error', 'file'],
            'level': 'INFO'
        }
    }
}
  • 此配置表示会在控制台打印错误信息,并且会将信息同时输出到 log/server.log 文件中,error 级别的错误信息会输出到 log/error.log 文件中,由于 logger 模块不会自动创建 logs 目录,并且直接启动的时候它会报错,所以需要在 manage.py 中的开始位置检查目录是否存在,如果不存在创建该目录:
if __name__ == '__main__':
    if not os.path.exists('log'):
        os.makedirs('log')
    ...
  • 在 server/settings.py 的 INSTALLED_APPS 中将 trees 模块添加进去
INSTALLED_APPS = [
	...
    'server.trees',
]
  • 在 server/trees/models.py 中添加 Tree Model:
class Tree(models.Model):
    name = models.CharField(max_length=100, null=False)
    age = models.IntegerField(default=0)
    create_time = models.DateTimeField(auto_now_add=True)
    update_time = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = '"test_tree"'
        ordering = ('-update_time',)
  • Tree Model 包含四个字段,django 会自行创建的 id 字段,名称 name, 年龄 age,创建时间 create_time 以及此行数据最后的更新时间 update_time,create_time 中的 auto_now_add 表示此数据会在第一次创建时自动添加为当前时间,不需要人为设置时间,update_time 中的 auto_now 则表示每次更新数据都会更新为当前时间;
  • Meta 中设置数据库表名称为 test_tree,并且默认查询排序以 update_time 降序(负号表示降序,升序的话只需要去掉字段名前的负号即可);
  • 同时,还需要为其创建一个 Serializer,供后面的 view 调用,在 server/tress 下创建 serializers.py 并添加:
class TreeSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(required=True, max_length=100)
    age = serializers.IntegerField(required=True, validators=[MinValueValidator(0)])
    create_time = serializers.DateTimeField(read_only=True)
    update_time = serializers.DateTimeField(read_only=True)

    def create(self, validated_data):
        return Tree.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.name = validated_data.get('name', instance.name)
        instance.age = validated_data.get('age', instance.age)
        instance.save()
        return instance
  • 定义 create 方法直接将请求内容(post体)中的数据转换为对应的字段,update 方法只更新传过来的 name 和 age。
  • 最后,为 Tree 创建 view,在 server/trees/views.py 中添加增删改查:
logger = logging.getLogger('custom')

class TreeView(RetrieveAPIView):
    queryset = Tree.objects.all()
    serializer_class = TreeSerializer
    permission_classes = (IsAuthenticated,)

class TreeDestroyView(DestroyAPIView):
    queryset = Tree.objects.all()
    serializer_class = TreeSerializer
    permission_classes = (IsAuthenticated,)

class TreeListView(ListAPIView):
    serializer_class = TreeSerializer
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        """
        Optionally restricts the returned trees by filtering against a `name` query parameter in the URL.
        """
        queryset = Tree.objects.all()
        name = self.request.query_params.get('name', None)
        if name is not None:
            queryset = queryset.filter(name=name)
        return queryset

class TreeCreateView(GenericAPIView):
    serializer_class = TreeSerializer
    permission_classes = (IsAuthenticated,)

    def save(self, request):
        try:
            tree = None
            _id = request.data.get('id')
            if _id:
                try:
                    tree = Tree.objects.get(id=request.data.get('id'))
                except Tree.DoesNotExist:
                    pass
            if not tree:
                serializer = self.get_serializer(data=request.data)
                if serializer.is_valid():
                    serializer.save()
                    return Response(serializer.data, status=status.HTTP_200_OK)
            else:
                serializer = self.get_serializer(data=request.data)
                if serializer.is_valid():
                    serializer.update(tree, request.data)
                    return Response(serializer.data, status=status.HTTP_200_OK)
            raise exceptions.ValidationError('tree serializer not valid')
        except Exception as e:
            logger.error('save tree data error')
            logger.exception(e)
            return Response({"message": "unexpected error: %s" % str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    def post(self, request, *args, **kwargs):
        return self.save(request)
  • permission_classes 全部设置为 IsAuthenticated,表示只有登录后才能使用这些 api;
  • TreeListView 中设置一个参数 name,表示可以传递 name 参数来过滤名称包含此信息的内容;
  • TreeCreateView 中的逻辑为当记录存在时更新信息,否则为新建信息;
  • 当然你也可以将所有的操作方法写在一个 view 里面,这里的写法只代表个人风格;
  • 在 server/urls.py 中添加对应的 url配置:
urlpatterns = {
	...
    url(r'^api/tree/(?P<pk>\d+)$', TreeView.as_view(), name='tree_detail'),
    url(r'^api/tree/(?P<pk>\d+)/del$', TreeDestroyView.as_view(), name='tree_delete'),
    url(r'^api/trees/$', TreeListView.as_view(), name='tree_list'),
    url(r'^api/tree/$', TreeCreateView.as_view(), name='tree_save'),
}
  • 由于默认的分页请求时,请求中不能传递当前页面查询个数大小(可以查看类 PageNumberPagination,发现 page_size_query_param = None),需要自定义,所以需要添加一个自定义的分页类,只需要指定 page_size_query_param 即可,在 server 目录下添加文件 page.py,添加如下内容:
class DynamicPagination(PageNumberPagination):
    page_size_query_param = 'size'
  • size 即为分页请求中控制查询个数控制的请求参数;
  • 然后,在 server/settings.py 中,添加权限控制代码:
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'timed_auth_token.authentication.TimedAuthTokenAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
    ),
    'DEFAULT_PAGINATION_CLASS': 'server.page.DynamicPagination'
}
  • DEFAULT_AUTHENTICATION_CLASSES 表示使用基于超时的 TimedAuthTokenAuthentication 来做登录token验证, DEFAULT_PERMISSION_CLASSES 中设置默认请求都需要验证登录信息,DEFAULT_RENDERER_CLASSES 表示请求及返回数据以 json 格式为主,DEFAULT_PAGINATION_CLASS 指定默认分页类;
  • 现在,执行 ‘python manage.py makemigrations trees’,创建 Model 对应的 migration 文件,再执行 ‘python manage.py migrate’,使其更新到数据库中。
  • 启动项目,尝试访问 ‘http://localhost:8000/api/tree/’ ,会发现后台返回了如下内容:
{"detail":"Authentication credentials were not provided."}

4. 搭建前端

  • 现在,后台就全部架设完毕了,然后需要再为之创建一个基于Angular6的前端,关于怎么创建前端项目,此处不会详细描述,这里为了快速构建,使用 Angular 提供的 angular-cli 控件来创建前端项目;
  • 安装 angular-cli,执行 ‘ng new client --style=less’ 创建前端项目,并添加相关组件(登录,主页面,树列表页面等),具体内容请查看项目最终的内容;
  • 访问 ‘localhost:4200’ 登录后即可看到相关内容。

5. 编译项目

  • 由于django对于前后端分离后挂在前端静态页面的处理并不好,这里我选择使用nginx代理的方式来处理前端后端的配置,nginx配置例如:
server {
    listen       8008;
    server_name  localhost;
    
    root /Test/client/dist/client;
    index index.html index.htm;
    
    location / {
        try_files $uri $uri/ /index.html =404;
    }
    
    location ^~ /api {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://127.0.0.1:8000;
    }
}
  • root 为前端编译后的文件夹所在位置,通过 try_files 代理前端静态文件;
  • /api 的反向代理将 /api 开头的请求转发到后端server中。
  • 前端只需要通过 npm run build 编译即可;
  • 后端通过 pyinstaller 编译项目;
  • 后端编译前先安装如下依赖:
pip3 install psycopg2
pip3 install pypiwin32
pip3 install pycrypto
pip3 install pyinstaller
  • pycrypto Windows10可能会无法安装(我就遇到了这种问题),本地在安装了VS2017 Community的前提下,出现无法安装的时候,开始菜单搜索 ‘VS 2017的 x64_x86交叉工具命令提示符’,以管理员身份运行,执行 set CL=-FI"%VCINSTALLDIR%Tools\MSVC\14.14.26428\include\stdint.h", 14.14.26428 为我安装的这个版本的数字,需要检查具体的数字,直接取VS2017安装目录下查看此目录即可,然后再执行 pip3 install pycrypto 就能安装成功了。
  • 依赖全部安装完毕后,执行如下命令:
pyi-makespec -F --name=test --key test manage.py
pyinstaller --clean test.spec
  • -F 表示编译成一个单独的exe文件,–key 表示使用 test 加密exe文件,这里会用到 pycrypto 来做源文件加密,pycrypto会使用 AES 加密程序,能够一定程度的防止程序被破解;
  • 编译成功后,执行 ./dist/test.exe runserver localhost:8000 运行后端;
  • 然后等一会后就会发现控制台出现了如下信息:
ModuleNotFoundError: No module named 'rest_framework.apps'
  • 此处出错的原因是一些隐藏的导入,比如说 DynamicPagination 这个类,由于是在 server/settings.py 中的某个配置项中的字符串中,在动态解析的情况下,编译器可以找到这个类,但是在提前编译好的时候,pyinstaller并不会去解析这些内容,所以会出现很多某块找不到的情况,所以这里需要用一些特殊处理来解决这个问题,经过一段时间的研究后,对于一些单个文件无法找到的情况下,选择使用指定 --hidden-import 参数来解决,对于一些整个某块大部分内容都加载不正常的时候,选择使用 pyinstaller 提供的 hooks 文件的方式来解决,先创建文件夹pyinstaller_hooks,然后依次加入如下几个文件和内容:

  • hook-rest_auth.py

from PyInstaller.utils.hooks import collect_submodules
hiddenimports = collect_submodules('rest_auth')
  • hook-rest_framework.authtoken.py
from PyInstaller.utils.hooks import collect_submodules
hiddenimports = collect_submodules('rest_framework.authtoken')
  • hook-rest_framework.py
from PyInstaller.utils.hooks import collect_submodules
hiddenimports = collect_submodules('rest_framework')
  • hook-timed_auth_token.py
from PyInstaller.utils.hooks import collect_submodules
hiddenimports = collect_submodules('timed_auth_token')
  • 最后执行如下命令编译:
pyi-makespec -F --additional-hooks-dir pyinstaller_hooks --name=test --hidden-import timed_auth_token.authentication --hidden-import server.page --key test manage.py
pyinstaller --clean test.spec
  • 最后运行后台exe ./dist/test.exe runserver localhost:8000,nginx配置好后,访问 localhost:8008即可看到运行不再报错。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值