Merge pull request #1625 from boehlke/master

Add upload feature to mediafiles module.
This commit is contained in:
Norman Jäckel 2015-09-17 12:41:10 +02:00
commit 4600fd9b02
11 changed files with 151 additions and 66 deletions

View File

@ -24,6 +24,7 @@
"sockjs": "~0.3.4",
"font-awesome-bower": "4.3.0",
"js-data": "~2.3.0",
"js-data-angular": "~3.0.0"
"js-data-angular": "~3.0.0",
"ng-file-upload": "~7.0.17"
}
}

View File

@ -0,0 +1,20 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mediafiles', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='mediafile',
name='is_presentable',
),
migrations.AlterField(
model_name='mediafile',
name='title',
field=models.CharField(max_length=255, null=True, unique=True, blank=True, verbose_name='Title'),
),
]

View File

@ -0,0 +1,20 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mediafiles', '0002_auto_20150906_1246'),
]
operations = [
migrations.RemoveField(
model_name='mediafile',
name='filetype',
),
migrations.AlterField(
model_name='mediafile',
name='title',
field=models.CharField(unique=True, verbose_name='Title', max_length=255, default='', blank=True),
preserve_default=False,
),
]

View File

@ -1,5 +1,3 @@
import mimetypes
from django.db import models
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop
@ -13,7 +11,6 @@ class Mediafile(RESTModelMixin, models.Model):
Class for uploaded files which can be delivered under a certain url.
"""
slide_callback_name = 'mediafile'
PRESENTABLE_FILE_TYPES = ['application/pdf']
mediafile = models.FileField(upload_to='file', verbose_name=ugettext_lazy('File'))
"""
@ -21,7 +18,7 @@ class Mediafile(RESTModelMixin, models.Model):
for more information.
"""
title = models.CharField(max_length=255, unique=True, verbose_name=ugettext_lazy('Title'))
title = models.CharField(max_length=255, unique=True, blank=True, verbose_name=ugettext_lazy('Title'))
"""A string representing the title of the file."""
uploader = models.ForeignKey(User, null=True, blank=True, verbose_name=ugettext_lazy('Uploaded by'))
@ -30,15 +27,6 @@ class Mediafile(RESTModelMixin, models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
"""A DateTimeField to save the upload date and time."""
filetype = models.CharField(max_length=255, editable=False)
"""A string used to show the type of the file."""
is_presentable = models.BooleanField(
default=False,
verbose_name=ugettext_lazy("Is Presentable"),
help_text=ugettext_lazy("If checked, this file can be presented on the projector. "
"Currently, this is only possible for PDFs."))
class Meta:
"""
Meta class for the mediafile model.
@ -55,16 +43,6 @@ class Mediafile(RESTModelMixin, models.Model):
"""
return self.title
def save(self, *args, **kwargs):
"""
Method to read filetype and then save to the database.
"""
if self.mediafile:
self.filetype = mimetypes.guess_type(self.mediafile.path)[0] or ugettext_noop('unknown')
else:
self.filetype = ugettext_noop('unknown')
return super(Mediafile, self).save(*args, **kwargs)
def get_filesize(self):
"""
Transforms bytes to kilobytes or megabytes. Returns the size as string.

View File

@ -16,11 +16,9 @@ class MediafileSlide(ProjectorElement):
def get_context(self):
pk = self.config_entry.get('id')
try:
mediafile = Mediafile.objects.get(pk=pk)
Mediafile.objects.get(pk=pk)
except Mediafile.DoesNotExist:
raise ProjectorException(_('File does not exist.'))
if not (mediafile.is_presentable and mediafile.filetype == 'application/pdf'):
raise ProjectorException(_('File is not presentable.'))
return {'id': pk}
def get_requirements(self, config_entry):

View File

