6.1

一、需求及注意事项

需求:
1.列出图书列表、出版社列表、作者列表
2.点击作者,会列出其出版的图书列表
3.点击出版社,会列出其下的图书列表
4.可以创建、修改、删除 图书、作者、出版社

完成及注意事项:
1.注册
/register/
2.登录
/login/
3.注销
/logout/
4.图书列表
/book_list/
/add_book/
/update_book/303/
/del_book/
5.出版社列表
/publisher_list/
/add_publisher/
/update_publisher/105/
/update_book/306/105/publisher/ # 通过出版社,修改该书,之后返回出版社页面
/del_publisher/
/book_list/105/publisher/ # 通过出版社,查看该出版社得图书列表
/add_book/105/publisher/ # 通过出版社,增加该出版社得某本书
/del_book/105/publisher/ # 通过出版社,删除该出版社得某本书
6.作者列表
/author_list/
/add_author/
/update_author/67/
/update_book/307/67/author/ # 通过作者,修改该书,之后返回作者页面
/del_author/
/book_list/67/author/ # 通过作者,查看该作者得图书列表
/add_book/67/author/ # 通过作者,增加该作者得某本书
/del_book/67/author/ # 通过作者,删除该作者得某本书

7.验证是否登录(session),跳转到之前访问得页面:
采用中间件 mymiddleware.py
def process_request(self,request):pass
白名单 黑名单

8.验证是否登录,装饰器!
def check_login(func):pass
最后采用中间件,
否则每一个函数都有要加装饰器。(@check_login)

9.批量插入测试数据
脚本 myscript.py
models.Book.objects.bulk_create(ret1)

10.自定义分页器
mypage.py
from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger
封装
上一页,下一页,首页,尾页

11.模板继承
base.html
{% extends 'base.html' %}
{{ block.super }} 可以引用基类模板的内容

12.静态文件
jquery-3.2.1.js
bootstrap-3.3.7

ajax发送post请求时
init_ajax.js
取 csrf token 的值发送

删除,页面得模态框 插件
sweetalert

13.settings配置文件
中间件
mysql

终端打印sql语句

logging
登录注册,会有日志记录

14.自定义过滤器和标签
templatetags/my_tag_filter.py
{% load my_tag_filter %}
{{ path|update_path:book.id}}

15.urls
分发,名称空间,有名分组,反向解析
re_path(r'^update_book/(?P<id>d+)',views.update_book),
正则反向解析,需要赋值
return redirect(reverse('book:book_publisher',args=(type_id,)))

16.FBV CBV
FBV(function base views) 视图里使用函数处理请求
CBV(class base views) 视图里使用类处理请求

path('login/',views.LoginView.as_view(), name = 'login')
class LoginView(View):pass
给CBV加装饰器:
@method_decorator(check_login)
def dispatch(self, request, *args, **kwargs):pass

17.表单forms组件
myforms.py

为forms组件赋初值
https://docs.djangoproject.com/en/2.0/ref/forms/api/
publisher_form.initial = {'name':publisher.name}

表单控件
给每一个key添加样式
def __init__(self,*args,**kwargs):pass
select下拉框:新增加的出版社,想不重启,立刻就显示:
def __init__(self,*args,**kwargs):pass
新增加书时,选择新增加的出版社
不能立刻通过校验is_valid 需要等一会,就通过了

18.DateTimeField
    注意点:
      TIME_ZONE = 'Asia/Shanghai'
      USE_TZ = False

  https://blog.csdn.net/win_turn/article/details/53000770

    auto_now

auto_now_add
timezone

http://www.nanerbang.com/article/5488/
django:DateTimeField如何自动设置为当前时间并且能被修改:

DateTimeField.auto_now 如果为true 无法赋值,每次更新为最新时间;
DateTimeField.auto_now_add 如果为true 无法赋值,第一次创建时间

from django.db import models
import django.utils.timezone as timezone
class Doc(models.Model):
add_date = models.DateTimeField('保存日期',default = timezone.now)
mod_date = models.DateTimeField('最后修改日期', auto_now = True)

19.ORM表关系
一对一,(author authordetail)
删除author时,应该删除authordetail,关联的author就被删除了!

一对多,(book publisher)
删除出版社下面的某本书,拿到书的id,删除这本书;

多对多,(book author)
清除绑定关系,不应该删除书;

启动:
配置sql
python manage.py makemigrations
python manage.py migrate


详情:
需查看代码!!

二、数据库

三、页面效果

四、主要代码

urls.py

from django.urls import path,re_path

from book import views

