class="lines-num lines-num-old">
40
|
+ def data(self):
|
|
41
|
+ return {
|
|
42
|
+ 'course_id': self.course_id,
|
|
43
|
+ 'course_name': self.course_name,
|
|
44
|
+ 'course_time': self.course_time,
|
|
45
|
+ 'course_cover_url': self.course_cover_url,
|
|
46
|
+ }
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+class CourseVideoInfo(CreateUpdateMixin):
|
|
50
|
+ course = models.ForeignKey(CourseInfo, verbose_name=_(u'course'), blank=True, null=True, help_text=u'课程', db_index=True)
|
|
51
|
+ course_video_id = ShortUUIDField(_(u'course_video_id'), max_length=255, help_text=u'课程视频唯一标识', db_index=True, unique=True)
|
|
52
|
+ course_video_type = models.CharField(_(u'course_video_type'), max_length=255, blank=True, null=True, help_text=u'课程视频类型')
|
|
53
|
+ course_video_name = models.CharField(_(u'course_video_name'), max_length=255, blank=True, null=True, help_text=u'课程视频标题')
|
|
54
|
+ course_video_desc = models.TextField(_(u'course_video_desc'), blank=True, null=True, help_text=u'课程视频描述')
|
|
55
|
+ course_video_time = models.IntegerField(_(u'course_video_time'), default=0, help_text=u'课程视频时间')
|
|
56
|
+ course_video_cover = models.ImageField(_(u'course_video_cover'), upload_to=upload_path, blank=True, null=True, help_text=u'课程视频缩略图')
|
|
57
|
+ course_video = models.FileField(_(u'course_video'), upload_to=upload_path, blank=True, null=True, help_text=u'课程视频')
|
|
58
|
+ course_video_position = models.IntegerField(_(u'course_video_position'), default=0, help_text=u'课程视频排序')
|
|
59
|
+
|
|
60
|
+ class Meta:
|
|
61
|
+ verbose_name = _(u'coursevideoinfo')
|
|
62
|
+ verbose_name_plural = _(u'coursevideoinfo')
|
|
63
|
+
|
|
64
|
+ def __unicode__(self):
|
|
65
|
+ return unicode(self.pk)
|
|
66
|
+
|
|
67
|
+ @property
|
|
68
|
+ def course_video_cover_url(self):
|
|
69
|
+ return upload_file_url(self.course_video_cover)
|
|
70
|
+
|
|
71
|
+ @property
|
|
72
|
+ def course_video_url(self):
|
|
73
|
+ return upload_file_url(self.course_video)
|
|
74
|
+
|
|
75
|
+ @property
|
|
76
|
+ def data(self):
|
|
77
|
+ return {
|
|
78
|
+ 'course_id': self.course.course_id,
|
|
79
|
+ 'course_video_id': self.course_video_id,
|
|
80
|
+ 'course_video_type': self.course_video_type,
|
|
81
|
+ 'course_video_name': self.course_video_name,
|
|
82
|
+ 'course_video_desc': self.course_video_desc,
|
|
83
|
+ 'course_video_time': self.course_video_time,
|
|
84
|
+ 'course_video_cover_url': self.course_video_cover_url,
|
|
85
|
+ 'course_video_url': self.course_video_url,
|
|
86
|
+ 'course_video_position': self.course_video_position,
|
|
87
|
+ }
|
|
|
@@ -0,0 +1,7 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+from __future__ import unicode_literals
|
|
3
|
+
|
|
4
|
+from django.test import TestCase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+# Create your tests here.
|
|
|
@@ -0,0 +1,7 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+from __future__ import unicode_literals
|
|
3
|
+
|
|
4
|
+from django.shortcuts import render
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+# Create your views here.
|
|
|
@@ -0,0 +1,3 @@
|
|
1
|
+#!/bin/bash
|
|
2
|
+
|
|
3
|
+isort -rc -sp . .
|
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+#!/usr/bin/env python
|
|
2
|
+import os
|
|
3
|
+import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+if __name__ == "__main__":
|
|
7
|
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "course.settings")
|
|
8
|
+ try:
|
|
9
|
+ from django.core.management import execute_from_command_line
|
|
10
|
+ except ImportError:
|
|
11
|
+ # The above import may fail for some other reason. Ensure that the
|
|
12
|
+ # issue is really that Django is missing to avoid masking other
|
|
13
|
+ # exceptions on Python 2.
|
|
14
|
+ try:
|
|
15
|
+ import django
|
|
16
|
+ except ImportError:
|
|
17
|
+ raise ImportError(
|
|
18
|
+ "Couldn't import Django. Are you sure it's installed and "
|
|
19
|
+ "available on your PYTHONPATH environment variable? Did you "
|
|
20
|
+ "forget to activate a virtual environment?"
|
|
21
|
+ )
|
|
22
|
+ raise
|
|
23
|
+ execute_from_command_line(sys.argv)
|
|
|
@@ -0,0 +1,4 @@
|
|
1
|
+from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+# Register your models here.
|
|
|
@@ -0,0 +1,67 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+from __future__ import division
|
|
4
|
+
|
|
5
|
+from django.conf import settings
|
|
6
|
+from django.core.urlresolvers import reverse
|
|
7
|
+from django.db import transaction
|
|
8
|
+from django.shortcuts import redirect, render
|
|
9
|
+from furl import furl
|
|
10
|
+
|
|
11
|
+from account.models import UserInfo
|
|
12
|
+from codes.models import CourseCodeInfo
|
|
13
|
+from course.decorators import check_token
|
|
14
|
+from utils.error.errno_utils import CourseCodeStatusCode, ProfileStatusCode
|
|
15
|
+from utils.error.response_utils import response
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+@check_token
|
|
19
|
+@transaction.atomic
|
|
20
|
+def course_code(request):
|
|
21
|
+ unique_identifier = request.GET.get(settings.WECHAT_UNIQUE_IDENTIFICATION, '')
|
|
22
|
+
|
|
23
|
+ user, created = UserInfo.objects.select_for_update().get_or_create(**{settings.WECHAT_UNIQUE_IDENTIFICATION: unique_identifier})
|
|
24
|
+ user.unionid = request.GET.get('unionid', '')
|
|
25
|
+ user.openid = request.GET.get('openid', '')
|
|
26
|
+ user.nickname = request.GET.get('nickname', '')
|
|
27
|
+ user.avatar = request.GET.get('headimgurl', '')
|
|
28
|
+ user.save()
|
|
29
|
+
|
|
30
|
+ try:
|
|
31
|
+ course_code = CourseCodeInfo.objects.get(user_id=user.user_id, exchanged=True, status=True)
|
|
32
|
+ except CourseCodeInfo.DoesNotExist:
|
|
33
|
+ course_code = None
|
|
34
|
+
|
|
35
|
+ if course_code:
|
|
36
|
+ return redirect(furl(reverse('page:course_list')).add(request.GET).add({'user_id': user.user_id}).url)
|
|
37
|
+
|
|
38
|
+ return render(request, 'page/course_code.html', {
|
|
39
|
+ 'domain': settings.DOMAIN,
|
|
40
|
+ 'user_info': user.data,
|
|
41
|
+ 'params': 'user_id={}&vtoken={}'.format(user.user_id, request.GET.get('vtoken', '')),
|
|
42
|
+ })
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+@transaction.atomic
|
|
46
|
+def code_exchange(request):
|
|
47
|
+ user_id = request.POST.get('user_id', '')
|
|
48
|
+ code = request.POST.get('code', '')
|
|
49
|
+
|
|
50
|
+ try:
|
|
51
|
+ user = UserInfo.objects.select_for_update().get(user_id=user_id)
|
|
52
|
+ except UserInfo.DoesNotExist:
|
|
53
|
+ return response(ProfileStatusCode.PROFILE_NOT_FOUND)
|
|
54
|
+
|
|
55
|
+ try:
|
|
56
|
+ course_code = CourseCodeInfo.objects.select_for_update().get(code=code, status=True)
|
|
57
|
+ except CourseCodeInfo.DoesNotExist:
|
|
58
|
+ return response(CourseCodeStatusCode.COURSE_CODE_NOT_FOUND)
|
|
59
|
+
|
|
60
|
+ if course_code.exchanged:
|
|
61
|
+ return response(CourseCodeStatusCode.COURSE_CODE_HAS_EXCHANGED)
|
|
62
|
+
|
|
63
|
+ course_code.user_id = user.user_id
|
|
64
|
+ course_code.exchanged = True
|
|
65
|
+ course_code.save()
|
|
66
|
+
|
|
67
|
+ return response(200, 'Course Code Exchanged Success', u'课程兑换码兑换成功')
|
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+from __future__ import division
|
|
4
|
+
|
|
5
|
+from django.conf import settings
|
|
6
|
+from django.shortcuts import render
|
|
7
|
+
|
|
8
|
+from account.models import UserInfo
|
|
9
|
+from codes.models import CourseCodeInfo
|
|
10
|
+from course.decorators import check_token
|
|
11
|
+from courses.models import CourseInfo, CourseVideoInfo
|
|
12
|
+from utils.error.errno_utils import CourseCodeStatusCode, CourseStatusCode, ProfileStatusCode
|
|
13
|
+from utils.error.response_utils import response
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+@check_token
|
|
17
|
+def course_info(request):
|
|
18
|
+ user_id = request.GET.get('user_id', '')
|
|
19
|
+ course_id = request.GET.get('course_id', '')
|
|
20
|
+
|
|
21
|
+ try:
|
|
22
|
+ user = UserInfo.objects.get(user_id=user_id, status=True)
|
|
23
|
+ except UserInfo.DoesNotExist:
|
|
24
|
+ return response(ProfileStatusCode.PROFILE_NOT_FOUND)
|
|
25
|
+
|
|
26
|
+ try:
|
|
27
|
+ course_code = CourseCodeInfo.objects.get(user_id=user.user_id, exchanged=True, status=True)
|
|
28
|
+ except CourseCodeInfo.DoesNotExist:
|
|
29
|
+ response(CourseCodeStatusCode.COURSE_CODE_NOT_FOUND)
|
|
30
|
+
|
|
31
|
+ try:
|
|
32
|
+ course = CourseInfo.objects.get(course_id=course_id)
|
|
33
|
+ except CourseInfo.DoesNotExist:
|
|
34
|
+ response(CourseStatusCode.COURSE_NOT_FOUND)
|
|
35
|
+
|
|
36
|
+ videos = CourseVideoInfo.objects.filter(course=course, status=True).order_by('course_video_position')
|
|
37
|
+ videos = [video.data for video in videos]
|
|
38
|
+
|
|
39
|
+ video_count = len(videos)
|
|
40
|
+
|
|
41
|
+ return render(request, 'page/course_info.html', {
|
|
42
|
+ 'domain': settings.DOMAIN,
|
|
43
|
+ 'video_default': videos[0] if video_count else '',
|
|
44
|
+ 'video_count': video_count,
|
|
45
|
+ 'videos': videos,
|
|
46
|
+ 'params': 'user_id={}&vtoken={}'.format(user_id, request.GET.get('vtoken', '')),
|
|
47
|
+ })
|
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+from __future__ import division
|
|
4
|
+
|
|
5
|
+from django.conf import settings
|
|
6
|
+from django.shortcuts import render
|
|
7
|
+
|
|
8
|
+from account.models import UserInfo
|
|
9
|
+from codes.models import CourseCodeInfo
|
|
10
|
+from course.decorators import check_token
|
|
11
|
+from courses.models import CourseInfo
|
|
12
|
+from utils.error.errno_utils import CourseCodeStatusCode, ProfileStatusCode
|
|
13
|
+from utils.error.response_utils import response
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+@check_token
|
|
17
|
+def course_list(request):
|
|
18
|
+ user_id = request.GET.get('user_id', '')
|
|
19
|
+
|
|
20
|
+ try:
|
|
21
|
+ user = UserInfo.objects.get(user_id=user_id, status=True)
|
|
22
|
+ except UserInfo.DoesNotExist:
|
|
23
|
+ return response(ProfileStatusCode.PROFILE_NOT_FOUND)
|
|
24
|
+
|
|
25
|
+ try:
|
|
26
|
+ course_code = CourseCodeInfo.objects.get(user_id=user.user_id, exchanged=True, status=True)
|
|
27
|
+ except CourseCodeInfo.DoesNotExist:
|
|
28
|
+ response(CourseCodeStatusCode.COURSE_CODE_NOT_FOUND)
|
|
29
|
+
|
|
30
|
+ courses = CourseInfo.objects.filter(status=True)
|
|
31
|
+ courses = [course.data for course in courses]
|
|
32
|
+
|
|
33
|
+ return render(request, 'page/course_list.html', {
|
|
34
|
+ 'domain': settings.DOMAIN,
|
|
35
|
+ 'courses': courses,
|
|
36
|
+ 'params': 'user_id={}&vtoken={}'.format(user_id, request.GET.get('vtoken', '')),
|
|
37
|
+ })
|
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+# Generated by Django 1.11.3 on 2017-09-24 14:50
|
|
3
|
+from __future__ import unicode_literals
|
|
4
|
+
|
|
5
|
+from django.db import migrations, models
|
|
6
|
+import page.models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+class Migration(migrations.Migration):
|
|
10
|
+
|
|
11
|
+ initial = True
|
|
12
|
+
|
|
13
|
+ dependencies = [
|
|
14
|
+ ]
|
|
15
|
+
|
|
16
|
+ operations = [
|
|
17
|
+ migrations.CreateModel(
|
|
18
|
+ name='CourseCodeSettingInfo',
|
|
19
|
+ fields=[
|
|
20
|
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
21
|
+ ('status', models.BooleanField(db_index=True, default=True, help_text='\u72b6\u6001', verbose_name='status')),
|
|
22
|
+ ('created_at', models.DateTimeField(auto_now_add=True, help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at')),
|
|
23
|
+ ('updated_at', models.DateTimeField(auto_now=True, help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at')),
|
|
24
|
+ ('cover_image', models.ImageField(blank=True, help_text='\u5151\u6362\u8bfe\u7a0b\u9875\u56fe\u7247', null=True, upload_to=page.models.upload_path, verbose_name='cover_image')),
|
|
25
|
+ ],
|
|
26
|
+ options={
|
|
27
|
+ 'verbose_name': 'coursecodesettinginfo',
|
|
28
|
+ 'verbose_name_plural': 'coursecodesettinginfo',
|
|
29
|
+ },
|
|
30
|
+ ),
|
|
31
|
+ ]
|
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+import os
|
|
4
|
+
|
|
5
|
+from django.db import models
|
|
6
|
+from django.utils.translation import ugettext_lazy as _
|
|
7
|
+from TimeConvert import TimeConvert as tc
|
|
8
|
+
|
|
9
|
+from course.basemodels import CreateUpdateMixin
|
|
10
|
+from utils.url_utils import upload_file_url
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+def upload_path(instance, old_filename):
|
|
14
|
+ return 'file/{ym}/{stamp}{ext}'.format(
|
|
15
|
+ ym=tc.local_string(format='%Y%m'),
|
|
16
|
+ stamp=tc.local_timestamp(ms=True),
|
|
17
|
+ ext=os.path.splitext(old_filename)[1].lower(),
|
|
18
|
+ )
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+class CourseCodeSettingInfo(CreateUpdateMixin):
|
|
22
|
+ cover_image = models.ImageField(_(u'cover_image'), upload_to=upload_path, blank=True, null=True, help_text=u'兑换课程页图片')
|
|
23
|
+
|
|
24
|
+ class Meta:
|
|
25
|
+ verbose_name = _(u'coursecodesettinginfo')
|
|
26
|
+ verbose_name_plural = _(u'coursecodesettinginfo')
|
|
27
|
+
|
|
28
|
+ def __unicode__(self):
|
|
29
|
+ return unicode(self.pk)
|
|
30
|
+
|
|
31
|
+ @property
|
|
32
|
+ def cover_image_url(self):
|
|
33
|
+ return upload_file_url(self.cover_image)
|
|
|
@@ -0,0 +1,67 @@
|
|
1
|
+/* Input valid or invalid */
|
|
2
|
+input:required:invalid {
|
|
3
|
+ color: #E64340;
|
|
4
|
+}
|
|
5
|
+input:required:valid {
|
|
6
|
+ color: rgb(0, 0, 0);
|
|
7
|
+}
|
|
8
|
+/* Input Placeholder */
|
|
9
|
+ input::-webkit-input-placeholder, textarea::-webkit-input-placeholder {
|
|
10
|
+ font-size: 13px;
|
|
11
|
+}
|
|
12
|
+input:-moz-placeholder, textarea:-moz-placeholder {
|
|
13
|
+ font-size: 13px;
|
|
14
|
+}
|
|
15
|
+input::-moz-placeholder, textarea::-moz-placeholder {
|
|
16
|
+ font-size: 13px;
|
|
17
|
+}
|
|
18
|
+input:-ms-input-placeholder, textarea:-ms-input-placeholder {
|
|
19
|
+ font-size: 13px;
|
|
20
|
+}
|
|
21
|
+/* Radio Cells */
|
|
22
|
+.radio_cells {
|
|
23
|
+ margin-top: 0;
|
|
24
|
+ margin-left: 15px;
|
|
25
|
+}
|
|
26
|
+.radio_cells label {
|
|
27
|
+ padding: 8px 10px;
|
|
28
|
+ font-size: 15px;
|
|
29
|
+}
|
|
30
|
+/*.radio_cells>div:first-child .quartern:after {*/
|
|
31
|
+ /*border-left: none;*/
|
|
32
|
+/*}*/
|
|
33
|
+.radio_cells>div:last-child .quartern:after {
|
|
34
|
+ border-right: none;
|
|
35
|
+}
|
|
36
|
+/* Quartern */
|
|
37
|
+.quartern {
|
|
38
|
+ width: 25%;
|
|
39
|
+ box-sizing: border-box;
|
|
40
|
+ text-align: center;
|
|
41
|
+ border-radius: 5px;
|
|
42
|
+ float: left;
|
|
43
|
+}
|
|
44
|
+.quartern:after {
|
|
45
|
+ content: " ";
|
|
46
|
+ width: 200%;
|
|
47
|
+ height: 200%;
|
|
48
|
+ position: absolute;
|
|
49
|
+ top: 0;
|
|
50
|
+ left: 0;
|
|
51
|
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
|
|
52
|
+ /*border-width: 0 1px 0 1px;*/
|
|
53
|
+ /*border-color: rgba(0, 0, 0, 0.2);*/
|
|
54
|
+ /*border-style: solid;*/
|
|
55
|
+ -webkit-transform: scale(0.5);
|
|
56
|
+ transform: scale(0.5);
|
|
57
|
+ -webkit-transform-origin: 0 0;
|
|
58
|
+ transform-origin: 0 0;
|
|
59
|
+ box-sizing: border-box;
|
|
60
|
+ border-radius: 10px;
|
|
61
|
+}
|
|
62
|
+/* Radio Checked Relative */
|
|
63
|
+.weui_check:checked + .quartern {
|
|
64
|
+ color: white;
|
|
65
|
+ background: #04BE02;
|
|
66
|
+ border-width: 0;
|
|
67
|
+}
|
|
|
@@ -0,0 +1,135 @@
|
|
1
|
+{% load staticfiles %}
|
|
2
|
+
|
|
3
|
+<!DOCTYPE html>
|
|
4
|
+<html lang="zh-CN">
|
|
5
|
+ <head>
|
|
6
|
+ <meta charset="utf-8">
|
|
7
|
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
8
|
+ <meta name="format-detection" content="telephone=no,email=no,address=no">
|
|
9
|
+ <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
|
|
10
|
+ <title>课程兑换</title>
|
|
11
|
+
|
|
12
|
+ <link href="//res.wx.qq.com/open/libs/weui/0.4.3/weui.min.css" rel="stylesheet" type="text/css" />
|
|
13
|
+{# <link href="{% static 'page/css/weui.ext.css' %}?v=1" rel="stylesheet" type="text/css" />#}
|
|
14
|
+
|
|
15
|
+ <style>
|
|
16
|
+ .code-cover, .code-cover>img {
|
|
17
|
+ width: 100%;
|
|
18
|
+ }
|
|
19
|
+ .code-area {
|
|
20
|
+ padding: 5px 10px;
|
|
21
|
+ }
|
|
22
|
+ .code-label {
|
|
23
|
+ width: 70%;
|
|
24
|
+ color: #c6c6c6;
|
|
25
|
+ font-size: 13px;
|
|
26
|
+ margin: 15px auto;
|
|
27
|
+ }
|
|
28
|
+ .code-input, .code-input>input, .code-submit {
|
|
29
|
+ width: 80%;
|
|
30
|
+ height: 40px;
|
|
31
|
+ line-height: 40px;
|
|
32
|
+ margin: 5px auto;
|
|
33
|
+ border-radius: 25px;
|
|
34
|
+ box-sizing: border-box;
|
|
35
|
+ }
|
|
36
|
+ .code-input>input {
|
|
37
|
+ width: 100%;
|
|
38
|
+ border: 1px solid #c6c6c6;
|
|
39
|
+ padding: 0 15px;
|
|
40
|
+ outline: medium;
|
|
41
|
+ }
|
|
42
|
+ .code-submit {
|
|
43
|
+ text-align: center;
|
|
44
|
+ background: #20a1f5;
|
|
45
|
+ color: #c6eaf9;
|
|
46
|
+ margin-top: 15px;
|
|
47
|
+ }
|
|
48
|
+ </style>
|
|
49
|
+ </head>
|
|
50
|
+ <body>
|
|
51
|
+ <div class="container">
|
|
52
|
+ <div class="code-cover"><img src="{% static 'page/img/code_cover.png' %}"></div>
|
|
53
|
+ <div class="code-area">
|
|
54
|
+ <div class="code-label">输入兑换码兑换课程</div>
|
|
55
|
+ <div class="code-input"><input id="code" placeholder="请输入兑换码"></div>
|
|
56
|
+ <div id="submit" class="code-submit">确认兑换</div>
|
|
57
|
+ </div>
|
|
58
|
+
|
|
59
|
+ <div class="weui_dialog_alert" id="dialog" style="display: none">
|
|
60
|
+ <div class="weui_mask"></div>
|
|
61
|
+ <div class="weui_dialog">
|
|
62
|
+ <div class="weui_dialog_hd"><strong id="title" class="weui_dialog_title">弹窗标题</strong></div>
|
|
63
|
+ <div id="content" class="weui_dialog_bd">弹窗内容,告知当前页面信息等</div>
|
|
64
|
+ <div class="weui_dialog_ft">
|
|
65
|
+ <a href="javascript:;" class="weui_btn_dialog primary">确定</a>
|
|
66
|
+ </div>
|
|
67
|
+ </div>
|
|
68
|
+ </div>
|
|
69
|
+ </div>
|
|
70
|
+
|
|
71
|
+ <script src="//cdn.bootcss.com/zepto/1.1.6/zepto.min.js"></script>
|
|
72
|
+ <script>
|
|
73
|
+ $(function() {
|
|
74
|
+ function show_error_dialog(title, content) {
|
|
75
|
+ $('#dialog #title').text(title);
|
|
76
|
+ $('#dialog #content').text(content);
|
|
77
|
+ $('#dialog').show();
|
|
78
|
+ }
|
|
79
|
+
|
|
80
|
+ function data_check() {
|
|
81
|
+ var user_id = '{{ user_info.user_id }}';
|
|
82
|
+ if (!user_id) {
|
|
83
|
+ show_error_dialog('微信授权', '微信授权失败,请重新打开页面');
|
|
84
|
+ return false;
|
|
85
|
+ }
|
|
86
|
+
|
|
87
|
+ var code = $('#code').val();
|
|
88
|
+ if (!code) {
|
|
89
|
+ show_error_dialog('兑换码', '兑换码错误,请检查重新输入');
|
|
90
|
+ return false;
|
|
91
|
+ }
|
|
92
|
+
|
|
93
|
+ return {
|
|
94
|
+ user_id: user_id,
|
|
95
|
+ code: code,
|
|
96
|
+ }
|
|
97
|
+ }
|
|
98
|
+
|
|
99
|
+ $('#submit').click(function () {
|
|
100
|
+ var check_result = data_check();
|
|
101
|
+ if (check_result){
|
|
102
|
+ $.ajax({
|
|
103
|
+ type: 'POST',
|
|
104
|
+ url: '{{ domain }}/api/code/exchange',
|
|
105
|
+ data: check_result,
|
|
106
|
+ success: function(data) {
|
|
107
|
+ if (data.status == 200) {
|
|
108
|
+ window.location.href = '{{ domain }}/page/course/list?{{ params|safe }}';
|
|
109
|
+ } else {
|
|
110
|
+ show_error_dialog('错误', data.description);
|
|
111
|
+ }
|
|
112
|
+ }
|
|
113
|
+ })
|
|
114
|
+ }
|
|
115
|
+ });
|
|
116
|
+
|
|
117
|
+ $('#dialog .weui_btn_dialog').click(function () {
|
|
118
|
+ $('#dialog').hide();
|
|
119
|
+ })
|
|
120
|
+ });
|
|
121
|
+ </script>
|
|
122
|
+ <script type="text/javascript" src="//res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
|
|
123
|
+ <script type="text/javascript" src="{% static 'course/js/jswe-0.0.1.js' %}"></script>
|
|
124
|
+ <script>
|
|
125
|
+ V.initWxData({
|
|
126
|
+ imgUrl: 'http://pai.ai/static/pai2/img/paiai_96_96.png',
|
|
127
|
+ link: 'http://api.pai.ai/wx_oauth2?redirect_url=http://tamron.xfoto.com.cn/page/clerk',
|
|
128
|
+ desc: '店员授权',
|
|
129
|
+ title: '店员授权',
|
|
130
|
+ timeLine: ''
|
|
131
|
+ }, true);
|
|
132
|
+{# V.hideOptionMenu();#}
|
|
133
|
+ </script>
|
|
134
|
+ </body>
|
|
135
|
+</html>
|
|
|
@@ -0,0 +1,124 @@
|
|
1
|
+{% load staticfiles %}
|
|
2
|
+
|
|
3
|
+<!DOCTYPE html>
|
|
4
|
+<html lang="zh-CN">
|
|
5
|
+ <head>
|
|
6
|
+ <meta charset="utf-8">
|
|
7
|
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
8
|
+ <meta name="format-detection" content="telephone=no,email=no,address=no">
|
|
9
|
+ <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
|
|
10
|
+ <title>课程详情</title>
|
|
11
|
+
|
|
12
|
+ <link href="//res.wx.qq.com/open/libs/weui/0.4.3/weui.min.css" rel="stylesheet" type="text/css" />
|
|
13
|
+ <link href="{% static 'page/css/weui.ext.css' %}?v=1" rel="stylesheet" type="text/css" />
|
|
14
|
+
|
|
15
|
+ <style>
|
|
16
|
+ .container {
|
|
17
|
+ position: absolute;
|
|
18
|
+ top: 0;
|
|
19
|
+ right: 0;
|
|
20
|
+ bottom: 0;
|
|
21
|
+ left: 0;
|
|
22
|
+ background: #efefef;
|
|
23
|
+ }
|
|
24
|
+ .video_wrapper, .video_select {
|
|
25
|
+ width: 100%;
|
|
26
|
+ background: #fff;
|
|
27
|
+ margin-bottom: 10px;
|
|
28
|
+ box-sizing: border-box;
|
|
29
|
+ }
|
|
30
|
+ .video_text, .video_select {
|
|
31
|
+ padding: 15px;
|
|
32
|
+ }
|
|
33
|
+ .course_video_name, .video_select_text {
|
|
34
|
+ font-size: 18px;
|
|
35
|
+ font-weight: bold;
|
|
36
|
+ color: #020001;
|
|
37
|
+ padding-bottom: 10px;
|
|
38
|
+ }
|
|
39
|
+ .course_video_desc {
|
|
40
|
+ font-size: 12px;
|
|
41
|
+ color: #999;
|
|
42
|
+ }
|
|
43
|
+ .video_select_item {
|
|
44
|
+ width: 100%;
|
|
45
|
+ text-align: center;
|
|
46
|
+ height: 40px;
|
|
47
|
+ line-height: 40px;
|
|
48
|
+ color: #020001;
|
|
49
|
+ border: 1px solid #e1e1e1;
|
|
50
|
+ border-radius: 5px;
|
|
51
|
+ margin-bottom: 15px;
|
|
52
|
+ }
|
|
53
|
+ .video_selected {
|
|
54
|
+ color: #ce8f8a !important;
|
|
55
|
+ border: 1px solid #ce8f8a;
|
|
56
|
+ }
|
|
57
|
+ </style>
|
|
58
|
+ </head>
|
|
59
|
+ <body>
|
|
60
|
+ <div class="container" >
|
|
61
|
+ <div class="video_wrapper">
|
|
62
|
+ <video id="video" width="100%" height="100%" autoplay controls x-webkit-airplay="true" webkit-playsinline="" playsinline="true" preload="none" poster="" src="{{ video_default.course_video_url }}" data-cursrc="1"></video>
|
|
63
|
+ <div class="video_text">
|
|
64
|
+ <div class="course_video_name">{{ video_default.course_video_name }}</div>
|
|
65
|
+ <div class="course_video_desc">{{ video_default.course_video_desc }}</div>
|
|
66
|
+ </div>
|
|
67
|
+ </div>
|
|
68
|
+
|
|
69
|
+ <div class="video_select">
|
|
70
|
+ <div class="video_select_text">选择视频</div>
|
|
71
|
+ {% for video in videos %}
|
|
72
|
+ <div id="video{{ forloop.counter }}" class="video_select_item {% ifequal forloop.counter 1 %}video_selected{% endifequal %}" data-src="{{ video.course_video_url }}">{{ video.course_video_type }}</div>
|
|
73
|
+ {% endfor %}
|
|
74
|
+ </div>
|
|
75
|
+ </div>
|
|
76
|
+
|
|
77
|
+ <script src="//cdn.bootcss.com/zepto/1.1.6/zepto.min.js"></script>
|
|
78
|
+ <script src="//cdn.bootcss.com/video.js/6.2.8/video.min.js"></script>
|
|
79
|
+ <script>
|
|
80
|
+ $(function() {
|
|
81
|
+ var video_count = {{ video_count }};
|
|
82
|
+
|
|
83
|
+ $('.video_select_item').click(function () {
|
|
84
|
+ $this = $(this);
|
|
85
|
+ $('.video_select_item').removeClass('video_selected');
|
|
86
|
+ $this.addClass('video_selected');
|
|
87
|
+ $('#video').attr('src', $this.attr('data-src'));
|
|
88
|
+ })
|
|
89
|
+
|
|
90
|
+ $('#video')[0].onended = function() {
|
|
91
|
+ var curscr = $(this).attr('data-cursrc');
|
|
92
|
+ if (curscr >= video_count) {
|
|
93
|
+ return
|
|
94
|
+ }
|
|
95
|
+ var next_video = $('#video' + (parseInt(curscr) + 1));
|
|
96
|
+ $('#video').attr('src', next_video.attr('data-src'));
|
|
97
|
+ $('.video_select_item').removeClass('video_selected');
|
|
98
|
+ next_video.addClass('video_selected');
|
|
99
|
+ };
|
|
100
|
+ });
|
|
101
|
+ </script>
|
|
102
|
+ <script type="text/javascript" src="//res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
|
|
103
|
+ <script type="text/javascript" src="{% static 'course/js/jswe-0.0.1.js' %}"></script>
|
|
104
|
+ <script>
|
|
105
|
+ V.initWxData({
|
|
106
|
+ imgUrl: 'http://pai.ai/static/pai2/img/paiai_96_96.png',
|
|
107
|
+ link: 'http://api.pai.ai/wx_oauth2?redirect_url=http://tamron.xfoto.com.cn/page/clerk',
|
|
108
|
+ desc: '店员授权',
|
|
109
|
+ title: '店员授权',
|
|
110
|
+ timeLine: ''
|
|
111
|
+ }, true);
|
|
112
|
+ V.hideOptionMenu();
|
|
113
|
+
|
|
114
|
+ $('#scan').click(function () {
|
|
115
|
+ V.scanQRCode({
|
|
116
|
+ needResult: 1
|
|
117
|
+ });
|
|
118
|
+ });
|
|
119
|
+ V.wxScanQRCodeSuccess = function (res) {
|
|
120
|
+ $('#code').val(V.parseScanQRCodeResultStr(res.resultStr));
|
|
121
|
+ }
|
|
122
|
+ </script>
|
|
123
|
+ </body>
|
|
124
|
+</html>
|
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+{% load staticfiles %}
|
|
2
|
+
|
|
3
|
+<!DOCTYPE html>
|
|
4
|
+<html lang="zh-CN">
|
|
5
|
+ <head>
|
|
6
|
+ <meta charset="utf-8">
|
|
7
|
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
8
|
+ <meta name="format-detection" content="telephone=no,email=no,address=no">
|
|
9
|
+ <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
|
|
10
|
+ <title>课程列表</title>
|
|
11
|
+
|
|
12
|
+ <link href="//res.wx.qq.com/open/libs/weui/0.4.3/weui.min.css" rel="stylesheet" type="text/css" />
|
|
13
|
+ <link href="{% static 'page/css/weui.ext.css' %}?v=1" rel="stylesheet" type="text/css" />
|
|
14
|
+
|
|
15
|
+ <style>
|
|
16
|
+ .container {
|
|
17
|
+ position: absolute;
|
|
18
|
+ top: 0;
|
|
19
|
+ right: 0;
|
|
20
|
+ bottom: 0;
|
|
21
|
+ left: 0;
|
|
22
|
+ background: #efefef;
|
|
23
|
+ }
|
|
24
|
+ .course_wrapper {
|
|
25
|
+ text-align: center;
|
|
26
|
+ background: white;
|
|
27
|
+ margin-bottom: 20px;
|
|
28
|
+ padding: 15px 0;
|
|
29
|
+ }
|
|
30
|
+ .course_name {
|
|
31
|
+ font-size: 18px;
|
|
32
|
+ font-weight: bold;
|
|
33
|
+ color: #020001;
|
|
34
|
+ }
|
|
35
|
+ .course_time {
|
|
36
|
+ font-size: 12px;
|
|
37
|
+ color: #999;
|
|
38
|
+ }
|
|
39
|
+ .course_cover>img {
|
|
40
|
+ width: 80%;
|
|
41
|
+ border-radius: 5px;
|
|
42
|
+ }
|
|
43
|
+ </style>
|
|
44
|
+ </head>
|
|
45
|
+ <body>
|
|
46
|
+ <div class="container" >
|
|
47
|
+ {% for course in courses %}
|
|
48
|
+ <div class="course_wrapper" data-courseid="{{ course.course_id }}">
|
|
49
|
+ <div class="course_name">{{ course.course_name }}</div>
|
|
50
|
+ <div class="course_time">{{ course.course_time }}分钟</div>
|
|
51
|
+ <div class="course_cover"><img src="{{ course.course_cover_url }}"></div>
|
|
52
|
+ </div>
|
|
53
|
+ {% endfor %}
|
|
54
|
+ </div>
|
|
55
|
+
|
|
56
|
+ <script src="//cdn.bootcss.com/zepto/1.1.6/zepto.min.js"></script>
|
|
57
|
+ <script>
|
|
58
|
+ $(function() {
|
|
59
|
+ $('.course_wrapper').click(function () {
|
|
60
|
+ window.location.href = '{{ domain }}/page/course/info?course_id=' + $(this).attr('data-courseid') + '&{{ params|safe }}';
|
|
61
|
+ })
|
|
62
|
+ });
|
|
63
|
+ </script>
|
|
64
|
+ <script type="text/javascript" src="//res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
|
|
65
|
+ <script type="text/javascript" src="{% static 'course/js/jswe-0.0.1.js' %}"></script>
|
|
66
|
+ <script>
|
|
67
|
+ V.initWxData({
|
|
68
|
+ imgUrl: 'http://pai.ai/static/pai2/img/paiai_96_96.png',
|
|
69
|
+ link: 'http://api.pai.ai/wx_oauth2?redirect_url=http://tamron.xfoto.com.cn/page/clerk',
|
|
70
|
+ desc: '店员授权',
|
|
71
|
+ title: '店员授权',
|
|
72
|
+ timeLine: ''
|
|
73
|
+ }, true);
|
|
74
|
+{# V.hideOptionMenu();#}
|
|
75
|
+ </script>
|
|
76
|
+ </body>
|
|
77
|
+</html>
|
|
|
@@ -0,0 +1,4 @@
|
|
1
|
+from django.test import TestCase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+# Create your tests here.
|
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+from django.conf.urls import url
|
|
4
|
+
|
|
5
|
+from page import code_views, info_views, list_views
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+urlpatterns = [
|
|
9
|
+ url(r'^course/code$', code_views.course_code, name='course_code'),
|
|
10
|
+ url(r'^course/list$', list_views.course_list, name='course_list'),
|
|
11
|
+ url(r'^course/info$', info_views.course_info, name='course_info'),
|
|
12
|
+]
|
|
|
@@ -0,0 +1,4 @@
|
|
1
|
+from django.shortcuts import render
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+# Create your views here.
|
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+#!/bin/bash
|
|
2
|
+
|
|
3
|
+# Ignoring autogenerated files
|
|
4
|
+# -- Migration directories
|
|
5
|
+# Ignoring error codes
|
|
6
|
+# -- E128 continuation line under-indented for visual indent
|
|
7
|
+# -- E501 line too long
|
|
8
|
+
|
|
9
|
+pep8 --exclude=migrations --ignore=E128,E501 .
|
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+-e git+https://github.com/Brightcells/django-q.git#egg=django-q
|
|
2
|
+CodeConvert==2.0.4
|
|
3
|
+Django==1.11.3
|
|
4
|
+MySQL-python==1.2.5
|
|
5
|
+Pillow==3.4.2
|
|
6
|
+StatusCode==1.0.0
|
|
7
|
+TimeConvert==1.4.1
|
|
8
|
+cryptography==2.0.3
|
|
9
|
+django-curtail-uuid==1.0.0
|
|
10
|
+django-detect==1.0.5
|
|
11
|
+django-file-md5==1.0.1
|
|
12
|
+django-ip==1.0.1
|
|
13
|
+django-json-response==1.1.5
|
|
14
|
+django-logit==1.0.6
|
|
15
|
+django-multidomain==1.1.4
|
|
16
|
+django-paginator2==1.0.3
|
|
17
|
+django-rlog==1.0.7
|
|
18
|
+django-shortuuidfield==0.1.3
|
|
19
|
+django-six==1.0.2
|
|
20
|
+django-uniapi==1.0.0
|
|
21
|
+django-we==1.0.14
|
|
22
|
+djangorestframework==3.6.3
|
|
23
|
+furl==1.0.1
|
|
24
|
+hiredis==0.2.0
|
|
25
|
+isoweek==1.3.3
|
|
26
|
+jsonfield==2.0.2
|
|
27
|
+mock==2.0.0
|
|
28
|
+pep8==1.7.0
|
|
29
|
+pysnippets==1.0.4
|
|
30
|
+pywe-miniapp==1.0.0
|
|
31
|
+pywe-oauth==1.0.5
|
|
32
|
+pywe-response==1.0.1
|
|
33
|
+qiniu==7.1.5
|
|
34
|
+redis==2.10.6
|
|
35
|
+redis-extensions==1.1.1
|
|
36
|
+requests==2.18.4
|
|
37
|
+rlog==0.2
|
|
38
|
+shortuuid==0.5.0
|
|
39
|
+uWSGI==2.0.15
|
|
40
|
+versions==0.10.0
|
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+from StatusCode import BaseStatusCode, StatusCodeField
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+class ProfileStatusCode(BaseStatusCode):
|
|
7
|
+ """ 用户相关错误码 4000xx """
|
|
8
|
+ PROFILE_NOT_FOUND = StatusCodeField(400001, 'Profile Not Found', description=u'用户不存在')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+class CourseCodeStatusCode(BaseStatusCode):
|
|
12
|
+ """ 课程兑换码相关错误码 4001xx """
|
|
13
|
+ COURSE_CODE_NOT_FOUND = StatusCodeField(400101, 'Course Code Not Found', description=u'课程兑换码不存在')
|
|
14
|
+ COURSE_CODE_HAS_EXCHANGED = StatusCodeField(400102, 'Course Code Has Exchanged', description=u'课程兑换码已兑换')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+class CourseStatusCode(BaseStatusCode):
|
|
18
|
+ """ 课程相关错误码 4002xx """
|
|
19
|
+ COURSE_NOT_FOUND = StatusCodeField(400201, 'Course Not Found', description=u'课程不存在')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+class OrderStatusCode(BaseStatusCode):
|
|
23
|
+ """ 订单/支付相关错误码 4040xx """
|
|
24
|
+ WX_UNIFIED_ORDER_FAIL = StatusCodeField(404000, 'WX Unified Order Fail', description=u'微信统一下单失败')
|
|
25
|
+ WX_ORDER_NOT_FOUND = StatusCodeField(404001, 'WX Order Not Found', description=u'订单不存在')
|
|
26
|
+ WX_ORDER_NOT_PAY = StatusCodeField(404002, 'WX Order Not Pay', description=u'订单未支付')
|
|
27
|
+ WX_ORDER_PAYING = StatusCodeField(404003, 'WX Order Paying', description=u'订单支付中')
|
|
28
|
+ WX_ORDER_PAY_FAIL = StatusCodeField(404009, 'WX Order Pay Fail', description=u'微信支付失败')
|
|
29
|
+ SIGN_CHECK_FAIL = StatusCodeField(404010, 'Sign Check Fail', description=u'签名校验失败')
|
|
30
|
+ FEE_CHECK_FAIL = StatusCodeField(404011, 'FEE Check Fail', description=u'金额校验失败')
|
|
31
|
+ NO_DETAIL_PERMISSION = StatusCodeField(404015, 'No Detail Permission', description=u'无详情权限')
|
|
32
|
+ WX_ORDER_PAID_ALREADY_EXISTS = StatusCodeField(404020, 'WX Order Paid Already Exists', description=u'照片已购买')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+class PayStatusCode(BaseStatusCode):
|
|
36
|
+ """ 支付相关错误码 4041xx """
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+class WithdrawStatusCode(BaseStatusCode):
|
|
40
|
+ """ 提现相关错误码 4042xx """
|
|
41
|
+ BALANCE_NOT_ENOUGH = StatusCodeField(404200, 'Balance Not Enough', description=u'提现金额不足')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+class MessageStatusCode(BaseStatusCode):
|
|
45
|
+ """ 消息相关错误码 4090xx """
|
|
46
|
+ MESSAGE_NOT_FOUND = StatusCodeField(409001, 'Message Not Found', description=u'消息不存在')
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+class TokenStatusCode(BaseStatusCode):
|
|
50
|
+ """ 票据相关错误码 4090xx """
|
|
51
|
+ TOKEN_NOT_FOUND = StatusCodeField(409901, 'Token Not Found', description=u'票据不存在')
|
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+from django.http import JsonResponse
|
|
4
|
+from StatusCode import StatusCodeField
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+def response_data(status_code=200, message=None, description=None, data={}, **kwargs):
|
|
8
|
+ return dict({
|
|
9
|
+ 'status': status_code,
|
|
10
|
+ 'message': message,
|
|
11
|
+ 'description': description,
|
|
12
|
+ 'data': data,
|
|
13
|
+ }, **kwargs)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+def response(status_code=200, message=None, description=None, data={}, **kwargs):
|
|
17
|
+ message, description = (message or status_code.message, description or status_code.description) if isinstance(status_code, StatusCodeField) else (message, description)
|
|
18
|
+ return JsonResponse(response_data(status_code, message, description, data, **kwargs), safe=False)
|
|
|
@@ -0,0 +1,6 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+from django.conf import settings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+r = settings.REDIS_CACHE
|
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+# 唯一标识相关
|
|
4
|
+UUID_LIST = 'uuid:list' # List,唯一标识列表
|
|
5
|
+
|
|
6
|
+# 用户相关
|
|
7
|
+PROFILE_INFO = 'profile:info:%s' # STRING,用户信息,user_id
|
|
8
|
+
|
|
9
|
+# 导游相关
|
|
10
|
+TOUR_GUIDE_GROUP_GEO_INFO = 'tour:guide:group:geo:info:%s' # ZSET,旅游团地理位置信息,group_id
|
|
11
|
+TOUR_GUIDE_GROUP_GEO_SUBMIT_DT = 'tour:guide:group:geo:submit:dt:%s' # ZSET,旅游团地理位置最后上传时间,group_id
|
|
12
|
+TOUR_GUIDE_GROUP_CUR_SESSION = 'tour:guide:group:cur:session:%s' # STRING,旅游团当前Session,group_id,导游设置集合时间的时候更新
|
|
13
|
+TOUR_GUIDE_GROUP_CUR_GATHER_INFO = 'tour:guide:group:cur:gather:info:%s' # STRING,旅游团当前Session,group_id,导游设置集合时间的时候更新
|
|
14
|
+TOUR_GUIDE_GROUP_USER_GEO_LIST = 'tour:guide:group:user:geo:list:%s:%s:%s' # LIST,旅游团当前用户地理位置列表,group_id、session_id、user_id
|
|
15
|
+
|
|
16
|
+TOUR_GUIDE_GROUP_USER_OWN = 'tour:guide:group:user:own:%s' # STRING,导游当前拥有的旅行团,user_id,导游创建旅行团的时候更新
|
|
17
|
+TOUR_GUIDE_GROUP_USER_BELONG = 'tour:guide:group:user:belong:%s' # STRING,用户当前所属旅行团,user_id,用户加入旅行团的时候更新
|
|
18
|
+
|
|
19
|
+# 群组相关
|
|
20
|
+GROUP_INFO = 'group:info:%s' # STRING,群组信息,group_id
|
|
21
|
+
|
|
22
|
+# 群组用户相关
|
|
23
|
+GROUP_USERS_INFO = 'group:users:info:%s' # STRING,群组用户信息,group_id
|
|
24
|
+GROUP_USERS_KV_INFO = 'group:users:kv:info:%s' # STRING,群组用户信息,group_id
|
|
25
|
+GROUP_USERS_APPLYING_SET = 'group:users:applying:set:%s' # SET,群组用户申请集合,group_id
|
|
26
|
+GROUP_USERS_PASSED_SET = 'group:users:passed:set:%s' # SET,群组用户通过集合,group_id
|
|
27
|
+GROUP_USERS_REFUSED_SET = 'group:users:refused:set:%s' # SET,群组用户拒绝集合,group_id
|
|
28
|
+GROUP_USERS_DELETED_SET = 'group:users:deleted:set:%s' # SET,群组用户移除集合,group_id
|
|
29
|
+GROUP_USERS_QUIT_SET = 'group:users:quit:set:%s' # SET,群组用户退出集合,group_id
|
|
30
|
+
|
|
31
|
+# 群组照片相关
|
|
32
|
+GROUP_PHOTO_DATA = 'group:photo:data:%s' # STRING,群组数据记录,group_id
|
|
33
|
+GROUP_PHOTO_THUMB_UP = 'group:photo:thumb:up:%s:%s' # STRING,群组照片用户点赞记录,photo_id、user_id
|
|
34
|
+GROUP_PHOTO_COMMENT_LIST = 'group:photo:comment:list:%s' # STRING,群组照片用户评论列表,photo_id
|
|
35
|
+GROUP_PHOTO_THUMB_UP_LIST = 'group:photo:thumb:up:list:%s' # STRING,群组照片用户点赞列表,photo_id
|
|
36
|
+GROUP_PHOTO_WATCHER_SET = 'group:photo:watcher:set:%s' # SET,群组照片用户关注集合,photo_id,关注即评论点赞
|
|
37
|
+GROUP_LAST_PHOTO_PK = 'group:last:photo:pk:%s' # STRING,群组最后一张照片PK,group_id
|
|
38
|
+
|
|
39
|
+# 摄影师照片相关
|
|
40
|
+LENSMAN_PHOTO_ORDER_RECORD = 'lensman:photo:order:record:%s:%s' # STRING,摄影师照片购买记录,photo_id、user_id
|
|
41
|
+
|
|
42
|
+# 摄影师简报相关
|
|
43
|
+# 收入
|
|
44
|
+TOTAL_INCOME = 'total:income:%s:%s' # STRING,总收入,user_id、photo_type
|
|
45
|
+WEEK_INCOME = 'week:income:%s:%s:%s' # STRING,周收入,user_id、photo_type、Week.thisweek().isoformat()
|
|
46
|
+TODAY_INCOME = 'today:income:%s:%s:%s' # STRING,日收入,user_id、photo_type、tc.local_string(format='%Y%m%d')
|
|
47
|
+# 上传
|
|
48
|
+TODAY_UPLOAD_PHOTO_AMOUNT = 'today:upload:photo:amount:%s:%s' # STRING,日上传照片数量,user_id、tc.local_string(format='%Y%m%d')
|
|
49
|
+# 售出
|
|
50
|
+WEEK_SOLD = 'week:sold:%s:%s:%s' # STRING,周售出,user_id、photo_type、Week.thisweek().isoformat()
|
|
51
|
+
|
|
52
|
+# 摄影师定价相关
|
|
53
|
+LENSMAN_PHOTO_PRICE_FIXED = 'lensman:photo:price:fixed:%s' # STRING,摄影师照片定价(单位:分),user_id
|
|
54
|
+
|
|
55
|
+# 系统消息相关
|
|
56
|
+SYSTEM_MESSAGE_READ_INFO = 'system:message:read:info:%s' # STRING,系统消息读取信息,user_id
|
|
57
|
+SYSTEM_MESSAGE_DELETED_INFO = 'system:message:deleted:info:%s' # STRING,系统消息删除信息,user_id
|
|
58
|
+
|
|
59
|
+# 游客入口相关
|
|
60
|
+GUEST_ENTRANCE_CONTROL_INFO = 'guest:entrance:control:info:%s' # STRING,游客入口控制信息,src
|
|
61
|
+
|
|
62
|
+# APP 相关
|
|
63
|
+LATEST_APP_INFO = 'latest:app:info:%s' # STRING,最新 APP 信息,src
|
|
64
|
+APP_SETTINGS_INFO = 'app:settings:info:%s:%s:%s' # STRING,APP 设置信息,platform、channel、version
|
|
65
|
+APP_PATCH_INFO = 'app:patch:info:%s:%s:%s' # STRING,APP 补丁信息,platform、version、src
|
|
66
|
+
|
|
67
|
+# BOX 相关
|
|
68
|
+BOX_PROGRAM_VERSION_INFO = 'box:program:version:info' # STRING,BOX 程序版本信息
|
|
|
@@ -0,0 +1,7 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+from django.conf import settings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+def upload_file_url(file_path):
|
|
7
|
+ return file_path and ('{}{}'.format(settings.DOMAIN, file_path.url)) or ''
|