+            model_name='coursevideoinfo',
18
+            name='course_video',
19
+            field=models.FileField(blank=True, help_text='\u8bfe\u7a0b\u89c6\u9891', null=True, upload_to=courses.models.upload_path, verbose_name='course_video'),
20
+        ),
21
+    ]

+ 0 - 0
courses/migrations/__init__.py


+ 87 - 0
courses/models.py

@@ -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
+        }

+ 7 - 0
courses/tests.py

@@ -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.

+ 7 - 0
courses/views.py

@@ -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.

+ 3 - 0
isort.sh

@@ -0,0 +1,3 @@
1
+#!/bin/bash
2
+
3
+isort -rc -sp . .

+ 23 - 0
manage.py

@@ -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
page/__init__.py


+ 4 - 0
page/admin.py

@@ -0,0 +1,4 @@
1
+from django.contrib import admin
2
+
3
+
4
+# Register your models here.

+ 67 - 0
page/code_views.py

@@ -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'课程兑换码兑换成功')

+ 47 - 0
page/info_views.py

@@ -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
+    })

+ 37 - 0
page/list_views.py

@@ -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
+    })

+ 31 - 0
page/migrations/0001_initial.py

@@ -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
page/migrations/__init__.py


+ 33 - 0
page/models.py

@@ -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)

+ 67 - 0
page/static/page/css/weui.ext.css

@@ -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
+}

BIN
page/static/page/img/code_cover.png


+ 135 - 0
page/templates/page/course_code.html

@@ -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>

+ 124 - 0
page/templates/page/course_info.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>

+ 77 - 0
page/templates/page/course_list.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>

+ 4 - 0
page/tests.py

@@ -0,0 +1,4 @@
1
+from django.test import TestCase
2
+
3
+
4
+# Create your tests here.

+ 12 - 0
page/urls.py

@@ -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
+]

+ 4 - 0
page/views.py

@@ -0,0 +1,4 @@
1
+from django.shortcuts import render
2
+
3
+
4
+# Create your views here.

+ 9 - 0
pep8.sh

@@ -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 .

+ 40 - 0
requirements.txt

@@ -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
utils/__init__.py


+ 0 - 0
utils/error/__init__.py


+ 51 - 0
utils/error/errno_utils.py

@@ -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'票据不存在')

+ 18 - 0
utils/error/response_utils.py

@@ -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
utils/redis/__init__.py


+ 6 - 0
utils/redis/connect.py

@@ -0,0 +1,6 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.conf import settings
4
+
5
+
6
+r = settings.REDIS_CACHE

+ 68 - 0
utils/redis/rkeys.py

@@ -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 程序版本信息

+ 7 - 0
utils/url_utils.py

@@ -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 ''

kodo - Gogs: Go Git Service

Nenhuma Descrição

admin.py 313B

    # -*- coding: utf-8 -*- from django.contrib import admin from miniapp.models import SceneInfo class SceneInfoAdmin(admin.ModelAdmin): list_display = ('sid', 'scene', 'page', 'qiniu_url', 'status', 'created_at', 'updated_at') list_filter = ('status', ) admin.site.register(SceneInfo, SceneInfoAdmin)
kodo - Gogs: Go Git Service

No Description

