diff --git a/bower.json b/bower.json index dfed8f57a..71c302343 100644 --- a/bower.json +++ b/bower.json @@ -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" } } diff --git a/openslides/mediafiles/migrations/0002_auto_20150906_1246.py b/openslides/mediafiles/migrations/0002_auto_20150906_1246.py new file mode 100644 index 000000000..1d1efd94a --- /dev/null +++ b/openslides/mediafiles/migrations/0002_auto_20150906_1246.py @@ -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'), + ), + ] diff --git a/openslides/mediafiles/migrations/0003_auto_20150917_1226.py b/openslides/mediafiles/migrations/0003_auto_20150917_1226.py new file mode 100644 index 000000000..3c89fd894 --- /dev/null +++ b/openslides/mediafiles/migrations/0003_auto_20150917_1226.py @@ -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, + ), + ] diff --git a/openslides/mediafiles/models.py b/openslides/mediafiles/models.py index d645d9948..f2489c482 100644 --- a/openslides/mediafiles/models.py +++ b/openslides/mediafiles/models.py @@ -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. diff --git a/openslides/mediafiles/projector.py b/openslides/mediafiles/projector.py index 32ec3bd44..fbe2a6be9 100644 --- a/openslides/mediafiles/projector.py +++ b/openslides/mediafiles/projector.py @@ -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): diff --git a/openslides/mediafiles/serializers.py b/openslides/mediafiles/serializers.py index db2c9a074..01946cd93 100644 --- a/openslides/mediafiles/serializers.py +++ b/openslides/mediafiles/serializers.py @@ -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() diff --git a/openslides/mediafiles/static/js/mediafiles/mediafiles.js b/openslides/mediafiles/static/js/mediafiles/mediafiles.js index e08cb4f77..8288841d8 100644 --- a/openslides/mediafiles/static/js/mediafiles/mediafiles.js +++ b/openslides/mediafiles/static/js/mediafiles/mediafiles.js @@ -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: "", + }) .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); }); diff --git a/openslides/mediafiles/static/templates/mediafiles/mediafile-form.html b/openslides/mediafiles/static/templates/mediafiles/mediafile-form.html index 1b979171a..1a3924280 100644 --- a/openslides/mediafiles/static/templates/mediafiles/mediafile-form.html +++ b/openslides/mediafiles/static/templates/mediafiles/mediafile-form.html @@ -9,22 +9,25 @@
+ +
+
+ Current value: {{ mediafile.filename }} +
+ +
+
-
- -
+ Upload Successful + {{ errorMsg }} -
- -
+ *required
+ File too large + {{ picFile.size / 1000000|number:1}}MB: max {{ mediafile.mediafile.$errorParam}}