From e84567ad86e5f39a4c1bb22ad8b88002e1891325 Mon Sep 17 00:00:00 2001 From: Team Alice <> Date: Thu, 15 Nov 2018 06:28:11 +0800 Subject: [PATCH] --- .travis.yml | 18 ++ WeChatTicket/settings.py | 5 +- WeChatTicket/urls.py | 1 + adminpage/admin.py | 4 + adminpage/models.py | 2 +- adminpage/urls.py | 15 +- adminpage/views.py | 322 +++++++++++++++++++ codex/baseerror.py | 2 +- static/img/activityImage/readme.txt | 1 + templates/news.xml | 2 +- userpage/urls.py | 2 + userpage/views.py | 70 +++- wechat/handlers.py | 229 ++++++++++++- wechat/migrations/0003_auto_20181012_1915.py | 19 ++ wechat/migrations/0004_auto_20181016_0334.py | 20 ++ wechat/models.py | 13 +- wechat/views.py | 12 +- wechat/wrapper.py | 75 ++++- 18 files changed, 792 insertions(+), 20 deletions(-) create mode 100644 .travis.yml create mode 100644 static/img/activityImage/readme.txt create mode 100644 wechat/migrations/0003_auto_20181012_1915.py create mode 100644 wechat/migrations/0004_auto_20181016_0334.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..acea320 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: python +sudo: enabled +services: + - mysql +python: + - "3.5" +env: + global: + - TRAVIS=true +before_install: + - mysql -e 'CREATE DATABASE IF NOT EXISTS wechat_ticket;' + - mv configs.example.json configs.json +# command to install dependencies +install: + - pip install -r requirements.txt + +script: + python manage.py test diff --git a/WeChatTicket/settings.py b/WeChatTicket/settings.py index 706e469..07aa1d6 100644 --- a/WeChatTicket/settings.py +++ b/WeChatTicket/settings.py @@ -38,7 +38,10 @@ WECHAT_APPID = CONFIGS['WECHAT_APPID'] WECHAT_SECRET = CONFIGS['WECHAT_SECRET'] -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = [ + 'd4a939f0.ngrok.io', + 'localhost' +] # Application definition diff --git a/WeChatTicket/urls.py b/WeChatTicket/urls.py index c3e5a8a..94b86dd 100644 --- a/WeChatTicket/urls.py +++ b/WeChatTicket/urls.py @@ -15,6 +15,7 @@ """ from django.conf.urls import url, include from django.contrib import admin +from django.contrib.auth import authenticate, login from wechat.views import CustomWeChatView from WeChatTicket.views import StaticFileView diff --git a/adminpage/admin.py b/adminpage/admin.py index 8c38f3f..992ecaa 100644 --- a/adminpage/admin.py +++ b/adminpage/admin.py @@ -1,3 +1,7 @@ from django.contrib import admin +from wechat.models import * # Register your models here. + +admin.site.register(Activity) +admin.site.register(Ticket) \ No newline at end of file diff --git a/adminpage/models.py b/adminpage/models.py index 71a8362..d49766e 100644 --- a/adminpage/models.py +++ b/adminpage/models.py @@ -1,3 +1,3 @@ from django.db import models -# Create your models here. +# Create your models here. \ No newline at end of file diff --git a/adminpage/urls.py b/adminpage/urls.py index db56849..871a666 100644 --- a/adminpage/urls.py +++ b/adminpage/urls.py @@ -1,9 +1,20 @@ # -*- coding: utf-8 -*- # - +from adminpage.views import * +from django.conf.urls import url __author__ = "Epsirom" -urlpatterns = [] +urlpatterns = [ + url(r'^login/?$',adminLogin.as_view()), + url(r'^logout/?$',adminLogout.as_view()), + url(r'^activity/list/?$',activityList.as_view()), + url(r'^activity/create/?$',activityCreate.as_view()), + url(r'^activity/delete/?$',activityDelete.as_view()), + url(r'^image/upload/?$',imageUpload.as_view()), + url(r'^activity/detail/?$',activityDetail.as_view()), + url(r'^activity/menu/?$',activityMenu.as_view()), + url(r'^activity/checkin/?$',activityCheckin.as_view()), +] diff --git a/adminpage/views.py b/adminpage/views.py index 91ea44a..3fb7b66 100644 --- a/adminpage/views.py +++ b/adminpage/views.py @@ -1,3 +1,325 @@ from django.shortcuts import render +from django.contrib import auth +from django.utils import timezone +from codex.baseerror import * +from codex.baseview import * +from wechat.models import Activity, Ticket +import datetime +from WeChatTicket.settings import SITE_DOMAIN +from wechat.views import * +from wechat.wrapper import * + # Create your views here. + +# 登录 +class adminLogin(APIView): + #获取登录状态 + def get(self): + if not self.request.user.is_authenticated(): + raise ValidateError(self.input) + + #验证用户名和密码登陆(django——superuser) + def post(self): + self.check_input('username', 'password') + username = self.input['username'] + password = self.input['password'] + # try: + # user = auth.models.User.objects.filter(username__exact = username) + # if not user: + # raise ValidateError("User name does not exit!") + # if user.first().password != password: + # raise ValidateError("User password error!") + # user = auth.authenticate(username=username,password=password) + # auth.login(self.request, user) + # except: + # raise ValidateError("System error!") + try: + user = auth.authenticate(username=username,password=password) + auth.login(self.request, user) + except: + raise ValidateError("Fail to login!") + +#登出 +class adminLogout(APIView): + #登出 + def post(self): + try: + auth.logout(self.request) + except: + raise ValidateError("Fail to logout!") + +#活动列表 +class activityList(APIView): + #获取活动列表 + def get(self): + # if self.request.user.is_authenticated(): + activity_List = [] + activites = Activity.objects.all() + for activity in activites: + if activity.status >= 0: + info = {} + info['id'] = activity.id + info['name'] = activity.name + info['description'] = activity.description + info['startTime'] = activity.start_time.timestamp() + info['endTime'] = activity.end_time.timestamp() + info['place'] = activity.place + info['bookStart'] = activity.book_start.timestamp() + info['bookEnd'] = activity.book_end.timestamp() + info['currentTime'] = datetime.datetime.now().timestamp() + info['status'] = activity.status + activity_List.append(info) + return activity_List + # else: + # raise ValidateError('Please login!') + +#创建活动 +class activityCreate(APIView): + def post(self): + # if self.request.user.is_authenticated(): + self.check_input('name', 'key', 'place', 'description', 'picUrl', 'startTime', 'endTime', + 'bookStart', 'bookEnd', 'totalTickets', 'status') + + name = self.input['name'] + key = self.input['key'] + place = self.input['place'] + description = self.input['description'] + pic_url = self.input['picUrl'] + start_time = self.input['startTime'] + end_time = self.input['endTime'] + book_start = self.input['bookStart'] + book_end = self.input['bookEnd'] + total_tickets = self.input['totalTickets'] + remain_tickets = self.input['totalTickets'] + status = self.input['status'] + try: + new = Activity(name=name, key=key, place=place, + description=description, pic_url=pic_url, start_time=start_time, + end_time=end_time, book_start=book_start, book_end=book_end, + total_tickets=total_tickets, remain_tickets=remain_tickets, status=status) + new.save() + return new.id + except: + raise ValidateError('Create activity error!') + # else: + # raise ValidateError('Please login!') + +#删除活动 +class activityDelete(APIView): + def post(self): + # if self.request.user.is_authenticated(): + self.check_input('id') + id = self.input['id'] + try: + activity = Activity.objects.get(id__exact = id) + except: + raise ValidateError("ID does not exit!") + if activity: + if activity.status != Activity.STATUS_DELETED: + activity.status = Activity.STATUS_DELETED + activity.save() + else: + raise ValidateError("Activity is already deleted!") + + # else: + # raise ValidateError('Please login!') + +#上传图像并保存到服务器 +class imageUpload(APIView): + def post(self): + # if self.request.user.is_authenticated(): + self.check_input('image') + image = self.input['image'][0] + try: + f = open("static/img/activityImage/" + image.name, "wb") + for index in image.chunks(): + f.write(index) + f.close() + return SITE_DOMAIN + '/img/activityImage/' + image.name + except: + raise ValidateError("Fail to upload image!") + # else: + # raise ValidateError('Please login!') + +#活动详情 +class activityDetail(APIView): + #获取活动详情 + def get(self): + # if self.request.user.is_authenticated(): + self.check_input('id') + id = self.input['id'] + try: + activity = Activity.objects.get(id=id) + ticket_List = Ticket.objects.filter(activity=activity) + bookedTickets = 0 + usedTickets = 0 + for t in ticket_List: + if t.status == Ticket.STATUS_VALID: + bookedTickets += 1 + elif t.status == Ticket.STATUS_USED: + usedTickets += 1 + + detail = {} + detail['name'] = activity.name + detail['key'] = activity.key + detail['description'] = activity.description + detail['startTime'] = activity.start_time.timestamp() + detail['endTime'] = activity.end_time.timestamp() + detail['place'] = activity.place + detail['bookStart'] = activity.book_start.timestamp() + detail['bookEnd'] = activity.book_end.timestamp() + detail['totalTickets'] = activity.total_tickets + detail['picUrl'] = activity.pic_url + detail['bookedTickets'] = bookedTickets + detail['usedTickets'] = usedTickets + detail['currentTime'] = datetime.datetime.now().timestamp() + detail['status'] = activity.status + return detail + except: + raise ValidateError("Fail to get activity details!") + # else: + # raise ValidateError('Please login!') + + # #修改活动详情 + def post(self): + # if self.request.user.is_authenticated(): + self.check_input('id', 'name', 'place', 'description', 'picUrl', 'startTime', 'endTime', 'bookStart', 'bookEnd', 'totalTickets', 'status') + id = self.input['id'] + try: + activity = Activity.objects.get(id=id) + activity.id = self.input['id'] + activity.name = self.input['name'] + activity.place = self.input['place'] + activity.description = self.input['description'] + activity.pic_url = self.input['picUrl'] + activity.start_time = self.input['startTime'] + activity.end_time = self.input['endTime'] + activity.book_start = self.input['bookStart'] + activity.book_end = self.input['bookEnd'] + activity.total_tickets = self.input['totalTickets'] + activity.status = self.input['status'] + + activity.save() + except: + raise ValidateError("Fail to change activity details!") + # else: + # raise ValidateError('Please login!') + +#微信抢票菜单调整 +class activityMenu(APIView): + #获取当前微信抢票菜单 + def get(self): + # if self.request.user.is_authenticated(): + try: + wechat_menu = CustomWeChatView.lib.get_wechat_menu() + current_btns = list() + for b in wechat_menu: + if b['name'] == '抢票': + current_btns += b.get('sub_button', list()) + activity_ids = list() + for b in current_btns: + if 'key' in b: + activity_id = b['key'] + if activity_id.startswith(CustomWeChatView.event_keys['book_header']): + activity_id = activity_id[len(CustomWeChatView.event_keys['book_header']):] + if activity_id and activity_id.isdigit(): + activity_ids.append(int(activity_id)) + acts = Activity.objects.filter( + id__in=activity_ids, status=Activity.STATUS_PUBLISHED, book_end__gt=timezone.now() + ).order_by('book_end')[: 5] + + activities = [] + index = 0 + for a in acts: + index += 1 + activities.append({ + 'id':a.id, + 'name':a.name, + 'menuIndex':index + }) + + allActs = Activity.objects.all() + for a in allActs: + if a not in acts: + if a.book_end.timestamp() > datetime.datetime.now().timestamp() and a.status == Activity.STATUS_PUBLISHED: + activities.append({ + 'id':a.id, + 'name':a.name, + 'menuIndex':0 + }) + return activities + except: + raise ValidateError('Fail to get WeChat Menu!') + # else: + # raise ValidateError('Please login!') + + #修改微信抢票菜单 + def post(self): + # if self.request.user.is_authenticated(): + try: + acts = [] + for id in self.input: + act = Activity.objects.get(id=id) + acts.append(act) + CustomWeChatView.update_menu(acts) + except: + raise ValidateError('Fail to change WeChat Menu!') + # else: + # raise ValidateError('Please login!') + +#检票 +class activityCheckin(APIView): + #检票 + def post(self): + # if self.request.user.is_authenticated(): + self.check_input('actId') + if 'ticket' in self.input.keys(): + self.check_input('ticket') + actId = self.input['actId'] + ticket = self.input['ticket'] + try: + activity = Activity.objects.get(id=actId) + except: + raise ValidateError("Activity for this ID does not exit!") + + try: + T = Ticket.objects.get(activity=activity, unique_id=ticket) + if T.status == Ticket.STATUS_VALID: + info = {} + info['ticket'] = T.unique_id + info['studentId'] = T.student_id + T.status = Ticket.STATUS_USED + T.save() + return info + else: + ValidateError('Ticket is invalid') + except: + ValidateError('Fail to Checkin!') + + elif 'studentId' in self.input.keys(): + self.check_input('studentId') + actId = self.input['actId'] + studentId = self.input['studentId'] + try: + activity = Activity.objects.get(id=actId) + except: + raise ValidateError("Activity for this ID does not exit!") + try: + T = Ticket.objects.filter(activity=activity, student_id=studentId) + for t in T: + if t.status == Ticket.STATUS_VALID: + info = {} + info['ticket'] = t.unique_id + info['studentId'] = t.student_id + t.status = Ticket.STATUS_USED + t.save() + return info + else: + ValidateError('Ticket is invalid') + except: + ValidateError('Fail to Checkin!') + + raise ValidateError('Fail to Checkin!') + # else: + # raise ValidateError('Please login!') \ No newline at end of file diff --git a/codex/baseerror.py b/codex/baseerror.py index 1247d25..87c56e7 100644 --- a/codex/baseerror.py +++ b/codex/baseerror.py @@ -32,4 +32,4 @@ def __init__(self, msg): class ValidateError(BaseError): def __init__(self, msg): - super(ValidateError, self).__init__(3, msg) + super(ValidateError, self).__init__(3, msg) \ No newline at end of file diff --git a/static/img/activityImage/readme.txt b/static/img/activityImage/readme.txt new file mode 100644 index 0000000..46325bf --- /dev/null +++ b/static/img/activityImage/readme.txt @@ -0,0 +1 @@ +#保存活动图片 diff --git a/templates/news.xml b/templates/news.xml index 7732a52..e787d0d 100644 --- a/templates/news.xml +++ b/templates/news.xml @@ -8,7 +8,7 @@ <![CDATA[{{ Article.Title }}]]> - + {% endfor %} diff --git a/userpage/urls.py b/userpage/urls.py index 2ff76f9..d0e07ca 100644 --- a/userpage/urls.py +++ b/userpage/urls.py @@ -10,4 +10,6 @@ urlpatterns = [ url(r'^user/bind/?$', UserBind.as_view()), + url(r'^activity/detail?$',UserActivityDetail.as_view()), + url(r'^ticket/detail?$',TicketDetail.as_view()), ] diff --git a/userpage/views.py b/userpage/views.py index 3e5c5dc..eecdfed 100644 --- a/userpage/views.py +++ b/userpage/views.py @@ -1,7 +1,9 @@ from codex.baseerror import * from codex.baseview import APIView -from wechat.models import User +from wechat.models import User,Activity,Ticket +import re +import datetime class UserBind(APIView): @@ -11,7 +13,18 @@ def validate_user(self): input: self.input['student_id'] and self.input['password'] raise: ValidateError when validating failed """ - raise NotImplementedError('You should implement UserBind.validate_user method') + self.check_input('student_id') + student_id = self.input['student_id'] + if User.objects.filter(student_id=student_id): + raise ValidateError('该学号已被占用!') + #check the validity of student_id: + if re.match('^[0-9]{10}$',student_id): + #check the year + year = int(re.match('^[0-9]{4}',student_id).group(0)) + if (year < 1911 or year > datetime.datetime.now().year): + raise ValidateError('无效学号!') + else: + raise ValidateError('无效学号!') def get(self): self.check_input('openid') @@ -23,3 +36,56 @@ def post(self): self.validate_user() user.student_id = self.input['student_id'] user.save() + +class UserActivityDetail(APIView): + def get(self): + self.check_input('id') + try: + activity_list = Activity.objects.filter(id = self.input['id']) + if not activity_list: + raise ValidateError('Not found') + activity = activity_list[0] + if(activity.status != Activity.STATUS_PUBLISHED): + raise ValidateError("活动已截止") + detail = {} + detail['name'] = activity.name + detail['key'] = activity.key + detail['description'] = activity.description + detail['startTime'] = activity.start_time.timestamp() + detail['endTime'] = activity.end_time.timestamp() + detail['place'] = activity.place + detail['bookStart'] = activity.book_start.timestamp() + detail['bookEnd'] = activity.book_end.timestamp() + detail['totalTickets'] = activity.total_tickets + detail['picUrl'] = activity.pic_url + detail['remainTickets'] = activity.remain_tickets + detail['currentTime'] = datetime.datetime.now().timestamp() + return detail + except: + raise ValidateError('Activity not found!') + +class TicketDetail(APIView): + + def get(self): + self.check_input('openid','ticket') + user = User.objects.filter(open_id=self.input['openid']) + if not user: + raise LogicError('User not found') + ticketlist = Ticket.objects.filter(student_id=user[0].student_id,unique_id=self.input['ticket']) + if not ticketlist: + errormsg = user[0].student_id+' '+self.input['ticket'] + raise LogicError(errormsg) + ticket = ticketlist[0] + activity = ticket.activity + detail = { + 'activityName': activity.name, + 'place': activity.place, + 'activityKey': activity.key, + 'uniqueId': ticket.unique_id, + 'startTime': activity.start_time.timestamp(), + 'endTime': activity.end_time.timestamp(), + 'currentTime': datetime.datetime.now().timestamp(), + 'status': ticket.status, + } + return detail + \ No newline at end of file diff --git a/wechat/handlers.py b/wechat/handlers.py index 4211d91..5b62365 100644 --- a/wechat/handlers.py +++ b/wechat/handlers.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- # from wechat.wrapper import WeChatHandler - +from wechat.models import User,Activity,Ticket +import datetime +from django.db import transaction +import uuid __author__ = "Epsirom" @@ -65,3 +68,227 @@ def check(self): def handle(self): return self.reply_text(self.get_message('book_empty')) + +#抢票 +class BookActivityHandler(WeChatHandler): + + def check(self): + if self.is_event('CLICK'): + event_key = self.view.event_keys['book_header'] + event_key += self.input['EventKey'][len(event_key):] + return self.is_event_click(event_key) + else: + return self.is_text('抢票') + return False + + def handle(self): + if not self.user.student_id: + return self.reply_text(self.get_message('bind_account')) + + currentTime = datetime.datetime.now().timestamp() + if self.is_event('CLICK'): + event_key = self.view.event_keys['book_header'] + event_key += self.input['EventKey'][len(event_key):] + if self.is_event_click(event_key): + act_id = self.input['EventKey'][len(self.view.event_keys['book_header']):] + activity = self.get_activity(act_id) + if not activity: + return self.reply_text('对不起,没有该项活动') + if currentTime < activity.book_start.timestamp(): + return self.reply_text('对不起,还未开放抢票') + if currentTime > activity.book_end.timestamp(): + return self.reply_text('对不起,抢票已经截止') + #check if the student has book a ticket: + if Ticket.objects.filter(student_id = self.user.student_id, activity = activity, status = Ticket.STATUS_VALID): + return self.reply_text('一个人只能抢一张票哦 ^口..口^') + if Ticket.objects.filter(student_id = self.user.student_id, activity = activity, status = Ticket.STATUS_USED): + return self.reply_text('您已经抢过该活动的票且使用过') + #lock!lock!lock! + ticket = self.book_ticket(act_id) + activity = self.get_activity(act_id) + if ticket: + return self.reply_single_news({ + 'Title':activity.name, + 'Description':'抢票成功', + 'Url':ticket, + 'PicUrl':activity.pic_url, + }) + else: + return self.reply_text('没有多的票了!请自行尝试劝退抢到票的朋友们~') + else: + return False + + elif self.is_text('抢票'): + query = self.input['Content'][3:] + activity = Activity.objects.filter(key = query).first() + if not activity: + activity = Activity.objects.filter(name = query).first() + if not activity: + return self.reply_text('对不起,没有该项活动') + if currentTime < activity.book_start.timestamp(): + return self.reply_text('对不起,还未开放抢票') + if currentTime > activity.book_end.timestamp(): + return self.reply_text('对不起,抢票已经截止') + if Ticket.objects.filter(student_id = self.user.student_id, activity = activity, status = Ticket.STATUS_VALID): + return self.reply_text('一个人只能抢一张票哦 ^口..口^') + if Ticket.objects.filter(student_id = self.user.student_id, activity = activity, status = Ticket.STATUS_USED): + return self.reply_text('您已经抢过该活动的票且使用过') + if activity.remain_tickets == 0: + return self.reply_text('没有多的票了!请自行尝试劝退抢到票的朋友们~') + unique_id = uuid.uuid5(uuid.NAMESPACE_DNS,self.user.student_id + activity.name + str(currentTime)) + Ticket.objects.create(student_id = self.user.student_id, unique_id = unique_id, + activity = activity, status = Ticket.STATUS_VALID) + activity.remain_tickets -= 1 + activity.save() + ticket = self.url_ticket(unique_id) + return self.reply_single_news({ + 'Title':activity.name, + 'Description':'抢票成功', + 'Url':ticket, + 'PicUrl':activity.pic_url, + }) + + + #抢啥 +class BookWhatHandler(WeChatHandler): + def check(self): + return self.is_event_click(self.view.event_keys['book_what']) + + def handle(self): + + activities = self.get_activities() + if not activities: + return self.reply_text('对不起,现在没有正在抢票的活动') + articles = [] + currentTime = datetime.datetime.now().timestamp() + for activity in activities: + if currentTime < activity.end_time.timestamp(): + articles.append({ + 'Title': activity.name, + 'Description': activity.description, + 'Url': self.url_book(activity.id), + 'PicUrl': activity.pic_url, + }) + if len(articles) > 0: + return self.reply_news(articles) + else: + return self.reply_text('对不起,现在没有正在抢票的活动') + +#查票 +class GetTicketHandler(WeChatHandler): + def check(self): + return self.is_text('查票') or self.is_event_click(self.view.event_keys['get_ticket']) + + def handle(self): + if self.is_event_click(self.view.event_keys['get_ticket']): + tickets = self.get_tickets() + if not tickets: + return self.reply_text('对不起,当前没有已经购买的票') + articles = [] + currentTime = datetime.datetime.now().timestamp() + for ticket in tickets: + if ticket.status == Ticket.STATUS_VALID: + str1 = '有效票未使用' + if currentTime > ticket.activity.end_time.timestamp(): + str1 += ' 活动已结束' + elif ticket.status == Ticket.STATUS_USED: + str1 = '有效票已使用' + else: + str1 = '已退票' + self.logger.warn(str1) + articles.append({ + 'Title':ticket.activity.name + ' ' + str1, + 'Description':'电子票查看', + 'Url':self.url_ticket(ticket.unique_id), + 'PicUrl':ticket.activity.pic_url, + }) + return self.reply_news(articles) + else: + query = self.input['Content'][3:] + activity = Activity.objects.filter(key = query).first() + if not activity: + activity = Activity.objects.filter(name = query).first() + if not activity: + return self.reply_text('对不起,没有这场活动') + tickets = Ticket.objects.filter(student_id = self.user.student_id, activity = activity) + if not tickets: + return self.reply_text('对不起,当前没有已经购买的有效票') + for ticket in tickets: + if ticket.status == Ticket.STATUS_VALID: + currentTime = datetime.datetime.now().timestamp() + str1 = '有效票未使用' + if currentTime > ticket.activity.end_time.timestamp(): + str1 += ' 活动已结束' + return self.reply_single_news({ + 'Title':ticket.activity.name + ' ' + str1, + 'Description':'电子票查看', + 'Url':self.url_ticket(ticket.unique_id), + 'PicUrl':ticket.activity.pic_url, + }) + return self.reply_text('对不起,当前没有已经购买的有效票') + +#取票 +class PickTicketHandler(WeChatHandler): + def check(self): + return self.is_text('取票') + + def handle(self): + query = self.input['Content'][3:] + activity = Activity.objects.filter(key = query).first() + if not activity: + activity = Activity.objects.filter(name = query).first() + if not activity: + return self.reply_text('对不起,没有这场活动') + tickets = Ticket.objects.filter(student_id = self.user.student_id, activity = activity) + if not tickets: + return self.reply_text('对不起,当前没有已经购买的有效票') + for ticket in tickets: + if ticket.status == Ticket.STATUS_VALID: + currentTime = datetime.datetime.now().timestamp() + str1 = '有效票未使用' + if currentTime > ticket.activity.end_time.timestamp(): + str1 += ' 活动已结束' + return self.reply_single_news({ + 'Title':ticket.activity.name + ' ' + str1, + 'Description':'电子票查看', + 'Url':self.url_ticket(ticket.unique_id), + 'PicUrl':ticket.activity.pic_url, + }) + return self.reply_text('对不起,当前没有已经购买的有效票') + +#退票 +class CancelTicketHandler(WeChatHandler): + def check(self): + return self.is_text('退票') + + def handle(self): + query = self.input['Content'][3:] + with transaction.atomic(): + activity = Activity.objects.select_for_update().filter(key = query).first() + if not activity: + activity = Activity.objects.select_for_update().filter(name = query).first() + if not activity: + return self.reply_text('对不起,没有这场活动') + ticket = Ticket.objects.filter(student_id = self.user.student_id, activity = activity, status = Ticket.STATUS_VALID).first() + if not ticket: + return self.reply_text('对不起,您没有本场活动未使用的有效票') + else: + currentTime = datetime.datetime.now().timestamp() + if currentTime > ticket.activity.end_time.timestamp(): + return self.reply_text('对不起,活动已经结束,不能退票') + activity.remain_tickets += 1 + activity.save() + ticket.status = Ticket.STATUS_CANCELLED + ticket.save() + return self.reply_single_news({ + 'Title':ticket.activity.name, + 'Description':'电子票退票成功', + 'Url':self.url_ticket(ticket.unique_id), + 'PicUrl':ticket.activity.pic_url, + }) + if Ticket.objects.filter(student_id = self.user.student_id, activity = activity, status = Ticket.STATUS_USED).first(): + return self.reply_text('对不起,这张票您已经使用过') + if Ticket.objects.filter(student_id = self.user.student_id, activity = activity, status = Ticket.STATUS_CANCELLED).first(): + return self.reply_text('这张票已经退票过') + return self.reply_text('对不起,您未购买过本场活动的票') + diff --git a/wechat/migrations/0003_auto_20181012_1915.py b/wechat/migrations/0003_auto_20181012_1915.py new file mode 100644 index 0000000..52bd8ff --- /dev/null +++ b/wechat/migrations/0003_auto_20181012_1915.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.1 on 2018-10-12 11:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wechat', '0002_auto_20160502_1529'), + ] + + operations = [ + migrations.AlterField( + model_name='ticket', + name='activity', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='wechat.Activity'), + ), + ] diff --git a/wechat/migrations/0004_auto_20181016_0334.py b/wechat/migrations/0004_auto_20181016_0334.py new file mode 100644 index 0000000..0058807 --- /dev/null +++ b/wechat/migrations/0004_auto_20181016_0334.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-10-16 03:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wechat', '0003_auto_20181012_1915'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='student_id', + field=models.CharField(db_index=True, max_length=32), + ), + ] diff --git a/wechat/models.py b/wechat/models.py index 58dc0ee..805a01b 100644 --- a/wechat/models.py +++ b/wechat/models.py @@ -5,7 +5,7 @@ class User(models.Model): open_id = models.CharField(max_length=64, unique=True, db_index=True) - student_id = models.CharField(max_length=32, unique=True, db_index=True) + student_id = models.CharField(max_length=32, unique=False, db_index=True) @classmethod def get_by_openid(cls, openid): @@ -33,13 +33,20 @@ class Activity(models.Model): STATUS_SAVED = 0 STATUS_PUBLISHED = 1 + @classmethod + def get_by_id(cls, id): + try: + return cls.objects.get(id=id) + except cls.DoesNotExist: + raise LogicError('Activity not found') + class Ticket(models.Model): student_id = models.CharField(max_length=32, db_index=True) unique_id = models.CharField(max_length=64, db_index=True, unique=True) - activity = models.ForeignKey(Activity) + activity = models.ForeignKey(Activity, on_delete=models.DO_NOTHING) status = models.IntegerField() STATUS_CANCELLED = 0 STATUS_VALID = 1 - STATUS_USED = 2 + STATUS_USED = 2 \ No newline at end of file diff --git a/wechat/views.py b/wechat/views.py index c0626fc..630499f 100644 --- a/wechat/views.py +++ b/wechat/views.py @@ -11,8 +11,17 @@ class CustomWeChatView(WeChatView): lib = WeChatLib(WECHAT_TOKEN, WECHAT_APPID, WECHAT_SECRET) handlers = [ - HelpOrSubscribeHandler, UnbindOrUnsubscribeHandler, BindAccountHandler, BookEmptyHandler, + HelpOrSubscribeHandler, + UnbindOrUnsubscribeHandler, + BindAccountHandler, + BookEmptyHandler, + BookActivityHandler, + BookWhatHandler, + GetTicketHandler, + PickTicketHandler, + CancelTicketHandler, ] + error_message_handler = ErrorHandler default_handler = DefaultHandler @@ -108,3 +117,4 @@ def update_menu(cls, activities=None): id__in=activity_ids, status=Activity.STATUS_PUBLISHED, book_end__gt=timezone.now() ).order_by('book_end')[: 5]) cls.lib.set_wechat_menu(cls.menu) + diff --git a/wechat/wrapper.py b/wechat/wrapper.py index 136ff04..2e07ab1 100644 --- a/wechat/wrapper.py +++ b/wechat/wrapper.py @@ -5,15 +5,16 @@ import json import logging import urllib.request +import uuid import xml.etree.ElementTree as ET from WeChatTicket.settings import WECHAT_TOKEN, WECHAT_APPID, WECHAT_SECRET from django.http import Http404, HttpResponse from django.template.loader import get_template - +from django.db import transaction from WeChatTicket import settings from codex.baseview import BaseView -from wechat.models import User +from wechat.models import User,Activity,Ticket __author__ = "Epsirom" @@ -33,6 +34,35 @@ def __init__(self, view, msg, user): self.user = user self.view = view + def book_ticket(self,act_id): + acts = Activity.objects.select_for_update().filter(id=int(act_id)) + with transaction.atomic(): + act = acts[0] + if act.remain_tickets > 0: + act.remain_tickets -= 1 + act.save() + else: + return '' + #not return: there's tickets left! + return self.create_ticket(act_id) + + def create_ticket(self,id): + currentTime = datetime.datetime.now().timestamp() + unique_id = uuid.uuid5(uuid.NAMESPACE_DNS,self.user.student_id + id + str(currentTime)) + activity = Activity.objects.get(id = int(id)) + Ticket.objects.create(student_id = self.user.student_id, unique_id = unique_id, + activity = activity, status = Ticket.STATUS_VALID) + return self.url_ticket(unique_id) + + def get_ticket_by_act(self,id): + activity = self.get_activity(id) + ticket = Ticket.objects.filter(student_id = self.user.student_id, activity = activity,status = Ticket.STATUS_VALID) + if ticket: + return True + else: + return False + + def check(self): raise NotImplementedError('You should implement check() in sub-class of WeChatHandler') @@ -52,10 +82,10 @@ def reply_text(self, content): )) def reply_news(self, articles): - if len(articles) > 10: - self.logger.warn('Reply with %d articles, keep only 10', len(articles)) + if len(articles) > 8: + self.logger.warn('Reply with %d articles, keep only 8', len(articles)) return get_template('news.xml').render(self.get_context( - Articles=articles[:10] + Articles=articles[:8] )) def reply_single_news(self, article): @@ -67,12 +97,37 @@ def get_message(self, name, **data): return get_template('messages/' + name + '.html').render(dict( handler=self, user=self.user, **data )) + #self.logger.warn(repr(result)) + #return result + + def get_activity(self,id): + activity = Activity.objects.filter(id=int(id)) + if not activity: + return activity + return activity[0] + + def get_activities(self): + activities = Activity.objects.filter(status = Activity.STATUS_PUBLISHED) + return activities + + def get_tickets(self): + ticket_list = [] + tickets = Ticket.objects.filter(student_id = self.user.student_id,status = Ticket.STATUS_VALID) + for i in tickets: + ticket_list.append(i) + tickets = Ticket.objects.filter(student_id = self.user.student_id,status = Ticket.STATUS_USED) + for i in tickets: + ticket_list.append(i) + tickets = Ticket.objects.filter(student_id = self.user.student_id,status = Ticket.STATUS_CANCELLED) + for i in tickets: + ticket_list.append(i) + return ticket_list def is_msg_type(self, check_type): return self.input['MsgType'] == check_type def is_text(self, *texts): - return self.is_msg_type('text') and (self.input['Content'].lower() in texts) + return self.is_msg_type('text') and (self.input['Content'].split()[0] in texts) def is_event_click(self, *event_keys): return self.is_msg_type('event') and (self.input['Event'] == 'CLICK') and (self.input['EventKey'] in event_keys) @@ -88,6 +143,12 @@ def url_help(self): def url_bind(self): return settings.get_url('u/bind', {'openid': self.user.open_id}) + + def url_book(self,id): + return settings.get_url('u/activity',{'id': id}) + + def url_ticket(self,ticket): + return settings.get_url('u/ticket',{'openid': self.user.open_id,'ticket':ticket}) class WeChatEmptyHandler(WeChatHandler): @@ -114,7 +175,7 @@ class WeChatLib(object): logger = logging.getLogger('wechatlib') access_token = '' - access_token_expire = datetime.datetime.fromtimestamp(0) + access_token_expire = datetime.datetime.fromtimestamp(123456789) token = WECHAT_TOKEN appid = WECHAT_APPID secret = WECHAT_SECRET