Add upload feature to mediafiles module
This commit is contained in:
parent
ddafb6682a
commit
10b8a1d838
@ -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"
|
||||
}
|
||||
}
|
||||
|
20
openslides/mediafiles/migrations/0002_auto_20150906_1246.py
Normal file
20
openslides/mediafiles/migrations/0002_auto_20150906_1246.py
Normal 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'),
|
||||
),
|
||||
]
|
20
openslides/mediafiles/migrations/0003_auto_20150917_1226.py
Normal file
20
openslides/mediafiles/migrations/0003_auto_20150917_1226.py
Normal 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,
|
||||
),
|
||||
]
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -13,6 +13,7 @@ from rest_framework.serializers import ( # noqa
|
||||
CharField,
|
||||
DictField,
|
||||
Field,
|
||||
FileField,
|
||||
IntegerField,
|
||||
ListField,
|
||||
ListSerializer,
|
||||
|
Loading…
Reference in New Issue
Block a user