@@ -1,14 +1,26 @@ |
||
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 |
|
3 | 3 |
from django.contrib import admin |
4 |
+from django.contrib.auth.hashers import make_password |
|
4 | 5 |
|
5 | 6 |
from account.models import LensmanInfo |
6 | 7 |
|
8 |
+from utils.uuid_utils import curtailUUID |
|
9 |
+ |
|
7 | 10 |
|
8 | 11 |
class LensmanInfoAdmin(admin.ModelAdmin): |
12 |
+ readonly_fields = ('lensman_id', 'encryption', ) |
|
9 | 13 |
list_display = ('lensman_id', 'name', 'sex', 'phone', 'location', 'proportion', 'status', 'created_at', 'updated_at') |
10 | 14 |
search_fields = ('name', 'phone', 'location') |
11 | 15 |
list_filter = ('sex', 'status') |
12 | 16 |
|
17 |
+ def save_model(self, request, obj, form, change): |
|
18 |
+ if not obj.lensman_id: |
|
19 |
+ obj.lensman_id = curtailUUID(LensmanInfo, 'lensman_id') |
|
20 |
+ if obj.password: |
|
21 |
+ obj.encryption = make_password(obj.password, None, 'pbkdf2_sha256') |
|
22 |
+ obj.password = None |
|
23 |
+ obj.save() |
|
24 |
+ |
|
13 | 25 |
|
14 | 26 |
admin.site.register(LensmanInfo, LensmanInfoAdmin) |
@@ -0,0 +1,29 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+from __future__ import unicode_literals |
|
3 |
+ |
|
4 |
+from django.db import models, migrations |
|
5 |
+ |
|
6 |
+ |
|
7 |
+class Migration(migrations.Migration): |
|
8 |
+ |
|
9 |
+ dependencies = [ |
|
10 |
+ ('account', '0001_initial'), |
|
11 |
+ ] |
|
12 |
+ |
|
13 |
+ operations = [ |
|
14 |
+ migrations.AddField( |
|
15 |
+ model_name='lensmaninfo', |
|
16 |
+ name='encryption', |
|
17 |
+ field=models.CharField(help_text='\u6444\u5f71\u5e08\u5bc6\u7801', max_length=255, null=True, verbose_name='encryption', blank=True), |
|
18 |
+ ), |
|
19 |
+ migrations.AddField( |
|
20 |
+ model_name='lensmaninfo', |
|
21 |
+ name='password', |
|
22 |
+ field=models.CharField(help_text='\u6444\u5f71\u5e08\u5bc6\u7801', max_length=255, null=True, verbose_name='password', blank=True), |
|
23 |
+ ), |
|
24 |
+ migrations.AddField( |
|
25 |
+ model_name='lensmaninfo', |
|
26 |
+ name='username', |
|
27 |
+ field=models.CharField(null=True, max_length=255, blank=True, help_text='\u6444\u5f71\u5e08\u7528\u6237\u540d', unique=True, verbose_name='username', db_index=True), |
|
28 |
+ ), |
|
29 |
+ ] |
@@ -0,0 +1,19 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+from __future__ import unicode_literals |
|
3 |
+ |
|
4 |
+from django.db import models, migrations |
|
5 |
+ |
|
6 |
+ |
|
7 |
+class Migration(migrations.Migration): |
|
8 |
+ |
|
9 |
+ dependencies = [ |
|
10 |
+ ('account', '0002_auto_20151202_2251'), |
|
11 |
+ ] |
|
12 |
+ |
|
13 |
+ operations = [ |
|
14 |
+ migrations.AlterField( |
|
15 |
+ model_name='lensmaninfo', |
|
16 |
+ name='lensman_id', |
|
17 |
+ field=models.CharField(help_text='\u6444\u5f71\u5e08\u552f\u4e00\u6807\u8bc6', unique=True, max_length=255, verbose_name='lensman_id', db_index=True), |
|
18 |
+ ), |
|
19 |
+ ] |
@@ -5,8 +5,6 @@ from django.utils.translation import ugettext_lazy as _ |
||
5 | 5 |
|
6 | 6 |
from pai2.basemodels import CreateUpdateMixin |
7 | 7 |
|
8 |
-from shortuuidfield import ShortUUIDField |
|
9 |
- |
|
10 | 8 |
|
11 | 9 |
class LensmanInfo(CreateUpdateMixin): |
12 | 10 |
MALE = 0 |
@@ -17,7 +15,12 @@ class LensmanInfo(CreateUpdateMixin): |
||
17 | 15 |
(FEMALE, u'女'), |
18 | 16 |
) |
19 | 17 |
|
20 |
- lensman_id = ShortUUIDField(_(u'lensman_id'), max_length=255, help_text=u'摄影师唯一标识', db_index=True) |
|
18 |
+ lensman_id = models.CharField(_(u'lensman_id'), max_length=255, blank=True, null=True, help_text=u'摄影师唯一标识', db_index=True, unique=True) |
|
19 |
+ |
|
20 |
+ username = models.CharField(_(u'username'), max_length=255, blank=True, null=True, help_text=u'摄影师用户名', db_index=True, unique=True) |
|
21 |
+ password = models.CharField(_(u'password'), max_length=255, blank=True, null=True, help_text=u'摄影师密码') |
|
22 |
+ encryption = models.CharField(_(u'encryption'), max_length=255, blank=True, null=True, help_text=u'摄影师密码') |
|
23 |
+ |
|
21 | 24 |
name = models.CharField(_(u'name'), max_length=255, blank=True, null=True, help_text=u'摄影师姓名') |
22 | 25 |
sex = models.IntegerField(_(u'sex'), choices=SEX_TYPE, default=MALE, help_text=u'摄影师性别') |
23 | 26 |
phone = models.CharField(_(u'phone'), max_length=255, blank=True, null=True, help_text=u'摄影师电话', db_index=True, unique=True) |
@@ -1,12 +1,43 @@ |
||
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 |
|
3 |
+from django.contrib.auth.hashers import check_password |
|
3 | 4 |
from django.contrib.auth.models import User, Group |
5 |
+from django.http import JsonResponse |
|
6 |
+ |
|
4 | 7 |
from rest_framework import viewsets |
5 | 8 |
|
6 | 9 |
from account.models import LensmanInfo |
7 | 10 |
from account.serializers import UserSerializer, GroupSerializer, LensmanInfoSerializer |
8 | 11 |
|
9 | 12 |
|
13 |
+# curl -X POST -F username=xxxxxxx -F password=xxxxxxx http://api.xfoto.com.cn/login |
|
14 |
+def user_login(request): |
|
15 |
+ username = request.POST.get('username', '') |
|
16 |
+ password = request.POST.get('password', '') |
|
17 |
+ |
|
18 |
+ try: |
|
19 |
+ lensman = LensmanInfo.objects.get(username=username) |
|
20 |
+ except LensmanInfo.DoesNotExist: |
|
21 |
+ return JsonResponse({ |
|
22 |
+ 'status': 4000, |
|
23 |
+ 'message': u'用户不存在', |
|
24 |
+ }) |
|
25 |
+ |
|
26 |
+ if not check_password(password, lensman.encryption): |
|
27 |
+ return JsonResponse({ |
|
28 |
+ 'status': 4001, |
|
29 |
+ 'message': u'用户密码错误', |
|
30 |
+ }) |
|
31 |
+ |
|
32 |
+ return JsonResponse({ |
|
33 |
+ 'status': 200, |
|
34 |
+ 'message': u'登录成功', |
|
35 |
+ 'data': { |
|
36 |
+ 'user': lensman.lensman_id |
|
37 |
+ }, |
|
38 |
+ }) |
|
39 |
+ |
|
40 |
+ |
|
10 | 41 |
class UserViewSet(viewsets.ModelViewSet): |
11 | 42 |
""" |
12 | 43 |
API endpoint that allows users to be viewed or edited. |
@@ -2,10 +2,15 @@ |
||
2 | 2 |
|
3 | 3 |
from django.conf.urls import url |
4 | 4 |
|
5 |
+from account import views as account_views |
|
5 | 6 |
from photo import views as photo_views |
6 | 7 |
|
7 | 8 |
|
8 | 9 |
urlpatterns = [ |
10 |
+ url(r'^login$', account_views.user_login, name='user_login'), |
|
11 |
+] |
|
12 |
+ |
|
13 |
+urlpatterns += [ |
|
9 | 14 |
url(r'^uuid_init$', photo_views.uuid_init, name='uuid_init'), |
10 | 15 |
url(r'^uuid$', photo_views.uuid, name='uuid'), |
11 | 16 |
url(r'^photos/upload$', photo_views.upload_photo, name='upload_photo'), |
@@ -1,4 +1,8 @@ |
||
1 |
-1、照片上传 —— 401 |
|
2 |
- 4010 —— 参数错误 |
|
3 |
- 4011 —— 摄影师不存在 |
|
4 |
- 4012 —— 照片已存在 |
|
1 |
+1、用户信息 —— 400 |
|
2 |
+ 4000 —— 用户不存在 |
|
3 |
+ 4001 —— 用户密码错误 |
|
4 |
+ |
|
5 |
+2、照片上传 —— 401 |
|
6 |
+ 4010 —— 参数错误 |
|
7 |
+ 4011 —— 摄影师不存在 |
|
8 |
+ 4012 —— 照片已存在 |
@@ -46,6 +46,8 @@ INSTALLED_APPS = ( |
||
46 | 46 |
'photo', |
47 | 47 |
) |
48 | 48 |
|
49 |
+INSTALLED_APPS += ('multidomain', ) |
|
50 |
+ |
|
49 | 51 |
MIDDLEWARE_CLASSES = ( |
50 | 52 |
'django.contrib.sessions.middleware.SessionMiddleware', |
51 | 53 |
'django.middleware.common.CommonMiddleware', |
@@ -57,6 +59,13 @@ MIDDLEWARE_CLASSES = ( |
||
57 | 59 |
'django.middleware.security.SecurityMiddleware', |
58 | 60 |
) |
59 | 61 |
|
62 |
+MIDDLEWARE_CLASSES += ('multidomain.middleware.DomainMiddleware', ) |
|
63 |
+ |
|
64 |
+URL_CONFIG = ( |
|
65 |
+ # (r'^(.+\.)?xfoto\.com\.cn', 'pai2.urls_www'), |
|
66 |
+ (r'^(.+\.)?api\.xfoto\.com\.cn', 'pai2.urls_api'), |
|
67 |
+) |
|
68 |
+ |
|
60 | 69 |
ROOT_URLCONF = 'pai2.urls' |
61 | 70 |
|
62 | 71 |
TEMPLATES = [ |
@@ -138,6 +147,9 @@ REST_FRAMEWORK = { |
||
138 | 147 |
'PAGE_SIZE': 1 |
139 | 148 |
} |
140 | 149 |
|
150 |
+# 唯一标识设置 |
|
151 |
+CURTAIL_UUID_LENGTH = 7 |
|
152 |
+ |
|
141 | 153 |
# 域名设置 |
142 | 154 |
DOMAIN = 'http://xfoto.com.cn' |
143 | 155 |
|
@@ -33,14 +33,18 @@ urlpatterns = [ |
||
33 | 33 |
] |
34 | 34 |
|
35 | 35 |
urlpatterns += [ |
36 |
- url(r'^api/', include('api.urls', namespace='api')), |
|
37 |
- # url(r'^photo/', include('photo.urls', namespace='photo')) |
|
36 |
+ # url(r'^api/', include('api.urls', namespace='api')), |
|
37 |
+ url(r'^s/(?P<session>\w+)$', photo_views.session_detail, name='session_detail'), |
|
38 |
+ url(r'^p/(?P<photo>\w+)$', photo_views.photo_standard, name='photo_standard'), # standard thumbnail, available for free |
|
39 |
+ url(r'^m/(?P<photo>\w+)$', photo_views.photo_medium, name='photo_medium'), # medium/mobile version, without watermark, login or paid by others |
|
40 |
+ url(r'^l/(?P<photo>\w+)$', photo_views.photo_large, name='photo_large'), # large, might support server side panning later, login required |
|
41 |
+ url(r'^r/(?P<photo>\w+)$', photo_views.photo_raw, name='photo_raw'), # raw image, only for finishers |
|
38 | 42 |
] |
39 | 43 |
|
40 | 44 |
# Wire up our API using automatic URL routing. |
41 | 45 |
# Additionally, we include login URLs for the browsable API. |
42 | 46 |
urlpatterns += [ |
43 |
- url(r'^apihome/', include(router.urls)), |
|
47 |
+ url(r'^api/', include(router.urls)), |
|
44 | 48 |
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) |
45 | 49 |
] |
46 | 50 |
|
@@ -0,0 +1,10 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+ |
|
3 |
+ |
|
4 |
+from django.conf import settings |
|
5 |
+from django.conf.urls import include, url |
|
6 |
+ |
|
7 |
+ |
|
8 |
+urlpatterns = [ |
|
9 |
+ url(r'^', include('api.urls', namespace='api')), |
|
10 |
+] |
@@ -26,10 +26,6 @@ server { |
||
26 | 26 |
alias /home/paiai/work/pai2/collect_static; # your Django project's static files - amend as required |
27 | 27 |
} |
28 | 28 |
|
29 |
- location /p/ { |
|
30 |
- alias /home/paiai/work/pai2/media/photo; # Photo |
|
31 |
- } |
|
32 |
- |
|
33 | 29 |
# Finally, send all non-media requests to the Django server. |
34 | 30 |
location / { |
35 | 31 |
# uwsgi_pass pai2; |
@@ -47,16 +47,16 @@ class PhotosInfo(CreateUpdateMixin): |
||
47 | 47 |
|
48 | 48 |
@property |
49 | 49 |
def photo_url(self): |
50 |
- # return u'{0}/media/{1}'.format(settings.DOMAIN, self.photo_path) if self.photo_path else '' |
|
51 |
- return u'{0}/p/{1}'.format(settings.DOMAIN, self.photo_name) if self.photo_name else '' |
|
50 |
+ return u'{0}/media/{1}'.format(settings.DOMAIN, self.photo_path) if self.photo_path else '' |
|
51 |
+ # return u'{0}/p/{1}'.format(settings.DOMAIN, self.photo_name) if self.photo_name else '' |
|
52 | 52 |
|
53 | 53 |
def _data(self): |
54 | 54 |
return { |
55 | 55 |
'pk': self.pk, |
56 |
- 'lensman_id': self.lensman_id, |
|
57 |
- 'session_id': self.session_id, |
|
58 |
- 'photo_id': self.photo_id, |
|
59 |
- 'photo_url': self.photo_url, |
|
56 |
+ 'user': self.lensman_id, |
|
57 |
+ 'session': self.session_id, |
|
58 |
+ 'photo': self.photo_id, |
|
59 |
+ # 'photo_url': self.photo_url, |
|
60 | 60 |
} |
61 | 61 |
|
62 | 62 |
data = property(_data) |
@@ -0,0 +1,10 @@ |
||
1 |
+<!DOCTYPE html> |
|
2 |
+<html> |
|
3 |
+<head lang="en"> |
|
4 |
+ <meta charset="UTF-8"> |
|
5 |
+ <title></title> |
|
6 |
+</head> |
|
7 |
+<body> |
|
8 |
+ <img src="{{ photo_url }}"> |
|
9 |
+</body> |
|
10 |
+</html> |
@@ -0,0 +1,12 @@ |
||
1 |
+<!DOCTYPE html> |
|
2 |
+<html> |
|
3 |
+<head lang="en"> |
|
4 |
+ <meta charset="UTF-8"> |
|
5 |
+ <title></title> |
|
6 |
+</head> |
|
7 |
+<body> |
|
8 |
+ {% for photo in photos %} |
|
9 |
+ <div><img src="{{ photo.photo_url }}"></div> |
|
10 |
+ {% endfor %} |
|
11 |
+</body> |
|
12 |
+</html> |
@@ -3,6 +3,7 @@ |
||
3 | 3 |
from django.core.files.storage import default_storage |
4 | 4 |
from django.db import transaction |
5 | 5 |
from django.http import JsonResponse |
6 |
+from django.shortcuts import render, redirect |
|
6 | 7 |
|
7 | 8 |
from rest_framework import viewsets |
8 | 9 |
|
@@ -19,7 +20,7 @@ def uuid_init(request): |
||
19 | 20 |
num = int(request.GET.get('num', 1000)) |
20 | 21 |
|
21 | 22 |
for i in xrange(num): |
22 |
- UUIDInfo.objects.create(uuid=curtailUUID()) |
|
23 |
+ UUIDInfo.objects.create(uuid=curtailUUID(UUIDInfo)) |
|
23 | 24 |
|
24 | 25 |
return JsonResponse({ |
25 | 26 |
'status': 200, |
@@ -28,10 +29,10 @@ def uuid_init(request): |
||
28 | 29 |
}) |
29 | 30 |
|
30 | 31 |
|
31 |
-# curl -X POST -F lensman_id=123 -F num=100 http://xfoto.com.cn/api/uuid |
|
32 |
+# curl -X POST -F user=xxxxxxx -F num=100 http://api.xfoto.com.cn/uuid |
|
32 | 33 |
@transaction.atomic |
33 | 34 |
def uuid(request): |
34 |
- lensman_id = request.POST.get('lensman_id', '') |
|
35 |
+ lensman_id = request.POST.get('user', '') |
|
35 | 36 |
num = int(request.POST.get('num', 100)) |
36 | 37 |
|
37 | 38 |
uuids = UUIDInfo.objects.select_for_update().filter(status=True)[:num] |
@@ -58,10 +59,10 @@ def uuid(request): |
||
58 | 59 |
# name with the symbol <. The difference between @ and < is then that @ makes a file get attached in the post as a file upload, |
59 | 60 |
# while the < makes a text field and just get the contents for that text field from a file. |
60 | 61 |
# |
61 |
-# curl -X POST -F lensman_id=123 -F session_id=456 -F photo=@7056288a9ddf2db294cf50a943920989.jpg;filename=789 http://xfoto.com.cn/api/photos/upload |
|
62 |
+# curl -X POST -F user=xxxxxxx -F session=xxxxxxx -F photo=@xxxxxxx.jpg http://api.xfoto.com.cn/photos/upload |
|
62 | 63 |
def upload_photo(request): |
63 |
- lensman_id = request.POST.get('lensman_id', '') |
|
64 |
- session_id = request.POST.get('session_id', '') |
|
64 |
+ lensman_id = request.POST.get('user', '') |
|
65 |
+ session_id = request.POST.get('session', '') |
|
65 | 66 |
|
66 | 67 |
photo = request.FILES.get('photo', '') |
67 | 68 |
|
@@ -79,7 +80,7 @@ def upload_photo(request): |
||
79 | 80 |
'message': u'摄影师不存在', |
80 | 81 |
}) |
81 | 82 |
|
82 |
- photo_id = curtailUUID() |
|
83 |
+ photo_id = curtailUUID(PhotosInfo, 'photo_id') |
|
83 | 84 |
|
84 | 85 |
_, extension = os.path.splitext(photo.name) |
85 | 86 |
# photo_path = 'photo/{0}/{1}/{2}{3}'.format(lensman_id, session_id, photo_id, extension) |
@@ -105,6 +106,31 @@ def upload_photo(request): |
||
105 | 106 |
}) |
106 | 107 |
|
107 | 108 |
|
109 |
+def session_detail(request, session): |
|
110 |
+ photos = PhotosInfo.objects.filter(session_id=session) |
|
111 |
+ return render(request, 'photo/session_detail.html', {'photos': photos}) |
|
112 |
+ |
|
113 |
+ |
|
114 |
+def photo_standard(request, photo): |
|
115 |
+ photo = PhotosInfo.objects.get(photo_id=photo) |
|
116 |
+ return render(request, 'photo/photo_detail.html', {'photo_url': photo.photo_url}) |
|
117 |
+ |
|
118 |
+ |
|
119 |
+def photo_medium(request, photo): |
|
120 |
+ photo = PhotosInfo.objects.get(photo_id=photo) |
|
121 |
+ return render(request, 'photo/photo_detail.html', {'photo_url': photo.photo_url}) |
|
122 |
+ |
|
123 |
+ |
|
124 |
+def photo_large(request, photo): |
|
125 |
+ photo = PhotosInfo.objects.get(photo_id=photo) |
|
126 |
+ return render(request, 'photo/photo_detail.html', {'photo_url': photo.photo_url}) |
|
127 |
+ |
|
128 |
+ |
|
129 |
+def photo_raw(request, photo): |
|
130 |
+ photo = PhotosInfo.objects.get(photo_id=photo) |
|
131 |
+ return render(request, 'photo/photo_detail.html', {'photo_url': photo.photo_url}) |
|
132 |
+ |
|
133 |
+ |
|
108 | 134 |
class PhotoInfoViewSet(viewsets.ModelViewSet): |
109 | 135 |
queryset = PhotosInfo.objects.all().order_by('-created_at') |
110 | 136 |
serializer_class = PhotosInfoSerializer |
@@ -1,16 +1,18 @@ |
||
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 |
|
3 |
+from django.conf import settings |
|
4 |
+ |
|
3 | 5 |
from photo.models import UUIDInfo |
4 | 6 |
|
5 | 7 |
import shortuuid |
6 | 8 |
|
7 | 9 |
|
8 |
-def curtailUUID(length=10): |
|
10 |
+def curtailUUID(model, field='uuid', length=settings.CURTAIL_UUID_LENGTH): |
|
9 | 11 |
flag = True |
10 | 12 |
while flag: |
11 | 13 |
uuid = shortuuid.uuid()[-length:] |
12 | 14 |
try: |
13 |
- UUIDInfo.objects.get(uuid=uuid) |
|
14 |
- except UUIDInfo.DoesNotExist: |
|
15 |
+ model.objects.get(**{field: uuid}) |
|
16 |
+ except model.DoesNotExist: |
|
15 | 17 |
flag = False |
16 | 18 |
return uuid |