#主项目urls.py中
from django.conf import settings
from django.contrib import admin
from django.urls import path, include
from rest_framework_swagger.views import get_swagger_view
schema_view = get_swagger_view(title='项目接口文档')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
path('api/docs/', schema_view),
]
# urlpatterns += static(settings.MEDIA_URL,
# document_root=settings.MEDIA_ROOT)
#
if settings.DEBUG:
import debug_toolbar
urlpatterns.insert(0, path('__debug__/', include(debug_toolbar.urls)))
#settings.py中修改
# djangorestframework的配置
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
# # 配置默认页面大小
'PAGE_SIZE': 5,
# 配置默认的分页类
'DEFAULT_PAGINATION_CLASS': 'api.helpers.CustomPagePagination',}
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'debug_toolbar',
'rest_framework',
'rest_framework_swagger',
'django_filters',
'common',
'api',
]
#api接口下
#新建consts.py,代码如下
MAX_PHOTO_SIZE = int(2.5 * 1024 * 1024)
MOBILE_CODE_SUCCESS = (100001, '短信验证码发送成功')
CODE_TOO_FREQUENCY = (100002, '请不要在120秒以内重复发送手机验证码')
INVALID_TEL_NUM = (100003, '请提供有效的手机号')
USER_LOGIN_SUCCESS = (200001, '用户登录成功')
USER_LOGIN_FAILED = (200002, '用户名或密码错误')
INVALID_LOGIN_INFO = (200003, '请输入有效的登录信息')
FILE_UPLOAD_SUCCESS = (300001, '文件上传成功')
FILE_SIZE_EXCEEDED = (300002, f'未指定上传文件或文件大小超过{MAX_PHOTO_SIZE}字节')
#serialiezers.py中
import ujson
from django.core.cache import caches
from django.db.models import Q
from django.db.transaction import atomic
from django_redis import get_redis_connection
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from common.models import District, Agent, Estate, HouseType, Tag, \
HouseInfo, HousePhoto, User, Role, UserRole
from common.utils import to_md5_hex
from common.validators import USERNAME_PATTERN, TEL_PATTERN, EMAIL_PATTERN
class DistrictSimpleSerializer(serializers.ModelSerializer):
"""地区简单序列化器"""
class Meta:
model = District
fields = ('distid', 'name')
class DistrictDetailSerializer(serializers.ModelSerializer):
"""地区详情序列化器"""
cities = serializers.SerializerMethodField()
@staticmethod
def get_cities(district):
redis_cli = get_redis_connection()
data = redis_cli.get(f'izufang:district:{district.distid}:cities')
if data:
data = ujson.loads(data)
else:
queryset = District.objects.filter(parent=district).only('name')
data = DistrictSimpleSerializer(queryset, many=True).data
redis_cli.set(f'izufang:district:{district.distid}:cities',
ujson.dumps(data), ex=900)
return data
class Meta:
model = District
exclude = ('parent', )
class AgentSimpleSerializer(serializers.ModelSerializer):
"""经理人简单序列化器"""
class Meta:
model = Agent
fields = ('agentid', 'name', 'tel', 'servstar')
class AgentCreateSerializer(serializers.ModelSerializer):
"""创建经理人序列化器"""
class Meta:
model = Agent
exclude = ('estates', )
class AgentDetailSerializer(serializers.ModelSerializer):
"""经理人详情序列化器"""
estates = serializers.SerializerMethodField()
@staticmethod
def get_estates(agent):
queryset = agent.estates.all().order_by('-hot')[:5]
return EstateSimpleSerializer(queryset, many=True).data
class Meta:
model = Agent
fields = '__all__'
class EstateSimpleSerializer(serializers.ModelSerializer):
"""楼盘简单序列化器"""
class Meta:
model = Estate
fields = ('estateid', 'name', 'hot')
class EstateCreateSerializer(serializers.ModelSerializer):
"""创建楼盘序列化器"""
class Meta:
model = Estate
fields = '__all__'
class EstateDetailSerializer(serializers.ModelSerializer):
"""楼盘详情序列化器"""
district = DistrictSimpleSerializer()
class Meta:
model = Estate
fields = '__all__'
class HouseTypeSerializer(serializers.ModelSerializer):
"""户型序列化器"""
class Meta:
model = HouseType
fields = '__all__'
class TagSerializer(serializers.ModelSerializer):
"""房源标签序列化器"""
class Meta:
model = Tag
fields = '__all__'
class HouseInfoSimpleSerializer(serializers.ModelSerializer):
"""房源基本信息序列化器"""
mainphoto = serializers.SerializerMethodField()
district = serializers.SerializerMethodField()
type = HouseTypeSerializer()
tags = TagSerializer(many=True)
@staticmethod
def get_mainphoto(houseinfo):
return '/media/images/' + houseinfo.mainphoto
@staticmethod
def get_district(houseinfo):
return DistrictSimpleSerializer(houseinfo.district_level3).data
class Meta:
model = HouseInfo
fields = ('houseid', 'title', 'area', 'floor', 'totalfloor',
'price', 'priceunit', 'mainphoto', 'street',
'district', 'type', 'tags')
class HouseInfoCreateSerializer(serializers.ModelSerializer):
"""创建房源序列化器"""
class Meta:
model = HouseInfo
fields = '__all__'
class HouseInfoDetailSerializer(serializers.ModelSerializer):
"""房源详情序列化器"""
photos = serializers.SerializerMethodField()
district = serializers.SerializerMethodField()
type = HouseTypeSerializer()
agent = AgentSimpleSerializer()
estate = EstateSimpleSerializer()
tags = TagSerializer(many=True)
@staticmethod
def get_photos(houseinfo):
queryset = HousePhoto.objects.filter(house=houseinfo)
return HousePhotoSerializer(queryset, many=True).data
@staticmethod
def get_district(houseinfo):
return DistrictSimpleSerializer(houseinfo.district_level3).data
class Meta:
model = HouseInfo
exclude = ('district_level2', 'district_level3', 'user')
class HousePhotoSerializer(serializers.ModelSerializer):
"""房源照片序列化器"""
class Meta:
model = HousePhoto
fields = ('photoid', 'path')
class UserSimpleSerializer(serializers.ModelSerializer):
"""用户简单序列化器"""
class Meta:
model = User
exclude = ('password', 'roles')
class UserUpdateSerializer(serializers.ModelSerializer):
"""更新用户序列化器"""
class Meta:
model = User
fields = ('realname', 'tel', 'email', 'sex')
class UserCreateSerializer(serializers.ModelSerializer):
"""创建用户序列化器"""
username = serializers.RegexField(regex=USERNAME_PATTERN)
password = serializers.CharField(min_length=6)
realname = serializers.RegexField(regex=r'[\u4e00-\u9fa5]{2,20}')
tel = serializers.RegexField(regex=TEL_PATTERN)
email = serializers.RegexField(regex=EMAIL_PATTERN)
code = serializers.CharField(write_only=True, min_length=6, max_length=6)
def validate(self, attrs):
code_from_user = attrs['code']
code_from_redis = caches['default'].get(f'{attrs["tel"]}:valid')
if code_from_redis != code_from_user:
raise ValidationError('请输入有效的手机验证码', 'invalid')
user = User.objects.filter(Q(username=attrs['username']) |
Q(tel=attrs['tel']) |
Q(email=attrs['email']))
if user:
raise ValidationError('用户名、手机或邮箱已被注册', 'invalid')
return attrs
def create(self, validated_data):
del validated_data['code']
caches['default'].delete(f'{validated_data["tel"]}:valid')
validated_data['password'] = to_md5_hex(validated_data['password'])
with atomic():
user = User.objects.create(**validated_data)
role = Role.objects.get(roleid=1)
UserRole.objects.create(user=user, role=role)
return user
class Meta:
model = User
exclude = ('userid', 'regdate', 'point', 'lastvisit', 'roles')
class RoleSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Role
fields = ('roleid', )
#新建helpers.py中
from functools import lru_cache
import jwt
from django.db.models import Q, Prefetch
from django_filters import filterset
from jwt import InvalidTokenError
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.pagination import PageNumberPagination, CursorPagination
from rest_framework.response import Response
from common.models import Estate, HouseInfo, User, Role
from izufang.settings import SECRET_KEY
class DefaultResponse(Response):
"""定义返回JSON数据的响应类"""
def __init__(self, code=100000, message='操作成功',
data=None, status=None, template_name=None,
headers=None, exception=False, content_type=None):
_data = {'code': code, 'message': message}
if data:
_data.update(data)
super().__init__(_data, status, template_name,
headers, exception, content_type)
class LoginRequiredAuthentication(BaseAuthentication):
"""登录认证"""
# 如果用户身份验证成功需要返回一个二元组(user, token)
def authenticate(self, request):
token = request.META.get('HTTP_TOKEN')
if token:
try:
payload = jwt.decode(token, SECRET_KEY)
user = User()
user.userid = payload['data']['userid']
user.is_authenticated = True
return user, token
except InvalidTokenError:
raise AuthenticationFailed('无效的令牌或令牌已过期')
raise AuthenticationFailed('请提供用户身份令牌')
# class RbacPermission(BasePermission):
# """RBAC授权"""
#
# # 返回True表示有操作权限,返回False表示没有操作权限
# def has_permission(self, request, view):
# privs = get_privs_by_userid(request.user.userid)
# for priv in privs:
# if request.path.startswith(priv.url) and \
# request.method == priv.method:
# return True
# return False
@lru_cache(maxsize=256)
def get_privs_by_userid(userid):
user = User.objects.filter(userid=userid)\
.prefetch_related(
Prefetch(
'roles',
queryset=Role.objects.all().prefetch_related('privs'))
).first()
return [priv for role in user.roles.all()
for priv in role.privs.all()]
class CustomPagePagination(PageNumberPagination):
"""自定义页码分页类"""
page_size_query_param = 'size'
max_page_size = 50
class AgentCursorPagination(CursorPagination):
"""经理人游标分页类"""
page_size_query_param = 'size'
max_page_size = 50
ordering = '-agentid'
class EstateFilterSet(filterset.FilterSet):
"""自定义楼盘筛选器"""
# filter(name__contains=name)
name = filterset.CharFilter(lookup_expr='contains')
# filter(hot__gte=minhot, hot__lte=maxhot)
minhot = filterset.NumberFilter(field_name='hot', lookup_expr='gte')
maxhot = filterset.NumberFilter(field_name='hot', lookup_expr='lte')
dist = filterset.NumberFilter(field_name='district')
class Meta:
model = Estate
fields = ('name', 'minhot', 'maxhot', 'dist')
class HouseInfoFilterSet(filterset.FilterSet):
"""自定义房源筛选器"""
title = filterset.CharFilter(lookup_expr='contains')
minprice = filterset.NumberFilter(field_name='price', lookup_expr='gte')
maxprice = filterset.NumberFilter(field_name='price', lookup_expr='lte')
minarea = filterset.NumberFilter(field_name='area', lookup_expr='gte')
maxarea = filterset.NumberFilter(field_name='area', lookup_expr='lte')
district = filterset.NumberFilter(method='filter_by_district')
@staticmethod
def filter_by_district(queryset, name, value):
return queryset.filter(Q(district_level2=value) |
Q(district_level3=value))
class Meta:
model = HouseInfo
fields = ('title', 'minprice', 'maxprice', 'minarea', 'maxarea', 'type', 'district')
#views.py中
import datetime
import os
import random
import jwt
import ujson
from django.core.cache import caches
from django.db.models import Prefetch, Q
from django.db.transaction import atomic
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django_filters.rest_framework import DjangoFilterBackend
from django_redis import get_redis_connection
from rest_framework.decorators import api_view, action
from rest_framework.filters import OrderingFilter
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from api.consts import MAX_PHOTO_SIZE, FILE_UPLOAD_SUCCESS, FILE_SIZE_EXCEEDED, CODE_TOO_FREQUENCY, MOBILE_CODE_SUCCESS, \
INVALID_TEL_NUM, USER_LOGIN_SUCCESS, USER_LOGIN_FAILED, INVALID_LOGIN_INFO
from api.helpers import EstateFilterSet, HouseInfoFilterSet, \
LoginRequiredAuthentication, DefaultResponse
from api.serializers import DistrictSimpleSerializer, DistrictDetailSerializer, \
AgentCreateSerializer, AgentDetailSerializer, AgentSimpleSerializer, \
HouseTypeSerializer, TagSerializer, EstateCreateSerializer, \
EstateDetailSerializer, EstateSimpleSerializer, HouseInfoDetailSerializer, \
HousePhotoSerializer, HouseInfoCreateSerializer, HouseInfoSimpleSerializer, \
UserCreateSerializer, UserUpdateSerializer, UserSimpleSerializer
from common.models import District, Agent, HouseType, Tag, User, LoginLog, \
HousePhoto, Estate, HouseInfo
from common.utils import gen_mobile_code, send_sms_by_luosimao, to_md5_hex, \
get_ip_address, upload_stream_to_qiniu
from common.validators import check_tel, check_username, check_email
from izufang.settings import SECRET_KEY
@api_view(('POST', ))
def upload_house_photo(request):
file_obj = request.FILES.get('mainphoto')
if file_obj and len(file_obj) < MAX_PHOTO_SIZE:
prefix = to_md5_hex(file_obj.file)
filename = f'{prefix}{os.path.splitext(file_obj.name)[1]}'
upload_stream_to_qiniu.delay(file_obj, filename, len(file_obj))
photo = HousePhoto()
photo.path = f'http://qea6re99o.bkt.clouddn.com/{filename}'
photo.ismain = True
photo.save()
resp = DefaultResponse(*FILE_UPLOAD_SUCCESS, data={
'photoid': photo.photoid,
'url': photo.path
})
else:
resp = DefaultResponse(*FILE_SIZE_EXCEEDED)
return resp
@api_view(('GET', ))
def get_code_by_sms(request, tel):
"""获取短信验证码"""
if check_tel(tel):
if caches['default'].get(f'{tel}:block'):
resp = DefaultResponse(*CODE_TOO_FREQUENCY)
else:
code = gen_mobile_code()
message = f'您的短信验证码是{code},打死也不能告诉别人哟!【Python小课】'
send_sms_by_luosimao.apply_async((tel, message),
countdown=random.random() * 5)
caches['default'].set(f'{tel}:block', code, timeout=120)
caches['default'].set(f'{tel}:valid', code, timeout=1800)
resp = DefaultResponse(*MOBILE_CODE_SUCCESS)
else:
resp = DefaultResponse(*INVALID_TEL_NUM)
return resp
@api_view(('POST', ))
def login(request):
"""登录(获取用户身份令牌)"""
username = request.data.get('username')
password = request.data.get('password')
if (check_username(username) or check_tel(username) or
check_email(username)) and len(password) >= 6:
password = to_md5_hex(password)
q = Q(username=username, password=password) | \
Q(tel=username, password=password) | \
Q(email=username, password=password)
user = User.objects.filter(q)\
.only('username', 'realname').first()
if user:
# 用户登录成功通过JWT生成用户身份令牌
payload = {
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1),
'data': {
'userid': user.userid,
}
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256').decode()
with atomic():
current_time = timezone.now()
if not user.lastvisit or \
(current_time - user.lastvisit).days >= 1:
user.point += 2
user.lastvisit = current_time
user.save()
loginlog = LoginLog()
loginlog.user = user
loginlog.logdate = current_time
loginlog.ipaddr = get_ip_address(request)
loginlog.save()
resp = DefaultResponse(*USER_LOGIN_SUCCESS, data={
'token': token, 'username': user.username, 'realname': user.realname
})
else:
resp = DefaultResponse(*USER_LOGIN_FAILED)
else:
resp = DefaultResponse(*INVALID_LOGIN_INFO)
return resp
@api_view(('GET', ))
@cache_page(timeout=365 * 86400)
def get_provinces(request):
"""获取省级行政单位"""
queryset = District.objects.filter(parent__isnull=True).only('name')
serializer = DistrictSimpleSerializer(queryset, many=True)
return Response({'results': serializer.data})
@api_view(('GET', ))
def get_district(request, distid):
"""获取地区详情"""
redis_cli = get_redis_connection()
data = redis_cli.get(f'izufang:district:{distid}')
if data:
data = ujson.loads(data)
else:
district = District.objects.filter(distid=distid).defer('parent').first()
data = DistrictDetailSerializer(district).data
redis_cli.set(f'izufang:district:{distid}', ujson.dumps(data), ex=900)
return Response(data)
@method_decorator(decorator=cache_page(timeout=86400), name='get')
class HotCityView(ListAPIView):
"""热门城市视图
get:
获取热门城市
"""
queryset = District.objects.filter(ishot=True).only('name')
serializer_class = DistrictSimpleSerializer
pagination_class = None
@method_decorator(decorator=cache_page(timeout=120), name='list')
@method_decorator(decorator=cache_page(timeout=300), name='retrieve')
class AgentViewSet(ModelViewSet):
"""经理人视图
list:
获取经理人列表
retrieve:
获取经理人详情
create:
创建经理人
update:
更新经理人信息
partial_update:
更新经理人信息
delete:
删除经理人
"""
queryset = Agent.objects.all()
def get_queryset(self):
name = self.request.GET.get('name')
if name:
self.queryset = self.queryset.filter(name__contains=name)
servstar = self.request.GET.get('servstar')
if servstar:
self.queryset = self.queryset.filter(servstar__gte=servstar)
if self.action == 'list':
self.queryset = self.queryset.only('name', 'tel', 'servstar')
else:
self.queryset = self.queryset.prefetch_related(
Prefetch('estates',
queryset=Estate.objects.all().only('name').order_by('-hot'))
)
return self.queryset.order_by('-servstar')
def get_serializer_class(self):
if self.action in ('create', 'update'):
return AgentCreateSerializer
return AgentDetailSerializer if self.action == 'retrieve' \
else AgentSimpleSerializer
@method_decorator(decorator=cache_page(timeout=86400), name='list')
@method_decorator(decorator=cache_page(timeout=86400), name='retrieve')
class HouseTypeViewSet(ModelViewSet):
"""户型视图集"""
queryset = HouseType.objects.all()
serializer_class = HouseTypeSerializer
pagination_class = None
@method_decorator(decorator=cache_page(timeout=3600), name='list')
class TagViewSet(ModelViewSet):
"""房源标签视图集"""
queryset = Tag.objects.all()
serializer_class = TagSerializer
@method_decorator(decorator=cache_page(timeout=300), name='list')
@method_decorator(decorator=cache_page(timeout=300), name='retrieve')
class EstateViewSet(ModelViewSet):
"""楼盘视图集"""
queryset = Estate.objects.all()
filter_backends = (DjangoFilterBackend, OrderingFilter)
# 只能做精确查询,而且多个条件间只能是而且的关系
# filter_fields = ('name', 'hot', 'district')
filterset_class = EstateFilterSet
ordering = '-hot'
ordering_fields = ('district', 'hot', 'name')
# authentication_classes = (LoginRequiredAuthentication, )
def get_queryset(self):
if self.action == 'list':
queryset = self.queryset.only('name', 'hot')
else:
queryset = self.queryset\
.defer('district__parent', 'district__ishot', 'district__intro')\
.select_related('district')
return queryset
def get_serializer_class(self):
if self.action in ('create', 'update'):
return EstateCreateSerializer
return EstateDetailSerializer if self.action == 'retrieve' \
else EstateSimpleSerializer
@method_decorator(decorator=cache_page(timeout=120), name='list')
@method_decorator(decorator=cache_page(timeout=300), name='retrieve')
class HouseInfoViewSet(ModelViewSet):
"""房源视图集"""
queryset = HouseInfo.objects.all()
serializer_class = HouseInfoDetailSerializer
filter_backends = (DjangoFilterBackend, OrderingFilter)
filterset_class = HouseInfoFilterSet
ordering = ('-pubdate', )
ordering_fields = ('pubdate', 'price')
@action(methods=('GET', ), detail=True)
def photos(self, request, pk):
queryset = HousePhoto.objects.filter(house=self.get_object())
return Response(HousePhotoSerializer(queryset, many=True).data)
def get_queryset(self):
if self.action == 'list':
return self.queryset\
.only('houseid', 'title', 'area', 'floor', 'totalfloor', 'price',
'mainphoto', 'priceunit', 'street', 'type',
'district_level3__distid', 'district_level3__name')\
.select_related('district_level3', 'type')\
.prefetch_related('tags')
return self.queryset\
.defer('user', 'district_level2',
'district_level3__parent', 'district_level3__ishot', 'district_level3__intro',
'estate__district', 'estate__hot', 'estate__intro',
'agent__realstar', 'agent__profstar', 'agent__certificated')\
.select_related('district_level3', 'type', 'estate', 'agent')\
.prefetch_related('tags')
def get_serializer_class(self):
if self.action in ('create', 'update'):
return HouseInfoCreateSerializer
return HouseInfoDetailSerializer if self.action == 'retrieve' \
else HouseInfoSimpleSerializer
class UserViewSet(ModelViewSet):
"""用户模型视图集"""
queryset = User.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return UserCreateSerializer
elif self.action == 'update':
return UserUpdateSerializer
return UserSimpleSerializer
#urls.py中
from django.urls import path
from rest_framework.routers import DefaultRouter
from api.views import get_provinces, get_district, HotCityView, \
AgentViewSet, HouseTypeViewSet, EstateViewSet, TagViewSet, \
HouseInfoViewSet, get_code_by_sms, login, UserViewSet, \
upload_house_photo
urlpatterns = [
path('photos/', upload_house_photo),
path('tokens/', login),
path('mobile/<str:tel>/', get_code_by_sms),
path('districts/', get_provinces),
path('districts/<int:distid>/', get_district),
path('hotcities/', HotCityView.as_view()),
]
router = DefaultRouter()
router.register('housetypes', HouseTypeViewSet)
router.register('estates', EstateViewSet)
router.register('agents', AgentViewSet)
router.register('tags', TagViewSet)
router.register('houseinfos', HouseInfoViewSet)
router.register('users', UserViewSet)
urlpatterns += router.urls