Right now uploading to a fixed directory. This is to be made more flexible.
The url parameter in imge is not being used. Upload working. But the direct upload works, but since no DB entries are not made, it is useless.
This commit is contained in:
parent
17fec1a506
commit
d586e8fe86
@ -39,6 +39,8 @@ def main(global_config, **settings):
|
||||
config.add_route('logout', '/logout')
|
||||
|
||||
add_route(config, 'picture', '/picture')
|
||||
config.add_route('upload', '/upload')
|
||||
config.add_route('api_upload', '/v1/upload')
|
||||
|
||||
config.add_route('album_id_view' , '/view/{id}')
|
||||
config.add_route('album_id' , '/album/{id}')
|
||||
|
@ -1,6 +1,9 @@
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import Column, PickleType, Unicode, ForeignKey, Boolean, Table, UniqueConstraint
|
||||
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from soter.models import Base, DBSession
|
||||
from soter.models.guidtype import GUID
|
||||
|
||||
@ -40,6 +43,7 @@ picture_tag = Table(
|
||||
UniqueConstraint('picture_id', 'tag_id')
|
||||
)
|
||||
|
||||
|
||||
class Album(Base):
|
||||
__tablename__ = 'albums'
|
||||
|
||||
@ -86,15 +90,18 @@ class Picture(Base):
|
||||
|
||||
id = Column('id', GUID(), primary_key=True, default=uuid.uuid4)
|
||||
name = Column('name', Unicode(255), unique=True)
|
||||
url = Column('url', Unicode(255), unique=True)
|
||||
user_id = Column('user_id', GUID(), ForeignKey('users.id'), nullable=False)
|
||||
album_id = Column('album_id', GUID(), ForeignKey('albums.id'), nullable=False)
|
||||
is_public = Column('is_public', Boolean, nullable=False)
|
||||
|
||||
user = relationship('User', primaryjoin="User.id==Picture.user_id", cascade=None)
|
||||
|
||||
def __init__(self, name, user_id, is_public, id=None, is_fixture=False):
|
||||
def __init__(self, name, url, user_id, album_id, is_public, id=None, is_fixture=False):
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.user_id = user_id
|
||||
self.album_id = album_id
|
||||
self.is_public = is_public
|
||||
self.id = id
|
||||
self.is_fixture = is_fixture
|
||||
@ -136,4 +143,3 @@ class Tag(Base):
|
||||
@classmethod
|
||||
def list(cls):
|
||||
return DBSession.query(cls).all()
|
||||
|
||||
|
@ -3,7 +3,8 @@
|
||||
|
||||
angular.module('soter')
|
||||
.controller('AlbumListController', ['$scope', 'albums', AlbumListController])
|
||||
.controller('AlbumController', ['$scope', '$location', 'album', AlbumController]);
|
||||
.controller('AlbumController', ['$scope', '$location', 'album', AlbumController])
|
||||
.controller('AlbumPicturesController', ['$scope', 'info', 'Upload', AlbumPicturesController]);
|
||||
|
||||
function AlbumListController($scope, albums) {
|
||||
$scope.info = albums;
|
||||
@ -32,6 +33,29 @@
|
||||
$('#txtName').focus();
|
||||
}
|
||||
|
||||
function AlbumPicturesController($scope, info, Upload) {
|
||||
|
||||
$scope.info = info;
|
||||
$scope.upload = function (files) {
|
||||
if (files && files.length) {
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var file = files[i];
|
||||
Upload.upload({
|
||||
url: '/v1/upload',
|
||||
fields: {'album': info.id},
|
||||
file: file
|
||||
}).progress(function (evt) {
|
||||
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
|
||||
console.log('progress: ' + progressPercentage + '% ' + evt.config.file.name);
|
||||
}).success(function (data, status, headers, config) {
|
||||
console.log('file ' + config.file.name + 'uploaded. Response: ' + data);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
var AlbumCtrlResolve = {
|
||||
@ -47,3 +71,10 @@ var AlbumListCtrlResolve = {
|
||||
}]
|
||||
};
|
||||
|
||||
var AlbumPicturesCtrlResolve = {
|
||||
info: ['$route', 'Album', function ($route, Album) {
|
||||
var id = $route.current.params.id;
|
||||
return Album.pictures({id: id}).$promise;
|
||||
}]
|
||||
};
|
||||
|
||||
|
@ -6,7 +6,8 @@
|
||||
function Album($resource) {
|
||||
return $resource('/v1/album/:id',
|
||||
{id: '@id'}, {
|
||||
query: {method: 'GET', params: {list: true}, isArray: true}
|
||||
query: {method: 'GET', params: {list: true}, isArray: true},
|
||||
pictures: {method: 'GET', params: {pictures: true}, isArray: false}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
<p>{{item.description}}</p>
|
||||
|
||||
<p><a class="btn" href="{{item.editUrl}}">View <i
|
||||
<p><a class="btn" href="{{item.viewUrl}}">View <i
|
||||
class="glyphicon glyphicon-eye-open"></i></a><a class="btn" href="{{item.editUrl}}">Edit <i
|
||||
class="glyphicon glyphicon-edit"></i></a></p>
|
||||
</div>
|
||||
|
28
soter/static/app/album/pictures.html
Normal file
28
soter/static/app/album/pictures.html
Normal file
@ -0,0 +1,28 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-3" ng-repeat="item in info.pictures">
|
||||
<div class="thumbnail"><img ng-src="{{item.url}}"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div ngf-drop ng-model="files" class="drop-box" style="width: 200px; height: 200px;"
|
||||
ngf-drag-over-class="dragover" ngf-multiple="true" ngf-allow-dir="true"
|
||||
ngf-accept="'image/*,application/pdf'">Drop Images here
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-3" ng-repeat="f in files">
|
||||
<div class="thumbnail">
|
||||
<img class="resized" ngf-src="f" ngf-default-src="'/thumb.jpg'" ngf-accept="'image/*'">
|
||||
|
||||
<div class="caption">
|
||||
<p>Lorem ipsum dolor sit amet</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<br>
|
||||
|
||||
<div class="btn btn-default" ngf-select ng-model="files">Browse</div>
|
||||
<button class="btn" ng-click="upload(files)"><i class="glyphicon glyphicon-upload"></i> Upload</button>
|
||||
</div>
|
@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var soter = angular.module('soter', ['ngRoute', 'ngResource', 'chieffancypants.loadingBar'])
|
||||
var soter = angular.module('soter', ['ngRoute', 'ngResource', 'chieffancypants.loadingBar', 'ngFileUpload'])
|
||||
.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
|
||||
$routeProvider
|
||||
.when('/', {templateUrl: '/app/home/view.html'})
|
||||
@ -22,10 +22,15 @@
|
||||
controller: 'AlbumController',
|
||||
resolve: AlbumCtrlResolve
|
||||
})
|
||||
.when('/upload', {
|
||||
templateUrl: '/app/picture/upload.html',
|
||||
controller: 'UploadController',
|
||||
resolve: UploadCtrlResolve
|
||||
})
|
||||
.when('/view/:id', {
|
||||
templateUrl: '/app/album/view.html',
|
||||
controller: 'AlbumController',
|
||||
resolve: AlbumCtrlResolve
|
||||
templateUrl: '/app/album/pictures.html',
|
||||
controller: 'AlbumPicturesController',
|
||||
resolve: AlbumPicturesCtrlResolve
|
||||
})
|
||||
//.when('/picture', {
|
||||
// templateUrl: '/app/picture/view.html',
|
||||
|
27
soter/static/app/picture/picture.controller.js
Normal file
27
soter/static/app/picture/picture.controller.js
Normal file
@ -0,0 +1,27 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular.module('soter')
|
||||
.controller('UploadController', ['$scope', 'Upload', UploadController]);
|
||||
|
||||
function UploadController($scope, Upload) {
|
||||
$scope.upload = function (files) {
|
||||
if (files && files.length) {
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var file = files[i];
|
||||
Upload.upload({
|
||||
url: '/v1/upload',
|
||||
file: file
|
||||
}).progress(function (evt) {
|
||||
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
|
||||
console.log('progress: ' + progressPercentage + '% ' + evt.config.file.name);
|
||||
}).success(function (data, status, headers, config) {
|
||||
console.log('file ' + config.file.name + 'uploaded. Response: ' + data);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
var UploadCtrlResolve = {};
|
13
soter/static/app/picture/picture.service.js
Normal file
13
soter/static/app/picture/picture.service.js
Normal file
@ -0,0 +1,13 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular.module('soter').factory('Picture', ['$resource', Picture]);
|
||||
|
||||
function Picture($resource) {
|
||||
return $resource('/v1/picture/:id',
|
||||
{id: '@id'}, {
|
||||
query: {method: 'GET', params: {list: true}, isArray: true},
|
||||
pictures: {method: 'GET', params: {pictures: true}, isArray: true}
|
||||
});
|
||||
}
|
||||
})();
|
45
soter/static/app/picture/upload.html
Normal file
45
soter/static/app/picture/upload.html
Normal file
@ -0,0 +1,45 @@
|
||||
<div class="manage albums">
|
||||
|
||||
<div class="row hero-unit blurb">
|
||||
<h2>What are albums?</h2>
|
||||
|
||||
<p>
|
||||
Albums are a collection of photos. You can use them to share photos from a vacation or a child's birthday
|
||||
party.
|
||||
<br>
|
||||
They're similar to tags but have a few key differences.
|
||||
<ol>
|
||||
<li>The permission for who can view a photo applies even when it's in an album. If your photo is private
|
||||
then only you'll be able to see them in your album.
|
||||
</li>
|
||||
<li>You can specify if an album shows up on the <em>Albums</em> page.</li>
|
||||
<li>Albums are fixed unless you explicitly add a photo to it.</li>
|
||||
<li>Add photos to an album using the edit form or on the <a href="<?php $this->url->managePhotos(); ?>">manage
|
||||
photos</a> page.
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div ngf-drop ng-model="files" class="drop-box"
|
||||
ngf-drag-over-class="dragover" ngf-multiple="true" ngf-allow-dir="true"
|
||||
ngf-accept="'image/*,application/pdf'">Drop Images here
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
Image thumbnails:
|
||||
<div class="col-sm-6 col-md-4" ng-repeat="f in files">
|
||||
<div class="thumbnail">
|
||||
<img class="resized" ngf-src="f" ngf-default-src="'/thumb.jpg'" ngf-accept="'image/*'">
|
||||
|
||||
<div class="caption">
|
||||
<p>Lorem ipsum dolor sit amet</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<br>
|
||||
<button class="btn" ng-click="upload(files)"><i class="glyphicon glyphicon-upload"></i> Upload</button>
|
||||
</div>
|
||||
</div>
|
32
soter/static/assets/css/style.css
Normal file
32
soter/static/assets/css/style.css
Normal file
@ -0,0 +1,32 @@
|
||||
.drop-box {
|
||||
background: #F8F8F8;
|
||||
border: 5px dashed #DDD;
|
||||
width: 200px;
|
||||
height: 65px;
|
||||
text-align: center;
|
||||
padding-top: 25px;
|
||||
margin: 10px;
|
||||
}
|
||||
.dragover {
|
||||
border: 5px dashed blue;
|
||||
}
|
||||
|
||||
.resized {
|
||||
max-height: 200px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 1px;
|
||||
}
|
||||
|
||||
|
||||
/*.overlay {*/
|
||||
/*position: absolute;*/
|
||||
/*top: 100%;*/
|
||||
/*left: 0;*/
|
||||
/*width: 100%;*/
|
||||
/*height:auto;*/
|
||||
/*}*/
|
6
soter/static/assets/js/FileAPI.min.js
vendored
Normal file
6
soter/static/assets/js/FileAPI.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
soter/static/assets/js/ng-file-upload.min.js
vendored
Normal file
2
soter/static/assets/js/ng-file-upload.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -8,8 +8,7 @@
|
||||
<meta name="description" content="The">
|
||||
<meta name="author" content="Tanshu">
|
||||
|
||||
<!-- Le styles -->
|
||||
<!-- <link rel="stylesheet" type="text/css" media="screen" href="/css/style.css"/> -->
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/assets/css/style.css"/>
|
||||
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="/assets/css/bootstrap.min.css"/>
|
||||
|
||||
@ -33,6 +32,9 @@
|
||||
<script src="/assets/js/angular-sanitize.min.js"></script>
|
||||
<script src="/assets/js/loading-bar.min.js"></script>
|
||||
|
||||
<script src="/assets/js/FileAPI.min.js"></script>
|
||||
<script src="/assets/js/ng-file-upload.min.js"></script>
|
||||
|
||||
<script src="/app/app.module.js"></script>
|
||||
|
||||
<script src="/app/shared/auth.service.js"></script>
|
||||
@ -48,6 +50,8 @@
|
||||
<script src="/app/album/album.controller.js"></script>
|
||||
<script src="/app/album/album.service.js"></script>
|
||||
|
||||
<script src="/app/picture/picture.controller.js"></script>
|
||||
|
||||
<script src="/script/angular_directive.js"></script>
|
||||
<script src="/script/angular_filter.js"></script>
|
||||
<script src="/script/angular_service.js"></script>
|
||||
@ -86,6 +90,9 @@
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/albums">Albums</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/upload">Upload</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Reports <b
|
||||
class="caret"></b></a>
|
||||
|
@ -1,66 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="${request.locale_name}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="pyramid web application">
|
||||
<meta name="author" content="Pylons Project">
|
||||
<link rel="shortcut icon" href="${request.static_url('soter:static/pyramid-16x16.png')}">
|
||||
|
||||
<title>Starter Scaffold for The Pyramid Web Framework</title>
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Custom styles for this scaffold -->
|
||||
<link href="${request.static_url('soter:static/theme.css')}" rel="stylesheet">
|
||||
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="starter-template">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<img class="logo img-responsive" src="${request.static_url('soter:static/pyramid.png')}" alt="pyramid web framework">
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<div class="content">
|
||||
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter scaffold</span></h1>
|
||||
<p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework 1.5.7</span>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="links">
|
||||
<ul>
|
||||
<li class="current-version">Generated by v1.5.7</li>
|
||||
<li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.5-branch/">Docs</a></li>
|
||||
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
|
||||
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
|
||||
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="copyright">
|
||||
Copyright © Pylons Project
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,7 +1,7 @@
|
||||
import pkg_resources
|
||||
from pyramid.response import FileResponse, Response
|
||||
from pyramid.view import view_config
|
||||
from webob.exc import HTTPForbidden, HTTPFound
|
||||
from pyramid.httpexceptions import HTTPForbidden, HTTPFound
|
||||
|
||||
|
||||
@view_config(route_name='home')
|
||||
|
@ -14,6 +14,7 @@ from soter.models.validation_exception import TryCatchFunction, ValidationError
|
||||
|
||||
@view_config(route_name='album_list', permission='Albums')
|
||||
@view_config(request_method='GET', route_name='album_id', permission='Albums')
|
||||
@view_config(request_method='GET', route_name='album_id_view', permission='Albums')
|
||||
@view_config(request_method='GET', route_name='album', permission='Albums')
|
||||
def html(request):
|
||||
package, resource = 'soter:static/index.html'.split(':', 1)
|
||||
@ -76,11 +77,21 @@ def show_list(request):
|
||||
for item in list:
|
||||
albums.append({'name': item.name, 'description': item.description, 'isPublic': item.is_public,
|
||||
'user': item.user.name, 'isFixture': item.is_fixture, 'pictures': [],
|
||||
'viewUrl': request.route_url('album_id', id=item.id, ),
|
||||
'viewUrl': request.route_url('album_id_view', id=item.id, ),
|
||||
'editUrl': request.route_url('album_id', id=item.id)})
|
||||
return albums
|
||||
|
||||
|
||||
@view_config(request_method='GET', route_name='api_album_id', renderer='json', request_param='pictures',
|
||||
permission='Albums')
|
||||
def show_pics(request):
|
||||
album = Album.by_id(uuid.UUID(request.matchdict['id']))
|
||||
info = album_info(uuid.UUID(request.matchdict['id']))
|
||||
for item in album.pictures:
|
||||
info['pictures'].append({'name': item.name, 'url': request.route_url('api_picture_id', id=item.id)})
|
||||
return info
|
||||
|
||||
|
||||
def album_info(id):
|
||||
if id is None:
|
||||
album = {'name': "", 'description': "", 'isPublic': False, 'pictures': []}
|
||||
|
@ -0,0 +1,44 @@
|
||||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
|
||||
import pkg_resources
|
||||
from pyramid.response import FileResponse
|
||||
from pyramid.security import authenticated_userid
|
||||
from pyramid.view import view_config
|
||||
import transaction
|
||||
|
||||
from soter.models import DBSession
|
||||
|
||||
from soter.models.master import Picture
|
||||
|
||||
|
||||
@view_config(request_method='GET', route_name='upload', permission='Albums')
|
||||
def html(request):
|
||||
package, resource = 'soter:static/index.html'.split(':', 1)
|
||||
file = pkg_resources.resource_filename(package, resource)
|
||||
return FileResponse(file, request=request)
|
||||
|
||||
|
||||
@view_config(request_method='POST', route_name='api_upload', renderer='json', permission='Albums')
|
||||
def upload(request):
|
||||
input_file = request.POST['file'].file
|
||||
file_id = uuid.uuid4()
|
||||
file_path = pkg_resources.resource_filename('soter', 'upload/' + str(file_id) + '.jpg')
|
||||
album = request.POST['album']
|
||||
temp_file_path = file_path + '~'
|
||||
input_file.seek(0)
|
||||
with open(temp_file_path, 'wb') as output_file:
|
||||
shutil.copyfileobj(input_file, output_file)
|
||||
os.rename(temp_file_path, file_path)
|
||||
pic = Picture(file_id, str(file_id), uuid.UUID(authenticated_userid(request)), uuid.UUID(album), True, file_id)
|
||||
DBSession.add(pic)
|
||||
transaction.commit()
|
||||
return {'location': file_path}
|
||||
|
||||
|
||||
@view_config(request_method='GET', route_name='api_picture_id', permission='Albums')
|
||||
def show_id(request):
|
||||
package, resource = ('soter:upload/' + request.matchdict['id'] + '.jpg').split(':', 1)
|
||||
file = pkg_resources.resource_filename(package, resource)
|
||||
return FileResponse(file, request=request)
|
Loading…
Reference in New Issue
Block a user