urlpatterns = [
    # path('login/',views.login, name = 'login'),                 # FBV
    path('login/',views.LoginView.as_view(), name = 'login'),     # CBV
    # path('register/',views.register, name = 'register'),
    path('register/',views.RegisterView.as_view(), name = 'register'),
    path('exist_user/',views.exist_user),
    path('logout/',views.logout),

    path('book_list/',views.book_list, name = 'book_list'),
    # re_path(r'^book_list/(d+)/(publisher)',views.book_list),
    re_path(r'^book_list/(?P<field_id>d+)/(?P<field_type>publisher)',views.book_list),  # 有名分组
    # re_path(r'^book_list/(d+)/(author)',views.book_list),
    re_path(r'^book_list/(?P<field_id>d+)/(?P<field_type>author)',views.book_list),     # 有名分组

    path('del_book/',views.del_book, name = 'del_book'),
    re_path(r'^del_book/(d+)/(publisher)',views.del_book),
    re_path(r'^del_book/(d+)/(author)',views.del_book),

    path('add_book/',views.add_book,name='add_book'),
    re_path(r'^add_book/(d+)/(publisher)',views.add_book),
    re_path(r'^add_book/(d+)/(author)',views.add_book),

    re_path(r'^update_book/(?P<book_id>d+)/(?P<field_id>d+)/(?P<field_type>publisher)',views.update_book),
    re_path(r'^update_book/(?P<book_id>d+)/(?P<field_id>d+)/(?P<field_type>author)',views.update_book),
    re_path(r'^update_book/(?P<book_id>d+)',views.update_book),
    # 这个得放到下面 否则会截走 上面两个 因为第一个也是正则

    path('publisher_list/',views.publisher_list, name = 'publisher_list'),
    path('del_publisher/',views.del_publisher, name = 'del_publisher'),
    path('add_publisher/',views.add_publisher, name = 'add_publisher'),
    re_path(r'^update_publisher/(d+)',views.update_publisher),

    path('author_list/',views.author_list, name = 'author_list'),
    path('del_author/',views.del_author, name = 'del_author'),
    path('add_author/',views.add_author, name = 'add_author'),
    re_path(r'^update_author/(d+)',views.update_author),

]

models.py

from django.db import models
from django.utils import timezone


# 出版社类
class Publisher(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)

    def __str__(self):
        return self.name


# 书类
class Book(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8,decimal_places=2)
    publish_date = models.DateTimeField(default=timezone.now())
    # 书只能关联一个出版社, 外键通常建在多的那一边
    publisher = models.ForeignKey(to='Publisher',on_delete=models.CASCADE)

    def __str__(self):
        return self.title


# 作者类
class Author(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=16)
    # 多对多, 建在哪边都可以
    books = models.ManyToManyField(to='Book',related_name='authors')

    detail = models.OneToOneField(to='AuthorDetail',null=True,on_delete=models.CASCADE)

    def __str__(self):
        return self.name


# 作者详情
class AuthorDetail(models.Model):
    age = models.IntegerField()
    addr = models.TextField()


# 登录注册的用户信息
class UserInfo(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=16)
    pwd = models.CharField(max_length=32)

myforms.py

# -*- coding:utf-8 -*-
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError

from book import models

widget_input = widgets.TextInput(attrs={'class':'form-control'})
widg_num = widgets.NumberInput(attrs={'class':'form-control'})
widget_password = widgets.PasswordInput(attrs={'class':'form-control'})


class PublisherForm(forms.Form):
    name = forms.CharField(
        label='出版社名称',
        min_length=4,
        max_length=32,
        error_messages={
            'required':'不能为空',
            'min_length':'不能小于4位',
        },
        widget=widget_input
    )

    def clean_name(self):
        val = self.cleaned_data.get('name')
        if not val.isdigit():
            return val
        else:
            raise ValidationError('名称不能是纯数字')


class AuthorForm(forms.Form):
    name = forms.CharField(
        label='姓名',
        min_length=2,
        max_length=16,
        error_messages={
            'required':'不能为空',
            'min_length':'不能小于2位',
        },
        # widget = widget_input
    )
    age = forms.IntegerField(
        label='年龄',
        min_value=1,
        max_value=150,
        error_messages={
            'required':'不能为空',
            'invalid':'格式错误',
            'min_value':'不能小于1岁',
            'max_value':'不能大于100岁',
        },
        # widget = widget_input
    )
    addr = forms.CharField(
        label='地址',
        min_length=2,
        max_length=32,
        error_messages={
            'required':'不能为空',
            'min_length':'不能小于2位'
        },
        # widget=widget_input,

    )

    # 给每一个key 添加样式
    def __init__(self,*args,**kwargs):
        super(AuthorForm, self).__init__(*args,**kwargs)
        for field in iter(self.fields):
            self.fields[field].widget.attrs.update({
                'class':'form-control'
            })

    def clean_name(self):
        val = self.cleaned_data.get('name')
        if not val.isdigit():
            return val
        else:
            raise ValidationError('姓名不能是纯数字')

    def clean_addr(self):
        val = self.cleaned_data.get('addr')
        if not val.isdigit():
            return val
        else:
            raise ValidationError('地址不能是纯数字')