@ -1,14 +1,41 @@
from openslides.utils.rest_api import ModelSerializer, SerializerMethodField
import mimetypes
from django.db import models as dbmodels
from ..utils.rest_api import FileField, ModelSerializer, SerializerMethodField
from .models import Mediafile
class AngularCompatibleFileField(FileField):
def to_internal_value(self, data):
if data == '':
return None
return super(AngularCompatibleFileField, self).to_internal_value(data)
def to_representation(self, value):
if value is None:
return None
return {
'name': value.name,
'type': mimetypes.guess_type(value.path)[0]
}
class MediafileSerializer(ModelSerializer):
"""
Serializer for mediafile.models.Mediafile objects.
"""
filesize = SerializerMethodField()
def __init__(self, *args, **kwargs):
"""
This constructor overwrites the FileField field serializer to return the file meta data in a way that the
angualarjs upload module likes
"""
super(MediafileSerializer, self).__init__(*args, **kwargs)
self.serializer_field_mapping[dbmodels.FileField] = AngularCompatibleFileField
class Meta:
model = Mediafile
fields = (
@ -18,8 +45,7 @@ class MediafileSerializer(ModelSerializer):
'uploader',
'filesize',
'filetype',
'timestamp',
'is_presentable',)
'timestamp',)
def get_filesize(self, mediafile):
return mediafile.get_filesize()

View File

