一、认证
1、自定义认证
在前面说的 APIView 中封装了三大认证,分别为认证、权限、频率。认证即登录认证,权限表示该用户是否有权限访问接口,频率表示用户指定时间内能访问接口的次数。整个请求最开始的也是认证。
(1)需求
- 登陆认证
- 用户登陆成功--》签发token
- 以后需要登陆才能访问的接口,必须写到当时登陆签发的token过来
- 后端验证--》验证通过--》继续后续操作
(2)定义模型表
| from django.contrib.auth.models import User |
| from django.db import models |
| |
| |
| class Book(models.Model): |
| name = models.CharField(max_length=64) |
| price = models.IntegerField() |
| publish = models.CharField(max_length=64) |
| |
| class UserInfo(AbstractUser): |
| username = models.CharField(max_length=32) |
| password = models.CharField(max_length=32) |
| user_type=models.IntegerField(choices=((1,'注册用户'),(2,'普通管理员'),(3,'超级管理员')),default=1) |
| |
| @property |
| def is_authenticated(self): |
| return True |
| |
| class Meta: |
| verbose_name_plural = '用户表' |
| |
| |
| class UserToken(models.Model): |
| |
| token = models.CharField(max_length=64) |
| user = models.OneToOneField(to='UserInfo', on_delete=models.CASCADE) |
(3)登陆接口
| import uuid |
| from rest_framework.viewsets import ViewSet |
| from django.contrib import auth |
| |
| |
| class LoginView(ViewSet): |
| |
| authentication_classes = [] |
| permission_classes = [] |
| |
| @action(methods=['post', ], detail=False) |
| def login(self, request): |
| username = request.data.get('username') |
| password = request.data.get('password') |
| |
| user = auth.authenticate(request, username=username, password=password).first() |
| if not user: |
| return Response({'code': 10001, 'msg': '用户名或密码错误'}) |
| else: |
| token = str(uuid.uuid4()) |
| |
| |
| models.UserToken.objects.update_or_create(defaults={'token': token}, user=user) |
| return Response({'code': 10000, 'msg': '登录成功', 'token': token}) |
(4) 认证类
| from rest_framework.authentication import BaseAuthentication |
| from .models import UserToken |
| from rest_framework.exceptions import AuthenticationFailed |
| ''' |
| 1 写个类,继承BaseAuthentication |
| 2 重写 authentication 方法 |
| 3 在authentication 中完成用户登陆的校验 |
| -用户携带token--》能查到,就是登陆了 |
| -用户没带,或查不到,就是没登录 |
| 4 如果校验通过,返回当前登录用户和token |
| 5 如果没校验通过,抛AuthenticationFailed |
| ''' |
| class LoginAuth(BaseAuthentication): |
| |
| def authenticate(self, request): |
| |
| |
| |
| token=request.META.get('HTTP_TOKEN') |
| |
| user_token=UserToken.objects.filter(token=token).first() |
| if user_token: |
| |
| user=user_token.user |
| return user,token |
| else: |
| raise AuthenticationFailed('很抱歉,您没有登陆,不能操作') |
(5)使用
① 局部使用
| class BookView(GenericViewSet, ListModelMixin, CreateModelMixin, DestroyModelMixin): |
| |
| authentication_classes = [LoginAuth] |
② 全局使用
- 在配置文件中添加配置,DEFAULT_AUTHENTICATION_CLASSES 的值为列表,其包含认证类路径
| REST_FRAMEWORK = { |
| |
| 'DEFAULT_AUTHENTICATION_CLASSES': [ |
| 'utils.authentication.LoginAuth' |
| ], |
| } |
| class UserView(ViewSet): |
| authentication_classes = [] |
③ 选择使用
- 可以选择某一些方法可以认证,在视图类中添加 get_authenticators
| def get_authenticators(self): |
| if self.request.method != 'GET': |
| return [LoginAuth(), ] |
| |
(6)总结流程
- 继承 BaseAuthentication 类
- 重写 authenticate 方法,方法中编写逻辑,存在需要返回元组,为登录用户和 token,不存在则报 AuthenticationFailed 认证异常
- 编写好认证类可以在局部使用,也可以在全局使用
2、内置认证类
- SessionAuthentication 之前老的 session 认证登录方式用,后期不用
- BasicAuthentication 基本认证方式
- TokenAuthentication 使用 token 认证方式,也可以自己写
可以在配置文件中配置全局默认的认证方案
| REST_FRAMEWORK = { |
| 'DEFAULT_AUTHENTICATION_CLASSES': ( |
| 'rest_framework.authentication.SessionAuthentication', |
| 'rest_framework.authentication.BasicAuthentication', |
| ) |
| } |
也可以在每个视图中通过设置authentication_classess属性来设置
| from rest_framework.authentication import SessionAuthentication, BasicAuthentication |
| from rest_framework.views import APIView |
| |
| class ExampleView(APIView): |
| authentication_classes = [SessionAuthentication, BasicAuthentication] |
| ... |
二、权限
登录认证成功后,还需要认证权限,有一些接口需要指定权限才能访问。所以权限需要和登录认证相关联。每个人的权限在表中默认设为普通用户。
注意:如果设置了权限,那么必需先通过用户认证才能进行权限校验。
1、自定义权限
- 自定义权限需要继承 BasePermission 编写权限类
- 导入语句:
from rest_framework.permissions import BasePermission
| from rest_framework.permissions import BasePermission |
| from .models import UserToken, UserInfo |
| |
| class UserPermission(BasePermission): |
| |
| message = '' |
| |
| |
| def has_permission(self, request, view): |
| |
| user = request.user |
| |
| user_type = user.user_type |
| if user_type == 1: |
| |
| return True |
| else: |
| |
| |
| self.message = '你是: %s,权限不够' % user.get_user_type_display() |
| return False |
2、使用
(1)局部使用
| permission_classes = [UserPermission,] |
(2)全局使用
| REST_FRAMEWORK = { |
| |
| 'DEFAULT_AUTHENTICATION_CLASSES': [ |
| 'app01.authentication.LoginAuth' |
| ], |
| 'DEFAULT_Permission_CLASSES': [ |
| 'utils.permission.UserPermission' |
| ], |
| } |
| |
| class PublishView(GenericViewSet): |
| permission_classes = [] |
(3)选择使用
- 在视图类中添加 get_permissions 判断如果请求方式符合就去认证
| def get_permissions(self): |
| if self.request.method == 'DELETE': |
| return [UserPermission(), ] |
3、总结流程
- 继承 BasePermission 类
- 重写 has_permission 方法,方法中编写逻辑,有权限返回 True,没有权限可以添加 message 并返回 False
- 编写好权限类可以在局部使用,也可以在全局使用
4、内置权限类
| from rest_framework.permissions import AllowAny,IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly |
| |
| -AllowAny 允许所有用户 |
| -IsAdminUser 校验是不是 auth 的超级管理员权限 |
| -IsAuthenticated 后面用,验证用户是否登录,登录后才有权限,没登录就没有权限 |
| -IsAuthenticatedOrReadOnly 了解即可 |
(1)局部使用
- 可以在具体的视图中通过 permission_classes 属性来设置,如下
| from rest_framework.permissions import IsAuthenticated |
| from rest_framework.views import APIView |
| |
| class ExampleView(APIView): |
| permission_classes = [IsAuthenticated,] |
| ... |
(2)全局使用
| REST_FRAMEWORK = { |
| .... |
| 'DEFAULT_PERMISSION_CLASSES': [ |
| 'rest_framework.permissions.IsAuthenticated', |
| ] |
| } |
| 'DEFAULT_PERMISSION_CLASSES': [ |
| 'rest_framework.permissions.AllowAny', |
| ] |
三、 频率
作用:限制接口的访问频率
1、 频率类
| from rest_framework.throttling import BaseThrottle, SimpleRateThrottle |
| class CommonThrottle(SimpleRateThrottle): |
| rate = '3/m' |
| def get_cache_key(self, request, view): |
| |
| return request.META.get('REMOTE_ADDR') |
2、使用
(1)局部使用
在视图类中添加
| from .throttling import CommonThrottle |
| class PublishView(GenericViewSet): |
| throttle_classes = [CommonThrottle] |
(2)全局使用
同样,局部禁用只要赋值空列表即可
| REST_FRAMEWORK = { |
| 'DEFAULT_THROTTLE_CLASSES': [ |
| 'app01.throttling.CommonThrottle' |
| ], |
| } |
3、总结流程
-
继承 SimpleRateThrottle 类
-
重写 get_cache_key 方法,该方法返回的值是限制的依据,例如按照 IP 地址来作为限制条件,设置每个 IP 地址访问的次数。需要注意的是,IP 地址在 request.META 中获取 'REMOTE_ADDR'
-
接下来需要配置 setting ,需要设置访问的频率。首先需要设置属性 scope,该属性的值会作为频率的键名,在 setting 配置文件 REST_FRAMEWORK 中的 DEFAULT_THROTTLE_RATES 配置,键名是 scope,键值是字符串,格式为 'x/y',x 表示访问的次数,y 表示访问的时间区间(可以为 s(秒)、m(份)、h(时)、d(天))
-
编写好频率类可以在局部使用,也可以在全局使用
4、 内置频率类
(1)AnonRateThrottle
AnonRateThrottle 内置频率类的功能:对于登录用户不限制次数,只未登录用户限制次数,限制的次数需要在配置文件中配置。使用也支持全局和局部。
| REST_FRAMEWORK = { |
| 'DEFAULT_THROTTLE_CLASSES': [ |
| 'rest_framework.throttling.AnonRateThrottle', |
| ], |
| 'DEFAULT_THROTTLE_RATES': { |
| 'anon': '5/day', |
| } |
| } |
(2)UserRateThrottle
UserRateThrottle 内置频率类的功能:限制登录用户的频率,限制的次数需要在配置文件中配置。也支持全局和局部使用。
| REST_FRAMEWORK = { |
| 'DEFAULT_THROTTLE_CLASSES': [ |
| 'rest_framework.throttling.UserRateThrottle', |
| ], |
| 'DEFAULT_THROTTLE_RATES': { |
| 'user': '1000/day', |
| } |
| } |
5、自定义频率类
| VISIT_RECORD = {} |
| |
| |
| class CommonThrottle(BaseThrottle): |
| def __init__(self): |
| self.history = None |
| |
| def allow_request(self, request, view): |
| |
| |
| |
| |
| ip = request.META.get("REMOTE_ADDR") |
| |
| now = time.time() |
| |
| if ip not in VISIT_RECORD: |
| |
| VISIT_RECORD[ip] = [now, ] |
| return True |
| |
| history = VISIT_RECORD[ip] |
| history.insert(0, now) |
| |
| |
| while history and history[0] - history[-1] > 60: |
| history.pop() |
| self.history = history |
| |
| if len(history) > 3: |
| return False |
| else: |
| return True |
| |
| def wait(self): |
| |
| if not self.history: |
| return 0 |
| |
| time = max(0, 60 - (self.history[0] - self.history[-1])) |
| return time |
在这段代码中,history 是一个记录特定 IP 地址访问时间戳的列表。这个列表存储了特定 IP 地址在一分钟内的访问时间戳,用于限制该 IP 地址的访问频率。
在 allow_request 方法中,当一个请求到达时,会检查该请求的 IP 地址是否已经存在于 VISIT_RECORD 中。如果存在,会将当前时间戳插入到该 IP 地址对应的历史访问时间戳列表中,并且清理超过一分钟的旧时间戳。
在 wait 方法中,会计算当前时间与最新访问时间戳的时间差,以确定需要等待多久才能再次允许该 IP 地址的访问。如果 self.history 为空,表示该 IP 地址尚未有访问记录,因此返回等待时间为 0。
因此,self.history 在这段代码中用于跟踪特定 IP 地址的访问时间戳列表,以便在限制访问频率时进行判断和计算。
6、其他频率类
- AnonRateThrottle
- 限制所有匿名未认证用户,使用 IP 区分用户。
- 使用 DEFAULT_THROTTLE_RATES[‘anon’] 来设置频次
- UserRateThrottle
- 限制认证用户,使用 User id 来区分。
- 使用 DEFAULT_THROTTLE_RATES[‘user’] 来设置频次
- ScopedRateThrottle
- 限制用户对于每个视图的访问频次,使用 ip 或 user id
四、排序
一般涉及到了查询所有才有排序,对于表模型查询所有数据时需要根据某个字段进行排序时,我们就可以使用到REST framework
提供的内置排序组件OrderingFilter
来帮助我们快速指名数据按照指定字段进行排序,遵循了restful规范中:地址栏中带过滤条件。
http://127.0.0.1:8008/app01/api/v1/books/?ordering=price 升序
http://127.0.0.1:8008/app01/api/v1/books/?ordering=price 倒序
1、使用步骤
| from rest_framework.filters import OrderingFilter |
| |
| class BookListView(GenericViewSet,ListModelMixin): |
| |
| filter_backends = [OrderingFilter] |
| |
| ordering_fields=['price','name'] |
| |
| |
| http://127.0.0.1:8008/app01/api/v1/books1/?ordering=-price |
| http://127.0.0.1:8008/app01/api/v1/books1/?ordering=price |
| http://127.0.0.1:8008/app01/api/v1/books1/?ordering=-price,-name |
| |
| |
| -如果纯自己写了:不生了 |
| -如果还是使用父类的list方法:生 |
| -半自己写:只要有他就会生效 self.filter_queryset() |
| |
| -filter_queryset(get_queryset()) |
| {code:100,msg:成功,results:[]} |
注意:继承APIView的是使用不了以上DRF提供的排序组件,需要自己写,自己从请求地址中取出排序规则,然后自己排序
2、继承APIView编写排序
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| from rest_framework.viewsets import ViewSetMixin |
| from rest_framework.mixins import ListModelMixin |
| |
| |
| class BookView(ViewSetMixin, APIView, ListModelMixin): |
| |
| def list(self, request, *args, **kwargs): |
| |
| print(request.query_params) |
| query_params = request.query_params |
| '''支持多个条件排序ordering=-price,id''' |
| |
| |
| if ',' in query_params.get('ordering'): |
| query = query_params.get('ordering').split(',') |
| book_list = models.Book.objects.all().order_by(*query) |
| else: |
| book_list = models.Book.objects.all().order_by(query_params.get('ordering')) |
| ser = BookSerializer(instance=book_list, many=True) |
| return Response(ser.data) |
五、过滤
restful规范中,要求请求地址中带过滤条件,五个接口中,只有查询所有接口需要过滤和排序。其实排序也是过滤的一种,所以过滤跟排序并不冲突,是可以同时使用多个的。但是要注意一个大原则:把一次性过滤掉很多的往前放,因为我们猜测过滤的内部实现也就是for循环一个个过滤类,执行过滤类的filter_queryset方法。另外,实现过滤也有三种方式:
1、继承GenericAPIView使用DRF内置过滤器实现过滤
首先导入:from rest_framework.filters import SearchFilter
| |
| http://127.0.0.1:8008/app01/api/v1/books1/?search=梦&ordering=price |
| |
| http://127.0.0.1:8008/app01/api/v1/books1/?search=海 |
| |
| |
| from rest_framework.filters import OrderingFilter,SearchFilter |
| class BookListView(GenericViewSet,ListModelMixin): |
| filter_backends = [OrderingFilter,SearchFilter] |
| ordering_fields = ['price', 'name'] |
| |
| search_fields=['name','publish'] |
使用DRF内置过滤器,它搜索结果的关系为or(或)的关系,不是and关系
。并且只能是使用关键字search
才能使用,如果我们想要用name=东&price=66
这种方式的得使用别的方法来实现。
2、使用第三方模块django-filter实现and关系的过滤
| pip install django-filter |
| from django_filters.rest_framework import DjangoFilterBackend |
| from rest_framework.viewsets import ViewSetMixin |
| from rest_framework.generics import ListAPIView |
| |
| class BookView(ViewSetMixin,ListAPIView): |
| queryset = models.Book.objects.all() |
| serializer_class = BookSerializer |
| |
| filter_backends = [DjangoFilterBackend] |
| filterset_fields = ['name', 'price'] |
| |
| |
| |
| http://127.0.0.1:8008/app01/api/v1/books1/?price=66 |
| |
| |
| http://127.0.0.1:8008/app01/api/v1/books1/?price=66&name=两天 |
- 此外,django-filter还有更强大的搜索,感兴趣的可以深入了解一下
3、自定制过滤类
(1)使用步骤
- 先定义一个过滤类,并且继承BaseFilterBackend
- 然后重写filter_queryset方法,并且在内部完成过滤规则
- 最后在视图类中配置自定义的过滤类
(2)过滤类
| from rest_framework.filters import BaseFilterBackend |
| from django.db.models import Q |
| class CommonFilter(BaseFilterBackend): |
| def filter_queryset(self, request, queryset, view): |
| |
| |
| |
| price = request.query_params.get('price', None) |
| name = request.query_params.get('name', None) |
| if price and name: |
| queryset = queryset.filter(Q(price=price) | Q(name__contains=name)) |
| if price: |
| queryset=queryset.filter(price=price) |
| if name: |
| queryset = queryset.filter(name__contains=name) |
| return queryset |
(3)视图函数
| from .filters import CommonFilter |
| class BookView(ViewSetMixin,ListAPIView): |
| queryset = models.Book.objects.all() |
| serializer_class = BookSerializer |
| |
| filter_backends = [CommonFilter] |
| |
| |
| |
4、排序搭配过滤使用
| from .filters import CommonFilter |
| from rest_framework.filters import OrderingFilter |
| |
| class BookView(ViewSetMixin,ListAPIView): |
| queryset = models.Book.objects.all() |
| serializer_class = BookSerializer |
| filter_backends = [OrderingFilter,CommonFilter] |
| ordering_fields = ['price','id'] |
六、分页
分页也是只针对查询所有的接口,其他四个接口不需要分页。drf内置了三个分页器,对应三种分页方式,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用。一个接口只能有一种分页方式,不能混合分页方式。
另外,分页跟过滤排序不冲突,都可以一起使用。分页在web端、移动端都有涉及,后端一定会需要配合实现分页接口。
使用步骤:
- 写个类,继承某个分页类
- 在视图类中配置:视图类必须继承 GenericAPIView
1、分页器一:Pagination(基本分页)
- 通过
自定义Pagination类
,来为视图添加不同分页行为。在视图中通过pagination_clas
属性来指明。
| from rest_framework.pagination import PageNumberPagination |
| |
| class CommonPageNumberPagination(PageNumberPagination): |
| page_size = 3 |
| |
| page_query_param = 'page' |
| |
| page_size_query_param = 'size' |
| |
| max_page_size = 5 |
2、分页器二:LimitOffsetPagination(偏移分页)
- 偏移分页,该分页组件作用是:从哪一条数据之后开始显示,以及制定数据显示的条数
- 前端访问形式:
http://127.0.0.1:8080/book/?offset=2&limit=4
,这表示从第3条数据之后显示4条数据
| from rest_framework.pagination import LimitOffsetPagination |
| |
| class CommonLimitOffsetPagination(LimitOffsetPagination): |
| |
| default_limit = 2 |
| limit_query_param = 'limit' |
| '每页显示条数,查询的条数 例子 ?limit=100 意思就是每页显示100条,如果不传应用default_limit的参数' |
| offset_query_param = 'offset' |
| '偏移量 举个例子offset=6&limit=30 意思就是从第6条开始,拿30条' |
| |
| max_limit = 5 |
- 从第二条数据之后开始指定获取数据的条数,使用了
limit
指定了获取6条,但是被max_limit=5
给限制了,所以后端只能返回2条数据,如果不使用limit
指定获取的数据条数,那么默认使用default_limit=2
后端会返回2条数据。
3、分页器三:CursorPagination(游标分页)
- 使用游标分页的方式后就不能再其中使用排序了,因为其内部已经排好序了'
- 并且只能选择上一页和下一页,不能指定跳转某一页,但是速度快,针对与特别大的数据量,游标分页有优势
| from rest_framework.pagination import CursorPagination |
| class CommonCursorPagination(CursorPagination): |
| cursor_query_param = 'cursor' |
| page_size = 2 |
| ordering = 'id' |
4、视图类
| class BookListView(GenericViewSet, ListModelMixin): |
| queryset = Book.objects.all() |
| serializer_class = BookSerializer |
| permission_classes = [] |
| throttle_classes = [] |
| |
| |
| pagination_class = CommonCursorPagination |
5、了解 基于APIView实现分页功能
| |
| from rest_framework.viewsets import ViewSet |
| from .pagination import CommonPageNumberPagination,CommonLimitOffsetPagination |
| from .serializer import BookSerializer |
| from rest_framework.response import Response |
| |
| class BookView(ViewSet): |
| def list(self, reqeust): |
| queryset = models.Book.objects.all() |
| |
| '''使用PageNumberPagination(普通分页)''' |
| |
| '''使用LimitOffsetPagination(偏移分页)''' |
| |
| '''使用CursorPagination(游标分页)''' |
| pagenation = CommonCursorPagination() |
| page = pagenation.paginate_queryset(queryset, reqeust, self) |
| serializer = BookSerializer(page, many=True) |
| '''使用它自己的方法(三种分页方式都兼容)''' |
| |
| ''' |
| 使用定制的分页返回格式,得去pagination源码里面的对应每种分页方法中的 |
| def get_paginated_response(self, data):方法里面看返回格式怎么写 |
| ''' |
| '''也可以自己定制PageNumberPagination(普通分页)''' |
| return Response({ |
| 'status': 100, |
| 'message': '查询成功', |
| 'count': pagenation.page.paginator.count, |
| 'next': pagenation.get_next_link(), |
| 'previous': pagenation.get_previous_link(), |
| 'results': serializer.data, |
| }) |
| '''定制LimitOffsetPagination''' |
| return Response({ |
| 'status': 100, |
| 'message': '查询成功', |
| 'count': pagenation.count, |
| 'next': pagenation.get_next_link(), |
| 'previous': pagenation.get_previous_link(), |
| 'results': serializer.data, |
| }) |
| |
| '''定制CursorPagination''' |
| return Response({ |
| 'status': 100, |
| 'message': '查询成功', |
| 'next': pagenation.get_next_link(), |
| 'previous': pagenation.get_previous_link(), |
| 'results': serializer.data, |
| }) |
七、补充
1、断言
- 在python库的源码中经常见到断言,那么怎么理解断言呢?
| |
| assert 条件,'字符串' |
| |
| |
| if 条件: |
| 条件成立,继续走后续代码 |
| else: |
| raise Exception('字符串') |
| a = '1' |
| assert isinstance(a,int),'不行,a必须是int' |