class BookForm(forms.Form):
    title = forms.CharField(
        label='书名称',
        max_length=32,
        error_messages={
            'required':'不能为空',
        },
        widget=widget_input
    )
    price = forms.DecimalField(
        label='价格',
        max_digits=8,
        decimal_places=2,
        error_messages={
            'required':'不能为空',
            'max_digits':'不能超过8位',
        },
        widget=widg_num
    )

    # 最后选择了自己去添加 select 。。。。。。

    # publisher = forms.ChoiceField(
    #     label='出版社',
    #     # choices=((1,'篮球'),(2,'足球')),
    #     choices=models.Publisher.objects.all().values_list('id','name'),
    #     initial=1,
    #     widget=widgets.Select(attrs={'class':'form-control'})
    # )
    #
    # # 可以不用重启 刷新就有新的数据
    # def __init__(self,*args,**kwargs):
    #     super(BookForm, self).__init__(*args,**kwargs)
    #     self.fields['publisher'].widget.choices = models.Publisher.objects.all().values_list('id','name')

    def clean_title(self):
        val = self.cleaned_data.get('title')
        if not val.isdigit():
            return val
        else:
            raise ValidationError('名称不能为纯数字')

    def clean_price(self):
        val = self.cleaned_data.get('price')
        if val < 0:
            raise ValidationError('价格不能为负数')
        else:
            return val


class LoginForm(forms.Form):
    username = forms.CharField(
        label='用户名',
        min_length=2,
        max_length=16,
        error_messages={
            'required':'不能为空',
            'min_length':'不能小于2位'
        },
        widget=widget_input
    )
    password = forms.CharField(
        label='密码',
        min_length=6,
        max_length=16,
        error_messages={
            'required':'不能为空',
            'min_length':'不能小于6位'
        },
        widget=widget_password

    )

    def clean_username(self):
        val = self.cleaned_data.get('username')
        if not val.isdigit():
            return val
        else:
            raise ValidationError('用户名不能是纯数字')

views.py

from django.shortcuts import render,HttpResponse,redirect,reverse
from django.utils.decorators import method_decorator
from django.core import serializers
from django.views import View
import json
import datetime
import logging
from functools import wraps

from book import models
from book import myforms
from utils.mypage import MyPaginator
from utils.hash_pwd import salt_pwd

logger = logging.getLogger(__name__)
# 生成一个名为collect的实例
collect_logger = logging.getLogger('collect')


def check_login(func):
    '''
    判断用户有没有登录得装饰器
    :param func:
    :return:
    '''
    @wraps(func)
    def inner(request,*args,**kwargs):
        # 拿到当前访问网址
        url = request.get_full_path()
        if request.session.get('user'):
            return func(request,*args,**kwargs)
        else:
            return redirect('/login/?next={}'.format(url))

    return inner


# def login(request):
#     '''
#     登录
#     :param request:
#     :return:
#     '''
#     login_form = myforms.LoginForm()
#     return render(request,'login.html',{'login_form':login_form})
#
#
# def register(request):
#     '''
#     注册
#     :param request:
#     :return:
#     '''
#     register_form = myforms.LoginForm()
#     return render(request,'register.html',{'register_form':register_form})


class LoginView(View):
    '''
    CBV 登录视图
    '''

    def get(self,request):
        login_form = myforms.LoginForm()
        return render(request, 'login.html', {'login_form': login_form})

    def post(self,request):
        login_form = myforms.LoginForm(request.POST)
        if login_form.is_valid():
            username = login_form.cleaned_data.get('username')
            password = login_form.cleaned_data.get('password')
            password = salt_pwd(password, username)
            if models.UserInfo.objects.filter(name=username,pwd=password):
                # 设置session
                request.session['user'] = username
                # request.session.set_expiry(1800) # 设置session得失效时间

                # 登录成功 写log
                logger.info('用户:'+username+' 登录成功')

                next_url = request.GET.get('next')
                if next_url:
                    return redirect(next_url)   # 跳转到之前访问得页面
                else:
                    return redirect(reverse('book:book_list'))

            else:
                # 登录失败,写log
                logger.error('用户:'+username+' 登录时,用户名或密码错误')

                return render(request, 'login.html', {'login_form': login_form,'error_msg':'用户名或密码错误'})
        else:
            return render(request, 'login.html', {'login_form': login_form})


class RegisterView(View):
    '''
    CBV 注册视图
    '''
    # @method_decorator(check_login)  给cbv 加装饰器 逻辑上不应该加在这里,但可以验证装饰器加成功了
    def dispatch(self, request, *args, **kwargs):
        return super(RegisterView, self).dispatch(request,*args,**kwargs)

    def get(self,request):
        register_form = myforms.LoginForm()
        return render(request,'register.html',{'register_form':register_form})

    def post(self,request):
        register_form = myforms.LoginForm(request.POST)
        if register_form.is_valid():
            username = register_form.cleaned_data.get('username')
            password = register_form.cleaned_data.get('password')
            r_password = request.POST.get('r_password')
            if r_password == password:
                # 后台也需要判断用户名是否已存在,
                if not models.UserInfo.objects.filter(name=username).first():
                    password = salt_pwd(password,username)
                    models.UserInfo.objects.create(name=username,pwd=password)

                    # 注册成功之后,设置session登录状态
                    request.session['user'] = username

                    # 注册成功 写入log  并收集特定信息的日志
                    collect_logger.info('用户:'+username+' 注册')

                    return redirect(reverse('book:book_list'))
            else:
                return render(request,'register.html',{'register_form':register_form,'error_msg':'确认密码不符合'})

        return render(request,'register.html',{'register_form':register_form})


