Django实战总结

一, 在模板和视图中反解Form组件中的choices.

// 前端中获得FORM中choices选项的对应值
obj.get_字段名称_display

// PY代码中获得FORM中choices选项的对应值
obj.get_字段名称_display()

二, 分页

方式一

# 先来一个视图
class CustomerList(View):
    def get(self, request):
    	# 获取所有的数据
        resp = models.Customer.objects.all()
        # 每页显示20条数据
        paginator = Paginator(resp, 20)
        page = request.GET.get('page', 1)
        all_customer = paginator.page(page)
        return render(request, 'customer_list.html', {'all_customer': all_customer})
// 前端代码如下
<nav aria-label="Page navigation" class="text-right">
    <ul class="pagination">
        {% if all_customer.has_previous %}
            <li>
                <a href="?page=1" aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
            <li><a href="?page={{ all_customer.previous_page_number  }}">上一页</a></li>
        {% endif %}

        <li>
            <a>
                第 {{ all_customer.number }}页 共 {{ all_customer.paginator.num_pages }}页
            </a>
        </li>
        {% if all_customer.has_next %}
            <li><a href="?page={{ all_customer.next_page_number }}">下一页</a></li>
            <li>
                <a href="?page={{ all_customer.paginator.num_pages }}" aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        {% endif %}

    </ul>
</nav>

方式二

# 视图函数
# 测试分页
class Pagination(View):
    def get(self, request):
        # 所有数据(模拟309条数据)
        all_data = [{'name': f'user{i}', 'pwd': '123'} for i in range(1, 310)]
        max_data_count = len(all_data)
        # 每页显示的数据条数
        every_page_count = 10
        # 最大分页数
        page_count_max, more = divmod(max_data_count, every_page_count)
        if more:
            page_count_max += 1
        # 请求的页数(当前页)
        page = request.GET.get('page')
        try:
            page = int(page)
            if page < 1:
                page = 1
            elif page > page_count_max:
                page = page_count_max
        except Exception:
            page = 1
        # 起始数据
        start_count = (page - 1) * every_page_count
        # 结束数据
        end_count = page * every_page_count

        # 每页最大展示页数按钮数
        page_show_count = 9
        page_show_count_half = page_show_count // 2
        # 如果每页展示的页数按钮大于等于总共的页数按钮
        if page_show_count >= page_count_max:
            start_page = 1
            end_page = page_count_max
        else:
            # 起始页
            start_page = page - page_show_count_half
            # 终止页
            end_page = page + page_show_count_half
            if start_page < 1:
                start_page = 1
                end_page = page_show_count
            elif end_page > page_count_max:
                end_page = page_count_max
                start_page = end_page - page_show_count + 1

        # 生成按钮
        button_list = []
        if page <= 1:
            button_list.append(
                f'<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>')
        else:
            button_list.append(
                f'<li><a href="?page={page - 1}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>')
        for i in range(start_page, end_page + 1):
            if i == page:
                button_list.append(f'<li class="active"><a href="?page={i}">{i}</a></li>')
            else:
                button_list.append(f'<li><a href="?page={i}">{i}</a></li>')

        if page >= page_count_max:
            button_list.append(
                f'<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>')
        else:
            button_list.append(
                f'<li><a href="?page={page + 1}" aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>')
        button_html = mark_safe(''.join(button_list))
        return render(request, 'pagination.html', {'all_data': all_data[start_count:end_count],
                                                   'button_html': button_html
                                                   })
// 前端
    <table class="table table-bordered table-hover">
        <thead>
        <tr>
            <th>用户名</th>
            <th>密码</th>
        </tr>
        </thead>
        <tbody>
        {% for data in all_data %}
            <tr>
                <td>{{ data.name }}</td>
                <td>{{ data.pwd }}</td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
    <nav aria-label="Page navigation" class="text-right">
        <ul class="pagination">
            {{ button_html }}
        </ul>
    </nav>

