1 # models.py
2 from django.db import models
3
4 from django.contrib.auth.models import AbstractUser
5
6
7 class User(AbstractUser):
8 telephone = models.CharField(max_length=11)
9 icon = models.ImageField(upload_to='icon', default='default.png')
10
11
12 # class Book(models.Model):
13 # title = models.CharField(max_length=64)
14 # price = models.DecimalField(max_digits=5, decimal_places=2)
15 # sex = models.IntegerField(choices=((1, '男'), (2, '女')), blank=True, null=True)
16 # author = models.CharField(max_length=64)
17 # is_delete = models.BooleanField(default=False, blank=True, null=True)
18 #
19 # def __str__(self):
20 # return self.get_sex_display()
21
22 class BaseModels(models.Model):
23 create_time = models.DateTimeField(auto_now_add=True)
24 last_time = models.DateTimeField(auto_now=True)
25 is_delete = models.BooleanField(default=False)
26
27 class Meta:
28 abstract = True
29
30
31 class Book1(BaseModels):
32 title = models.CharField(max_length=64)
33 price = models.DecimalField(max_digits=5, decimal_places=2)
34 author = models.ManyToManyField(to='Author', db_constraint=False)
35 publish = models.ForeignKey(to='Publish', on_delete=models.DO_NOTHING, db_constraint=False)
36
37 class Meta:
38 verbose_name_plural = '书名'
39
40 def __str__(self):
41 return self.title
42
43 @property
44 def publish_name(self):
45 return self.publish.name
46
47 @property
48 def author_list(self):
49 author_list = self.author.all()
50 li = []
51 for author in author_list:
52 li.append({'name': author.name, 'sex': author.get_sex_display()})
53 return li
54
55
56 class Publish(BaseModels):
57 name = models.CharField(max_length=64)
58 add = models.CharField(max_length=64)
59
60 class Meta:
61 verbose_name_plural = '出版社'
62
63 def __str__(self):
64 return self.name
65
66
67 class Author(BaseModels):
68 name = models.CharField(max_length=64)
69 sex = models.IntegerField(choices=((1, '男'), (2, '女')))
70 author_details = models.OneToOneField(to='AuthorDetails', db_constraint=False, on_delete=models.CASCADE)
71
72 class Meta:
73 verbose_name_plural = '作者'
74
75 def __str__(self):
76 return self.name
77
78
79 class AuthorDetails(BaseModels):
80 telephone = models.CharField(max_length=11)
81
82 class Meta:
83 verbose_name_plural = '作者详情'
84 # urls.py
85 from django.contrib import admin
86 from django.urls import path, re_path, include
87 from django.views.static import serve
88 from django.conf import settings
89 from rest_framework.routers import SimpleRouter
90 from app01 import views
91
92 router = SimpleRouter()
93 # 群单 单增
94 router.register('list_create', views.BookGenericViewSet, 'list_create')
95 # 用户注册
96 router.register('register', views.RegisterGenericViewSet, 'register')
97 # # 单群改
98 # router.register('', views.PutSGenericViewSet, 'puts')
99 # 删
100 # router.register('', views.DeletesGenericViewSet, 'delete')
101
102 # 群增
103 router.register('', views.PostsGenericViewSet, 'posts')
104 # 登录
105 router.register('', views.loginViewSet, 'login')
106 urlpatterns = [
107
108 path('admin/', admin.site.urls),
109 # # 删除
110 path('deletes/', views.DeletesGenericViewSet.as_view()),
111 re_path(r'^deletes/(?P<pk>\d+)/$', views.DeletesGenericViewSet.as_view()),
112
113 # # 单改群改
114 path('puts/', views.PutSGenericViewSet.as_view()),
115 re_path(r'^puts/(?P<pk>\d+)/$', views.PutSGenericViewSet.as_view()),
116
117 # 头像
118 re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
119
120 path('', include(router.urls)),
121
122 ]
123 # views.py
124 from django.shortcuts import render
125 from rest_framework.views import APIView
126 from rest_framework.generics import GenericAPIView
127 from rest_framework.viewsets import ViewSet, GenericViewSet
128 from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin
129
130 from rest_framework.response import Response
131 from rest_framework.decorators import action
132
133 from rest_framework.filters import OrderingFilter
134 from django_filters.rest_framework import DjangoFilterBackend
135 from rest_framework.throttling import UserRateThrottle
136
137 from . import models
138 from . import utils
139 from . import serializer
140 from .responses import comm_Response
141
142
143 # 登录
144 class loginViewSet(ViewSet):
145 @action(methods=['post'], detail=False)
146 def login(self, request, *args, **kwargs):
147 login_serializer = serializer.LoginModelSerializer(data=request.data)
148 if login_serializer.is_valid(raise_exception=True):
149 token = login_serializer.context['token']
150 username = login_serializer.context['user'].username
151 return Response(data={'code': 100, 'mag': '成功', 'token': token, 'username': username})
152 else:
153 return Response(data=login_serializer.errors)
154
155
156 # 注册接口
157 class RegisterGenericViewSet(GenericViewSet, CreateModelMixin):
158 queryset = models.User.objects.all()
159 serializer_class = serializer.RegisterModelSerializer
160
161 # def create(self, request, *args, **kwargs):
162 # response_data = self.get_serializer(request.data)
163
164
165 # book 单群查询 新增
166 class BookGenericViewSet(GenericViewSet, ListModelMixin, CreateModelMixin, RetrieveModelMixin):
167 authentication_classes = [utils.comm_BaseAuthentication]
168
169 permission_classes = [utils.comm_BasePermission]
170 # 内置可以和自定义混合使用
171 throttle_classes = [UserRateThrottle]
172 # 配置分页
173 pagination_class = utils.comm_PageNumberPagination
174 # filter_backends = [] # 过滤 排序text
175 filter_backends = [DjangoFilterBackend, OrderingFilter]
176 filter_fields = ('id', 'title')
177 ordering_fields = ('id',)
178
179 queryset = models.Book1.objects.all().filter(is_delete=False)
180 serializer_class = serializer.BookModelSerializer
181
182
183 # 群增
184 # class PostsGenericViewSet(GenericAPIView):
185 class PostsGenericViewSet(GenericViewSet):
186 queryset = models.Book1.objects.all()
187 serializer_class = serializer.BookModelSerializer
188
189 # 群增
190 @action(methods=['post'], detail=False)
191 def posts(self, request, *args, **kwargs):
192 # book_serializer = serializer.BookModelSerializer(data=request.data, many=True)
193 book_serializer = self.serializer_class(data=request.data, many=True)
194 if book_serializer.is_valid():
195 book_serializer.save()
196 return comm_Response(code=100, msg='成功', data=book_serializer.data)
197 return comm_Response(code=0, msg='失败', data=book_serializer.errors)
198
199
200 # 单改 群改 # 手动配路由
201 class PutSGenericViewSet(GenericAPIView):
202 # queryset = models.Book1.objects.filter(is_delete=False)
203 queryset = models.Book1.objects.all()
204 serializer_class = serializer.BookModelSerializer
205
206 def put(self, request, *args, **kwargs):
207 # 单改,
208 # 以pk判断 有为单改 无为群改
209 if kwargs.get('pk', None):
210 book = models.Book1.objects.filter(pk=kwargs.get('pk'), is_delete=False).first()
211 if book:
212 book_serializer = serializer.BookModelSerializer(instance=book, data=request.data)
213 if book_serializer.is_valid():
214 book_serializer.save()
215 return comm_Response(code=100, msg='成功', result=book_serializer.data)
216 else:
217 return comm_Response(code=0, msg='失败', result=book_serializer.errors)
218 else:
219 return comm_Response(code=0, msg='该书已经删除,无法修改,请联系管理员处理')
220
221 # 群改 [{'id':1,'name':cc},{}]---[{'name':cc},{}]
222 else:
223 if isinstance(request.data, list):
224 # 前端传过来的数据
225 books = []
226 validates = []
227 for dic in request.data:
228 pk = dic.pop('id')
229 book = models.Book1.objects.filter(pk=pk).first()
230 books.append(book)
231 validates.append(dic)
232 book_serializer = serializer.BookModelSerializer(instance=books, data=validates, many=True)
233 if book_serializer.is_valid():
234 book_serializer.save() # list 的save
235 return comm_Response(code=100, msg='成功', result=book_serializer.data)
236
237 else:
238 return comm_Response(code=0, msg='失败', result=book_serializer.errors)
239 else:
240 return comm_Response(code=0, msg='失败,请按照格式传')
241
242 # 删除 手动配路由
243 class DeletesGenericViewSet(APIView):
244
245 def delete(self, request, *args, **kwargs):
246 pks = []
247 pk = kwargs.get('pk', None)
248 if pk:
249 pks.append(pk)
250 else:
251 pks = request.data.get('pk', None)
252 nums = models.Book1.objects.filter(pk__in=pks, is_delete=False).update(is_delete=True)
253 if nums:
254 return comm_Response(code=100, msg='成功', nums=f'删除数据{nums}条')
255 else:
256 return comm_Response(code=0, msg='失败', nums=f'删除数据{nums}条')
257
258 # response.py
259 from rest_framework.response import Response
260
261
262 class comm_Response(Response):
263 def __init__(self, result=None, code=None, msg='成功', status=None, headers=None, content_type=None, **kwargs):
264 dic = {
265 'msg': msg,
266 'code': code,
267 }
268 if result:
269 dic['result'] = result
270 dic.update(kwargs)
271 super(comm_Response, self).__init__(data=dic, status=status, headers=headers, content_type=content_type)
272
273 # excetions.py
274 from rest_framework.views import exception_handler
275 from .responses import comm_Response
276
277
278 def comm_exception_handler(exc, context):
279 ret = exception_handler(exc, context)
280 if not ret:
281 if isinstance(exc, KeyError):
282 return comm_Response(code=0, result=str(exc), msg='key_error')
283 return comm_Response(code=0, result=str(exc), msg='error')
284 return comm_Response(code=0, result=ret.data, msg='error')
285 # utils.py
286 from rest_framework.authentication import BaseAuthentication
287 from rest_framework_jwt.serializers import jwt_decode_handler
288 from rest_framework.exceptions import AuthenticationFailed
289 from rest_framework.pagination import PageNumberPagination
290
291 from . import models
292 import jwt
293
294 # class comm_BaseAuthentication(BaseAuthentication):
295 # def authenticate(self, request):
296 # # 从头处获取Authentication
297 # jwt_value = request.META.get('HTTP_AUTHORIZATION')
298 # if jwt_value:
299 # # 反解出user对象(不完整)
300 # try:
301 # payload = jwt_decode_handler(jwt_value)
302 # # payload--user
303 # # print(payload)
304 # # return payload, jwt_value
305 # # 1.查库 or User对象i
306 # except jwt.ExpiredSignature:
307 # raise AuthenticationFailed('签名过期')
308 # except jwt.InvalidTokenError:
309 # raise AuthenticationFailed('用户非法')
310 # except Exception as e:
311 # # 所有异常都会走到这
312 # raise AuthenticationFailed(str(e))
313 # ret = models.User(id=payload.get('user_id'), username=payload.get('username'))
314 # if ret:
315 # return ret, payload
316 # else:
317 # raise AuthenticationFailed('用户不存在')
318 # else:
319 # raise AuthenticationFailed('没有携带token')
320
321 from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication # 基于它
322
323
324 # jwt 认证
325 class comm_BaseAuthentication(BaseJSONWebTokenAuthentication):
326 def authenticate(self, request):
327 # 从头处获取Authentication
328 jwt_value = request.META.get('HTTP_AUTHORIZATION')
329 if jwt_value:
330 # 反解出user对象(不完整)
331 try:
332 payload = jwt_decode_handler(jwt_value)
333 # payload--user
334 # print(payload)
335 # return payload, jwt_value
336 # 1.查库 or User对象i
337 except jwt.ExpiredSignature:
338 raise AuthenticationFailed('签名过期')
339 except jwt.InvalidTokenError:
340 raise AuthenticationFailed('用户非法')
341 except Exception as e:
342 # 所有异常都会走到这
343 raise AuthenticationFailed(str(e))
344 user = self.authenticate_credentials(payload)
345 # user--对象
346 # print(user)
347 return user, payload
348 else:
349 raise AuthenticationFailed('没有携带token')
350
351
352 # 分页
353 class comm_PageNumberPagination(PageNumberPagination):
354 page_size = 10 # 每页条数
355 page_query_param = 'page' # 查询第几页的key
356 page_size_query_param = 'size' # 每一页显示的条数
357 max_page_size = 5 # 每页最大显示条数
358
359
360 # 权限
361 from rest_framework.permissions import BasePermission
362
363
364 class comm_BasePermission(BasePermission):
365
366 def has_permission(self, request, view):
367 user = request.user
368 if not user.is_staff == 1:
369 raise AuthenticationFailed('没有权限,请付费观看')
370 return True
371
372
373 # 频率限制
374 from rest_framework.throttling import SimpleRateThrottle
375
376
377 # 自定义的逻辑
378 # (1)取出访问者ip
379 # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
380 # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
381 # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
382 # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
383 class MyThrottles():
384 VISIT_RECORD = {}
385
386 def __init__(self):
387 self.history = None
388
389 def allow_request(self, request, view):
390 # (1)取出访问者ip
391 # print(request.META)
392 ip = request.META.get('REMOTE_ADDR')
393 import time
394 ctime = time.time()
395 # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
396 if ip not in self.VISIT_RECORD:
397 self.VISIT_RECORD[ip] = [ctime, ]
398 return True
399 self.history = self.VISIT_RECORD.get(ip)
400 # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
401 while self.history and ctime - self.history[-1] > 60:
402 self.history.pop()
403 # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
404 # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
405 if len(self.history) < 3:
406 self.history.insert(0, ctime)
407 return True
408 else:
409 return False
410
411 def wait(self):
412 import time
413 ctime = time.time()
414 return 60 - (ctime - self.history[-1])
415
416 # serializer.py
417 from rest_framework import serializers
418 from . import models
419
420 import re
421 import uuid
422 from rest_framework.exceptions import ValidationError
423 from rest_framework_jwt.serializers import jwt_encode_handler, jwt_payload_handler
424
425
426 # 多方式登录--手机号 邮箱 账号
427 class LoginModelSerializer(serializers.ModelSerializer):
428 username = serializers.CharField()
429
430 class Meta:
431 model = models.User
432 fields = ['username', 'password']
433 extra_kwargs = {
434 'password': {'write_only': True},
435 'username': {'read_only': True}
436 }
437
438 def validate(self, attrs):
439 user = self._get_user(attrs)
440 token = self._get_token(user)
441 self.context['user'] = user
442 self.context['token'] = token
443 return attrs
444
445 def _get_user(self, attrs):
446 username = attrs.get('username')
447 if re.match('^1[3-9][0-9]{9}$', username):
448 user = models.User.objects.filter(telephone=username).first()
449 elif re.match(r'^[1-9]\d{7,10}@qq\.com$', username):
450 user = models.User.objects.filter(email=username).first()
451 else:
452 user = models.User.objects.filter(username=username).first()
453 if user:
454 password = attrs.get('password')
455 ret = user.check_password(password)
456 if ret:
457 return user
458 else:
459 raise ValidationError('密码错误')
460 else:
461 ValidationError('用户不存在')
462
463 def _get_token(self, user):
464 payload = jwt_payload_handler(user)
465 token = jwt_encode_handler(payload)
466 return token
467
468
469 # 注册
470 class RegisterModelSerializer(serializers.ModelSerializer):
471 re_password = serializers.CharField(write_only=True)
472
473 class Meta:
474 model = models.User
475 fields = ['username', 'password', 're_password']
476 extra_kwargs = {
477 # 're_password': {'write_only': True},
478 'username': {'read_only': True}
479 }
480
481 def validate(self, attrs):
482 password = attrs.get('password')
483 re_password = attrs.get('re_password')
484 username = attrs.get('username')
485 if password == re_password:
486 ret = models.User.objects.filter(username=username).first()
487 if not ret:
488 attrs.pop('re_password')
489 username = uuid.uuid4()
490 attrs['username'] = username
491 return attrs
492 else:
493 raise ValidationError('用户已经注册')
494 else:
495 raise ValidationError('两次密码输入不一致')
496
497 def create(self, validated_data):
498 print(validated_data)
499 # validated_data.pop('validated_data')
500 user = models.User.objects.create_user(**validated_data)
501 return user
502
503
504 # 重写update
505 class BookListSerializer(serializers.ListSerializer):
506 def update(self, instance, validated_data):
507 return [
508 self.child.update(instance[i], attrs) for i, attrs in enumerate(validated_data)
509 ]
510
511
512 # 书
513 class BookModelSerializer(serializers.ModelSerializer):
514 class Meta:
515 model = models.Book1
516 fields = ['id', 'title', 'price', 'publish', 'publish_name', 'author', 'author_list']
517 # depth=1
518 list_serializer_class = BookListSerializer
519 extra_kwargs = {
520 'publish': {'write_only': True},
521 'publish_name': {'read_only': True},
522 'author': {'write_only': True},
523 'author_list': {'read_only': True},
524 'id': {'read_only': True},
525 }
526
527 # admin.py
528 from django.contrib import admin
529 from . import models
530
531 admin.site.register(models.Book1)
532 admin.site.register(models.Publish)
533 admin.site.register(models.Author)
534 admin.site.register(models.AuthorDetails)
535 admin.site.register(models.User)
536
537 # settings.py
538 INSTALLED_APPS = [
539
540 'app01.apps.App01Config',
541 'rest_framework',
542 'rest_framework_jwt',
543 'django_filters',
544 ]
545
546 LANGUAGE_CODE = 'zh-hans'
547
548 TIME_ZONE = 'Asia/shanghai'
549
550 USE_I18N = True
551
552 USE_L10N = True
553
554 USE_TZ = False
555
556 # Static files (CSS, JavaScript, Images)
557 # https://docs.djangoproject.com/en/2.2/howto/static-files/
558
559 STATIC_URL = '/static/'
560
561 # 图像
562 MEDIA_URL = '/media/'
563 MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
564 # user表
565 AUTH_USER_MODEL = 'app01.user'
566
567 import datetime
568
569 JWT_AUTH = {
570 # 过期时间1天
571 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
572 # 自定义认证结果:见下方序列化user和自定义response
573 # 如果不自定义,返回的格式是固定的,只有token字段
574 # 'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
575 }
576
577 # 全局异常
578 REST_FRAMEWORK = {
579 'EXCEPTION_HANDLER': 'app01.exception.comm_exception_handler',
580 'DEFAULT_THROTTLE_RATES': {
581 'user': '5/m'
582 }
583
584 }