def exist_user(request):
    '''
    查看用户是否已经存在
    :param request:
    :return:
    '''
    if request.method == 'POST':
        username = request.POST.get('username')
        user_obj = models.UserInfo.objects.filter(name=username).first()

        reg = {'status':1,'msg':'用户已存在'} if user_obj else {'status':0,'msg':'用户不存在'}

        return HttpResponse(json.dumps(reg))


def logout(request):
    '''
    注销
    :param request:
    :return:
    '''
    request.session.delete()
    return redirect(reverse('book:login'))


# @check_login
def book_list(request,field_id = 0, field_type = 'src'):
    '''
    书列表 3中情况 从book_list下来的书  从publisher_list 下来的书 从author_list下来的书
    :param request:
    :param field_id:
    :param field_type:  /publisher_list / author_list
    :return:
    '''
    books = None

    filter_field = {
        'publisher': 'publisher_id',
        'author': 'authors__id'
    }
    field_dict = {filter_field.get(field_type): field_id} if field_type in ('publisher', 'author') else {}
    books = models.Book.objects.filter(**field_dict).values('id', 'title', 'price', 'publish_date', 'publisher__name').order_by('-id')

    ''' 注意上面得简化方法
    if field_type == 'publisher':
        books = models.Book.objects.filter(publisher_id=field_id).values('id','title','price','publish_date','publisher__name').order_by('-id')
    elif field_type == 'author':
        books = models.Book.objects.filter(authors__id=field_id).values('id','title','price','publish_date','publisher__name').order_by('-id')
    else:
         books = models.Book.objects.all().values('id', 'title', 'price', 'publish_date', 'publisher__name').order_by('-id')
    '''

    current_page_num = request.GET.get('page', 1)
    page_obj = MyPaginator(books,current_page_num)

    current_path ={'path':request.path}
    ret_dic = page_obj.show_page  # 页码返回的是字典
    ret_dic.update(current_path)  # 两个字典拼接

    # return render(request,'book_list.html',page_obj.show_page)
    return render(request,'book_list.html',ret_dic)


def del_book(request, field_id = 0, field_type = 'src'):
    '''
    删除一本书 3中情况 从book_list下来的书  从publisher_list 下来的书 从author_list下来的书
    :param request:
    :param field_id:
    :param field_type:
    :return:
    '''
    delete_id = request.POST.get('delete_id')
    if field_type == 'author':   # 清除绑定关系
        author_id = field_id
        author_obj = models.Author.objects.filter(id = author_id).first()
        try:
            author_obj.books.remove(delete_id)
            reg = {'status': 1, 'msg': '删除成功'}
        except Exception as e:
            reg = {'status':0,'msg':'删除失败'}

    else:  # 其他情况都删除书 book_list  publishe_list下来的书
        try:
            models.Book.objects.filter(id=delete_id).delete()
            reg = {'status':1,'msg':'删除成功'}
        except Exception as e:
            reg = {'status':0,'msg':'删除失败'}

    return HttpResponse(json.dumps(reg))


def add_book(request,field_id = 0, field_type = 'src'):
    '''
    增加书
    :param request:
    :param field_id:
    :param field_type:
    :return:
    '''
    current_publisher_id = 0
    current_author_id = 0
    if field_type == 'publisher':
        current_publisher_id = int(field_id)
    elif field_type == 'author':
        current_author_id = int(field_id)

    book_form = myforms.BookForm()

    if request.method == 'POST':
        if current_publisher_id:
            # 前端设置select disbaled  不能传到后台了,因此需要需要这样做
            publisher_id = current_publisher_id
        else:
            publisher_id = int(request.POST.get('publisher'))

        if request.POST.get('publish_date') != '':
            publish_date = request.POST.get('publish_date')
        else:
            publish_date = datetime.datetime.now()

        book_form = myforms.BookForm(request.POST)

        if book_form.is_valid():
            book_obj = models.Book.objects.create(
                title = book_form.cleaned_data.get('title'),
                price = book_form.cleaned_data.get('price'),
                publish_date = publish_date,
                publisher_id = publisher_id,
            )
            if current_author_id:
                book_obj.authors.add(current_author_id)  # 绑定多对多关系

            # return redirect(reverse('book:book_list')) #  因为有3种情况,分别跳到自己对应的页面下
            return redirect(request.path.replace('add_book','book_list'))

    publisher = models.Publisher.objects.all().values('id', 'name').order_by('-id')
    return render(request,'add_book.html',{'book_form':book_form,
                                            'publisher':publisher,
                                            "current_publisher_id":current_publisher_id})