方式三

# 将方式二提取出一个类
from django.utils.safestring import mark_safe

class MyPagination:
    def __init__(self, max_data_count, page, every_page_count=10, page_show_count=9):
        # 所有数据条数
        max_data_count = max_data_count
        # 每页显示的数据条数
        every_page_count = every_page_count
        # 最大分页数
        self.page_count_max, more = divmod(max_data_count, every_page_count)
        if more:
            self.page_count_max += 1
        # 请求的页数(当前页)
        try:
            self.page = int(page)
            if self.page < 1:
                self.page = 1
            elif self.page_count_max and self.page > self.page_count_max:
                self.page = self.page_count_max
        except Exception:
            self.page = 1
        # 起始数据
        self.start_count = (self.page - 1) * every_page_count
        # 结束数据
        self.end_count = self.page * every_page_count

        # 每页最大展示页数按钮数
        page_show_count = page_show_count
        page_show_count_half = page_show_count // 2
        # 如果每页展示的页数按钮大于等于总共的页数按钮
        if page_show_count >= self.page_count_max:
            self.start_page = 1
            self.end_page = self.page_count_max
        else:
            # 起始页
            self.start_page = self.page - page_show_count_half
            # 终止页
            self.end_page = self.page + page_show_count_half
            if self.start_page < 1:
                self.start_page = 1
                self.end_page = page_show_count
            elif self.end_page > self.page_count_max:
                self.end_page = self.page_count_max
                self.start_page = self.end_page - page_show_count + 1

    @property
    def show_html(self):
        # 生成按钮
        button_list = []
        if self.page <= 1:
            button_list.append(
                f'<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>')
        else:
            button_list.append(
                f'<li><a href="?page={self.page - 1}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>')
        for i in range(self.start_page, self.end_page + 1):
            if i == self.page:
                button_list.append(f'<li class="active"><a href="?page={i}">{i}</a></li>')
            else:
                button_list.append(f'<li><a href="?page={i}">{i}</a></li>')

        if self.page >= self.page_count_max:
            button_list.append(
                f'<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>')
        else:
            button_list.append(
                f'<li><a href="?page={self.page + 1}" aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>')
        return mark_safe(''.join(button_list))

# 视图函数中
# 测试分页
class Pagination(View):
    def get(self, request):
        # 所有数据(模拟309条数据)
        all_data = [{'name': f'user{i}', 'pwd': '123'} for i in range(1, 310)]
        max_data_count = len(all_data)
        page = MyPagination(max_data_count, request.GET.get('page', 1))
        return render(request, 'pagination.html', {'all_data': all_data[page.start_count:page.end_count],
                                                   'button_html': page.show_html
                                                   })

进阶: 分页保留原有参数

前提知识: QueryDict的方法

# request.POST获得包含所有POST数据的QueryDict对象
# request.GET获得包含所有Url中携带的参数的QueryDict对象
# 假设参数有search(搜索条件)和page(页码)
# {'search': 'xxx', 'page': 'xxx'}
query_dict = request.GET # 默认不可修改
query_dict._mutable = True # 设置成可修改
query_dict['page'] = 1
query_dict.copy() # 深拷贝一份QueryDict对象,并且改为可修改类型
query_dict.urlencode() # ==> 字符串 search=xxx&page=xxx

from django.utils.s9afestring import mark_safe
from django.http.request import QueryDict

