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", "sockjs": "~0.3.4",
"font-awesome-bower": "4.3.0", "font-awesome-bower": "4.3.0",
"js-data": "~2.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.db import models
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop 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. Class for uploaded files which can be delivered under a certain url.
""" """
slide_callback_name = 'mediafile' slide_callback_name = 'mediafile'
PRESENTABLE_FILE_TYPES = ['application/pdf']
mediafile = models.FileField(upload_to='file', verbose_name=ugettext_lazy('File')) mediafile = models.FileField(upload_to='file', verbose_name=ugettext_lazy('File'))
""" """
@ -21,7 +18,7 @@ class Mediafile(RESTModelMixin, models.Model):
for more information. 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.""" """A string representing the title of the file."""
uploader = models.ForeignKey(User, null=True, blank=True, verbose_name=ugettext_lazy('Uploaded by')) 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) timestamp = models.DateTimeField(auto_now_add=True)
"""A DateTimeField to save the upload date and time.""" """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: class Meta:
""" """
Meta class for the mediafile model. Meta class for the mediafile model.
@ -55,16 +43,6 @@ class Mediafile(RESTModelMixin, models.Model):
""" """
return self.title 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): def get_filesize(self):
""" """
Transforms bytes to kilobytes or megabytes. Returns the size as string. Transforms bytes to kilobytes or megabytes. Returns the size as string.

View File

@ -16,11 +16,9 @@ class MediafileSlide(ProjectorElement):
def get_context(self): def get_context(self):
pk = self.config_entry.get('id') pk = self.config_entry.get('id')
try: try:
mediafile = Mediafile.objects.get(pk=pk) Mediafile.objects.get(pk=pk)
except Mediafile.DoesNotExist: except Mediafile.DoesNotExist:
raise ProjectorException(_('File does not exist.')) 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} return {'id': pk}
def get_requirements(self, config_entry): 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 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): class MediafileSerializer(ModelSerializer):
""" """
Serializer for mediafile.models.Mediafile objects. Serializer for mediafile.models.Mediafile objects.
""" """
filesize = SerializerMethodField() 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: class Meta:
model = Mediafile model = Mediafile
fields = ( fields = (
@ -18,8 +45,7 @@ class MediafileSerializer(ModelSerializer):
'uploader', 'uploader',
'filesize', 'filesize',
'filetype', 'filetype',
'timestamp', 'timestamp',)
'is_presentable',)
def get_filesize(self, mediafile): def get_filesize(self, mediafile):
return mediafile.get_filesize() return mediafile.get_filesize()

View File

@ -5,13 +5,47 @@ angular.module('OpenSlidesApp.mediafiles', [])
.factory('Mediafile', ['DS', function(DS) { .factory('Mediafile', ['DS', function(DS) {
return DS.defineResource({ return DS.defineResource({
name: 'mediafiles/mediafile', 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) {}]); .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([ .config([
'mainMenuProvider', 'mainMenuProvider',
@ -45,6 +79,18 @@ angular.module('OpenSlidesApp.mediafiles.site', ['OpenSlidesApp.mediafiles'])
} }
}) })
.state('mediafiles.mediafile.create', {}) .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', { .state('mediafiles.mediafile.detail.update', {
views: { views: {
'@mediafiles.mediafile': {} '@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'); Mediafile.bindAll({}, $scope, 'mediafiles');
// setup table sorting // 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.mediafile = {};
$scope.save = function(mediafile) { $scope.save = uploadFile($timeout, $scope, $state, Upload);
Mediafile.create(mediafile).then(
function(success) {
$state.go('mediafiles.mediafile.list');
}
);
};
}) })
.controller('MediafileUpdateCtrl', function($scope, $state, Mediafile, mediafile) { .controller('MediafileUpdateCtrl', function($scope, $state, $timeout, Upload, Mediafile, mediafile) {
$scope.mediafile = mediafile; $scope.mediafile = mediafile;
$scope.save = function (mediafile) { $scope.save = uploadFile($timeout, $scope, $state, Upload, mediafile);
Mediafile.save(mediafile).then(
function(success) {
$state.go('mediafiles.mediafile.list');
}
);
};
}); });

View File

@ -9,22 +9,25 @@
</div> </div>
<form name="mediafileForm"> <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"> <div class="form-group">
<label for="inputTitle" translate>Title</label> <label for="inputTitle" translate>Title</label>
<input type="text" ng-model="mediafile.title" class="form-control" name="inputTitle"> <input type="text" ng-model="mediafile.title" class="form-control" name="inputTitle">
</div> </div>
<div class="form-group"> <span ng-show="mediafile.mediafile.result">Upload Successful</span>
<input type="file" ng-model="mediafile.mediafile"/> <span class="err" ng-show="errorMsg">{{ errorMsg }}</span>
</div>
<div class="form-group"> <i ng-show="mediafile.file.$error.required">*required</i><br>
<label class="checkbox-inline"> <i ng-show="mediafile.file.$error.maxSize">File too large
<input type="checkbox" ng-model="mediafile.is_presentable" ng-checked="mediafile.is_presentable" name="checkboxPresentable"> {{ picFile.size / 1000000|number:1}}MB: max {{ mediafile.mediafile.$errorParam}}</i>
<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>
<button type="submit" ng-click="save(mediafile)" class="btn btn-primary" translate> <button type="submit" ng-click="save(mediafile)" class="btn btn-primary" translate>
Save Save

View File

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

View File

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