def update_book(request, book_id, field_id = 0, field_type = 'src'):
    '''
    修改书
    :param request:
    :param book_id:
    :param field_id:
    :param field_type:
    :return:
    '''
    book = models.Book.objects.filter(id=book_id).first()
    book_form = myforms.BookForm()
    book_form.initial = {'title':book.title,'price':book.price}

    if request.method == 'POST':
        if request.POST.get('publish_date') != '':
            publish_date = request.POST.get('publish_date')
        else:
            publish_date = datetime.datetime.now()

        book_form = myforms.BookForm(request.POST)

        if book_form.is_valid():
            models.Book.objects.filter(id=book_id).update(
                title=book_form.cleaned_data.get('title'),
                price=book_form.cleaned_data.get('price'),
                publish_date=publish_date,
                publisher_id=request.POST.get('publisher'),
            )

            # 有3种情况,分别跳到自己对应的页面下

            if field_type in ('publisher','author'):
                new_url = reverse('book:book_list') + field_id + '/' + field_type
            else:
                new_url = reverse('book:book_list')

            return redirect(new_url)

    publisher = models.Publisher.objects.all().values('id', 'name').order_by('-id')
    return render(request,'update_book.html',{'book_form':book_form,
                                              'publisher':publisher,
                                              'current_publisher_id':book.publisher_id,
                                              'publish_date':book.publish_date})


# @check_login
def publisher_list(request):
    '''
    出版社列表
    :param request:
    :return:
    '''
    publishers = models.Publisher.objects.all().order_by('-id')
    current_page_num = request.GET.get('page',1)
    page_obj = MyPaginator(publishers,current_page_num)

    return render(request,'publisher_list.html',page_obj.show_page)


def del_publisher(request):
    '''
    删除一个出版社
    :param request:
    :return:
    '''
    delete_id = request.POST.get('delete_id')
    try:
        models.Publisher.objects.filter(id=delete_id).delete()
        reg = {'status':1,'msg':'删除成功'}
    except Exception as e:
        reg = {'status':0,'msg':'删除失败'}

    return HttpResponse(json.dumps(reg))


def add_publisher(request):
    '''
    增加出版社
    :param request:
    :return:
    '''
    publisher_form = myforms.PublisherForm()
    if request.method == 'POST':
        publisher_form = myforms.PublisherForm(request.POST)
        if publisher_form.is_valid():
            models.Publisher.objects.create(**publisher_form.cleaned_data)
            return redirect(reverse('book:publisher_list'))

    return render(request,'add_publisher.html',{'publisher_form':publisher_form})


def update_publisher(request,publisher_id):
    '''
    修改出版社
    :param request:
    :param publisher_id:
    :return:
    '''
    publisher = models.Publisher.objects.filter(id=publisher_id).first()
    publisher_form = myforms.PublisherForm()
    publisher_form.initial = {'name': publisher.name}  # 对forms组件初始化

    if request.method == 'POST':
        publisher_form = myforms.PublisherForm(request.POST)
        if publisher_form.is_valid():
            models.Publisher.objects.filter(id=publisher_id).update(**publisher_form.cleaned_data)
            return redirect(reverse('book:publisher_list'))

    return render(request, 'update_publisher.html', {'publisher_form': publisher_form})


# @check_login
def author_list(request):
    '''
     作者列表
    :param request:
    :return:
    '''
    authors = models.Author.objects.all().values('id','detail_id','name','detail__age','detail__addr').order_by('-id')
    current_page_num = request.GET.get('page')
    page_obj = MyPaginator(authors,current_page_num)

    return render(request,'author_list.html',page_obj.show_page)


def del_author(request):
    '''
    删除一个作者
    :param request:
    :return:
    '''
    delete_id = request.POST.get('delete_id')
    try:
        # 删Author关联的不会被删掉
        # models.Author.objects.filter(id=delete_id).delete()

        # 删AuthorDetail关联的才会被删掉
        models.AuthorDetail.objects.filter(id=delete_id).delete()
        reg = {'status':1,'msg':'删除成功'}
    except Exception as e:
        reg = {'status':0,'msg':'删除失败'}

    return HttpResponse(json.dumps(reg))


def add_author(request):
    '''
    增加作者
    :param request:
    :return:
    '''
    author_form = myforms.AuthorForm()
    if request.method == 'POST':
        author_form = myforms.AuthorForm(request.POST)
        if author_form.is_valid():
            name = author_form.cleaned_data.get('name')
            age = author_form.cleaned_data.get('age')
            addr = author_form.cleaned_data.get('addr')

            authordetail = models.AuthorDetail.objects.create(age=age,addr=addr)
            models.Author.objects.create(name=name,detail=authordetail)

            return redirect(reverse('book:author_list'))

    return render(request,'add_author.html',{'author_form':author_form})


def update_author(request,author_id):
    '''
    修改作者
    :param request:
    :param author_id:
    :return:
    '''
    author = models.Author.objects.filter(id=author_id).values('name','detail__age','detail__addr').first()
    author_form = myforms.AuthorForm()
    author_form.initial = {'name':author.get('name'),'age':author.get('detail__age'),'addr':author.get('detail__addr')}

    if request.method == 'POST':
        author_form = myforms.AuthorForm(request.POST)
        if author_form.is_valid():
            name = author_form.cleaned_data.get('name')
            age = author_form.cleaned_data.get('age')
            addr = author_form.cleaned_data.get('addr')

            models.Author.objects.filter(id=author_id).update(name=name)
            models.AuthorDetail.objects.filter(author__id=author_id).update(age=age,addr=addr)

            return redirect(reverse('book:author_list'))

    return render(request,'update_author.html',{'author_form':author_form})