class MyPagination:
    def __init__(self, max_data_count, page, query_dict=None, every_page_count=10, page_show_count=9):
        if not query_dict:
            # 实例化一个可编辑的QueryDict
            query_dict = QueryDict(mutable=True)
        self.query_dict = query_dict
        # 所有数据条数
        max_data_count = max_data_count
        # 每页显示的数据条数
        every_page_count = every_page_count
        # 最大分页数
        self.page_count_max, more = divmod(max_data_count, every_page_count)
        if more:
            self.page_count_max += 1
        # 请求的页数(当前页)
        try:
            self.page = int(page)
            if self.page < 1:
                self.page = 1
            elif self.page_count_max and self.page > self.page_count_max:
                self.page = self.page_count_max
        except Exception:
            self.page = 1
        # 起始数据
        self.start_count = (self.page - 1) * every_page_count
        # 结束数据
        self.end_count = self.page * every_page_count
        # 每页最大展示页数按钮数
        page_show_count = page_show_count
        page_show_count_half = page_show_count // 2
        # 如果每页展示的页数按钮大于等于总共的页数按钮
        if page_show_count >= self.page_count_max:
            self.start_page = 1
            self.end_page = self.page_count_max
        else:
            # 起始页
            self.start_page = self.page - page_show_count_half
            # 终止页
            self.end_page = self.page + page_show_count_half
            if self.start_page < 1:
                self.start_page = 1
                self.end_page = page_show_count
            elif self.end_page > self.page_count_max:
                self.end_page = self.page_count_max
                self.start_page = self.end_page - page_show_count + 1

    @property
    def show_html(self):
        # 生成按钮
        button_list = []
        # 获取完整的参数显示
        parameters = self.query_dict
        if self.page <= 1:
            button_list.append(
                f'<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>')
        else:
            parameters['page'] = self.page - 1
            button_list.append(
                f'<li><a href="?{parameters.urlencode()}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>')
        for i in range(self.start_page, self.end_page + 1):
            parameters['page'] = i
            if i == self.page:
                button_list.append(f'<li class="active"><a href="?{parameters.urlencode()}">{i}</a></li>')
            else:
                button_list.append(f'<li><a href="?{parameters.urlencode()}">{i}</a></li>')

        if self.page >= self.page_count_max:
            button_list.append(
                f'<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>')
        else:
            parameters['page'] = self.page + 1
            button_list.append(
                f'<li><a href="?{parameters.urlencode()}" aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>')
        return mark_safe(''.join(button_list))

三, 在模板中使用Model From

<form action="" method="post" class="form-horizontal col-md-8 col-md-offset-1" novalidate>
    {% csrf_token %}
    {% for field in form_obj %}
        <div class="form-group {% if field.errors %}has-error{% endif %}">
            <label for="{{ field.id_for_label }}"
                   class="col-md-4 control-label {% if not field.field.required %}not_required{% endif %}">
                {{ field.label }}</label>

            <div class="col-md-8">
                {{ field }}
                <span class="help-block">{{ field.errors.0 }}</span>
            </div>
        </div>
    {% endfor %}

    <p class="text-right">
        <button type="reset" class="btn btn-default">重置</button>
        <button type="submit" class="btn btn-primary">提交</button>
    </p>
</form>

在模板中可以直接使用 request 对象

四, 使用Model Form进行编辑

# 编辑客户
class CustomerEdit(View):
    def get(self, request, pk):
        customer = models.Customer.objects.filter(pk=pk).first()
        if not customer:
            return HttpResponse('要编辑的数据不存在')
        form_obj = CustomerForm(instance=customer)
        return render(request, 'customer_edit.html', {'form_obj': form_obj})

    def post(self, request, pk):
        customer = models.Customer.objects.filter(pk=pk).first()
        form_obj = CustomerForm(data=request.POST, instance=customer)
        if form_obj.is_valid():
            form_obj.save()  # 编辑
            return redirect('crm:customer_list')
        return render(request, 'customer_edit.html', {'form_obj': form_obj})

进阶:

Model Form的保存流程:

instance: 如果提供instance则使用传入的instance,如果未提供,根据meta中的model生成对应的model对象.

form_obj.save(): 遍历更新instance的字段(fileds中指定的字段),然后调用instance对象的save()方法

所以,可以在is_valid()方法后,通过form_obj.instance新增字段信息(exclude中排除的字段),然后调用save()

