from django.contrib import admin
-# Register your models here.
+from filex.models import Favorite
+
+
+class FavoriteAdmin(admin.ModelAdmin):
+ list_display = ('owner', 'host', 'path')
+ list_filter = ('owner',)
+ date_hierarchy = 'created'
+
+
+admin.site.register(Favorite, FavoriteAdmin)
# coding=utf-8
from django import forms
+from filex.models import Favorite
+
+
+class FavoriteForm(forms.ModelForm):
+ class Meta:
+ model = Favorite
+ fields = ('owner', 'host', 'path')
+ widgets = {'owner': forms.HiddenInput()}
+
class NewDirForm(forms.Form):
host = forms.CharField(label=u'Host', max_length=256, widget=forms.HiddenInput())
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Favorite',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('host', models.CharField(max_length=256, verbose_name='Host')),
+ ('path', models.CharField(max_length=1024, verbose_name='\u015acie\u017cka')),
+ ('created', models.DateTimeField(auto_now_add=True, verbose_name='Utworzono')),
+ ('updated', models.DateTimeField(auto_now=True, verbose_name='Uaktualniono')),
+ ('owner', models.ForeignKey(related_name='favorites', verbose_name='W\u0142a\u015bciciel', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'Ulubiona lokalizacja',
+ 'verbose_name_plural': 'Ulubione lokalizacje',
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AlterUniqueTogether(
+ name='favorite',
+ unique_together=set([('owner', 'host', 'path')]),
+ ),
+ ]
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('filex', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='favorite',
+ name='path',
+ field=models.CharField(default=b'/', max_length=1024, verbose_name='\u015acie\u017cka'),
+ preserve_default=True,
+ ),
+ ]
+# coding=utf-8
+from django.conf import settings
from django.db import models
-# Create your models here.
+
+class Favorite(models.Model):
+ owner = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u"Właściciel", related_name='favorites')
+ host = models.CharField(u"Host", max_length=256)
+ path = models.CharField(u"Ścieżka", max_length=1024, default='/')
+
+ created = models.DateTimeField(u"Utworzono", auto_now_add=True)
+ updated = models.DateTimeField(u"Uaktualniono", auto_now=True)
+
+ class Meta:
+ verbose_name = u"Ulubiona lokalizacja"
+ verbose_name_plural = u"Ulubione lokalizacje"
+ unique_together = ('owner', 'host', 'path')
+
+ def __unicode__(self):
+ return u'{}{} ({})'.format(self.host, self.path, self.owner)
model: Filex.PathBit,
initialize: function() {
- this.listenTo(this, 'reset', this.setActive);
- this.listenTo(this, 'add', this.setActive);
+ this.listenTo(this, 'reset add', this.setActive);
},
setActive: function() {
events: {
'change #show-hidden': 'toggleHidden',
'click #select-all': 'selectAll',
- 'click #btn-refresh': 'reloadFiles'
+ 'click #btn-refresh': 'reloadFiles',
+ 'click #btn-favorites': 'toggleFavorites',
+ 'click #btn-host': 'editHost'
},
initialize: function(options) {
this.$error = $('#error');
this.$showHidden = $('#show-hidden');
this.$selectAll = $('#select-all');
+ this.$favorites = $('#btn-favorites');
+ this.$host = $('#btn-host');
- this.host = options.host;
this.path = new Filex.Path();
this.files = new Filex.FileList();
this.listenTo(this.path, 'reset', this.resetPath);
- this.listenTo(this.path, 'add', this.reloadFiles);
this.listenTo(this.path, 'add', this.addPath);
+ this.listenTo(this.path, 'add reset', this.changedPath);
this.listenTo(this.path, 'selected', this.selectedPath);
this.listenTo(this.files, 'reset', this.resetFiles);
this.listenTo(this.files, 'selected:dir', this.selectedDir);
optionTemplate = _.template('<div><div><%= host %></div><div class="small text-muted"><%= path %></div></div>');
this.$('#host-selector').selectize({
- valueField: 'host',
+ optgroupField: 'group',
labelField: 'host',
searchField: ['host', 'path'],
sortField: [
{field: 'host', direction: 'asc'},
{field: 'path', direction: 'asc'}
],
- items: [this.host],
- options: options.hostOptions,
+ options: options.locations,
+ optgroups: [
+ {value: 'sys', label: 'Podstawowe'},
+ {value: 'usr', label: 'Użytkownika'}
+ ],
+ lockOptgroupOrder: true,
+ create: function(input, callback) {
+ var $form = $('#favorite-form'),
+ parts = input.split('/', 1),
+ callback_called = false;
+
+ $form.find('#id_host').val(parts[0]);
+
+ if (parts.length > 1)
+ $form.find('#id_path').val(parts[1]);
+
+ $form.on('submit', function(e) {
+ var $this = $(this),
+ $btn = $this.find('[type="submit"]');
+
+ e.preventDefault();
+ $btn.button('loading');
+
+ $.post($this.attr('action'), $this.serialize(), function(data) {
+ callback(data);
+ callback_called = true;
+
+ $this.modal('hide');
+ $btn.button('reset');
+ }, 'json').fail(function() {
+ console.error(arguments);
+ $btn.button('error');
+ });
+ });
+
+ $form.one('hide.bs.modal', function() {
+ if (!callback_called)
+ callback();
+ $form.off();
+ });
+
+ $form.modal();
+ },
render: {
option: function(item) {
return optionTemplate(item);
+ },
+ option_create : function(data, escape) {
+ return '<div class="create">Dodaj <em>' + escape(data.input) + '</em>…</div>';
}
},
- onDropdownClose: function() {
+ onItemRemove: function(value) {
+ this.oldValue = value;
+ },
+ onItemAdd: function(value) {
+ view.load(value);
this.blur();
},
onBlur: function() {
- var value = this.getValue();
- if (!value) {
- this.addItem(view.host, true);
- return;
- }
-
- if (value != view.host) {
- var location = this.options[value];
+ if (!this.getValue() && this.oldValue)
+ this.addItem(this.oldValue);
- view.host = location.host;
- view.navigate(location.path);
- }
+ $('#host').removeClass('edit');
+ this.clear();
}
});
this.hostSelectize = this.$('#host-selector')[0].selectize;
this.render();
- this.navigate(this.hostSelectize.options[this.host].path);
},
render: function() {
this.updateSelectAll();
+ this.updateFavorites();
this.$noItems.toggle(!Boolean(this.visibleFiles().length));
this.$error.hide();
},
- navigate: function(path) {
- var pathBits = [new Filex.PathBit({'text': '/', 'path': ''})];
-
- pathBits = pathBits.concat(_.map(path.replace(/(^\/+|\/+$)/g, '').split('/'), function(name) {
- return new Filex.PathBit({'text': name, 'path': name});
- }));
+ load: function(location) {
+ var path = location.replace(/(^\/+|\/+$)/g, '').split('/'),
+ host = path.shift(),
+ pathBits = [new Filex.PathBit({'text': '/', 'path': ''})].concat(_.map(path, function(name) {
+ return new Filex.PathBit({'text': name, 'path': name});
+ }));
+ this.host = host;
this.path.reset(pathBits);
+
+ this.$host.text(this.host);
},
reloadFiles: function() {
},
resetPath: function(models, options) {
- this.reloadFiles();
-
_.each(options.previousModels, function(model) {
model.trigger('remove');
});
},
selectedPath: function(bit) {
- var newPath = this.path.slice(0, this.path.indexOf(bit) + 1);
- this.path.set(newPath);
- this.reloadFiles();
+ this.path.reset(this.path.slice(0, this.path.indexOf(bit) + 1));
},
showHidden: function() {
_.each(this.visibleFiles(), function(item) {
item.set('checked', false);
});
+ },
- this.updateSelectAll();
+ toggleFavorites: function() {
+ var $btn = this.$favorites,
+ locations = this.hostSelectize,
+ is_active = $btn.hasClass('active'),
+ url = is_active ? '/filex/fav/delete/' : '/filex/fav/add/',
+ data = {
+ host: this.host,
+ path: this.path.full()
+ };
+
+ $btn.button('loading');
+
+ $.post(url, data, 'json').done(function () {
+ $btn.button('reset');
+
+ if (is_active) {
+ locations.removeOption(data.host + data.path);
+ }
+ else {
+ locations.addOption({
+ group: 'usr',
+ host: data.host,
+ path: data.path,
+ value: data.host + data.path
+ });
+ }
+ }).fail(function() {
+ $btn.button('reset');
+ $btn.button('toggle');
+
+ console.error(arguments);
+ });
+ },
+
+ initialLoad: function() {
+ if (!this.host) {
+ var opts = this.hostSelectize.options;
+
+ this.load(opts[Object.keys(opts)[0]].value);
+ }
+ },
+
+ changedPath: function () {
+ this.reloadFiles();
+ this.updateFavorites();
+ },
+
+ updateFavorites: function() {
+ var loc = this.host + this.path.full(),
+ favorites = this.hostSelectize.options;
+
+ if (favorites.hasOwnProperty(loc)) {
+ if (favorites[loc].group == 'sys') {
+ this.$favorites.addClass('disabled').prop('disabled', true);
+ if (this.$favorites.hasClass('active'))
+ this.$favorites.button('toggle');
+ }
+ else {
+ this.$favorites.removeClass('disabled').prop('disabled', false);
+ if (!this.$favorites.hasClass('active'))
+ this.$favorites.button('toggle');
+ }
+ }
+ else {
+ this.$favorites.removeClass('disabled').prop('disabled', false);
+ if (this.$favorites.hasClass('active'))
+ this.$favorites.button('toggle');
+ }
+ },
+
+ editHost: function() {
+ $('#host').addClass('edit');
+ this.hostSelectize.focus();
}
});
});
+{% load filex %}
+
<div id="filex">
<nav aria-label="Pasek narzędzi nawigacyjnych">
<div id="host">
- <label for="host-selector" class="sr-only">Wybierz host</label>
- <select id="host-selector"></select>
+ <div class="list">
+ <label for="host-selector" class="sr-only">Wybierz host</label>
+ <select id="host-selector"></select>
+ </div>
+ <button id="btn-host" type="button" class="btn btn-default"></button>
</div>
<div id="controls">
- <button id="btn-refresh" type="button" class="btn btn-default" aria-label="Odśwież">
+ <button id="btn-refresh" type="button" class="btn btn-default" title="Odśwież">
<span class="glyphicon glyphicon-refresh" aria-hidden="true"></span>
</button>
+ <button id="btn-favorites" type="button" class="btn btn-default" title="Ulubione" data-toggle="button" data-loading-text="Zapisywanie..." aria-pressed="false">
+ <span class="glyphicon glyphicon-star" aria-hidden="true"></span>
+ </button>
+
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-default">
<input id="show-hidden" type="checkbox" autocomplete="off"> Pokaż ukryte
<div id="spinner-overlay"></div>
</div>
+
+<form id="favorite-form" action="{% url 'filex:fav_add' %}" class="modal fade form-horizontal" tabindex="-1" role="dialog" aria-labelledby="favorite-modal-label" aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span>
+ </button>
+ <h4 class="modal-title" id="favorite-modal-label">Dodaj lokalizację do ulubionych</h4>
+ </div>
+ <div class="modal-body">
+ {% csrf_token %}
+ {% fav_form %}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">Anuluj</button>
+ <button type="submit" class="btn btn-primary" data-loading-text="Zapisywanie..." data-error-text="Błąd">OK</button>
+ </div>
+ </div>
+ </div>
+</form>
-{% load staticfiles %}
+{% load staticfiles filex %}
<script src="{% static 'qcg/selectize/selectize.min.js' %}"></script>
<script src="{% static 'filex/underscore/underscore.js' %}"></script>
<script>
var filex;
$(function() {
- filex = new Filex.FilexView({
- host: 'qcg.man.poznan.pl',
- hostOptions: [
- {host: 'qcg.man.poznan.pl', path: '/home/plgrid/{{ request.user.username }}/reef'},
- {host: 'ui.grid.icm.edu.pl', path: '/icm/hydra/home/grid/{{ request.user.username }}'},
- {host: 'ui.plgrid.wcss.wroc.pl', path: '/home/grid/users/{{ request.user.username }}'},
- {host: 'ui.grid.task.gda.pl', path: '/home/plgrid/{{ request.user.username }}'},
- {host: 'zeus.cyfronet.pl', path: '/people/{{ request.user.username }}'}
- ]
- });
+ filex = new Filex.FilexView({locations: {% locations %}});
});
</script>
--- /dev/null
+import json
+
+from bootstrap3.forms import render_form
+from django import template
+
+from ..forms import FavoriteForm
+
+
+register = template.Library()
+
+
+@register.simple_tag(takes_context=True)
+def locations(context):
+ user = context['request'].user
+
+ result = [
+ {'group': 'sys', 'host': 'moss.man.poznan.pl', 'path': '/home/plg-users/' + user.username},
+ {'group': 'sys', 'host': 'qcg.man.poznan.pl', 'path': '/home/plgrid/' + user.username},
+ {'group': 'sys', 'host': 'ui.grid.icm.edu.pl', 'path': '/icm/hydra/home/grid/' + user.username},
+ {'group': 'sys', 'host': 'ui.plgrid.wcss.wroc.pl', 'path': '/home/grid/users/' + user.username},
+ {'group': 'sys', 'host': 'ui.grid.task.gda.pl', 'path': '/home/plgrid/' + user.username},
+ {'group': 'sys', 'host': 'zeus.cyfronet.pl', 'path': '/people/' + user.username}
+ ]
+
+ for item in user.favorites.values('host', 'path'):
+ item['group'] = 'usr'
+ result.append(item)
+
+ for item in result:
+ item['value'] = item['host'] + item['path']
+
+ return json.dumps(result)
+
+
+@register.simple_tag
+def fav_form():
+ return render_form(FavoriteForm(), layout='horizontal')
url(r'^move/$', views.move, name='move'),
url(r'^compress/$', views.compress, name='compress'),
url(r'^extract/$', views.extract, name='extract'),
+
+ url(r'^fav/add/$', views.fav_add, name='fav_add'),
+ url(r'^fav/delete/$', views.fav_delete, name='fav_delete'),
)
import mimetypes
+from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.http import JsonResponse, StreamingHttpResponse
+from django.shortcuts import get_object_or_404
from django.template.defaultfilters import filesizeformat
from django.utils.formats import date_format
from django.views.decorators.http import require_POST
-from filex.forms import NewDirForm, RenameForm
+from filex.forms import NewDirForm, RenameForm, FavoriteForm
from filex.ftp import FTPOperation, FTPException
+from filex.models import Favorite
from filex.uploadhandler import with_ftp_upload_handler
return JsonResponse({'msg': e.message}, status=400)
else:
return JsonResponse({'success': True})
+
+
+@require_POST
+@login_required
+def fav_add(request):
+ data = request.POST.copy()
+ data['owner'] = request.user.id
+
+ form = FavoriteForm(data)
+
+ # TODO check if path exists
+ if form.is_valid():
+ instance = form.save()
+
+ return JsonResponse({'group': 'usr', 'host': instance.host, 'path': instance.path,
+ 'value': instance.host + instance.path})
+
+ return JsonResponse({'msg': form.errors}, status=400)
+
+
+@require_POST
+@login_required
+def fav_delete(request):
+ fav = get_object_or_404(Favorite, owner=request.user, host=request.POST['host'], path=request.POST['path'])
+ fav.delete()
+
+ return JsonResponse({'success': True})
}
#spinner-overlay {
- background: url('/static/filex/spinner.gif') white center 100px no-repeat;
+ background: url('/static/filex/spinner.gif') white center center no-repeat fixed;
opacity: 0.5;
position: absolute;
top: 0;
#host {
flex: 1 auto;
margin-right: 10px;
+ white-space: nowrap;
}
#controls {
@media (min-width: 768px) {
#host {
order: 1;
- flex: 1 auto;
+ flex: 0 auto;
margin-right: 10px;
}
flex: 0 auto;
}
}
+
+#host .list {
+ display: none;
+}
+
+#host.edit .list {
+ display: initial;
+}
+
+#host.edit #btn-host {
+ display: none;
+}
+
+#host .selectize-control {
+ min-width: 200px;
+}
+
+#host .selectize-input {
+ padding-right: 32px;
+}
$(function () {
'use strict';
+ filex.initialLoad();
+
var statusTimeout;
String.prototype.endsWith = function(suffix) {
$('#btn-extract').toggleClass('disabled', !is_archive);
}
else {
- $('#btn-extract').toggleClass('disabled', true);
+ $('#btn-extract').addClass('disabled');
}
});