views

templatetags/my_tag_filter.py

# -*- coding:utf-8 -*-
from django import template
from django.utils.safestring import mark_safe

register = template.Library()


# 自定义过滤器
@register.filter
def path_filter(x):
    new_path = x.replace('book_list','add_book')
    return new_path


@register.filter
def id_filter(x):
    return int(x)


@register.filter
def user_tags(request):
    return request.session.get('user')


# 自定义标签
@register.filter
def update_path(current_path,book_id):
    new_path = current_path.replace('book_list', 'update_book/'+str(book_id))
    return new_path


# 自定义标签
@register.simple_tag
def multi_tags(x,y,z):
    return x*y*z


@register.simple_tag
def my_input(id,arg):
    result = "<input type='text' id='%s' class='%s' />" %(id,arg,)
    return mark_safe(result)
my_tag_filter

settings.py

"""
Django settings for mybms project.

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

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

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

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


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

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '@#zsz!ri$$$7g9)=38%n-8y#g+wsc6c-*_8veuzhm%go=h@(2('

# 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',
    'book.apps.BookConfig',
]

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',
    'mymiddleware.MyAuthMD',
]

ROOT_URLCONF = 'mybms.urls'

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',
            ],
        },
    },
]

WSGI_APPLICATION = 'mybms.wsgi.application'


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

# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#     }
# }
DATABASES ={
    'default':{
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mybms',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': 'root',
        'PASSWORD': '123',
    }
}


# Password validation
# https://docs.djangoproject.com/en/2.0/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/2.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


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

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

# 终端打印sql语句
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

# logging 配置
BASE_LOG_DIR = os.path.join(BASE_DIR,'log')
LOGGING = {
    'version': 1,  # 保留字
    'disable_existing_loggers': False,  # 禁用已经存在的 logger 实例
    'formatters': {
        # 详细的日志格式
        'standard': {
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        # 简单的日志格式
        'simple': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },
        # 定义一个特殊的日志格式
        'collect': {
            'format': '%(message)s'
        }
    },
    # 过滤器
    'filters': {
        # DEBUG = True 的情况 才过滤
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    # 处理器
    'handlers': {
        # 在终端打印
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],  # 只有在Django debug为True时才在屏幕打印日志
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        # 默认
        'default': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "bms_info.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M 一般配500M
            'backupCount': 3, # 最多备份3个
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        # 专门用来记 错误日志
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "bms_err.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        # 专门 定义一个 收集特定信息的日志
        'collect': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "bms_collect.log"),
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 5,
            'formatter': 'collect',
            'encoding': "utf-8"
        }
    },
    'loggers': {
       # 默认的logger应用如下配置
        '': {
            'handlers': ['default', 'console', 'error'],  # 上线之后可以把'console'移除
            'level': 'DEBUG',
            'propagate': True,  # 向不向更高级别的logger传递
        },
        # 名为 'collect'的logger还单独处理
        'collect': {
            'handlers': ['console', 'collect'],
            'level': 'INFO',
        }
    },
}
settings

urls.py

from django.contrib import admin
from django.urls import path,re_path,include

from book import views

urlpatterns = [
    # re_path(r'^$', views.login),
    re_path(r'^$', views.LoginView.as_view()),
    re_path(r'^',include(('book.urls','book'))),
    
]
urls

init_ajax.js

// 从cooikie 取 csrf token 的值
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');


// 将csrftoken 设置到ajax 请求头中,后续的ajax请求就会自动携带这个csrf token
function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
    beforeSend: function (xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});
init_ajax

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    {% block title %}
        <title>base</title>
    {% endblock title %}

    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/plugins/sweetalert/sweetalert.css">
    <style type="text/css">
        .my-container{margin-top: 20px;}
        .showSweetAlert h2{padding-top: 20px;}
        .my-error-span{ color: red;display: block;font-size: 12px;}
    </style>

</head>
<body>

<div class="container my-container">
    <div class="row">
        <div class="col-md-2">
            {% block leftlist %}
                <div class="row">
                    <ul class="nav nav-pills nav-stacked my-left-con">
                        <li role="presentation"><a href="{% url 'book:book_list' %}">图书列表</a></li>
                        <li role="presentation"><a href="{% url 'book:publisher_list' %}">出版社列表</a></li>
                        <li role="presentation"><a href="{% url 'book:author_list' %}">作者列表</a></li>
                    </ul>
                </div>
            {% endblock %}

        </div>

        <div class="col-md-2">
            {% block info%}
                <h3>信息</h3>
            {% endblock info%}
            {% block cancel %}
                {% load my_tag_filter %}
                <p>欢迎:{{ request|user_tags }}</p>
                <a href="/logout/">注销</a>
            {% endblock %}
        </div>

        <div class="col-md-8">

            {% block con %}
                <h3>内容</h3>
            {% endblock %}

            {% block paginator %}
                <nav class="my-page" aria-label="Page navigation">
                    <ul class="pagination">
                        <li><a href="?page=1">首页</a></li>

                        {% if current_page_data.has_previous %}
                            <li>
                                <a href="?page={{ current_page_data.previous_page_number }}" aria-label="Previous">
                                    <span aria-hidden="true">&laquo;</span>
                                </a>
                            </li>
                        {% else %}
                            <li class="disabled">
                                <a href="#" aria-label="Previous">
                                    <span aria-hidden="true">&laquo;</span>
                                </a>
                            </li>
                        {% endif %}

                        {% for item in page_range %}
                            {% if current_page_num == item %}
                                <li class="active"><a href="?page={{ item }}">{{ item }}</a></li>
                            {% else %}
                                <li><a href="?page={{ item }}">{{ item }}</a></li>
                            {% endif %}
                        {% endfor %}

                        {% if current_page_data.has_next %}
                            <li>
                                <a href="?page={{ current_page_data.next_page_number }}" aria-label="Next">
                                    <span aria-hidden="true">&raquo;</span>
                                </a>
                            </li>
                        {% else %}
                            <li class="disabled">
                                <a href="#" aria-label="Next">
                                    <span aria-hidden="true">&raquo;</span>
                                </a>
                            </li>
                        {% endif %}
                        <li><a href="?page={{ paginator.num_pages }}">尾页</a></li>

                    </ul>
                </nav>
            {% endblock paginator%}

        </div>
    </div>
</div>


<script src="/static/jquery-3.2.1.min.js"></script>
<script src="/static/bootstrap-3.3.7/js/bootstrap.min.js"></script>
<script src="/static/plugins/sweetalert/sweetalert.min.js"></script>
<script src="/static/init_ajax.js"></script>

{% block script %}
    <script type="text/javascript">
        $(function () {
            //没有数据时,不显示页码
            var length = $('.table').children('tbody').children().length;
            if(length === 0){
                $('.my-page').css('display','none')
            }

            //页面加载时 左侧导航样式修改
            var pathname = window.location.pathname;
            if(pathname.indexOf("book_list/")>0){
                $('.my-left-con li').eq(0).addClass('active')
            }else if(pathname.indexOf("publisher_list/")>0){
                $('.my-left-con li').eq(1).addClass('active')
            }else if(pathname.indexOf("author_list/")>0){
                $('.my-left-con li').eq(2).addClass('active')
            }

        });

        // 删除按钮点击 获取url  会有这样的url(/book_list/2/publisher/)所以使用indexOf
        $('.my-delete').click(function () {
            var delete_id = $(this).parent().siblings('td').eq(1).text();
            var row = $(this).parent().parent();
            var pathname = window.location.pathname;
            var url = null;

            // replace 的原因 会有这样的url(/book_list/2/author/)
            // 一对多和多对多删除情况不一样,一个清除绑定关系,一个时删除
            if(pathname.indexOf("book_list/")>0){
                url = pathname.replace('/book_list/','/del_book/');
            }else if(pathname.indexOf("publisher_list/")>0){
                url = '/del_publisher/';
            }else if(pathname.indexOf("author_list/")>0){
                url = '/del_author/'
            }

            // 删除插件的应用
            swal({
                    title: "确定要删除吗? ",
                    text: "删了就找不回来了",
                    type: "warning",
                    showCancelButton: true,  // 显不显示取消按钮
                    confirmButtonClass: "btn-danger",
                    confirmButtonText: "是,就是删除",  //按钮上的文字
                    closeOnConfirm: false
                },
                function(){
                    $.ajax({
                        url:url,
                        type:'post',
                        data:{'delete_id':delete_id},
                        success:function (ret) {
                            var data = JSON.parse(ret);
                            if(data.status === 1){
                                row.remove();
                                swal(data.msg, "恭喜您", "success");
                            }else{
                                swal(data.msg, "你可以尝试在删一次", "error");
                            }
                        }
                    });
                });
        });

    </script>

{% endblock script %}

</body>
</html>
base

book_list.html

{% extends 'base.html' %}

{% block title %}
    <title>book_list</title>
{% endblock title %}

{% block info %}
    <h3>books</h3>
{% endblock info %}

{% block con %}
    {# 这里使用了自定义的过滤器 #}
    {% load my_tag_filter %}
    <a href="{{ path|path_filter }}" class="btn btn-info">创建</a>
    <table class="table table-bordered table-hover table-responsive table-striped" >
        <thead>
            <tr>
                <th>序号</th>
                <th>ID</th>
                <th>名称</th>
                <th>价格</th>
                <th>出版社</th>
                <th>出版日期</th>
                <th>修改操作</th>
                <th>删除操作</th>
            </tr>
        </thead>
        <tbody>
            {% for book in current_page_data %}
                <tr>
                    <td>{{ forloop.counter|add:seque }}</td>
                    <td>{{ book.id }}</td>
                    <td>{{ book.title }}</td>
                    <td>{{ book.price }}</td>
                    <td>{{ book.publisher__name }}</td>
                    <td>{{ book.publish_date|date:'Y-m-d' }}</td>
                    {# <td><a href="/update_book/{{ book.id }}/" class="btn btn-success">修改</a></td>#}
                    <td><a href="{{ path|update_path:book.id}}" class="btn btn-success">修改</a></td>
                    <td><button class="btn btn-danger my-delete">删除</button></td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

{% endblock con %}
book_list

hash_pwd.py

# -*- coding:utf-8 -*-
import hashlib


def salt_pwd(password,username):
    # 把用户名当作盐 用户名只能唯一
    return hashlib.md5(password.encode('utf-8') + username.encode('utf-8')).hexdigest()
hash_pwd

mypage.py

# -*- coding:utf-8 -*-
from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger


class MyPaginator(object):
    def __init__(self, data, current_page_num, per_count = 5, show_page_count = 7):
        """
        自定义分页器
        :param data: 要分页的所以数据
        :param current_page_num: 当前页码
        :param per_count: 每页显示的总数
        :param show_page_count: 显示页码的个数
        """
        self.data = data
        self.current_page_num = current_page_num
        self.per_count = per_count
        self.show_page_count = show_page_count

        self.half_page = int(self.show_page_count / 2)
        self.paginator = Paginator(self.data, self.per_count)

        # 获取当前页码
        try:
            self.current_page_num = int(self.current_page_num)
            if self.current_page_num < 1:
                self.current_page_num = 1
            elif self.current_page_num > self.paginator.num_pages:
                self.current_page_num = self.paginator.num_pages
        except Exception as e:
            self.current_page_num = 1

        # 显示页码的范围
        if self.paginator.num_pages > self.show_page_count:
            if self.current_page_num - self.half_page < 1:
                self.page_range = range(1, self.show_page_count + 1)
            elif self.current_page_num + self.half_page > self.paginator.num_pages:
                self.page_range = range(self.paginator.num_pages - self.show_page_count + 1, self.paginator.num_pages + 1)
            else:
                self.page_range = range(self.current_page_num - self.half_page, self.current_page_num + self.half_page + 1)
        else:
            self.page_range = self.paginator.page_range

        # 获取当前页的数据
        try:
            self.current_page_data = self.paginator.page(self.current_page_num)
        except EmptyPage as e:
            self.current_page_data = self.paginator.page(1)
        except PageNotAnInteger as e:
            self.current_page_data = self.paginator.page(self.paginator.num_pages)
        except Exception as e:
            self.current_page_data = self.paginator.page(1)
    
    @property
    def show_page(self):
        dic_page = {'current_page_data':self.current_page_data,
                    'paginator':self.paginator,
                    'current_page_num':self.current_page_num,
                    'page_range':self.page_range,
                    'seque':self.per_count*(self.current_page_num-1)}
        return dic_page
    

mymiddleware.py

# -*- coding:utf-8 -*-
# 自定义中间件
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect,HttpResponse,render


class MyAuthMD(MiddlewareMixin):
    white_list = ['/login/','/register/','/','/exist_user/']  # 白名单  一定要有,因为还没有登录,不需要验证是否登录
    black_list = ['/black/',]  # 黑名单

    def process_request(self,request):
        # 拿到当前访问网址
        next_url = request.path
        if next_url in self.white_list or request.session.get('user'):
            return
        elif next_url in self.black_list:
            return HttpResponse('This is an illegal URL')
        else:
            return redirect('/login/?next={}'.format(next_url))
        

myscript.py

# -*- coding:utf-8 -*-
import os


if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mybms.settings")
    import django
    django.setup()

    from book import models
    import random

    # 批量插入book
    # ret1 = [models.Book(title='水浒传{}'.format(i),price=random.randint(10,100),publisher_id=1) for i in range(50)]
    # ret2 = [models.Book(title='三国演义{}'.format(i),price=random.randint(10,100),publisher_id=2) for i in range(50)]

    # models.Book.objects.bulk_create(ret1)
    # models.Book.objects.bulk_create(ret2)

    # 批量插入publisher
    # ret1 = [models.Publisher(name='火星{}出版社'.format(i)) for i in range(50)]
    # models.Publisher.objects.bulk_create(ret1)

    # 批量插入Author
    # ret = [models.AuthorDetail(age=random.randint(20,50), addr = '北京{}'.format(i)) for i in range(50)]
    # models.AuthorDetail.objects.bulk_create(ret)

    # li = []
    # for item in models.AuthorDetail.objects.all().values('id'):
    #     ret = models.Author(name='alice_'+str(item.get('id')), detail_id=item.get('id'))
    #     li.append(ret)
    #
    # models.Author.objects.bulk_create(li)

    # 作者绑定书 author book
    # for item in models.Author.objects.all():
    #     item.books.add(random.randint(100,200),random.randint(100,200),random.randint(100,200),random.randint(100,200))

https://github.com/alice-bj/mybms

原文地址:https://www.cnblogs.com/alice-bj/p/9114084.html