也可以在实例化Model Form时给instance传入新创建的model对象(可以添加想要的字段),依据instance,在init方法中修改字段的相关选项等待.

五, 验证登录状态

class MyMiddleware(MiddlewareMixin):
    
    # 验证登录状态
    def process_request(self, request):
        url = request.path_info
        # 白名单
        if url in [reverse('crm:login'), reverse('crm:register')]:
            return None
        # admin也要免验证
        if url.startswith('/admin'):
            return None
        
        is_login = request.session.get('is_login')
        if not is_login:
            return redirect(f"{reverse('crm:login')}?next={url}")
        user_id = request.session.get('user_id')
        request.user_obj = models.UserProfile.objects.filter(pk=user_id).first()

六, CBV之反射

# 展示客户(公户/私户)/模糊查询/批量操作
class CustomerList(View):
    def get(self, request):
        url = request.path_info
        q = self.search_q(['qq', 'qq_name', 'name', 'phone'])
        if url == reverse('crm:customer_list'):
            all_customer = models.Customer.objects.filter(q, consultant_id=None)
        else:
            all_customer = models.Customer.objects.filter(q, consultant_id=request.user_obj)
        page = MyPagination(all_customer.count(), request.GET.get('page', 1), request.GET.copy())
        return render(request, 'customer_list.html', {
            'all_customer': all_customer[page.start_count:page.end_count],
            'button_html': page.show_html,
        })

    def post(self, request):
        action = request.POST.get('action')
        # 通过反射获取对应的方法
        if not hasattr(self, action):
            return HttpResponse('请求不合法')
        getattr(self, action)()
        # 仍然返回当前页面
        return self.get(request)

    # 转换为公户
    def to_public(self):
        pk_list = self.request.POST.getlist('customer_pk')
        customers = models.Customer.objects.filter(pk__in=pk_list)
        # 方式一
        customers.update(consultant=None)
        # 方式二
        # self.request.user_obj.customers.remove(*customers)

    # 转换为私户
    def to_private(self):
        pk_list = self.request.POST.getlist('customer_pk')
        customers = models.Customer.objects.filter(pk__in=pk_list)
        # 方式一
        customers.update(consultant=self.request.user_obj)
        # 方式二
        # self.request.user_obj.customers.add(*customers)

    # 生成查询语句
    def search_q(self, field_list):
        search = self.request.GET.get('search', '')
        q = Q()
        q.connector = 'OR'
        for field in field_list:
            q.children.append((f'{field}__contains', search))
        return q

七, Q查询的用法

q = self.search_q(['qq', 'qq_name', 'name', 'phone'])

# 生成查询语句
def search_q(self, field_list):
    search = self.request.GET.get('search', '')
    q = Q()
    q.connector = 'OR'
    for field in field_list:
    	q.children.append((f'{field}__contains', search))
    return q

八, 修改/新增后仍然返回当前页面

思路: 将本页面的完整url传入修改/新增页面,在完成修改/新增后重定向到此url

难点: url中有多个参数时,&等特殊字符会影响参数的获取

解决: QueryDict的urlencode()方法

创建一个自定义方法:

from django import template
from django.urls import reverse
from django.http.request import QueryDict

register = template.Library()

@register.simple_tag
def url_tag(request, url_name, *args, **kwargs):
    ulr = reverse(url_name, args=args, kwargs=kwargs)
    next_url = request.get_full_path()
    query_dict = QueryDict(mutable=True)
    query_dict['next'] = next_url
    return f'{ulr}?{query_dict.urlencode()}'

模板中使用:

{% load mytags %}
<a href="{% url_tag request 'crm:customer_add' %}">新增</a>
<a href="{% url_tag request 'crm:customer_edit' customer.pk %}">编辑</a>

九, MySql行级锁

# mysql中
begin;
select * from app01_user where id=1 for update;  # 加行级锁
commit;
# django中
from django.db import transaction
from django.conf import settings

