django项目搭建

django项目

01 虚拟环境

1.1 创建虚拟环境

# 创建虚拟环境 环境位置 环境名称
mkvirtualenv -p python3 dj_pro # dj_pro 虚拟环境名字

虚拟环境操作

workon  #查看运行环境
deactivate  #退出虚拟环境

# 删除虚拟环境
rmvirtualenv hello_django

# 切换环境
workon py3env 

软连接

# 系统中python默认版本为py2.7,可以将其改为py3
# 第一步:先删除python
rm -rf /user/bin/python

# 第二部:粗昂见软连接
ln -s /user/bin/python3 /user/bin/python

# 第三部:查看py版本是否改为py3
python -V

1.2 pycharm创建项目

New Project - 选择Dango模式、选择本地项目存储目录

连接远程虚拟机的虚拟环境中的python

1.3 pycharm连接虚拟机

Tools - Deployment - Configuration

详情看文档

创建SFTP远程连接

02 settings配置

2.1 访问权限配置

ALLOWED_HOSTS = ['*']

#设置项是否开启URL访问地址后面不为/跳转至带有/的路径
APPEND_SLASH=True

2.2 静态模版配置

创建static文件夹,指定为根目录

pycharm鼠标右键static文件夹(Mark Directory - Sources Root)

TEMPLATES = [
    {
     
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        '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',
            ],
            'builtins': ['django.templatetags.static'] # 添加此项
        },
    },
]

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

2.3 时区配置

TIME_ZONE = 'Asia/Shanghai'
LANGUAGE_CODE = 'zh-hans'

2.4 上传文件配置

在项目根目录下粗昂见media文件夹,用于存放用户上传文件

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

在根路由配置

from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
  path('admin/', admin.site.urls),
    #...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

03 mysql配置

3.1 安装mysql

安装mysqlclient,虚拟机

sudo apt-get update # 更新ubuntu包
sudo apt-get install default-libmysql

# 启动
mysql root -u -p

#password: qwe123

3.2 项目配置

方法一:

DATABASES = {
     
    'default': {
     
        'ENGINE': 'django.db.backends.mysql',  # 数据库引擎
        'NAME': 'ucan',  # 使用的数据库
        'USER': 'root',  # 用户
        'PASSWORD': '123456',  # 密码
        'HOST': '127.0.0.1',  # mysql服务器的ip地址
        'PORT': '3306'  # 端口
    }
}

方法二:

主目录下创建utils包(py包),里面创建dbs数据库文件夹

utils/dbs/dbs.cnf

[client]
database = django_blog
user = root
password = 123456
host = 127.0.0.1
port = 3306
default-character-set= utf8

在settings.py里配置mysql

DATABASES = {
     
    'default': {
     
        'ENGINE': 'django.db.backends.mysql',
        # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        'OPTIONS': {
     
            'read_default_file': 'utils/dbs/dbs.cnf'
        }
    }
}

安装插件(方法一二都要)

pip install pymysql 
# djangoStudy/djangoStudy/__init__.py

import pymysql
pymysql.install_as_MySQLdb() # 数据库连接器

3.3 上传排除

在 .gitignore中加入排除项,最后追加


04 redis缓存

4.1 配置redis

用于存放用户session信息、验证码以及图片验证码信息等,有16个库

pip install django-redis

settings.py指定redis配置