watermark_utils.py 3.0KB

    # -*- coding: utf-8 -*- try: import Image import ImageEnhance except ImportError: from PIL import Image, ImageEnhance def reduce_opacity(im, opacity): """Returns an image with reduced opacity.""" assert 0 <= opacity <= 1 im = im.convert('RGBA') if im.mode != 'RGBA' else im.copy() alpha = im.split()[3] alpha = ImageEnhance.Brightness(alpha).enhance(opacity) im.putalpha(alpha) return im def watermark(im, mark, position, opacity=1, maxsize=(0, 0), possize=(0, 0, None, None)): """ Add watermark to image """ if opacity < 1: mark = reduce_opacity(mark, opacity) # if im.mode != 'RGBA': # im = im.convert('RGBA') # Resize mark w, h = int(min(mark.size[0], maxsize[0]) if maxsize[0] else mark.size[0]), int(min(mark.size[1], maxsize[1]) if maxsize[1] else mark.size[1]) mark = mark.resize((w, h)) # Create a transparent layer the size of the image # Draw the watermark in that layer. layer = Image.new('RGBA', im.size, (0, 0, 0, 0)) if position == 'tile': for y in range(0, im.size[1], mark.size[1]): for x in range(0, im.size[0], mark.size[0]): layer.paste(mark, (x, y)) elif position == 'scale': # Scale, but preserve the aspect ratio ratio = min(float(im.size[0]) / mark.size[0], float(im.size[1]) / mark.size[1]) w, h = int(mark.size[0] * ratio), int(mark.size[1] * ratio) w, h = int(min(w, maxsize[0]) if maxsize[0] else w), int(min(h, maxsize[1]) if maxsize[1] else h) mark = mark.resize((w, h)) possize += (None, ) * (4 - len(possize)) lx, ly, rx, ry = possize if rx is None or ry is None: layer.paste(mark, ((im.size[0] - (lx or w)) / 2, (im.size[1] - (ly or h)) / 2)) else: layer.paste(mark, ((im.size[0] - rx - w), ry)) else: layer.paste(mark, position) # Composite the watermark with the layer return Image.composite(layer, im, layer) def watermark_wrap(im_path, mark_path, save_path=''): im, mark = Image.open(im_path), Image.open(mark_path) # new_im = watermark(im, mark, (50, 50), 0.5) # new_im = watermark(im, mark, position='scale', opacity=1.0, maxsize=(400, 505.2), possize=(400, 400)) new_im = watermark(im, mark, position='scale', opacity=1.0, maxsize=(200, 247.1875), possize=(0, 0, 15, 15)) new_im.save(save_path or im_path) def watermark_test(): im, mark = Image.open('original_CGzC_10a50000c8811190.jpg'), Image.open('paiai_water_mark.png') watermark(im, mark, position='tile', opacity=0.5, maxsize=(40, 49.4375)).show() watermark(im, mark, position='scale', opacity=1.0, maxsize=(400, 494.375), possize=(400, 400)).show() watermark(im, mark, position='scale', opacity=1.0, maxsize=(200, 247.1875), possize=(0, 0, 10, 10)).show() watermark(im, mark, position=(50, 50), opacity=0.5, maxsize=(40, 49.4375)).show() if __name__ == '__main__': # watermark_test() watermark_wrap('original_CGzC_10a50000c8811190.jpg', 'paiai_water_mark.png')
kodo - Gogs: Go Git Service

説明なし

admin.py 1.8KB

    # -*- coding: utf-8 -*- from django.contrib import admin from group.models import (GroupInfo, GroupPhotoInfo, GroupPhotoOrderInfo, GroupUserInfo, PhotoCommentInfo, PhotoThumbUpInfo) class GroupInfoAdmin(admin.ModelAdmin): list_display = ('group_id', 'admin_id', 'group_name', 'group_desc', 'group_from', 'session_id', 'group_lock', 'status', 'created_at', 'updated_at') list_filter = ('group_from', 'group_lock', 'status') class GroupUserInfoAdmin(admin.ModelAdmin): list_display = ('group_id', 'user_id', 'current_id', 'nickname', 'admin', 'user_status', 'passed_at', 'refused_at', 'status', 'created_at', 'updated_at') list_filter = ('user_status', 'status') class GroupPhotoInfoAdmin(admin.ModelAdmin): list_display = ('group_id', 'user_id', 'nickname', 'photo_path', 'photo_thumbnail_path', 'photo_thumbnail2_path', 'status', 'created_at', 'updated_at') list_filter = ('photo_from', 'status') class GroupPhotoOrderInfoAdmin(admin.ModelAdmin): list_display = ('group_id', 'user_id', 'photo_id', 'm_photo_path', 'l_photo_path', 'r_photo_path', 'status', 'created_at', 'updated_at') class PhotoCommentInfoAdmin(admin.ModelAdmin): list_display = ('photo_id', 'user_id', 'nickname', 'to_uid', 'comment', 'status', 'created_at', 'updated_at') list_filter = ('status', ) class PhotoThumbUpInfoAdmin(admin.ModelAdmin): list_display = ('photo_id', 'user_id', 'nickname', 'thumbup', 'status', 'created_at', 'updated_at') list_filter = ('thumbup', 'status') admin.site.register(GroupInfo, GroupInfoAdmin) admin.site.register(GroupUserInfo, GroupUserInfoAdmin) admin.site.register(GroupPhotoInfo, GroupPhotoInfoAdmin) admin.site.register(GroupPhotoOrderInfo, GroupPhotoOrderInfoAdmin) admin.site.register(PhotoCommentInfo, PhotoCommentInfoAdmin) admin.site.register(PhotoThumbUpInfo, PhotoThumbUpInfoAdmin)