源码中使用:
| assert isinstance(request, HttpRequest), ( |
| 'The `request` argument must be an instance of ' |
| '`django.http.HttpRequest`, not `{}.{}`.' |
| .format(request.__class__.__module__, request.__class__.__name__) |
| ) |
2、模块与包
(1)什么是模块?
- 一个py 文件,如果被导入使用,它就是模块
- 一个py文件,点右键运行,它就叫 脚本文件
(2)什么是包?
- 一个文件夹下 有 init.py ,下面又有很多文件夹和py文件,导入使用的
(3)报错信息
| ModuleNotFoundError: No module named 'xx' |
- 但是这个模块有
- 那么我们就应该知道是导入的问题:路径不对
3、相对导入和绝对导入
在Python中,相对导入和绝对导入是用于导入模块的两种不同方式。它们的主要区别在于导入模块时使用的路径方式。
(1)绝对导入
-
绝对导入是指从顶层包开始的完整导入路径,相对于环境变量 sys.path
。在绝对导入中,你需要指定完整的包路径来导入模块,从项目的根目录开始一直到目标模块。绝对导入可以通过使用绝对导入语法来实现,例如:
| from package.subpackage import module |
| |
| |
| ['D:\\PyCharm 2023.2.1\\workspace\\drf05', 'D:\\PyCharm 2023.2.1\\workspace\\drf05', 'D:\\PyCharm 2023.2.1\\plugins\\python\\helpers\\pycharm_display', 'D:\\python\\python311\\python311.zip', 'D:\\python\\python311\\Lib', 'D:\\python\\python311\\DLLs', 'D:\\python\\python311', 'D:\\python\\python311\\Lib\\site-packages', 'D:\\PyCharm 2023.2.1\\plugins\\python\\helpers\\pycharm_matplotlib_backend'] |
| 其中 |
| 1.项目根路径 |
| 2.site-packages 下载的第三方模块 |
| 3.python内置的,都在环境变量中 |
-
绝对导入的优点是清晰明确,能够避免模块名冲突,并且在代码重构时更加稳定。
(2)相对导入
(3)Python中相对导入的规则
- 单个点
.
表示当前目录。
- 两个点
..
表示父目录。
- 相对导入只能在Python包中使用,而不能在脚本中使用。因为如果脚本文件
- Python 3中默认禁用隐式相对导入,必须使用显式相对导入。
在实际开发中,通常建议使用绝对导入,因为它更加明确和稳定。相对导入在某些情况下可能会引起混乱,尤其是当项目结构较为复杂时。然而,相对导入在某些特定情况下也是很有用的,特别是在编写可移植的包或模块时。
(4)建议
- django项目,如果在同一个app下
- 建议:相对导入
from .views import index
- 如果用绝对导入,会相对来说要写很多路径
from app01.views import index
4、修改项目名字出现的问题及解决方案
(1)改文件夹名改不了
(2)改项目名改不了
- 项目路径下的 .idea文件夹----->项目配置
- 如果打开项目有问题----> 把 .idea删除---->再打开重新运行,它会再次生成的
- 注意:当我们打包文件的时候,.idea文件夹是不用加进去的,因为每个电脑的配置都不一样
(3)改项目中某个文件的名字(慎改)
- 首先模块路径都定好了
- 如果我们一旦改了----> 项目百分百运行不了
- 如果改了,那么我们需要右击项目----> 找到replace in files---> 替换掉项目中所有叫原来文件夹名的 字符串
(4)问题
- 如果Django项目运行不了了
- 尝试django-server删除重新创建
- setting中搜django---> 配置项目路径和配置文件路径

5、浏览器特点
- 浏览器默认访问http是80端口,https是443端口
- 先访问某个页面【域: 协议,地址,端口】,然后再访问另一个页面,只要域一样就会把当时存在cookie中的值,带到后端
- 这种思想方便我们写登录接口
- 登录接口:自定义用户表,UserToken表[用户登录信息存在后端--》本质就是django-session]
- 登录成功--》返回给前端随机字符串
- 目的:后期它再访问,要携带字符串---》我们通过校验--》确认是我们给的,在UserToken表中有记录
6、什么是重写,什么是派生,什么又是重载?
-
在继承关系下,子类再写一遍父类中的方法---》称之为重写
-
在继承关系下,子类写父类中没有的方法---》称之为派生方法
-
重载就是方法名一样,参数或返回值不一样----》多了参数或者返回值不一样
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现