CACHES = {
     
    "default": {
     
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/0",
        "OPTIONS": {
     
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

4.2 使用

from django.shortcuts import render,HttpResponse
from django_redis import get_redis_connection

def index(request):
    # 连接池中获取连接
    conn = get_redis_connection("default")
    # Redis Setex 命令为指定的 key 设置值及其过期时间
    conn.setex("key", 300 ,"value")
    # 获取值
    print(conn.get("key"))
    return HttpResponse('...')

4.3 配置session

# redis
CACHES = {
     
    # ...
    
    "session": {
     
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/2", 
        "OPTIONS": {
     
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
}

# 将用户的session保存到redis数据库中
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 指定缓存redis的别名
SESSION_CACHE_ALIAS = 'session'

05 配置日志器

用于记录系统运行过程中的各种日志信息

跟项目创建logs文件夹,用于存放日志文件

# 配置日志器,记录网站的日志信息
LOGGING = {
     
    # 版本
    'version': 1,
    # 是否禁用已存在的日志器
    'disable_existing_loggers': False,
    'formatters': {
     
        'verbose': {
     
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
     
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {
     
        'require_debug_true': {
     
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
     
        'console': {
     
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
     
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(BASE_DIR, "logs/dj_youkou.log"),  # 日志文件的位置
            'maxBytes': 300 * 1024 * 1024,
            'backupCount': 10,
            'formatter': 'verbose'
        },
    },
    'loggers': {
     
        'django': {
       # 定义了一个名为django的日志器
            'handlers': ['console', 'file'],
            'propagate': True,
            'level': 'INFO',  # 日志器接收的最低日志级别
        },
    }
}

06 应用

6.1 应用集

创建apps文件夹并指定为根目录,里面创建app,

python ../manage.py startapp user

6.2 注册

创建应用之后,把apps目录加入到sys.path中

# settings.py
import sys
sys.path.insert(0,BASE_DIR)
sys.path.insert(1, os.path.join(BASE_DIR, 'apps'))

注册app

# settings.py
INSTALLED_APPS = [
    # 'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'news', # 注册
    'user', # 注册
]

6.3 模型基类定义

创建utils/modules.py,为公共基类模型

# -*- coding: utf-8 -*-
"""
----------------------------------------------------
    @Time    : 2019/12/22 20:42
    @Author  : zzw
    @File    : models.py
----------------------------------------------------
"""

from django.db import models

class ModelBase(models.Model):
    """
    """
    create_time = models.DateTimeField(
        auto_now_add=True,
        verbose_name="创建时间"
    )
    update_time = models.DateTimeField(
        auto_now=True,
        verbose_name="更新时间"
    )
    is_delete = models.BooleanField(
        default=False,
        verbose_name="逻辑删除"
    )

    class Meta:
        # 定义前模版为抽象模型类,用于其他模版继承,数据库迁移不会创建ModelBase表
        abstract = True

导入基类

from django.db import models
from utils.models import ModelBase


class Articles(ModelBase):
    title = models.CharField(max_length=150, verbose_name="标题", help_text="标题")
    digest = models.CharField(max_length=200, verbose_name="摘要", help_text="摘要")
    content = models.TextField(verbose_name="内容", help_text="内容")
    clicks = models.IntegerField(default=0, verbose_name="点击量", help_text="点击量")
    image_url = models.URLField(default="", verbose_name="图片url", help_text="图片url")
    tag = models.ForeignKey('Tags', on_delete=models.SET_NULL, null=True)
    author = models.ForeignKey('users.Users', on_delete=models.SET_NULL, null=True)

    class Meta:
        ordering = ['-update_time','-id'] # 排序
        db_table = "tb_article" # 表名
        verbose_name = "文章"
        verbose_name_plural = verbose_name # 显示的复数名称

07 自定义用户模型

在原来django的user模型新增字段

from django.db import models
from django.contrib.auth.models import AbstractUser, UserManager as _UserManager

# 创建超级管理人默认为空修改
class UserManager(_UserManager):
    """

    """

    # email=None
    def create_superuser(self, username, password, email=None, **extra_fields):
        super(UserManager, self).create_superuser(
            username=username,
            password=password,
            email=email,
            **extra_fields
        )
        
        

# 用户表
class Users(AbstractUser):
    """
        添加mobile、email_active to user modules
    """
	
    # 添加手机号填项检测
    REQUIRED_FIELDS = ['mobile'] 
    object = UserManager() # 获取修改后的UserManager

    mobile = models.CharField(
        max_length=11,
        unique=True,
        help_text="手机号",
        verbose_name="手机号",
        error_messages={
     
            "unique": "此手机号已经注册"
        }
    )
    email_active = models.BooleanField(default=False, verbose_name="邮箱验证码")

    class Meta:
        db_table = 'tb_user'  # 数据表名称
        verbose_name = "用户"  # 视图
        verbose_name_plural = verbose_name  # 复数形式

    def __str__(self):
        return self.username

settings.py配置验证用户模型

# 验证用户模型
AUTH_USER_MODEL = 'user.User'

创建用户

python manage.py createuser # 创建用户
python manage.py createsuperuser # 创建超级用户

08 图片验证码

8.2 验证码模块

pip install Pillow

创建验证码app-verifications,拉人apps文件夹

导入验证码模块

tools-captcha-captcha.py

# captcha.py

from PIL import Image, ImageDraw, ImageFont, ImageFilter
import random


# 随机码 默认长度=1
def random_code(lenght=1):
    code = ''
    for char in range(lenght):
        code += chr(random.randint(65, 90))
    return code


# 随机颜色 默认颜色范围【1,255】
def random_color(s=1, e=255):
    return (random.randint(s, e), random.randint(s, e), random.randint(s, e))


# 生成验证码图片
# length 验证码长度
# width 图片宽度
# height 图片高度
# 返回验证码和图片
def veri_code(lenght=4, width=160, height=40):
    # 创建Image对象
    image = Image.new('RGB', (width, height), (255, 255, 255))
    # 创建Font对象
    font = ImageFont.truetype('Arial.ttf', 32)
    # 创建Draw对象
    draw = ImageDraw.Draw(image)
    # 随机颜色填充每个像素
    for x in range(width):
        for y in range(height):
            draw.point((x, y), fill=random_color(64, 255))
    # 验证码
    code = random_code(lenght)
    # 随机颜色验证码写到图片上
    for t in range(lenght):
        draw.text((40 * t + 5, 5), code[t], font=font, fill=random_color(32, 127))
    # 模糊滤镜
    image = image.filter(ImageFilter.BLUR)
    return code, image

8.2 配置redis库

在settings.py文件中配置verify库

CACHES = {
     
    "default": {
     
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/0",
        "OPTIONS": {
     
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    "verify": {
     
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1", # 1库
        "OPTIONS": {
     
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
}

8.4 配置路由

uuid是128位的全局唯一标识符,通常由32字节的字符串表示,保证时间和 空间的唯一性

from django.urls import path
from . import views

urlpatterns = [
    path('image_code//', views.ImageCode),
]

8.3 全局参数

验证码应用下创建constants.py文件,存放全局参数

# 图片验证码redis有效期,单位秒
IMAGE_CODE_EXPIRES = 5 * 60

8.4 功能实现

from django.shortcuts import render
from django.http import HttpResponse
from django.views import View
from utils.captcha.captcha import captcha
from django_redis import get_redis_connection
from . import constants


class ImageCode(View):
    """
    /image_code//
    """

    def get(self, request, image_code_id):
        text, image = captcha.generate_captcha()
        # print(text)
        # 建立redis,将图片验证码保存到redis
        con_redis = get_redis_connection(alias='verify')
        img_key = "img_{}".format(image_code_id)
        con_redis.setex(img_key, constants.IMAGE_CODE_EXPIRES, text)
        
        # 指定图片格式,并把图片返回前端
        return HttpResponse(content=image,content_type='images/jpg')

8.7 导入日志器

import logging
# 导入日志器
logger = logging.getLogger('django')

class ImageCode(View):
    """
    /image_code//
    """

    def get(self, request, image_code_id):
        text, image = captcha.generate_captcha()
        # print(text)
        # 建立redis,将图片验证码保存到redis
        con_redis = get_redis_connection(alias='verify')
        img_key = "img_{}".format(image_code_id)
        con_redis.setex(img_key, constants.IMAGE_CODE_EXPIRES, text)

        logger.info('img_code:{}'.format(text))  # 把验证码记录在logger日志中

        # 指定图片格式,并把图片返回前端
        return HttpResponse(content=image, content_type='images/jpg')

8.6 前端调用

js生成uuid请求接口

09 判断用户是否注册功能

注册路由

from django.urls import path, re_path
from . import views

urlpatterns = [
    path('image_code//', views.ImageCode.as_view(), name='image_code'),
    re_path('username/(?P\w{5,20})/', views.CheckUsernameView.as_view(), name='check_username'),
]

添加视图

from users.models import Users

class CheckUsernameView(View):
    """
    检测用户是否存在
    """

    def get(self, request, username):
        count = Users.objects.filter(username=username).count()
        data = {
     
            username: username,
            count: True if count else False
        }

        return HttpResponse(data)

10 短信验证码

10.1 全局参数

# 图片验证码redis有效期,单位秒
IMAGE_CODE_EXPIRES = 5 * 60

# 短信验证码有效期,单位分钟
SMS_CODEREDIS_EXPIRES = 5

# 发送间隔
SEND_SMS_CODE_INTERVAL = 60

# 短信发送模版
SMS_CODE_TEMP_ID = 1

# 短信验证码位数
SMS_CODE_NUMS = 6

10.2 60秒短信验证

from django.core.validators import RegexValidator
from django_redis import get_redis_connection

from django import forms
from users.models import Users

# 手机号验证,即错误返回
mobile_validator = RegexValidator(r'^1[3-9]\d{9}$', '手机格式不正确')


class CheckImgCodeForm(forms.Form):
    mobile = forms.CharField(
        max_length=11,
        min_length=11,
        validators=[mobile_validator],
        error_messages={
     
            'min_length': '手机号长度有误',
            'max_length': '手机号长度有误',
            'required': '手机号不能为空'
        }
    )
    image_code_id = forms.UUIDField(
        error_messages={
     
            'required': '图片uuid不能为空'
        }
    )
    text = forms.CharField(
        max_length=4,
        min_length=4,
        error_messages={
     
            'min_length': '手机号长度有误',
            'max_length': '手机号长度有误',
            'required': '手机号不能为空'
        }
    )

    # clean_xxx 固定形式,检测某个字段
    # 判断手机号是否注册
    def clean_mobile(self):
        mobile = self.cleaned_data.get('mobile')
        count = Users.objects.filter(mobile=mobile).count()

        if count:
            # 抛出异常
            raise forms.ValidationError('手机号已注册')

        return mobile

    # 联合判断
    # 判断是否重复获取验证码
    def clean(self):
        cleaned_data = super().clean()
        mobile = cleaned_data.get('mobile')
        image_uuid = cleaned_data.get('image_code_id')
        image_text = cleaned_data.get('text').upper()

        # 获取redis存储的图片验证码
        con_redis = get_redis_connection(alias='verify_code')
        img_key = "img_{}".format(image_uuid)
        real_image_code = con_redis.get(img_key)

        # 数据不为空转码
        if not real_image_code:
            real_image_code = ''
        else:
            # 解码转义
            real_image_code = real_image_code.decode()

        # 判断验证码正确性
        if (not real_image_code) or(image_text.upper() != real_image_code.upper()):
            raise forms.ValidationError("图片验证码验证失败")

        # 判断60秒内是否有发送短信记录
        sms_flag_fmt = "sms_flag_{}".format(mobile).encode('utf8')
        sms_flag = con_redis.get(sms_flag_fmt)

        # 验证码已经存在不允许再次获取
        if sms_flag:
            raise forms.ValidationError("获取短信验证码过于频繁")

10.3 短信方法

import requests

# APIID
account = 'C40665997'

# APIKEY
password = 'ebe6a6b70cd80a0bd3151f2170b2c7e9'

url = 'https://106.ihuyi.com/webservice/sms.php?method=Submit'

"""
    短信发送
"""


def send_sms(mobile, content):
    headers = {
     
        "Content-type": "application/x-www-form-urlencoded",
        "Accept": "text/plain"
    }
    data = {
     
        "account": account,
        "password": password,
        "mobile": mobile,
        "content": content,
        "format": "json"
    }
    resp = requests.post(
        url=url,
        headers=headers,
        data=data
    )
    return resp.content.decode()


if __name__ == '__main__':
    mobile = '13059224121'
    content = '您的验证码是:222222。请不要把验证码泄露给其他人。'
    result = send_sms(mobile, content)
    print(result)

10.4 发送手机验证码

import json
import random
import string

from . import forms

class SmsCodesView(View):
    """
    发送手机验证码
    POST /sms_codes/
    """

    def post(self, request):
        json_data = request.body
        # 参数为空判断
        if not json_data:
            return result_json(msg="参数为空")
        dict_data = json.loads(json_data)

        # 检测验证码是否重复
        form = forms.CheckImgCodeForm(data=dict_data)
        # 判断验证结果
        if form.is_valid():
            # 获取手机号
            mobile = form.cleaned_data.get('mobile')
            # 创建6位验证码
            sms_num = ''.join([random.choice(string.digits) for _ in range(6)])
            # 保存短信验证码
            redis_con = get_redis_connection(alias='verify_code')
            sms_flag_fmt = "sms_flag_{}".format(mobile)
            sms_text_fmt = "sms_{}".format(mobile)

            # 短信验证码发送60秒
            # redis_con.setex(sms_flag_fmt, constants.SEND_SMS_CODE_INTERVAL, 1)
            # 短信验证码有效5分钟
            # redis_con.setex(sms_text_fmt, constants.SMS_CODE_REDIS_EXPIRES, sms_num)

            # 通过管道调用
            p1 = redis_con.pipeline()
            try:
                p1.setex(sms_flag_fmt, constants.SEND_SMS_CODE_INTERVAL, 1)
                p1.setex(sms_text_fmt, constants.SMS_CODE_REDIS_EXPIRES, sms_num)
                p1.execute()
            except Exception as e:
                err = "redis 执行出现异常:{}".format(e)
                logging.error(err)
                return result_json(msg=err)
            else:
                # 模拟发送短信
                logger.info('发送短信成功[mobile:%s,sms_code:%s]' % (mobile, sms_num))
                return result_json(msg='发送短信码成功', code=0)

                # content = "您的验证码是:{}。请不要把验证码泄露给其他人。".format(sms_num)
                # try:
                #     result = send_sms(mobile, content)
                # except Exception as e:
                #     err = "发送短信验证码异常[mobile:%s,message:%s]".format(mobile,e)
                #     logging.error(err)
                #     return result_json(msg=err)
                # else:
                #     if result.code == 2:
                #         logger.info('发送短信成功[mobile:%s,sms_code:%s]' % (mobile, sms_num))
                #         return result_json(msg='发送短信验证码成功', code=0)
                #     else:
                #         logging.error('发送短信失败[mobile:%s,sms_code:%s]' % (mobile, result.msg))
                #         return result_json(msg='发送短信验证码失败')

        else:
            # 获取错误信息
            error_list = []  # 把具体的form表单验证错误信息 序列化成字符串 复制给err_msg
            for item in form.errors.get_json_data().values():
                error_list.append(item[0].get('message'))
            err_str = '/'.join(error_list)
            return result_json(msg=err_str)

11 docker

11.1 安装

看官方文档 :https://docs.docker.com/install/linux/docker-ce/ubuntu/

镜像:https://lug.ustc.edu.cn/wiki/mirrors/help/docker

11.2 elasticsearch搜索引擎

a.获取镜像

# 拉取镜像到本地仓库
docker image pull delron/elasticsearch-ik:2.4.6-1.0
# 查看本地仓库是否有这个镜像
docker images
或docker image ls

b.将百度云盘中的elasticsearch.zip文件传到虚拟机中的家目录,然后unzip解压。在虚拟机中的elasticsearch/config/elasticsearch.yml第54行,更改ip地址为0.0.0.0,端口改为8002,默认端口为9200

# 在xshell中使用rz命令将elasticsearch.zip文件传到虚拟机的家目录中
#然后在家目录中解压
unzip elasticsearch.zip
cd ~/elasticsearch/config
# network.host: 172.18.168.123
network.host: 0.0.0.0
#
# Set a custom port for HTTP:
#
http.port: 8002

c.创建docker容器并运行

# pwd获取当前的路径替换
# 根据拉取到本地的镜像创建容器,需要将/home/pyvip/elasticsearch/config配置文件所在目录修改为你自己的路径
docker run -dti --network=host --name=elasticsearch -v /home/pyvip/elasticsearch/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0

# 查看是否创建成功
docker container ls -a 
# 如果STATUS为Up则创建容器成功
CONTAINER ID        IMAGE                               COMMAND                  CREATED             STATUS              PORTS               NAMES
b254fe1ee0eb        delron/elasticsearch-ik:2.4.6-1.0   "/docker-entrypoint.…"   3 days ago          Up 3 days                               elasticsearch

# 启动容器
sudo docker start 容器id

# 运行如下命令,如果有显示则elasticsearch配置成功
curl 127.0.0.1:8002

d.进入项目虚拟环境中,安装相关包

# 进入项目虚拟环境
workon myblog_env

pip install django-haystack
pip install elasticsearch==2.4.1

e.在settings.py文件中加入如下配置:

INSTALLED_APPS = [
    'haystack',
]

ELASTICSEARCH_DSL = {
     
    'default': {
     
        'hosts': '127.0.0.1:8002'
    },
}

# Haystack
HAYSTACK_CONNECTIONS = {
     
    'default': {
     
        'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
        'URL': 'http://127.0.0.1:8002/',  # 此处为elasticsearch运行的服务器ip地址,端口号默认为9200
        'INDEX_NAME': 'dj_pre_class',  # 指定elasticsearch建立的索引库的名称
    },
}

# 设置每页显示的数据量
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
# 当数据库改变时,会自动更新索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

11.3 搜索配置

在文章app下创建search_indexes.py

# -*- coding: utf-8 -*-
"""
----------------------------------------------------
    @Time    : 2019/12/29 9:47
    @Author  : zzw
    @File    : search_indexes.py
----------------------------------------------------
"""
from  haystack import indexes

from .models import Articles

# 修改此处,类名为模型类的名称+Index,比如模型类为GoodsInfo,则这里类名为GoodsInfoIndex
class ArticlesIndex(indexes.SearchIndex,indexes.Indexable):
    """
    文章搜索引擎
    """
    # text是固定格式,其他与文章类以一一对应
    text = indexes.CharField(document=True,use_template=True)
    # 返回的数据
    id = indexes.IntegerField(model_attr='id')
    title = indexes.CharField(model_attr='title')
    digest = indexes.CharField(model_attr='digest')
    content = indexes.CharField(model_attr='content')
    image_url = indexes.CharField(model_attr='image_url')
    clicks = indexes.IntegerField(model_attr='clicks')

    def get_model(self):
        """
        返回简历索引的模型类
        """
        return Articles

    def index_queryset(self, using=None):
        """
        返回简历索引的数据查询集
        """
        return self.get_model().objects.filter(is_delete=False,tag_id__in=[1,2,3,4,5,6])

在创建文件template/search/indexes/articles/articles_text.txt

# articles 文章类
# articles_text.txt  固定格式 xxx_text.txt

# 指定搜索内容
{
     {
      object.title }}
{
     {
      object.digest }}
{
     {
      object.content }}

模版类修改

from haystack.views import SearchView as _SearchView
class SearchView(_SearchView):
    def create_response(self):
        keyword = self.request.GET.get('q','')
        if not keyword:
            show_all = True
            try:
                tag_id = int(self.request.GET.get('tag_id', 0))
            except Exception as e:
                logger.error("文章标签错误:\n{}".format(e))
                tag_id = 0

            try:
                page = int(self.request.GET.get('page', 1))
            except Exception as e:
                logger.error("当前页数错误:\n{}".format(e))
                page = 1

            try:
                pagesize = int(self.request.GET.get('pagesize', constants.PER_PAGE_ARTICLES_COUNT))
            except Exception as e:
                logger.error("当前页码数错误:\n{}".format(e))
                pagesize = constants.PER_PAGE_ARTICLES_COUNT

                # 获取文章
                # tag__xxx 获取外键表的字段
            articles_queryset = Articles.objects.select_related('tag', 'author') \
                .only('id', 'title', 'digest', 'image_url', 'create_time', 'update_time', 'tag__name',
                      'author__username')

            # 根据tag_id=0搜索文章不存在时,搜索全部
            articles = articles_queryset.filter(is_delete=False, tag_id=tag_id) or \
                       articles_queryset.filter(is_delete=False)

            # 分页
            paginator = Paginator(articles, pagesize)
            try:
                articles_info = paginator.page(page)
            except EmptyPage:
                # 用户访问页数大于实际页面,取最后一页
                logging.info("用户访问页数大于总页数")
                articles_info = paginator.page(paginator.num_pages)

            # 序列化输出
            articles_info_list = []
            for item in articles_info:
                articles_info_list.append({
     
                    'article_id': item.id,
                    'title': item.title,
                    'digest': item.digest,
                    'image_url': item.image_url,
                    'tag_name': item.tag.name,
                    'author': item.author.username,
                    # 时间格式化
                    'update_time': item.update_time.strftime('%Y-%m-%d %H:%M'),
                    'create_time': item.create_time.strftime('%Y-%m-%d %H:%M')
                })

            return result_json(
                code=0,
                data=articles_info_list,
                total_page=paginator.num_pages,
                page=page,
                count=paginator.count
            )
        else:
            show_all = False
            qs = super(SearchView,self).create_response()
            return qs

添加到路由

from django.urls import path, re_path
from . import views

urlpatterns = [
    path('search_article/', views.SearchView(), name='search_article'),
]

切换到项目虚拟环境

服务器上切换项目文件夹,创建索引表

python manage.py rebuild_index -k 1 # 创建索引表
python manage.py update_index -k 1 # 更新索引表

重构引擎方法

from haystack.views import SearchView as _SearchView

class SearchView(_SearchView):
    def create_response(self):
        kw = self.request.GET.get('q', '')

        try:
            page = int(self.request.GET.get('page', 1))
        except Exception as e:
            logger.error("当前页数错误:\n{}".format(e))
            page = 1

        try:
            pagesize = int(self.request.GET.get('pagesize', constants.PER_PAGE_ARTICLES_COUNT))
        except Exception as e:
            logger.error("当前页码数错误:\n{}".format(e))
            pagesize = constants.PER_PAGE_ARTICLES_COUNT

        if not kw:
            articles_queryset = Articles.objects.select_related('tag', 'author') \
                .only('id', 'title', 'digest', 'image_url', 'create_time', 'update_time', 'tag__name',
                      'author__username')

            articles = articles_queryset.filter(is_delete=False)

            # 分页
            paginator = Paginator(articles, pagesize)
            try:
                articles_info = paginator.page(page)
            except EmptyPage:
                # 用户访问页数大于实际页面,取最后一页
                logging.info("用户访问页数大于总页数")
                articles_info = paginator.page(paginator.num_pages)

            # 序列化输出
            articles_info_list = []
            for item in articles_info:
                articles_info_list.append({
     
                    'article_id': item.id,
                    'title': item.title,
                    'digest': item.digest,
                    'image_url': item.image_url,
                    'tag_name': item.tag.name,
                    'author': item.author.username,
                    # 时间格式化
                    'update_time': item.update_time.strftime('%Y-%m-%d %H:%M'),
                    'create_time': item.create_time.strftime('%Y-%m-%d %H:%M')
                })

            return result_json(
                code=0,
                data=articles_info_list,
                total_page=paginator.num_pages,
                page=page,
                count=paginator.count
            )
        else:
            context = super().get_context()

            # 分页
            paginator = Paginator(context['page'], pagesize)
            try:
                articles_info = paginator.page(page)
            except EmptyPage:
                # 用户访问页数大于实际页面,取最后一页
                logging.info("用户访问页数大于总页数")
                articles_info = paginator.page(paginator.num_pages)

            articles_info_list = []
            for item in articles_info:
                articles_info_list.append({
     
                    'article_id': item.id,
                    'title': item.title,
                    'digest': item.digest,
                    'image_url': item.image_url,
                    'tag_name': item.object.tag.name,
                    'author': item.object.author.username,
                    'clicks': item.clicks,
                    # 时间格式化
                    'update_time': item.update_time.strftime('%Y-%m-%d %H:%M'),
                    'create_time': item.create_time.strftime('%Y-%m-%d %H:%M')
                })

            return result_json(
                code=0,
                data=articles_info_list,
                total_page=paginator.num_pages,
                page=page,
                count=paginator.count
            )

12 文档下载

12.1 站点域名信息添加

setting.py

# 站点域名信息
SITE_DOMAIN_PORT = 'http://127.0.0.1:8000'

导入

from django.conf import settings

12.2 文件响应

from django.http import FileResponse
from django.utils.encoding import escape_uri_path

class DocDownload(View):
    """
    下载文档
    GET doc_download/
    """

    def get(self, request, doc_id):
        # 找到当前id的doc文档
        doc = Doc.objects.only('file_url').filter(is_delete=False, id=doc_id)

        if doc:
            # 拼接路径名称
            doc_url = settings.SITE_DOMAIN_PORT + doc.first().file_url

            # 请求下载的文档
            try:
                # stream=True 分流请求下载
                res = FileResponse(
                    requests.get(doc_url,stream=True)
                )
                # 下载方式二:open
            except Exception as e:
                err_txt = "文档下载失败:{}".format(e)
                logging.info(err_txt)
                return result_json(msg=err_txt)

            # 获取文档后缀名,根据后缀名返回相应的请求
            ex_name = doc_url.split('.')[-1]  # 后缀名从文件路由切割

            if not ex_name:
                err_txt = "文档类型错误"
                logger.info(err_txt)
                return result_json(msg=err_txt)
            else:
                # 后缀名一律转为小写
                ex_name = ex_name.lower()

            if ex_name == "pdf":
                res["Content-type"] = "application/pdf"
            elif ex_name == "zip":
                res["Content-type"] = "application/zip"
            elif ex_name == "doc":
                res["Content-type"] = "application/msword"
            elif ex_name == "xls":
                res["Content-type"] = "application/vnd.ms-excel"
            elif ex_name == "docx":
                res["Content-type"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
            elif ex_name == "ppt":
                res["Content-type"] = "application/vnd.ms-powerpoint"
            elif ex_name == "pptx":
                res["Content-type"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
            elif ex_name == 'txt':
                res["Content-type"] = "text/plain"
            else:
                raise result_json(msg="文档格式不正确!")

            # 中文文件名编译
            doc_filename = escape_uri_path(doc_url.split('/')[-1])

            # 返回给浏览器相应的参数,编译中文会在浏览者转回中文
            res["Content-Disposition"] = "attachment; filename*=UTF-8''{}".format(doc_filename)

            # 设置为inline,会直接打开
            # res["Content-Disposition"] = "设置为inline; filename*=UTF-8''{}".format(doc_filename)

            return res

        else:
            raise result_json(msg="文档不存在")

13 FastDFS分布式文件系统

13.1 文件上传流程

  1. 定时向tracker上传状态信息
  2. 上传连接请求
  3. 查询可以用的storage
  4. 返回信息(storage的ip和端口)
  5. 上传文件(file content 和 metadata)
  6. 生成文件file_id
  7. 将上传内容写入磁盘
  8. 返回file_id(路径信息 和 文件名)
  9. 存储文件信息

13.2 安装FastDFS

# 安装tracker
docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs youkou1/fastdfs tracker

# 安装storage
# 修改172.18.168.123 用ip a获取
# 不能写127.0.0.1
docker run -dti --network=host --name storage -e TRACKER_SERVER=172.18.168.123:22122 -v /var/fdfs/storage:/var/fdfs youkou1/fastdfs storage

13.3 测试是否安装成功

创建utils/fastdfs/logs日志文件夹,用于存放日志信息

创建utils/fastdfs/client.conf配置文件

修改path to store路径 : base_path=utils/fastdfs/logs

ip a获取的地址,端口不改

修改ip address路径 : tracker_server=10.0.2.15:22122

# 创建utils/fastdfs/client.conf配置文件

# connect timeout in seconds
# default value is 30s
connect_timeout=30

# network timeout in seconds
# default value is 30s
network_timeout=60

# the base path to store log files
# 修改相对路径对这log文件夹右键copy relative Path !!!
base_path=/Users/ninyoukou/PycharmProjects/dj_pre_class/utils/fastdfs/logs

# tracker_server can ocur more than once, and tracker_server format is
#  "host:port", host can be hostname or ip address
tracker_server=172.18.168.123:22122

#standard log level as syslog, case insensitive, value list:
### emerg for emergency
### alert
### crit for critical
### error
### warn for warning
### notice
### info
### debug
log_level=info

# if use connection pool
# default value is false
use_connection_pool = false

# connections whose the idle time exceeds this time will be closed
# unit: second
# default value is 3600
connection_pool_max_idle_time = 3600

# if load FastDFS parameters from tracker server
# default value is false
load_fdfs_parameters_from_tracker=false

# if use storage ID instead of IP address
# same as tracker.conf
# valid only when load_fdfs_parameters_from_tracker is false
# default value is false
use_storage_id = false

# specify storage ids filename, can use relative or absolute path
# same as tracker.conf
# valid only when load_fdfs_parameters_from_tracker is false
storage_ids_filename = storage_ids.conf


#HTTP settings
http.tracker_server_port=80

进入虚拟环境,把包fdfs_client.zip进去

安装相关包

# 安装相关包
# fdfs_client.zip文件从百度云中下载
pip install fdfs_client.zip
pip install mutagen
pip install requests

测试是否成功

服务器项目所在地 python manage.py shell

from fdfs_client.client import Fdfs_client

# 指定fdfs客户端配置文件所在路径
FDFS_Client = Fdfs_client('utils/fastdfs/client.conf')

 ret = FDFS_Client.upload_by_filename('media/youkou.jpg')
 
 ret # 获取打印数据
 {
     'Group name': 'group1', 'Remote file_id': 'group1/M00/00/00/CgACD14R7N-Af7y0AAfh_rrm7jw568.png', 'Status': 'Upload successed.', 'Local file name': 'media/2018.png', 'Uploaded size': '504.00KB', 'Storage IP': '10.0.2.15'}

# 访问图片
http://127.0.0.1:8888/group1/M00/00/00/CgACD14R7N-Af7y0AAfh_rrm7jw568.png

拉下docker

docker pull youkou1/fastdfs

你可能感兴趣的:(python)