def to_private(self):
	pk_list = self.request.POST.getlist('customer_pk')
	if len(pk_list) + models.Customer.objects.filter(
        # 在settings.py中设置MAX_CUSTOMER_NUM客户上限
		consultant=self.request.user_obj).count() > settings.MAX_CUSTOMER_NUM客户上线:
		return HttpResponse('太多了')
	try:
		with transaction.atomic():
			customers = models.Customer.objects.filter(pk__in=pk_list, consultant=None).select_for_update()
        	if len(pk_list) == customers.count():
        		customers.update(consultant=self.request.user_obj)
            else:
                return HttpResponse('手速太慢了')
    except Exception:
    	pass

十, 批量操作数据

# 批量插入
def make_study(self):
    course_pk_list = self.request.POST.getlist('course_pk')
    for course_pk in course_pk_list:
        course_obj = models.CourseRecord.objects.filter(pk=course_pk).first()
        class_obj = course_obj.re_class
        study_record_list = []
        for student in class_obj.customer_set.filter(status='studying'):
        	if not models.StudyRecord.objects.filter(course_record=course_obj, student=student).exists():
        		study_record_list.append(models.StudyRecord(course_record=course_obj, student=student))

        models.StudyRecord.objects.bulk_create(study_record_list)  # 批量插入

modelformset:

from django.forms import modelformset_factory
# 展示学习记录
class StudyList(MyView):
    def get(self, request, course_id=None, *args, **kwargs):
        # 工厂模型,针对model(StudyRecord)的每条数据,生成一个instance传入form中,最终生成一个form表单
        form_set = modelformset_factory(model=models.StudyRecord, form=StudyForm, extra=0)
        queryset = models.StudyRecord.objects.filter(course_record_id=course_id).order_by('pk')
        page = MyPagination(queryset.count(), request.GET.get('page', 1), request.GET.copy())
        formset_obj = form_set(queryset=queryset[page.start_count:page.end_count])
        return render(request, 'teacher/study_record.html', {
            'formset_obj': formset_obj,
            'button_html': page.show_html
        })

    def post(self, request, course_id=None, *args, **kwargs):
        form_set = modelformset_factory(model=models.StudyRecord, form=StudyForm, extra=0)
        queryset = models.StudyRecord.objects.filter(course_record_id=course_id).order_by('pk')
        page = MyPagination(queryset.count(), request.GET.get('page', 1), request.GET.copy())
        formset_obj = form_set(data=request.POST)
        # 依次验证每个form表单的数据,并更新,在from中可以用instance拿到对应的数据
        if formset_obj.is_valid():
            formset_obj.save()
            return self.get(request, course_id, *args, **kwargs)
        return render(request, 'teacher/study_record.html', {
            'formset_obj': formset_obj,
            'button_html': page.show_html
        })
# 模板中
<div class="table-responsive">
    <form action="" method="post">
        {% csrf_token %}
        {{ formset_obj.management_form }}
        <button class="btn btn-primary"><span><i
                class="fa fa-address-book"></i></span>
            保存
        </button>
        <table class="table table-bordered table-condensed text-center" style="font-size: 12px;">
            <thead>
            <tr>
                <th>序号</th>
                <th>学生姓名</th>
                <th>考勤</th>
                <th>成绩</th>
                <th>批语</th>
            </tr>
            </thead>
            <tbody>
            {% for form in formset_obj %}
                <tr>
                    {{ form.id }}
                    <td>{{ forloop.counter }}</td>
                    <td>{{ form.instance.student }}</td>
                    <td>{{ form.attendance }}</td>
                    <td>{{ form.score }}</td>
                    <td>{{ form.homework_note }}</td>
                </tr>
            {% endfor %}
            </tbody>
        </table>
    </form>
</div>

<nav aria-label="Page navigation" class="text-right">
    <ul class="pagination">
        {{ button_html }}
    </ul>
</nav>
原文地址:https://www.cnblogs.com/zyyhxbs/p/11678275.html