| @@ -6,5 +6,7 @@ from photo import views as photo_views | ||
| 6 | 6 |  | 
| 7 | 7 |  | 
| 8 | 8 | urlpatterns = [ | 
| 9 | + url(r'^uuid_init$', photo_views.uuid_init, name='uuid_init'), | |
| 10 | + url(r'^uuid$', photo_views.uuid, name='uuid'), | |
| 9 | 11 | url(r'^photos/upload$', photo_views.upload_photo, name='upload_photo'), | 
| 10 | 12 | ] | 
| @@ -0,0 +1,4 @@ | ||
| 1 | +1、照片上传 —— 401 | |
| 2 | + 4010 —— 参数错误 | |
| 3 | + 4011 —— 摄影师不存在 | |
| 4 | + 4012 —— 照片已存在 | 
| @@ -40,7 +40,7 @@ urlpatterns += [ | ||
| 40 | 40 | # Wire up our API using automatic URL routing. | 
| 41 | 41 | # Additionally, we include login URLs for the browsable API. | 
| 42 | 42 | urlpatterns += [ | 
| 43 | - url(r'^api/', include(router.urls)), | |
| 43 | + url(r'^apihome/', include(router.urls)), | |
| 44 | 44 |      url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) | 
| 45 | 45 | ] | 
| 46 | 46 |  | 
| @@ -26,6 +26,10 @@ 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 | + | |
| 29 | 33 | # Finally, send all non-media requests to the Django server. | 
| 30 | 34 |      location / { | 
| 31 | 35 | # uwsgi_pass pai2; | 
| @@ -2,12 +2,18 @@ | ||
| 2 | 2 |  | 
| 3 | 3 | from django.contrib import admin | 
| 4 | 4 |  | 
| 5 | -from photo.models import PhotosInfo | |
| 5 | +from photo.models import UUIDInfo, PhotosInfo | |
| 6 | + | |
| 7 | + | |
| 8 | +class UUIDInfoAdmin(admin.ModelAdmin): | |
| 9 | +    list_display = ('uuid', 'lensman_id', 'status', 'created_at', 'updated_at') | |
| 10 | +    list_filter = ('lensman_id', 'status') | |
| 6 | 11 |  | 
| 7 | 12 |  | 
| 8 | 13 | class PhotosInfoAdmin(admin.ModelAdmin): | 
| 9 | -    list_display = ('lensman_id', 'session_id', 'photo_id', 'photo_path', 'status', 'created_at', 'updated_at') | |
| 14 | +    list_display = ('lensman_id', 'session_id', 'photo_id', 'photo_name', 'photo_path', 'status', 'created_at', 'updated_at') | |
| 10 | 15 |      list_filter = ('lensman_id', 'status') | 
| 11 | 16 |  | 
| 12 | 17 |  | 
| 18 | +admin.site.register(UUIDInfo, UUIDInfoAdmin) | |
| 13 | 19 | admin.site.register(PhotosInfo, PhotosInfoAdmin) | 
| @@ -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 | +        ('photo', '0002_auto_20151113_1419'), | |
| 11 | + ] | |
| 12 | + | |
| 13 | + operations = [ | |
| 14 | + migrations.CreateModel( | |
| 15 | + name='UUIDInfo', | |
| 16 | + fields=[ | |
| 17 | +                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | |
| 18 | +                ('status', models.BooleanField(default=True, help_text='\u72b6\u6001', verbose_name='status')), | |
| 19 | +                ('created_at', models.DateTimeField(help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at', auto_now_add=True)), | |
| 20 | +                ('updated_at', models.DateTimeField(help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at', auto_now=True)), | |
| 21 | +                ('uuid', models.CharField(null=True, max_length=22, blank=True, help_text='\u552f\u4e00\u6807\u8bc6', unique=True, verbose_name='uuid', db_index=True)), | |
| 22 | +                ('lensman_id', models.CharField(max_length=255, blank=True, help_text='\u6444\u5f71\u5e08\u552f\u4e00\u6807\u8bc6', null=True, verbose_name='lensman_id', db_index=True)), | |
| 23 | + ], | |
| 24 | +            options={ | |
| 25 | + 'verbose_name': 'uuidinfo', | |
| 26 | + 'verbose_name_plural': 'uuidinfo', | |
| 27 | + }, | |
| 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 | +        ('photo', '0003_uuidinfo'), | |
| 11 | + ] | |
| 12 | + | |
| 13 | + operations = [ | |
| 14 | + migrations.AddField( | |
| 15 | + model_name='photosinfo', | |
| 16 | + name='photo_name', | |
| 17 | + field=models.CharField(help_text='\u7167\u7247\u5b58\u653e\u540d\u79f0', max_length=255, null=True, verbose_name='photo_name', blank=True), | |
| 18 | + ), | |
| 19 | + ] | 
| @@ -7,10 +7,32 @@ from django.utils.translation import ugettext_lazy as _ | ||
| 7 | 7 | from pai2.basemodels import CreateUpdateMixin | 
| 8 | 8 |  | 
| 9 | 9 |  | 
| 10 | +class UUIDInfo(CreateUpdateMixin): | |
| 11 | + uuid = models.CharField(_(u'uuid'), max_length=22, blank=True, null=True, help_text=u'唯一标识', db_index=True, unique=True) | |
| 12 | + lensman_id = models.CharField(_(u'lensman_id'), max_length=255, blank=True, null=True, help_text=u'摄影师唯一标识', db_index=True) | |
| 13 | + | |
| 14 | + class Meta: | |
| 15 | +        verbose_name = _('uuidinfo') | |
| 16 | +        verbose_name_plural = _('uuidinfo') | |
| 17 | + | |
| 18 | + def __unicode__(self): | |
| 19 | +        return u'{0.pk}'.format(self) | |
| 20 | + | |
| 21 | + def _data(self): | |
| 22 | +        return { | |
| 23 | + 'pk': self.pk, | |
| 24 | + 'uuid': self.uuid, | |
| 25 | + 'lensman_id': self.lensman_id, | |
| 26 | + } | |
| 27 | + | |
| 28 | + data = property(_data) | |
| 29 | + | |
| 30 | + | |
| 10 | 31 | class PhotosInfo(CreateUpdateMixin): | 
| 11 | 32 | lensman_id = models.CharField(_(u'lensman_id'), max_length=255, blank=True, null=True, help_text=u'摄影师唯一标识', db_index=True) | 
| 12 | 33 | session_id = models.CharField(_(u'session_id'), max_length=255, blank=True, null=True, help_text=u'照片组唯一标识', db_index=True) | 
| 13 | 34 | photo_id = models.CharField(_(u'photo_id'), max_length=255, blank=True, null=True, help_text=u'照片唯一标识', db_index=True, unique=True) | 
| 35 | + photo_name = models.CharField(_(u'photo_name'), max_length=255, blank=True, null=True, help_text=u'照片存放名称') | |
| 14 | 36 | photo_path = models.CharField(_(u'photo_path'), max_length=255, blank=True, null=True, help_text=u'照片存放路径') | 
| 15 | 37 |  | 
| 16 | 38 | class Meta: | 
| @@ -25,7 +47,8 @@ class PhotosInfo(CreateUpdateMixin): | ||
| 25 | 47 |  | 
| 26 | 48 | @property | 
| 27 | 49 | def photo_url(self): | 
| 28 | -        return u'{0}/media/{1}'.format(settings.DOMAIN, self.photo_path) if self.photo_path 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 '' | |
| 29 | 52 |  | 
| 30 | 53 | def _data(self): | 
| 31 | 54 |          return { | 
| @@ -1,17 +1,52 @@ | ||
| 1 | 1 | # -*- coding: utf-8 -*- | 
| 2 | 2 |  | 
| 3 | 3 | from django.core.files.storage import default_storage | 
| 4 | +from django.db import transaction | |
| 4 | 5 | from django.http import JsonResponse | 
| 5 | 6 |  | 
| 6 | 7 | from rest_framework import viewsets | 
| 7 | 8 |  | 
| 8 | 9 | from account.models import LensmanInfo | 
| 9 | -from photo.models import PhotosInfo | |
| 10 | +from photo.models import UUIDInfo, PhotosInfo | |
| 10 | 11 | from photo.serializers import PhotosInfoSerializer | 
| 11 | 12 |  | 
| 13 | +from utils.uuid_utils import curtailUUID | |
| 14 | + | |
| 12 | 15 | import os | 
| 13 | 16 |  | 
| 14 | 17 |  | 
| 18 | +def uuid_init(request): | |
| 19 | +    num = int(request.GET.get('num', 1000)) | |
| 20 | + | |
| 21 | + for i in xrange(num): | |
| 22 | + UUIDInfo.objects.create(uuid=curtailUUID()) | |
| 23 | + | |
| 24 | +    return JsonResponse({ | |
| 25 | + 'status': 200, | |
| 26 | + 'message': u'UUID 更新成功', | |
| 27 | + 'data': '', | |
| 28 | + }) | |
| 29 | + | |
| 30 | + | |
| 31 | +# curl -X POST -F lensman_id=123 -F num=100 http://xfoto.com.cn/api/uuid | |
| 32 | +@transaction.atomic | |
| 33 | +def uuid(request): | |
| 34 | +    lensman_id = request.POST.get('lensman_id', '') | |
| 35 | +    num = int(request.POST.get('num', 100)) | |
| 36 | + | |
| 37 | + uuids = UUIDInfo.objects.select_for_update().filter(status=True)[:num] | |
| 38 | + for uuid in uuids: | |
| 39 | + uuid.lensman_id = lensman_id | |
| 40 | + uuid.status = False | |
| 41 | + uuid.save() | |
| 42 | + | |
| 43 | +    return JsonResponse({ | |
| 44 | + 'status': 200, | |
| 45 | + 'message': u'获取唯一标识成功', | |
| 46 | + 'data': [uuid.uuid for uuid in uuids], | |
| 47 | + }) | |
| 48 | + | |
| 49 | + | |
| 15 | 50 | # [How to do a PUT request with curl?](http://stackoverflow.com/questions/13782198/how-to-do-a-put-request-with-curl) | 
| 16 | 51 | # Unfortunately, the -T is no substitute for -X PUT if you want to specify parameters with -d or -F. | 
| 17 | 52 | # -T sends the content of a file via PUT. To achieve the GET after a redirect, add the parameter --location | 
| @@ -23,17 +58,16 @@ import os | ||
| 23 | 58 | # name with the symbol <. The difference between @ and < is then that @ makes a file get attached in the post as a file upload, | 
| 24 | 59 | # while the < makes a text field and just get the contents for that text field from a file. | 
| 25 | 60 | # | 
| 26 | -# curl -X POST -F lensman_id=123 -F session_id=456 -F photo_id=789 -F photo=@7056288a9ddf2db294cf50a943920989.jpg;filename=789 http://xfoto.com.cn/api/photos/upload | |
| 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 | |
| 27 | 62 | def upload_photo(request): | 
| 28 | 63 |      lensman_id = request.POST.get('lensman_id', '') | 
| 29 | 64 |      session_id = request.POST.get('session_id', '') | 
| 30 | -    photo_id = request.POST.get('photo_id', '') | |
| 31 | 65 |  | 
| 32 | 66 |      photo = request.FILES.get('photo', '') | 
| 33 | 67 |  | 
| 34 | - if not (lensman_id and session_id and photo_id and photo): | |
| 68 | + if not (lensman_id and session_id and photo): | |
| 35 | 69 |          return JsonResponse({ | 
| 36 | - 'status': 400, | |
| 70 | + 'status': 4010, | |
| 37 | 71 | 'message': u'参数错误', | 
| 38 | 72 | }) | 
| 39 | 73 |  | 
| @@ -41,12 +75,16 @@ def upload_photo(request): | ||
| 41 | 75 | LensmanInfo.objects.get(lensman_id=lensman_id) | 
| 42 | 76 | except LensmanInfo.DoesNotExist: | 
| 43 | 77 |          return JsonResponse({ | 
| 44 | - 'status': 400, | |
| 45 | - 'message': u'参数错误', | |
| 78 | + 'status': 4011, | |
| 79 | + 'message': u'摄影师不存在', | |
| 46 | 80 | }) | 
| 47 | 81 |  | 
| 82 | + photo_id = curtailUUID() | |
| 83 | + | |
| 48 | 84 | _, extension = os.path.splitext(photo.name) | 
| 49 | -    photo_path = 'photo/{0}/{1}/{2}{3}'.format(lensman_id, session_id, photo_id, extension) | |
| 85 | +    # photo_path = 'photo/{0}/{1}/{2}{3}'.format(lensman_id, session_id, photo_id, extension) | |
| 86 | +    photo_name = '{0}{1}'.format(photo_id, extension) | |
| 87 | +    photo_path = 'photo/{0}'.format(photo_name) | |
| 50 | 88 |  | 
| 51 | 89 | if default_storage.exists(photo_path): | 
| 52 | 90 | default_storage.delete(photo_path) | 
| @@ -56,6 +94,7 @@ def upload_photo(request): | ||
| 56 | 94 | lensman_id=lensman_id, | 
| 57 | 95 | session_id=session_id, | 
| 58 | 96 | photo_id=photo_id, | 
| 97 | + photo_name=photo_name, | |
| 59 | 98 | photo_path=photo_path | 
| 60 | 99 | ) | 
| 61 | 100 |  | 
| @@ -9,4 +9,5 @@ ipython==4.0.0 | ||
| 9 | 9 | pep8==1.6.2 | 
| 10 | 10 | pillow==2.9.0 | 
| 11 | 11 | pytz==2015.7 | 
| 12 | +shortuuid==0.4.2 | |
| 12 | 13 | uWSGI==2.0.11.1 | 
| @@ -0,0 +1,16 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | + | |
| 3 | +from photo.models import UUIDInfo | |
| 4 | + | |
| 5 | +import shortuuid | |
| 6 | + | |
| 7 | + | |
| 8 | +def curtailUUID(length=10): | |
| 9 | + flag = True | |
| 10 | + while flag: | |
| 11 | + uuid = shortuuid.uuid()[-length:] | |
| 12 | + try: | |
| 13 | + UUIDInfo.objects.get(uuid=uuid) | |
| 14 | + except UUIDInfo.DoesNotExist: | |
| 15 | + flag = False | |
| 16 | + return uuid |