+ model_name='coursevideoinfo',
+ name='course_video',
+ field=models.FileField(blank=True, help_text='\u8bfe\u7a0b\u89c6\u9891', null=True, upload_to=courses.models.upload_path, verbose_name='course_video'),
+ ),
+ ]
@@ -0,0 +1,87 @@ |
||
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 shortuuidfield import ShortUUIDField |
|
8 |
+from TimeConvert import TimeConvert as tc |
|
9 |
+ |
|
10 |
+from course.basemodels import CreateUpdateMixin |
|
11 |
+from utils.url_utils import upload_file_url |
|
12 |
+ |
|
13 |
+ |
|
14 |
+def upload_path(instance, old_filename): |
|
15 |
+ return 'file/{ym}/{stamp}{ext}'.format( |
|
16 |
+ ym=tc.local_string(format='%Y%m'), |
|
17 |
+ stamp=tc.local_timestamp(ms=True), |
|
18 |
+ ext=os.path.splitext(old_filename)[1].lower(), |
|
19 |
+ ) |
|
20 |
+ |
|
21 |
+ |
|
22 |
+class CourseInfo(CreateUpdateMixin): |
|
23 |
+ course_id = ShortUUIDField(_(u'course_id'), max_length=255, help_text=u'课程唯一标识', db_index=True, unique=True) |
|
24 |
+ course_name = models.CharField(_(u'course_name'), max_length=255, blank=True, null=True, help_text=u'课程标题') |
|
25 |
+ course_time = models.IntegerField(_(u'course_time'), default=0, help_text=u'课程时间') |
|
26 |
+ course_cover = models.ImageField(_(u'course_cover'), upload_to=upload_path, blank=True, null=True, help_text=u'课程视频缩略图') |
|
27 |
+ |
|
28 |
+ class Meta: |
|
29 |
+ verbose_name = _(u'courseinfo') |
|
30 |
+ verbose_name_plural = _(u'courseinfo') |
|
31 |
+ |
|
32 |
+ def __unicode__(self): |
|
33 |
+ return unicode(self.course_name) |
|
34 |
+ |
|
35 |
+ @property |
|
36 |
+ def course_cover_url(self): |
|
37 |
+ return upload_file_url(self.course_cover) |
|
38 |
+ |
|
39 |
+ @property |
|
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 '' |