From 1f18b53130068037a3ce633df8daf1b2d083e6c3 Mon Sep 17 00:00:00 2001
From: Team Bob <>
Date: Thu, 15 Nov 2018 06:22:44 +0800
Subject: [PATCH]
---
.gitignore | 1 +
.travis.yml | 19 +++
README.md | 1 +
WeChatTicket/settings.py | 9 +-
adminpage/urls.py | 14 +-
adminpage/views.py | 271 ++++++++++++++++++++++++++++++++++++++-
configs.example.json | 14 --
configs.json.enc | Bin 0 -> 464 bytes
templates/news.xml | 2 +-
userpage/urls.py | 2 +
userpage/views.py | 54 +++++++-
wechat/handlers.py | 177 +++++++++++++++++++++++++
wechat/models.py | 15 +++
wechat/views.py | 9 +-
wechat/wrapper.py | 12 +-
15 files changed, 574 insertions(+), 26 deletions(-)
create mode 100644 .travis.yml
delete mode 100644 configs.example.json
create mode 100644 configs.json.enc
diff --git a/.gitignore b/.gitignore
index 3459640..8eafcd9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,3 +66,4 @@ target/
# Custom files
/configs.json
+/static/media
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..82f75f9
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,19 @@
+sudo: required
+language: python
+python: '3.6'
+branches:
+ only:
+ - master
+env: "-DJANGO=1.9"
+services:
+- mysql
+before_install:
+- openssl aes-256-cbc -K $encrypted_0fbea918c27f_key -iv $encrypted_0fbea918c27f_iv
+ -in configs.json.enc -out configs.json -d
+- mysql -e 'create database wechat_ticket_db;'
+install:
+- pip install -r requirements.txt
+script:
+- python manage.py makemigrations
+- python manage.py migrate
+- python manage.py test
diff --git a/README.md b/README.md
index 6a44db5..0b8a17b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
# WeChatTicket
+[](https://travis-ci.org/Altria-Ex/WeChatTicket)
Ticket management system based on WeChat public platform.
diff --git a/WeChatTicket/settings.py b/WeChatTicket/settings.py
index 706e469..99c9c79 100644
--- a/WeChatTicket/settings.py
+++ b/WeChatTicket/settings.py
@@ -38,7 +38,7 @@
WECHAT_APPID = CONFIGS['WECHAT_APPID']
WECHAT_SECRET = CONFIGS['WECHAT_SECRET']
-ALLOWED_HOSTS = []
+ALLOWED_HOSTS = ['*']
# Application definition
@@ -139,13 +139,13 @@
LANGUAGE_CODE = 'en-us'
-TIME_ZONE = 'UTC'
+TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
-USE_TZ = True
+USE_TZ = False
# Static files (CSS, JavaScript, Images)
@@ -154,7 +154,8 @@
STATIC_URL = '/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
-
+MEDIA_ROOT = os.path.join(BASE_DIR, 'static/media')
+MEDIA_URL = 'static/media/'
# Site and URL
SITE_DOMAIN = CONFIGS['SITE_DOMAIN'].rstrip('/')
diff --git a/adminpage/urls.py b/adminpage/urls.py
index db56849..d23008a 100644
--- a/adminpage/urls.py
+++ b/adminpage/urls.py
@@ -1,9 +1,21 @@
# -*- coding: utf-8 -*-
#
+from django.conf.urls import url
+from adminpage.views import *
__author__ = "Epsirom"
-urlpatterns = []
+urlpatterns = [
+ url(r'^login/?$', Login.as_view()),
+ url(r'^logout/?$', Logout.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'^activity/detail/?$', ActivityDetail.as_view()),
+ url(r'^activity/menu/?$', ActivityMenu.as_view()),
+ url(r'^image/upload/?$', ImageUpload.as_view()),
+ url(r'^activity/checkin/?$', ActivityCheckin.as_view()),
+]
diff --git a/adminpage/views.py b/adminpage/views.py
index 91ea44a..550a6ee 100644
--- a/adminpage/views.py
+++ b/adminpage/views.py
@@ -1,3 +1,272 @@
from django.shortcuts import render
-
+from codex.baseerror import *
+from codex.baseview import APIView
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate, login, logout
+from wechat.models import Activity
+from wechat.models import Ticket
+from wechat.views import CustomWeChatView
+from WeChatTicket.settings import SITE_DOMAIN
+import time
+from datetime import datetime, timedelta
# Create your views here.
+
+
+class Login(APIView):
+
+ def get(self):
+ if not self.request.user.is_authenticated():
+ raise ValidateError('User not logged in')
+ return 0
+
+ def post(self):
+ self.check_input('username', 'password')
+ username = self.input['username']
+ password = self.input['password']
+ user = authenticate(username=username, password=password)
+
+ if not username:
+ raise ValidateError('Username is empty!')
+ if not password:
+ raise ValidateError('Password is empty!')
+ if not User.objects.filter(username=username):
+ raise ValidateError('Username does not exist!')
+ if not user:
+ raise ValidateError('Wrong password!')
+
+ if user.is_active:
+ login(self.request, user)
+ return 0
+
+
+class Logout(APIView):
+
+ def post(self):
+ if self.request.user.is_authenticated():
+ logout(self.request)
+ else:
+ raise ValidateError('Logout failed!User has not logged in.')
+
+
+class ActivityList(APIView):
+
+ def get(self):
+ if not self.request.user.is_authenticated():
+ raise ValidateError('User not logged in!')
+
+ activityList = Activity.objects.all()
+ data = []
+ for item in activityList:
+ if item.status >= 0:
+ temp = {
+ 'id': item.id,
+ 'name': item.name,
+ 'description': item.description,
+ 'startTime': int(time.mktime(item.start_time.timetuple())),
+ 'endTime': int(time.mktime(item.end_time.timetuple())),
+ 'place': item.place,
+ 'bookStart': int(time.mktime(item.book_start.timetuple())),
+ 'bookEnd': int(time.mktime(item.book_end.timetuple())),
+ 'currentTime': int(time.time()),
+ 'status': item.status,
+ }
+ data.append(temp)
+ else:
+ continue
+ return data
+
+
+class ActivityDelete(APIView):
+
+ def post(self):
+ self.check_input('id')
+ activity = Activity.get_by_id(self.input['id'])
+ activity.delete()
+
+
+class ActivityCreate(APIView):
+ def post(self):
+ self.check_input('name', 'key', 'place', 'description', 'picUrl', 'startTime', 'endTime', 'bookStart', 'bookEnd', 'totalTickets', 'status')
+ if not self.request.user.is_authenticated():
+ raise ValidateError('User not logged in!')
+ new_activity = Activity(
+ name = self.input['name'],
+ key = self.input['key'],
+ place = self.input['place'],
+ description = self.input['description'],
+ pic_url = self.input['picUrl'],
+ start_time = datetime.strptime(self.input['startTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8),
+ end_time = datetime.strptime(self.input['endTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8),
+ book_start = datetime.strptime(self.input['bookStart'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8),
+ book_end = datetime.strptime(self.input['bookEnd'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8),
+ remain_tickets = self.input['totalTickets'],
+ total_tickets = self.input['totalTickets'],
+ status = self.input['status']
+ )
+ new_activity.save()
+ return new_activity.id
+
+
+class ActivityDetail(APIView):
+
+ def get(self):
+ self.check_input('id')
+ if not self.request.user.is_authenticated():
+ raise ValidateError('User has not logged in.')
+ activity = Activity.get_by_id(self.input['id'])
+ usedTickets = 0
+ for ticket in activity.ticket_set.all():
+ if ticket.status == Ticket.STATUS_USED:
+ usedTickets += 1
+ data = {
+ 'name': activity.name,
+ 'key': activity.key,
+ 'description': activity.description,
+ 'startTime': int(time.mktime(activity.start_time.timetuple())),
+ 'endTime': int(time.mktime(activity.end_time.timetuple())),
+ 'place': activity.place,
+ 'bookStart': int(time.mktime(activity.book_start.timetuple())),
+ 'bookEnd': int(time.mktime(activity.book_end.timetuple())),
+ 'totalTickets': activity.total_tickets,
+ 'picUrl': activity.pic_url,
+ 'bookedTickets': activity.total_tickets - activity.remain_tickets,
+ 'usedTickets': usedTickets,
+ 'currentTime': int(time.time()),
+ 'status': activity.status
+ }
+ return data
+
+ def post(self):
+ self.check_input('id', 'name', 'place', 'description', 'picUrl',
+ 'startTime', 'endTime', 'bookStart', 'bookEnd',
+ 'totalTickets', 'status')
+ if not self.request.user.is_authenticated():
+ raise ValidateError('User has not logged in.')
+ activity = Activity.get_by_id(self.input['id'])
+ if activity.status == Activity.STATUS_PUBLISHED:
+ if activity.name != self.input['name']:
+ raise LogicError('Cannot modify the name of a published activity ')
+ if activity.place != self.input['place']:
+ raise LogicError('Cannot modify the place of a published activity')
+ if activity.book_start != (datetime.strptime(self.input['bookStart'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)):
+ raise LogicError('Cannot modify the bookStart time of a published activity')
+ if self.input['status'] != 1:
+ raise LogicError('you cannot stage published activity')
+
+ if int(time.mktime(activity.end_time.timetuple())) < int(time.time()):
+ if activity.start_time != (datetime.strptime(self.input['startTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)):
+ raise LogicError('Cannot modify startTime of a activity ended')
+ if activity.end_time != (datetime.strptime(self.input['endTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)):
+ raise LogicError('Cannot modify endTime of a activity ended')
+
+ if int(time.mktime(activity.start_time.timetuple())) < int(time.time()):
+ if activity.book_end != (datetime.strptime(self.input['bookEnd'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8)):
+ raise LogicError('Cannot modify bookEnd time of a activity started')
+
+ if int(time.mktime(activity.book_start.timetuple())) < int(time.time()):
+ if activity.total_tickets != self.input['totalTickets']:
+ raise LogicError('Cannot modify totalTickets after book_start')
+
+ activity.name = self.input['name']
+ activity.place = self.input['place']
+ activity.description = self.input['description']
+ activity.pic_url = self.input['picUrl']
+ activity.start_time = (datetime.strptime(self.input['startTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8))
+ activity.end_time = (datetime.strptime(self.input['endTime'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8))
+ activity.book_start = (datetime.strptime(self.input['bookStart'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8))
+ activity.book_end = (datetime.strptime(self.input['bookEnd'], "%Y-%m-%dT%H:%M:%S.%fZ") + timedelta(hours=8))
+ activity.total_tickets = self.input['totalTickets']
+ activity.status = self.input['status']
+
+ activity.save()
+
+
+class ActivityMenu(APIView):
+
+ def get(self):
+ if not self.request.user.is_authenticated():
+ raise ValidateError('User has not logged in.')
+ activityListAll = Activity.objects.all()
+ wechat_menu = CustomWeChatView.lib.get_wechat_menu()
+ if len(wechat_menu) >= 2:
+ actiListInMenu = wechat_menu[1]['sub_button']
+ else:
+ actiListInMenu = []
+ data = []
+ for acti in activityListAll:
+ if acti.status == Activity.STATUS_DELETED:
+ continue
+ temp = {
+ 'id': acti.id,
+ 'name': acti.name,
+ 'menuIndex': 0
+ }
+ i = 0
+ while i < len(actiListInMenu):
+ if actiListInMenu[i]['name'] == acti.name:
+ temp['menuIndex'] = i+1
+ break
+ i += 1
+
+ data.append(temp)
+ return data
+
+ def post(self):
+ if not self.request.user.is_authenticated():
+ raise ValidateError('User has not logged in.')
+ activityList = []
+ for activity_id in self.input:
+ activity = Activity.get_by_id(activity_id)
+ activity.status = Activity.STATUS_PUBLISHED
+ activity.save()
+ activityList.append(activity)
+ CustomWeChatView.update_menu(activityList)
+
+
+class ImageUpload(APIView):
+
+ def post(self):
+ self.check_input('image')
+ if not self.request.user.is_authenticated():
+ raise ValidateError('User has not logged in.')
+ image = self.input['image'][0]
+ try:
+ with open('static/media/img/' + image.name, 'wb') as img_file:
+ for i in image.chunks():
+ img_file.write(i)
+ return SITE_DOMAIN + '/media/img/' + image.name
+ except:
+ raise ValidateError('Fail to upload image')
+
+
+class ActivityCheckin(APIView):
+ def post(self):
+ if not self.request.user.is_authenticated():
+ raise ValidateError("User not logged in!")
+ self.check_input('actId')
+ try:
+ activity = Activity.objects.get(id=self.input['actId'])
+ except:
+ raise LogicError('actID error!Activity not found')
+ try:
+ if "ticket" in self.input:
+ ticket = Ticket.objects.get(activity=activity,unique_id=self.input['ticket'])
+ elif 'studentId' in self.input.keys():
+ ticket = Ticket.objects.get(activity=activity,student_id=self.input['studentId'])
+ except:
+ raise LogicError("no ticket!")
+
+ if ticket.status == Ticket.STATUS_CANCELLED:
+ raise LogicError(" ticket canceled!")
+ elif ticket.status == Ticket.STATUS_USED:
+ raise LogicError("ticket used!")
+ elif ticket.status == Ticket.STATUS_VALID:
+ data = {
+ 'ticket': ticket.unique_id,
+ 'studentId': ticket.student_id
+ }
+ ticket.status = Ticket.STATUS_USED
+ ticket.save()
+ return data
+ else:
+ raise ValidateError("fail to checkin!")
diff --git a/configs.example.json b/configs.example.json
deleted file mode 100644
index 2050d5e..0000000
--- a/configs.example.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "SECRET_KEY": "ThisIsARandomStringWithLength=50!-----------------",
- "DEBUG": true,
- "IGNORE_WECHAT_SIGNATURE": false,
- "WECHAT_TOKEN": "ThisIsAWeChatTokenWhichCanBeARandomString",
- "WECHAT_APPID": "PleaseCopyFromWebsite",
- "WECHAT_SECRET": "PleaseCopyFromWebsite",
- "DB_NAME": "wechat_ticket",
- "DB_USER": "root",
- "DB_PASS": "",
- "DB_HOST": "127.0.0.1",
- "DB_PORT": "3306",
- "SITE_DOMAIN": "http://your.domain"
-}
\ No newline at end of file
diff --git a/configs.json.enc b/configs.json.enc
new file mode 100644
index 0000000000000000000000000000000000000000..25951c72b7eed3ad205dd24bce04dd8d2664a103
GIT binary patch
literal 464
zcmV;>0WbcNjTAXPbNp{5)&X_uH2W_U2tzGBDjL&iQ@+;b(Dg3s$%lE&rq;ud8fa4B
z#PIyE(?Tg}kRcA~D;~`QH>qA?d)<0ACwrDqK`G{nr+JDl(q&>(1Qf|?M0YHWbs=^s
zC0yW9U8SVhRjYpvfIF@O-||M4koC7)N!&lfhoi(u9cs2bt6&+w%v4CUd+`*u37NLq0o3dnHrd{BRfctQn5#S|2p_5keJFPDkWNjv}}g6Jh5T
z*g$ouS3ogyP9>qOhE|%}`neoo@$UuGl3Hr_Qm!P9)Uxy9XvJG(+=hqRTMZ}$M@p?@
z>S4-jnd4uBl$F$X3pS^EI1Snz#S8GszsF?wXFHx?LYJ1vxnDm7$QC&-d)ARYqXI9`
zDw#49-Y0V8C@QbXKi;wrYOMs1))wgu_gmPe!^|Y~0#eI5X!@m!qf~Fwd2;x<2Ua^F
zOC#H;#ed@R1n?=XR@Hw!k3}+9C_w*}7^kD>e{&X
-
+
{% endfor %}
diff --git a/userpage/urls.py b/userpage/urls.py
index 2ff76f9..26b11fe 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/?$', ActivityDetail.as_view()),
+ url(r'^ticket/detail/?$', TicketDetail.as_view()),
]
diff --git a/userpage/views.py b/userpage/views.py
index 3e5c5dc..606d53b 100644
--- a/userpage/views.py
+++ b/userpage/views.py
@@ -2,6 +2,9 @@
from codex.baseview import APIView
from wechat.models import User
+from wechat.models import Activity
+from wechat.models import Ticket
+import time
class UserBind(APIView):
@@ -11,11 +14,16 @@ 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')
+ if len(self.input['student_id']) > 32:
+ raise ValidateError('Invalid student id')
def get(self):
self.check_input('openid')
- return User.get_by_openid(self.input['openid']).student_id
+ try:
+ studentId = User.get_by_openid(self.input['openid']).student_id
+ except:
+ studentId = ''
+ return studentId
def post(self):
self.check_input('openid', 'student_id', 'password')
@@ -23,3 +31,45 @@ def post(self):
self.validate_user()
user.student_id = self.input['student_id']
user.save()
+
+
+class ActivityDetail(APIView):
+
+ def get(self):
+ self.check_input('id')
+ activity = Activity.get_by_id(self.input['id'])
+ if activity.status != Activity.STATUS_PUBLISHED:
+ raise LogicError("Activity hasn't been published")
+ activity_detail = {
+ 'name': activity.name,
+ 'key': activity.key,
+ 'description': activity.description,
+ 'startTime': int(time.mktime(activity.start_time.timetuple())),
+ 'endTime': int(time.mktime(activity.end_time.timetuple())),
+ 'place': activity.place,
+ 'bookStart': int(time.mktime(activity.book_start.timetuple())),
+ 'bookEnd': int(time.mktime(activity.book_end.timetuple())),
+ 'totalTickets': activity.total_tickets,
+ 'picUrl': activity.pic_url,
+ 'remainTickets': activity.remain_tickets,
+ 'currentTime': int(time.time())
+ }
+ return activity_detail
+
+
+class TicketDetail(APIView):
+
+ def get(self):
+ self.check_input('openid', 'ticket')
+ ticket = Ticket.get_by_uniqueid(self.input['ticket'])
+ ticket_detail = {
+ 'activityName': ticket.activity.name,
+ 'place': ticket.activity.place,
+ 'activityKey': ticket.activity.key,
+ 'uniqueId': ticket.unique_id,
+ 'startTime': int(time.mktime(ticket.activity.start_time.timetuple())),
+ 'endTime': int(time.mktime(ticket.activity.end_time.timetuple())),
+ 'currentTime': int(time.time()),
+ 'status': ticket.status
+ }
+ return ticket_detail
diff --git a/wechat/handlers.py b/wechat/handlers.py
index 4211d91..51eb738 100644
--- a/wechat/handlers.py
+++ b/wechat/handlers.py
@@ -1,10 +1,20 @@
# -*- coding: utf-8 -*-
#
from wechat.wrapper import WeChatHandler
+from wechat.models import Activity, Ticket
+from WeChatTicket.settings import WECHAT_TOKEN, WECHAT_APPID, WECHAT_SECRET
+from wechat.wrapper import WeChatLib
+import threading
+from datetime import datetime
+import time
+import random
+import string
+
__author__ = "Epsirom"
+remainTicketsLock = threading.Lock()
class ErrorHandler(WeChatHandler):
@@ -65,3 +75,170 @@ def check(self):
def handle(self):
return self.reply_text(self.get_message('book_empty'))
+
+
+class BookWhatHandler(WeChatHandler):
+
+ def check(self):
+ return self.is_text('抢啥') or self.is_event_click(self.view.event_keys['book_what'])
+
+ def handle(self):
+ lib = WeChatLib(WECHAT_TOKEN, WECHAT_APPID, WECHAT_SECRET)
+ wechat_menu = lib.get_wechat_menu()
+ if len(wechat_menu) < 2:
+ return self.reply_text('暂时没有可抢票的活动')
+ elif len(wechat_menu[1]['sub_button']) == 0:
+ return self.reply_text('暂时没有可抢票的活动')
+ else:
+ acti_list = wechat_menu[1]['sub_button']
+ news_list = []
+ for acti in acti_list:
+ try:
+ activity = Activity.objects.get(name=acti['name'])
+ news_list.append({
+ 'Title': activity.name,
+ 'Description': activity.description,
+ 'PicUrl': activity.pic_url,
+ 'Url': self.url_activity_detail(activity.id)
+ })
+ except:
+ return self.reply_text('数据库内出现了重名活动,请联系管理员-_-!')
+
+ return self.reply_news(news_list)
+
+
+class BookTicketHandler(WeChatHandler):
+
+ def check(self):
+ return self.is_text_command('抢票') or self.is_event_click(self.view.event_keys['book_header'])
+
+ def handle(self):
+ if self.is_msg_type('event'):
+ """
+ lib = WeChatLib(WECHAT_TOKEN, WECHAT_APPID, WECHAT_SECRET)
+ acti_list = lib.get_wechat_menu()[1]['sub_button']
+ for acti in acti_list:
+ if acti['key'] == self.input['EventKey']:
+ activity_name = acti['name']
+ break
+ try:
+ activity = Activity.objects.get(name=activity_name)
+ """
+ activity_id = int(self.input['EventKey'][len(self.view.event_keys['book_header']):])
+ try:
+ activity = Activity.objects.get(id=activity_id)
+ except:
+ return self.reply_text('该活动不存在')
+ elif self.is_msg_type('text'):
+ try:
+ activity_name = self.input['Content'].split()[1]
+ except:
+ return self.reply_text('格式错误0_0 抢票请输入"抢票 活动名"')
+ try:
+ activity = Activity.objects.get(name=activity_name)
+ except:
+ return self.reply_text('活动名错误?_? 该活动不存在或有重名活动')
+
+ if not self.user.student_id:
+ return self.reply_text('你还没绑定学号哟')
+
+ if len(Ticket.objects.filter(activity=activity, student_id=self.user.student_id)) > 0:
+ return self.reply_text('一个人只能抢一张票哦')
+
+ currentTime = int(time.time())
+ if int(time.mktime(activity.book_start.timetuple())) > currentTime:
+ return self.reply_text('抢票尚未开始')
+ elif int(time.mktime(activity.book_end.timetuple())) < currentTime:
+ return self.reply_text('抢票已结束')
+
+ remainTicketsLock.acquire()
+ if activity.remain_tickets <= 0:
+ remainTicketsLock.release()
+ return self.reply_text('来晚啦T_T 票都被抢光啦')
+ else:
+ activity.remain_tickets -= 1
+ activity.save()
+ remainTicketsLock.release()
+ ticket = Ticket(
+ student_id=self.user.student_id,
+ unique_id=self.user.student_id + ''.join(
+ random.choice(string.digits + string.ascii_letters) for x in range(32)),
+ activity=activity,
+ status=Ticket.STATUS_VALID,
+ )
+ ticket.save()
+ return self.reply_text('恭喜^_^抢票成功')
+
+
+class GetTicketHandler(WeChatHandler):
+
+ def check(self):
+ return self.is_text_command('取票') or self.is_event_click(self.view.event_keys['get_ticket'])
+
+ def handle(self):
+ if not self.user.student_id:
+ return self.reply_text('你还没有绑定学号')
+
+ ticket_list = []
+ news_list = []
+ if self.is_msg_type('event'):
+ ticket_list = list(Ticket.objects.filter(student_id=self.user.student_id))
+ elif self.is_msg_type('text'):
+ try:
+ activity_name = self.input['Content'].split()[1]
+ except:
+ return self.reply_text('格式错误0_0 取票请输入"取票 活动名"')
+ try:
+ activity = Activity.objects.get(name=activity_name)
+ except:
+ return self.reply_text('活动名错误?_? 该活动不存在或有重名活动')
+
+ ticket_list = list(Ticket.objects.filter(activity=activity, student_id=self.user.student_id))
+
+ if len(ticket_list) == 0:
+ return self.reply_text('你还没有抢到票哦')
+
+ for ticket in ticket_list:
+ news_list.append({
+ 'Title': '电子票:' + ticket.activity.name,
+ 'Description': ticket.student_id,
+ 'PicUrl': ticket.activity.pic_url,
+ 'Url': self.url_ticket_detail(self.user.open_id, ticket.unique_id)
+ })
+
+ return self.reply_news(news_list)
+
+
+class CancelTicketHandler(WeChatHandler):
+
+ def check(self):
+ return self.is_text_command('退票')
+
+ def handle(self):
+ if not self.user.student_id:
+ return self.reply_text('你还没有绑定学号哦')
+
+ try:
+ activity_name = self.input['Content'].split()[1]
+ except:
+ return self.reply_text('格式错误0_0 退票请输入"退票 活动名"')
+ try:
+ activity = Activity.objects.get(name=activity_name)
+ except:
+ return self.reply_text('活动名错误?_? 该活动不存在或有重名活动')
+
+ try:
+ ticket = Ticket.objects.get(activity=activity, student_id=self.user.student_id)
+ except:
+ return self.reply_text('你就没抢到,退啥退→_→')
+ if int(time.mktime(ticket.activity.book_end.timetuple())) < int(time.time()):
+ return self.reply_text('抢票结束后就不能再退了哦!')
+
+ ticket.status = Ticket.STATUS_CANCELLED
+ ticket.save()
+ remainTicketsLock.acquire()
+ ticket.activity.remain_tickets += 1
+ ticket.activity.save()
+ remainTicketsLock.release()
+
+ return self.reply_text('退票成功')
diff --git a/wechat/models.py b/wechat/models.py
index 58dc0ee..31e6ee9 100644
--- a/wechat/models.py
+++ b/wechat/models.py
@@ -33,6 +33,13 @@ class Activity(models.Model):
STATUS_SAVED = 0
STATUS_PUBLISHED = 1
+ @classmethod
+ def get_by_id(cls, activity_id):
+ try:
+ return cls.objects.get(id=activity_id)
+ except cls.DoesNotExist:
+ raise LogicError('Activity not found')
+
class Ticket(models.Model):
student_id = models.CharField(max_length=32, db_index=True)
@@ -43,3 +50,11 @@ class Ticket(models.Model):
STATUS_CANCELLED = 0
STATUS_VALID = 1
STATUS_USED = 2
+
+ @classmethod
+ def get_by_uniqueid(cls, uniqueid):
+ try:
+ return cls.objects.get(unique_id=uniqueid)
+ except cls.DoesNotExist:
+ raise LogicError('Ticket not found')
+
diff --git a/wechat/views.py b/wechat/views.py
index c0626fc..05c382c 100644
--- a/wechat/views.py
+++ b/wechat/views.py
@@ -11,7 +11,14 @@ class CustomWeChatView(WeChatView):
lib = WeChatLib(WECHAT_TOKEN, WECHAT_APPID, WECHAT_SECRET)
handlers = [
- HelpOrSubscribeHandler, UnbindOrUnsubscribeHandler, BindAccountHandler, BookEmptyHandler,
+ HelpOrSubscribeHandler,
+ UnbindOrUnsubscribeHandler,
+ BindAccountHandler,
+ BookEmptyHandler,
+ BookWhatHandler,
+ BookTicketHandler,
+ GetTicketHandler,
+ CancelTicketHandler,
]
error_message_handler = ErrorHandler
default_handler = DefaultHandler
diff --git a/wechat/wrapper.py b/wechat/wrapper.py
index 136ff04..0ba23e8 100644
--- a/wechat/wrapper.py
+++ b/wechat/wrapper.py
@@ -75,7 +75,9 @@ def is_text(self, *texts):
return self.is_msg_type('text') and (self.input['Content'].lower() 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)
+ return self.is_msg_type('event') and (self.input['Event'] == 'CLICK') and\
+ ((self.input['EventKey'] in event_keys) or (self.input['EventKey'].startswith('BOOKING_ACTIVITY_') and
+ 'BOOKING_ACTIVITY_' in event_keys))
def is_event(self, *events):
return self.is_msg_type('event') and (self.input['Event'] in events)
@@ -89,6 +91,12 @@ def url_help(self):
def url_bind(self):
return settings.get_url('u/bind', {'openid': self.user.open_id})
+ def url_activity_detail(self, activity_id):
+ return settings.get_url('u/activity', {'id': activity_id})
+
+ def url_ticket_detail(self, open_id, unique_id):
+ return settings.get_url('u/ticket', {'ticket': unique_id, 'openid': open_id})
+
class WeChatEmptyHandler(WeChatHandler):
@@ -114,7 +122,7 @@ class WeChatLib(object):
logger = logging.getLogger('wechatlib')
access_token = ''
- access_token_expire = datetime.datetime.fromtimestamp(0)
+ access_token_expire = datetime.datetime.fromtimestamp(1429417200.0)
token = WECHAT_TOKEN
appid = WECHAT_APPID
secret = WECHAT_SECRET