@ -5,13 +5,47 @@ angular.module('OpenSlidesApp.mediafiles', [])
.factory('Mediafile', ['DS', function(DS) {
return DS.defineResource({
name: 'mediafiles/mediafile',
computed: {
is_presentable: ['filetype', function (filetype) {
var PRESENTABLE_FILE_TYPES = ['application/pdf']
return _.contains(PRESENTABLE_FILE_TYPES, filetype);
}],
filename: [function () {
var filename = this.mediafile.name;
return /\/(.+?)$/.exec(filename)[1];
}],
title_or_filename: ['title', 'mediafile', function (title) {
return title || this.filename;
}]
}
});
}])
.run(['Mediafile', function(Mediafile) {}]);
function uploadFile($timeout, $scope, $state, Upload, mediafile) {
return function(file) {
file.upload = Upload.upload({
url: '/rest/mediafiles/mediafile/' + (mediafile ? mediafile.id : ''),
method: mediafile ? 'PUT' : 'POST',
fields: {title: file.title},
file: file.mediafile,
fileFormDataName: 'mediafile'
});
angular.module('OpenSlidesApp.mediafiles.site', ['OpenSlidesApp.mediafiles'])
file.upload.then(function (response) {
$timeout(function () {
file.result = response.data;
$state.go('mediafiles.mediafile.list');
});
}, function (response) {
if (response.status > 0)
$scope.errorMsg = response.status + ': ' + response.data;
});
}
}
angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.mediafiles'])
.config([
'mainMenuProvider',
@ -45,6 +79,18 @@ angular.module('OpenSlidesApp.mediafiles.site', ['OpenSlidesApp.mediafiles'])
}
})
.state('mediafiles.mediafile.create', {})
.state('mediafiles.mediafile.detail', {
url: '/{id:int}',
abstract: true,
resolve: {
mediafile: function(Mediafile, $stateParams) {
var id = $stateParams.id;
var file = Mediafile.find(id);
return file;
}
},
template: "<ui-view/>",
})
.state('mediafiles.mediafile.detail.update', {
views: {
'@mediafiles.mediafile': {}
@ -52,7 +98,7 @@ angular.module('OpenSlidesApp.mediafiles.site', ['OpenSlidesApp.mediafiles'])
});
})
.controller('MediafileListCtrl', function($scope, $http, Mediafile) {
.controller('MediafileListCtrl', function($scope, $http, $timeout, Upload, Mediafile) {
Mediafile.bindAll({}, $scope, 'mediafiles');
// setup table sorting
@ -78,26 +124,14 @@ angular.module('OpenSlidesApp.mediafiles.site', ['OpenSlidesApp.mediafiles'])
};
})
.controller('MediafileCreateCtrl', function($scope, $state, Mediafile) {
.controller('MediafileCreateCtrl', function($scope, $state, $timeout, Upload) {
$scope.mediafile = {};
$scope.save = function(mediafile) {
Mediafile.create(mediafile).then(
function(success) {
$state.go('mediafiles.mediafile.list');
}
);
};
$scope.save = uploadFile($timeout, $scope, $state, Upload);
})
.controller('MediafileUpdateCtrl', function($scope, $state, Mediafile, mediafile) {
.controller('MediafileUpdateCtrl', function($scope, $state, $timeout, Upload, Mediafile, mediafile) {
$scope.mediafile = mediafile;
$scope.save = function (mediafile) {
Mediafile.save(mediafile).then(
function(success) {
$state.go('mediafiles.mediafile.list');
}
);
};
$scope.save = uploadFile($timeout, $scope, $state, Upload, mediafile);
});

View File

@ -9,22 +9,25 @@
</div>
<form name="mediafileForm">
<div class="form-group">
<div ng-if="mediafile.id">
<span translate>Current value: </span>{{ mediafile.filename }}
</div>
<input type="file" ngf-select ng-model="mediafile.mediafile" required/>
</div>
<div class="form-group">
<label for="inputTitle" translate>Title</label>
<input type="text" ng-model="mediafile.title" class="form-control" name="inputTitle">
</div>
<div class="form-group">
<input type="file" ng-model="mediafile.mediafile"/>
</div>
<span ng-show="mediafile.mediafile.result">Upload Successful</span>
<span class="err" ng-show="errorMsg">{{ errorMsg }}</span>
<div class="form-group">
<label class="checkbox-inline">
<input type="checkbox" ng-model="mediafile.is_presentable" ng-checked="mediafile.is_presentable" name="checkboxPresentable">
<translate>Is presentable</translate>
<p class="help-block" translate>If checked, this file can be presented on the projector. Currently, this is only possible for PDFs.</p>
</label>
</div>
<i ng-show="mediafile.file.$error.required">*required</i><br>
<i ng-show="mediafile.file.$error.maxSize">File too large
{{ picFile.size / 1000000|number:1}}MB: max {{ mediafile.mediafile.$errorParam}}</i>
<button type="submit" ng-click="save(mediafile)" class="btn btn-primary" translate>
Save

View File

@ -11,16 +11,16 @@
<div class="col-sm-8"></div>
<div class="col-sm-4">
<input type="text" os-focus-me ng-model="filter.search" class="form-control"
placeholder="{{ 'Filter' | translate}}">
placeholder="{{ 'Filter' | translate }}">
</div>
</div>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th ng-click="toggleSort('title')" class="sortable">
<th ng-click="toggleSort('title_or_filename')" class="sortable">
<translate>Title</translate>
<i class="pull-right fa" ng-show="sortColumn === 'title' && header.sortable != false"
<i class="pull-right fa" ng-show="sortColumn === 'title_or_filename' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<th ng-click="toggleSort('filetype')" class="sortable">
@ -45,24 +45,27 @@
</i>
<th os-perms="mediafiles.can_manage core.can_manage_projector" class="minimum">
<translate>Actions</translate>
</th>
</tr>
<tbody>
<tr ng-repeat="mediafile in mediafiles | filter: filter.search |
orderBy: sortColumn:reverse">
<td><a ng-href="{{ mediafile.mediafile }}" target="_self">{{ mediafile.title }}</a>
<td><a ng-href="{{ mediafile.mediafile }}" target="_self">{{ mediafile.title_or_filename }}</a>
<td class="optional">{{ mediafile.filetype }}
<td>{{ mediafile.filesize }}
<td>{{ mediafile.timestamp }}
<td>{{ mediafile.uploader }}
<td os-perms="mediafiles.can_manage core.can_manage_projector" class="nobr">
<!-- projector, TODO: add link to activate slide -->
<a href="#TODO" ng-if="mediafile.is_presentable" os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
<a href="#TODO" ng-if="mediafile.is_presentable" os-perms-lite="core.can_manage_projector"
class="btn btn-default btn-sm"
title="{{ 'Show' | translate }}">
<i class="fa fa-video-camera"></i>
</a>
<!-- edit -->
<a ui-sref="mediafiles.mediafile.detail.update({id: mediafile.id })" os-perms="mediafiles.can_manage"
class="btn btn-default btn-sm"
title="{{ 'Edit' | translate}}">
title="{{ 'Edit' | translate }}">
<i class="fa fa-pencil"></i>
</a>
<!-- delete -->
@ -70,4 +73,6 @@
title="{{ 'Delete' | translate }}">
<i class="fa fa-trash-o"></i>
</a>
</tr>
</tbody>
</table>

View File

@ -1,5 +1,4 @@
from openslides.utils.rest_api import ModelViewSet
from ..utils.rest_api import ModelViewSet
from .models import Mediafile
from .serializers import MediafileSerializer

View File

@ -13,6 +13,7 @@ from rest_framework.serializers import ( # noqa
CharField,
DictField,
Field,
FileField,
IntegerField,
ListField,
ListSerializer,