diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index c60e463da4..6a0caadcf8 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -53,8 +53,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:40+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-16 09:07+0000\n" +"Last-Translator: siljaj \n" "Language-Team: German (http://www.transifex.com/haiwen/seahub/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1355,7 +1355,7 @@ msgstr "Gruppenname ist zu lang (maximal 255 Zeichen)" #: seahub/group/forms.py:37 msgid "" "Group name can only contain letters, numbers, blank, hyphen or underscore" -msgstr "" +msgstr "Gruppennamen dürfen nur Buchstaben, Zahlen, Leerzeichen, Bindestriche und Unterstriche enthalten" #: seahub/group/forms.py:47 msgid "Verification message can't be empty" @@ -1412,7 +1412,7 @@ msgstr "Sie haben die Gruppe gelöscht." msgid "" "Failed to rename group, group name can only contain letters, numbers, blank," " hyphen or underscore" -msgstr "" +msgstr "Umbenennen der Gruppe fehlgeschlagen. Gruppennamen dürfen nur Buchstaben, Zahlen, Leerzeichen, Bindestriche und Unterstriche enthalten" #: seahub/group/views.py:332 #, python-format @@ -4853,7 +4853,7 @@ msgstr "Ordner" #: seahub/templates/repo_recycle_view.html:19 #, python-format msgid "%(repo_name)s Trash" -msgstr "" +msgstr "%(repo_name)s Papierkorb" #: seahub/templates/repo_recycle_view.html:10 #: seahub/templates/repo_recycle_view.html:100 @@ -5160,7 +5160,7 @@ msgstr "Automatisch feststellen" msgid "" "You can use IE 10 or other browsers, for example, Firefox, to view it " "online." -msgstr "" +msgstr "Sie können IE 10 oder zum Beispiel Firefox benutzen, um es online zu betrachten." #: seahub/templates/view_file_svg.html:7 #: seahub/templates/snippets/file_content_js.html:63 @@ -5352,7 +5352,7 @@ msgstr "Passwort hinzufügen" #: seahub/templates/snippets/file_share_popup.html:70 #, python-format msgid "(at least %(share_link_password_min_length)s characters)" -msgstr "" +msgstr "(mindestens %(share_link_password_min_length)s Zeichen)" #: seahub/templates/js/templates.html:367 #: seahub/templates/js/templates.html:422 @@ -5440,7 +5440,7 @@ msgstr "Bibliotheken zum Freigeben auswählen" #: seahub/templates/js/templates.html:837 msgid "Deleted library" -msgstr "" +msgstr "Gelöschte Bibliotheken" #: seahub/templates/js/templates.html:844 #: seahub/templates/snippets/list_commit_detail.html:11 @@ -5758,7 +5758,7 @@ msgstr "Dokumentenkonvertierung fehlgeschlagen." #: seahub/templates/snippets/office_convert_js.html:76 msgid "Failed to load this page." -msgstr "" +msgstr "Fehler beim Laden dieser Seite." #: seahub/templates/snippets/password_strength_js.html:42 msgid "too weak" @@ -5985,7 +5985,7 @@ msgstr "Protokolle" #: seahub/templates/sysadmin/base.html:43 msgid "Virus Scan" -msgstr "" +msgstr "Viren-Scan" #: seahub/templates/sysadmin/repo_transfer_form.html:3 msgid "Transfer Library" @@ -6092,16 +6092,16 @@ msgstr "Upgrade auf die Pro Edition" #: seahub/templates/sysadmin/sys_info.html:23 #: seahub/templates/sysadmin/sys_info.html:32 msgid "Active Users" -msgstr "" +msgstr "Aktive Benutzer" #: seahub/templates/sysadmin/sys_info.html:23 #: seahub/templates/sysadmin/sys_info.html:32 msgid "Total Users" -msgstr "" +msgstr "Gesamtbenutzerzahl" #: seahub/templates/sysadmin/sys_info.html:23 msgid "Limits" -msgstr "" +msgstr "Obergrenze" #: seahub/templates/sysadmin/sys_list_system.html:7 #: seahub/templates/sysadmin/sys_repo_admin.html:12 @@ -6475,7 +6475,7 @@ msgstr "Eine neue Server-Version %(v)s ist verfügbar." #: seahub/templates/sysadmin/sys_useradmin.html:99 #: seahub/templates/sysadmin/sys_useradmin.html:112 msgid "Hide" -msgstr "" +msgstr "Verbergen" #: seahub/templates/sysadmin/sys_useradmin_admins.html:21 msgid "Add admin" @@ -6499,15 +6499,15 @@ msgstr "Bitte geben Sie E-Mail-Adressen an oder wählen Sie welche aus." #: seahub/templates/sysadmin/sys_virus_scan_records.html:8 msgid "Virus Scan Records" -msgstr "" +msgstr "Virendefinitionsdatei" #: seahub/templates/sysadmin/sys_virus_scan_records.html:13 msgid "Virus File" -msgstr "" +msgstr "Virus Datei" #: seahub/templates/sysadmin/sys_virus_scan_records.html:25 msgid "Handled" -msgstr "" +msgstr "Erledigt" #: seahub/templates/sysadmin/user_activation_email.html:12 #, python-format @@ -7186,11 +7186,11 @@ msgstr "Übertragung erfolgreich." #: seahub/views/sysadmin.py:1548 msgid "Successfully deleted." -msgstr "" +msgstr "Erfolgreicht gelöscht" #: seahub/views/sysadmin.py:1551 msgid "Failed to delete, please try again later." -msgstr "" +msgstr "Löschen fehlgeschlagen, versuchen Sie es bitte später noch einmal." #: seahub/views/sysadmin.py:1582 #, python-format diff --git a/locale/de/LC_MESSAGES/djangojs.po b/locale/de/LC_MESSAGES/djangojs.po index ae9b21f97e..d05549cc6e 100644 --- a/locale/de/LC_MESSAGES/djangojs.po +++ b/locale/de/LC_MESSAGES/djangojs.po @@ -8,13 +8,14 @@ # Christian , 2015 # Lingtao Pan , 2015 # pieceofsoul , 2015 +# siljaj , 2015 msgid "" msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:27+0000\n" -"Last-Translator: Lingtao Pan \n" +"PO-Revision-Date: 2015-09-16 08:19+0000\n" +"Last-Translator: siljaj \n" "Language-Team: German (http://www.transifex.com/haiwen/seahub/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -212,11 +213,11 @@ msgstr "Möchten Sie die ausgewählten Dateien wirklich löschen?" #: static/scripts/app/views/dir.js:617 msgid "Move selected item(s) to:" -msgstr "" +msgstr "Ausgewählte Element(e) verschieben nach:" #: static/scripts/app/views/dir.js:617 msgid "Copy selected item(s) to:" -msgstr "" +msgstr "Ausgewählte Element(e) kopieren nach:" #: static/scripts/app/views/dir.js:662 static/scripts/app/views/dirent.js:331 msgid "Invalid destination path" diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index e0650ce405..550b2cde83 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -15,8 +15,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:40+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-15 17:53+0000\n" +"Last-Translator: Rodolfo Cossalter \n" "Language-Team: Spanish (http://www.transifex.com/haiwen/seahub/language/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1317,7 +1317,7 @@ msgstr "El nombre del grupo es demasiado largo (máximo 255 caracteres)" #: seahub/group/forms.py:37 msgid "" "Group name can only contain letters, numbers, blank, hyphen or underscore" -msgstr "" +msgstr "El nombre del grupo sólo puede tener letras, números, espacio, guión o guión bajo" #: seahub/group/forms.py:47 msgid "Verification message can't be empty" @@ -1374,7 +1374,7 @@ msgstr "Grupo abandonado exitosamente" msgid "" "Failed to rename group, group name can only contain letters, numbers, blank," " hyphen or underscore" -msgstr "" +msgstr "Fallo al renombrar el grupo, el nombre del grupo sólo puede tener letras, números, espacio, guión o guión bajo" #: seahub/group/views.py:332 #, python-format @@ -4815,7 +4815,7 @@ msgstr "Carpeta" #: seahub/templates/repo_recycle_view.html:19 #, python-format msgid "%(repo_name)s Trash" -msgstr "" +msgstr "%(repo_name)s Papelera" #: seahub/templates/repo_recycle_view.html:10 #: seahub/templates/repo_recycle_view.html:100 @@ -5122,7 +5122,7 @@ msgstr "auto detectar" msgid "" "You can use IE 10 or other browsers, for example, Firefox, to view it " "online." -msgstr "" +msgstr "Puede utilizar IE 10 u otros navegadores, por ejemplo Firefox, para verlo en línea." #: seahub/templates/view_file_svg.html:7 #: seahub/templates/snippets/file_content_js.html:63 @@ -5314,7 +5314,7 @@ msgstr "Agregar protección por contraseña" #: seahub/templates/snippets/file_share_popup.html:70 #, python-format msgid "(at least %(share_link_password_min_length)s characters)" -msgstr "" +msgstr "(al menos %(share_link_password_min_length)s caracteres)" #: seahub/templates/js/templates.html:367 #: seahub/templates/js/templates.html:422 @@ -5402,7 +5402,7 @@ msgstr "Seleccionar bibliotecas para compartir" #: seahub/templates/js/templates.html:837 msgid "Deleted library" -msgstr "" +msgstr "Biblioteca eliminada" #: seahub/templates/js/templates.html:844 #: seahub/templates/snippets/list_commit_detail.html:11 @@ -5720,7 +5720,7 @@ msgstr "Falló la conversión del documento " #: seahub/templates/snippets/office_convert_js.html:76 msgid "Failed to load this page." -msgstr "" +msgstr "Fallo al cargar esta página." #: seahub/templates/snippets/password_strength_js.html:42 msgid "too weak" @@ -5947,7 +5947,7 @@ msgstr "Registros" #: seahub/templates/sysadmin/base.html:43 msgid "Virus Scan" -msgstr "" +msgstr "Análisis de Virus" #: seahub/templates/sysadmin/repo_transfer_form.html:3 msgid "Transfer Library" @@ -6437,7 +6437,7 @@ msgstr "Está disponible un nuevo servidor versión %(v)s." #: seahub/templates/sysadmin/sys_useradmin.html:99 #: seahub/templates/sysadmin/sys_useradmin.html:112 msgid "Hide" -msgstr "" +msgstr "Ocultar" #: seahub/templates/sysadmin/sys_useradmin_admins.html:21 msgid "Add admin" @@ -6461,15 +6461,15 @@ msgstr "Ingresa o selecciona los correos." #: seahub/templates/sysadmin/sys_virus_scan_records.html:8 msgid "Virus Scan Records" -msgstr "" +msgstr "Registros de Análisis de Virus" #: seahub/templates/sysadmin/sys_virus_scan_records.html:13 msgid "Virus File" -msgstr "" +msgstr "Archivo de Virus" #: seahub/templates/sysadmin/sys_virus_scan_records.html:25 msgid "Handled" -msgstr "" +msgstr "Manejado" #: seahub/templates/sysadmin/user_activation_email.html:12 #, python-format @@ -7148,11 +7148,11 @@ msgstr "Transferida con éxito." #: seahub/views/sysadmin.py:1548 msgid "Successfully deleted." -msgstr "" +msgstr "Eliminado con éxito" #: seahub/views/sysadmin.py:1551 msgid "Failed to delete, please try again later." -msgstr "" +msgstr "Fallo al eliminar, por favor intente más tarde." #: seahub/views/sysadmin.py:1582 #, python-format diff --git a/locale/es/LC_MESSAGES/djangojs.po b/locale/es/LC_MESSAGES/djangojs.po index 5b759b894a..d3f11b9537 100644 --- a/locale/es/LC_MESSAGES/djangojs.po +++ b/locale/es/LC_MESSAGES/djangojs.po @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:04+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-15 17:54+0000\n" +"Last-Translator: Rodolfo Cossalter \n" "Language-Team: Spanish (http://www.transifex.com/haiwen/seahub/language/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -82,27 +82,27 @@ msgstr "Las contraseñas no coinciden" #: static/scripts/app/views/details.js:47 msgid "New files" -msgstr "" +msgstr "Archivos nuevos" #: static/scripts/app/views/details.js:53 msgid "Deleted files" -msgstr "" +msgstr "Archivos eliminados" #: static/scripts/app/views/details.js:59 msgid "Renamed or Moved files" -msgstr "" +msgstr "Archivos movidos o renombrados" #: static/scripts/app/views/details.js:65 msgid "Modified files" -msgstr "" +msgstr "Archivos modificados" #: static/scripts/app/views/details.js:71 msgid "New directories" -msgstr "" +msgstr "Carpetas nuevas" #: static/scripts/app/views/details.js:77 msgid "Deleted directories" -msgstr "" +msgstr "Carpetas eliminadas" #: static/scripts/app/views/dir.js:45 #: static/scripts/app/views/starred-file.js:29 diff --git a/locale/es_AR/LC_MESSAGES/django.po b/locale/es_AR/LC_MESSAGES/django.po index 1ecc6c920e..519c8f35dc 100644 --- a/locale/es_AR/LC_MESSAGES/django.po +++ b/locale/es_AR/LC_MESSAGES/django.po @@ -12,8 +12,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:40+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-15 16:40+0000\n" +"Last-Translator: Rodolfo Cossalter \n" "Language-Team: Spanish (Argentina) (http://www.transifex.com/haiwen/seahub/language/es_AR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1314,7 +1314,7 @@ msgstr "El nombre del grupo es demasiado largo (máximo 255 caracteres)" #: seahub/group/forms.py:37 msgid "" "Group name can only contain letters, numbers, blank, hyphen or underscore" -msgstr "" +msgstr "El nombre del grupo sólo puede tener letras, números, espacio, guión o guión bajo" #: seahub/group/forms.py:47 msgid "Verification message can't be empty" @@ -1371,7 +1371,7 @@ msgstr "Grupo abandonado exitosamente" msgid "" "Failed to rename group, group name can only contain letters, numbers, blank," " hyphen or underscore" -msgstr "" +msgstr "Fallo al renombrar el grupo, el nombre del grupo sólo puede tener letras, números, espacio, guión o guión bajo" #: seahub/group/views.py:332 #, python-format @@ -4812,7 +4812,7 @@ msgstr "Carpeta" #: seahub/templates/repo_recycle_view.html:19 #, python-format msgid "%(repo_name)s Trash" -msgstr "" +msgstr "%(repo_name)s Papelera" #: seahub/templates/repo_recycle_view.html:10 #: seahub/templates/repo_recycle_view.html:100 @@ -5119,7 +5119,7 @@ msgstr "auto detectar" msgid "" "You can use IE 10 or other browsers, for example, Firefox, to view it " "online." -msgstr "" +msgstr "Puede utilizar IE 10 u otros navegadores, por ejemplo Firefox, para verlo en línea." #: seahub/templates/view_file_svg.html:7 #: seahub/templates/snippets/file_content_js.html:63 @@ -5311,7 +5311,7 @@ msgstr "Agregar protección por contraseña" #: seahub/templates/snippets/file_share_popup.html:70 #, python-format msgid "(at least %(share_link_password_min_length)s characters)" -msgstr "" +msgstr "(al menos %(share_link_password_min_length)s caracteres)" #: seahub/templates/js/templates.html:367 #: seahub/templates/js/templates.html:422 @@ -5399,7 +5399,7 @@ msgstr "Seleccionar bibliotecas para compartir" #: seahub/templates/js/templates.html:837 msgid "Deleted library" -msgstr "" +msgstr "Biblioteca eliminada" #: seahub/templates/js/templates.html:844 #: seahub/templates/snippets/list_commit_detail.html:11 @@ -5717,7 +5717,7 @@ msgstr "Falló la conversión del documento " #: seahub/templates/snippets/office_convert_js.html:76 msgid "Failed to load this page." -msgstr "" +msgstr "Fallo al cargar esta página." #: seahub/templates/snippets/password_strength_js.html:42 msgid "too weak" @@ -5944,7 +5944,7 @@ msgstr "Registros" #: seahub/templates/sysadmin/base.html:43 msgid "Virus Scan" -msgstr "" +msgstr "Análisis de Virus" #: seahub/templates/sysadmin/repo_transfer_form.html:3 msgid "Transfer Library" @@ -6434,7 +6434,7 @@ msgstr "Está disponible un nuevo servidor versión %(v)s." #: seahub/templates/sysadmin/sys_useradmin.html:99 #: seahub/templates/sysadmin/sys_useradmin.html:112 msgid "Hide" -msgstr "" +msgstr "Ocultar" #: seahub/templates/sysadmin/sys_useradmin_admins.html:21 msgid "Add admin" @@ -6458,15 +6458,15 @@ msgstr "Ingresa o selecciona los correos." #: seahub/templates/sysadmin/sys_virus_scan_records.html:8 msgid "Virus Scan Records" -msgstr "" +msgstr "Registros de Análisis de Virus" #: seahub/templates/sysadmin/sys_virus_scan_records.html:13 msgid "Virus File" -msgstr "" +msgstr "Archivo de Virus" #: seahub/templates/sysadmin/sys_virus_scan_records.html:25 msgid "Handled" -msgstr "" +msgstr "Manejado" #: seahub/templates/sysadmin/user_activation_email.html:12 #, python-format @@ -7145,11 +7145,11 @@ msgstr "Transferida con éxito." #: seahub/views/sysadmin.py:1548 msgid "Successfully deleted." -msgstr "" +msgstr "Eliminado con éxito" #: seahub/views/sysadmin.py:1551 msgid "Failed to delete, please try again later." -msgstr "" +msgstr "Fallo al eliminar, por favor intente más tarde." #: seahub/views/sysadmin.py:1582 #, python-format diff --git a/locale/es_AR/LC_MESSAGES/djangojs.po b/locale/es_AR/LC_MESSAGES/djangojs.po index 5b2800a4aa..3bd053b95c 100644 --- a/locale/es_AR/LC_MESSAGES/djangojs.po +++ b/locale/es_AR/LC_MESSAGES/djangojs.po @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:04+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-15 16:44+0000\n" +"Last-Translator: Rodolfo Cossalter \n" "Language-Team: Spanish (Argentina) (http://www.transifex.com/haiwen/seahub/language/es_AR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -82,27 +82,27 @@ msgstr "Las contraseñas no coinciden" #: static/scripts/app/views/details.js:47 msgid "New files" -msgstr "" +msgstr "Archivos nuevos" #: static/scripts/app/views/details.js:53 msgid "Deleted files" -msgstr "" +msgstr "Archivos eliminados" #: static/scripts/app/views/details.js:59 msgid "Renamed or Moved files" -msgstr "" +msgstr "Archivos movidos o renombrados" #: static/scripts/app/views/details.js:65 msgid "Modified files" -msgstr "" +msgstr "Archivos modificados" #: static/scripts/app/views/details.js:71 msgid "New directories" -msgstr "" +msgstr "Carpetas nuevas" #: static/scripts/app/views/details.js:77 msgid "Deleted directories" -msgstr "" +msgstr "Carpetas eliminadas" #: static/scripts/app/views/dir.js:45 #: static/scripts/app/views/starred-file.js:29 diff --git a/locale/es_MX/LC_MESSAGES/django.po b/locale/es_MX/LC_MESSAGES/django.po index 126dcb6d45..4bb735bfdd 100644 --- a/locale/es_MX/LC_MESSAGES/django.po +++ b/locale/es_MX/LC_MESSAGES/django.po @@ -14,8 +14,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:40+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-15 17:55+0000\n" +"Last-Translator: Rodolfo Cossalter \n" "Language-Team: Spanish (Mexico) (http://www.transifex.com/haiwen/seahub/language/es_MX/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1316,7 +1316,7 @@ msgstr "El nombre del grupo es demasiado largo (máximo 255 caracteres)" #: seahub/group/forms.py:37 msgid "" "Group name can only contain letters, numbers, blank, hyphen or underscore" -msgstr "" +msgstr "El nombre del grupo sólo puede tener letras, números, espacio, guión o guión bajo" #: seahub/group/forms.py:47 msgid "Verification message can't be empty" @@ -1373,7 +1373,7 @@ msgstr "Grupo abandonado exitosamente" msgid "" "Failed to rename group, group name can only contain letters, numbers, blank," " hyphen or underscore" -msgstr "" +msgstr "Fallo al renombrar el grupo, el nombre del grupo sólo puede tener letras, números, espacio, guión o guión bajo" #: seahub/group/views.py:332 #, python-format @@ -4814,7 +4814,7 @@ msgstr "Directorio" #: seahub/templates/repo_recycle_view.html:19 #, python-format msgid "%(repo_name)s Trash" -msgstr "" +msgstr "%(repo_name)s Papelera" #: seahub/templates/repo_recycle_view.html:10 #: seahub/templates/repo_recycle_view.html:100 @@ -5121,7 +5121,7 @@ msgstr "auto detectar" msgid "" "You can use IE 10 or other browsers, for example, Firefox, to view it " "online." -msgstr "" +msgstr "Puede utilizar IE 10 u otros navegadores, por ejemplo Firefox, para verlo en línea." #: seahub/templates/view_file_svg.html:7 #: seahub/templates/snippets/file_content_js.html:63 @@ -5313,7 +5313,7 @@ msgstr "Agregar protección por contraseña" #: seahub/templates/snippets/file_share_popup.html:70 #, python-format msgid "(at least %(share_link_password_min_length)s characters)" -msgstr "" +msgstr "(al menos %(share_link_password_min_length)s caracteres)" #: seahub/templates/js/templates.html:367 #: seahub/templates/js/templates.html:422 @@ -5401,7 +5401,7 @@ msgstr "Seleccionar bibliotecas para compartir" #: seahub/templates/js/templates.html:837 msgid "Deleted library" -msgstr "" +msgstr "Biblioteca eliminada" #: seahub/templates/js/templates.html:844 #: seahub/templates/snippets/list_commit_detail.html:11 @@ -5719,7 +5719,7 @@ msgstr "Falló la conversión del documento " #: seahub/templates/snippets/office_convert_js.html:76 msgid "Failed to load this page." -msgstr "" +msgstr "Fallo al cargar esta página." #: seahub/templates/snippets/password_strength_js.html:42 msgid "too weak" @@ -5946,7 +5946,7 @@ msgstr "Registros" #: seahub/templates/sysadmin/base.html:43 msgid "Virus Scan" -msgstr "" +msgstr "Análisis de Virus" #: seahub/templates/sysadmin/repo_transfer_form.html:3 msgid "Transfer Library" @@ -6436,7 +6436,7 @@ msgstr "Está disponible un nuevo servidor versión %(v)s." #: seahub/templates/sysadmin/sys_useradmin.html:99 #: seahub/templates/sysadmin/sys_useradmin.html:112 msgid "Hide" -msgstr "" +msgstr "Ocultar" #: seahub/templates/sysadmin/sys_useradmin_admins.html:21 msgid "Add admin" @@ -6460,15 +6460,15 @@ msgstr "Ingrese o selecciones los correos." #: seahub/templates/sysadmin/sys_virus_scan_records.html:8 msgid "Virus Scan Records" -msgstr "" +msgstr "Registros de Análisis de Virus" #: seahub/templates/sysadmin/sys_virus_scan_records.html:13 msgid "Virus File" -msgstr "" +msgstr "Archivo de Virus" #: seahub/templates/sysadmin/sys_virus_scan_records.html:25 msgid "Handled" -msgstr "" +msgstr "Manejado" #: seahub/templates/sysadmin/user_activation_email.html:12 #, python-format @@ -7147,11 +7147,11 @@ msgstr "Transferido correctamente." #: seahub/views/sysadmin.py:1548 msgid "Successfully deleted." -msgstr "" +msgstr "Eliminado con éxito" #: seahub/views/sysadmin.py:1551 msgid "Failed to delete, please try again later." -msgstr "" +msgstr "Fallo al eliminar, por favor intente más tarde." #: seahub/views/sysadmin.py:1582 #, python-format diff --git a/locale/es_MX/LC_MESSAGES/djangojs.po b/locale/es_MX/LC_MESSAGES/djangojs.po index 3e057efefe..4b7fe5ce50 100644 --- a/locale/es_MX/LC_MESSAGES/djangojs.po +++ b/locale/es_MX/LC_MESSAGES/djangojs.po @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:04+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-15 17:56+0000\n" +"Last-Translator: Rodolfo Cossalter \n" "Language-Team: Spanish (Mexico) (http://www.transifex.com/haiwen/seahub/language/es_MX/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -82,27 +82,27 @@ msgstr "Las contraseñas no coinciden" #: static/scripts/app/views/details.js:47 msgid "New files" -msgstr "" +msgstr "Archivos nuevos" #: static/scripts/app/views/details.js:53 msgid "Deleted files" -msgstr "" +msgstr "Archivos eliminados" #: static/scripts/app/views/details.js:59 msgid "Renamed or Moved files" -msgstr "" +msgstr "Archivos movidos o renombrados" #: static/scripts/app/views/details.js:65 msgid "Modified files" -msgstr "" +msgstr "Archivos modificados" #: static/scripts/app/views/details.js:71 msgid "New directories" -msgstr "" +msgstr "Carpetas nuevas" #: static/scripts/app/views/details.js:77 msgid "Deleted directories" -msgstr "" +msgstr "Carpetas eliminadas" #: static/scripts/app/views/dir.js:45 #: static/scripts/app/views/starred-file.js:29 diff --git a/locale/is/LC_MESSAGES/django.po b/locale/is/LC_MESSAGES/django.po index 52759063c2..b160231abf 100644 --- a/locale/is/LC_MESSAGES/django.po +++ b/locale/is/LC_MESSAGES/django.po @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:40+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-22 14:14+0000\n" +"Last-Translator: Hjörleifur Sveinbjörnsson \n" "Language-Team: Icelandic (http://www.transifex.com/haiwen/seahub/language/is/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1313,7 +1313,7 @@ msgstr "Hópanafn er of langt (hámakr er 255 stafir)" #: seahub/group/forms.py:37 msgid "" "Group name can only contain letters, numbers, blank, hyphen or underscore" -msgstr "" +msgstr "Hópanafn má aðeins innihalda stafi, númer, stafabil, bandstrik eða undirstrik" #: seahub/group/forms.py:47 msgid "Verification message can't be empty" @@ -1370,7 +1370,7 @@ msgstr "Tókst að henda grúppu." msgid "" "Failed to rename group, group name can only contain letters, numbers, blank," " hyphen or underscore" -msgstr "" +msgstr "Mistókst að endurnefna hóp, hópanafn má aðeins innihalda stafi, númer, stafabil, bandstrik eða undirstrik" #: seahub/group/views.py:332 #, python-format @@ -2922,7 +2922,7 @@ msgstr "Sjálfgefið Safn" #: seahub/profile/templates/profile/set_profile.html:123 #: seahub/profile/templates/profile/set_profile.html:155 msgid "Delete Account" -msgstr "Eyða reikningi." +msgstr "Eyða reikningi" #: seahub/profile/templates/profile/set_profile.html:32 msgid "Avatar:" @@ -4811,7 +4811,7 @@ msgstr "Skráasafn" #: seahub/templates/repo_recycle_view.html:19 #, python-format msgid "%(repo_name)s Trash" -msgstr "" +msgstr "%(repo_name)s Rusl" #: seahub/templates/repo_recycle_view.html:10 #: seahub/templates/repo_recycle_view.html:100 @@ -5118,7 +5118,7 @@ msgstr "finna út sjálfvirkt" msgid "" "You can use IE 10 or other browsers, for example, Firefox, to view it " "online." -msgstr "" +msgstr "Þú getur notað IE 10 eða aðra vafra, til dæmis Firefox til að skoða þetta á netinu" #: seahub/templates/view_file_svg.html:7 #: seahub/templates/snippets/file_content_js.html:63 @@ -5310,7 +5310,7 @@ msgstr "Bæta við lykilorðavörn" #: seahub/templates/snippets/file_share_popup.html:70 #, python-format msgid "(at least %(share_link_password_min_length)s characters)" -msgstr "" +msgstr "(að minsta kosti %(share_link_password_min_length)s stafi)" #: seahub/templates/js/templates.html:367 #: seahub/templates/js/templates.html:422 @@ -5398,7 +5398,7 @@ msgstr "Veldu safn til að deila" #: seahub/templates/js/templates.html:837 msgid "Deleted library" -msgstr "" +msgstr "Eytt safn" #: seahub/templates/js/templates.html:844 #: seahub/templates/snippets/list_commit_detail.html:11 @@ -5716,7 +5716,7 @@ msgstr "Umskráning skjala mistókst." #: seahub/templates/snippets/office_convert_js.html:76 msgid "Failed to load this page." -msgstr "" +msgstr "Mistókst að hlaða inn þessari síðu." #: seahub/templates/snippets/password_strength_js.html:42 msgid "too weak" @@ -5943,7 +5943,7 @@ msgstr "Kladdar" #: seahub/templates/sysadmin/base.html:43 msgid "Virus Scan" -msgstr "" +msgstr "Veiruskönnun" #: seahub/templates/sysadmin/repo_transfer_form.html:3 msgid "Transfer Library" @@ -6050,16 +6050,16 @@ msgstr "Uppfæra í Sérútgáfu" #: seahub/templates/sysadmin/sys_info.html:23 #: seahub/templates/sysadmin/sys_info.html:32 msgid "Active Users" -msgstr "" +msgstr "Virkir Notendur" #: seahub/templates/sysadmin/sys_info.html:23 #: seahub/templates/sysadmin/sys_info.html:32 msgid "Total Users" -msgstr "" +msgstr "Allir Notendur" #: seahub/templates/sysadmin/sys_info.html:23 msgid "Limits" -msgstr "" +msgstr "Takmörk" #: seahub/templates/sysadmin/sys_list_system.html:7 #: seahub/templates/sysadmin/sys_repo_admin.html:12 @@ -6433,7 +6433,7 @@ msgstr "Ný útgáfa þjóns %(v)s er tiltæk." #: seahub/templates/sysadmin/sys_useradmin.html:99 #: seahub/templates/sysadmin/sys_useradmin.html:112 msgid "Hide" -msgstr "" +msgstr "Fela" #: seahub/templates/sysadmin/sys_useradmin_admins.html:21 msgid "Add admin" @@ -6457,15 +6457,15 @@ msgstr "Bættu við eða veldu netföng" #: seahub/templates/sysadmin/sys_virus_scan_records.html:8 msgid "Virus Scan Records" -msgstr "" +msgstr "Veiruskönnunaryfirlit" #: seahub/templates/sysadmin/sys_virus_scan_records.html:13 msgid "Virus File" -msgstr "" +msgstr "Veiruskrá" #: seahub/templates/sysadmin/sys_virus_scan_records.html:25 msgid "Handled" -msgstr "" +msgstr "Meðhöndlað" #: seahub/templates/sysadmin/user_activation_email.html:12 #, python-format @@ -7144,11 +7144,11 @@ msgstr "Tókst að flytja" #: seahub/views/sysadmin.py:1548 msgid "Successfully deleted." -msgstr "" +msgstr "Giftursamlega eytt" #: seahub/views/sysadmin.py:1551 msgid "Failed to delete, please try again later." -msgstr "" +msgstr "Mistókst að eyða, vinsamlegast reyndu aftur síðar." #: seahub/views/sysadmin.py:1582 #, python-format diff --git a/locale/is/LC_MESSAGES/djangojs.po b/locale/is/LC_MESSAGES/djangojs.po index 6af73e1bff..b00a25fbd3 100644 --- a/locale/is/LC_MESSAGES/djangojs.po +++ b/locale/is/LC_MESSAGES/djangojs.po @@ -9,8 +9,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:04+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-22 14:17+0000\n" +"Last-Translator: Hjörleifur Sveinbjörnsson \n" "Language-Team: Icelandic (http://www.transifex.com/haiwen/seahub/language/is/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -80,27 +80,27 @@ msgstr "Lykilorðin stemma ekki" #: static/scripts/app/views/details.js:47 msgid "New files" -msgstr "" +msgstr "Nýjar skrár" #: static/scripts/app/views/details.js:53 msgid "Deleted files" -msgstr "" +msgstr "Eyddar skrár" #: static/scripts/app/views/details.js:59 msgid "Renamed or Moved files" -msgstr "" +msgstr "Endurnefndi eða Færði skrár" #: static/scripts/app/views/details.js:65 msgid "Modified files" -msgstr "" +msgstr "Breyttar skrár" #: static/scripts/app/views/details.js:71 msgid "New directories" -msgstr "" +msgstr "Ný skráasöfn" #: static/scripts/app/views/details.js:77 msgid "Deleted directories" -msgstr "" +msgstr "Eydd skráasöfn" #: static/scripts/app/views/dir.js:45 #: static/scripts/app/views/starred-file.js:29 @@ -208,11 +208,11 @@ msgstr "Ertu viss um að þú viljir eyða þessum völdu atriðum?" #: static/scripts/app/views/dir.js:617 msgid "Move selected item(s) to:" -msgstr "" +msgstr "Færði merkt atriði í:" #: static/scripts/app/views/dir.js:617 msgid "Copy selected item(s) to:" -msgstr "" +msgstr "Afrita merkt atriði í:" #: static/scripts/app/views/dir.js:662 static/scripts/app/views/dirent.js:331 msgid "Invalid destination path" diff --git a/locale/nb_NO/LC_MESSAGES/djangojs.po b/locale/nb_NO/LC_MESSAGES/djangojs.po index 68416d7452..304cc802bf 100644 --- a/locale/nb_NO/LC_MESSAGES/djangojs.po +++ b/locale/nb_NO/LC_MESSAGES/djangojs.po @@ -3,13 +3,14 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Bjørn Tore Hoem , 2015 msgid "" msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:04+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-22 18:59+0000\n" +"Last-Translator: Bjørn Tore Hoem \n" "Language-Team: Norwegian Bokmål (Norway) (http://www.transifex.com/haiwen/seahub/language/nb_NO/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -35,106 +36,106 @@ msgstr "" #: static/scripts/app/views/share.js:615 static/scripts/app/views/share.js:673 #: static/scripts/app/views/sub-lib.js:79 msgid "Failed. Please check the network." -msgstr "" +msgstr "Feilet. Vennligst sjekk nettverkstilkoblingen." #: static/scripts/common.js:513 msgid "Search users or enter emails" -msgstr "" +msgstr "Søk i brukere eller skriv inn epostadresser" #: static/scripts/common.js:523 msgid "Please enter 1 or more character" -msgstr "" +msgstr "Vennligst skriv inn 1 eller flere bokstaver" #: static/scripts/common.js:524 msgid "No matches" -msgstr "" +msgstr "Ingen treff" #: static/scripts/common.js:525 msgid "Searching..." -msgstr "" +msgstr "Søker..." #: static/scripts/common.js:526 msgid "Loading failed" -msgstr "" +msgstr "Innlasting feilet" #: static/scripts/app/models/repo.js:30 msgid "Name is required" -msgstr "" +msgstr "Navn er påkrevd" #: static/scripts/app/models/repo.js:33 static/scripts/app/views/share.js:203 msgid "Please enter password" -msgstr "" +msgstr "Vennligst oppgi passord" #: static/scripts/app/models/repo.js:34 static/scripts/app/views/share.js:211 msgid "Please enter the password again" -msgstr "" +msgstr "Vennligst skriv inn passordet på nytt." #: static/scripts/app/models/repo.js:36 static/scripts/app/views/share.js:207 msgid "Password is too short" -msgstr "" +msgstr "Passordet er for kort" #: static/scripts/app/models/repo.js:38 static/scripts/app/views/share.js:215 msgid "Passwords don't match" -msgstr "" +msgstr "Passordene stemmer ikke overens" #: static/scripts/app/views/details.js:47 msgid "New files" -msgstr "" +msgstr "Nye filer" #: static/scripts/app/views/details.js:53 msgid "Deleted files" -msgstr "" +msgstr "Slettede filer" #: static/scripts/app/views/details.js:59 msgid "Renamed or Moved files" -msgstr "" +msgstr "Omdøpte eller flyttede filer" #: static/scripts/app/views/details.js:65 msgid "Modified files" -msgstr "" +msgstr "Modifiserte filer" #: static/scripts/app/views/details.js:71 msgid "New directories" -msgstr "" +msgstr "Nye mapper" #: static/scripts/app/views/details.js:77 msgid "Deleted directories" -msgstr "" +msgstr "Slettede kataloger" #: static/scripts/app/views/dir.js:45 #: static/scripts/app/views/starred-file.js:29 msgid "Close (Esc)" -msgstr "" +msgstr "Lukk (Esc)" #: static/scripts/app/views/dir.js:46 #: static/scripts/app/views/starred-file.js:30 msgid "Loading..." -msgstr "" +msgstr "Laster..." #: static/scripts/app/views/dir.js:49 #: static/scripts/app/views/starred-file.js:33 msgid "Previous (Left arrow key)" -msgstr "" +msgstr "Forrige (Venstre piltast)" #: static/scripts/app/views/dir.js:50 #: static/scripts/app/views/starred-file.js:34 msgid "Next (Right arrow key)" -msgstr "" +msgstr "Neste (Høyre piltast)" #: static/scripts/app/views/dir.js:51 #: static/scripts/app/views/starred-file.js:35 msgid "%curr% of %total%" -msgstr "" +msgstr "%curr% av %total%" #: static/scripts/app/views/dir.js:57 #: static/scripts/app/views/starred-file.js:41 msgid "Open in New Tab" -msgstr "" +msgstr "Åpne i ny fane" #: static/scripts/app/views/dir.js:60 #: static/scripts/app/views/starred-file.js:44 msgid "The image could not be loaded." -msgstr "" +msgstr "Bildet kunne ikke lastes inn." #: static/scripts/app/views/dir.js:124 #: static/scripts/app/views/group-side-nav.js:39 @@ -144,16 +145,16 @@ msgstr "" #: static/scripts/app/views/myhome-sub-repos.js:78 #: static/scripts/app/views/organization.js:123 msgid "Please check the network." -msgstr "" +msgstr "Vennligst sjekk nettverkstilkoblingen." #: static/scripts/app/views/dir.js:150 msgid "Password is required." -msgstr "" +msgstr "Passord er påkrevd." #: static/scripts/app/views/dir.js:317 static/scripts/app/views/dir.js.c:374 #: static/scripts/app/views/dirent.js:235 msgid "It is required." -msgstr "" +msgstr "Påkrevd" #: static/scripts/app/views/dir.js:332 static/scripts/app/views/dir.js.c:399 #: static/scripts/app/views/dirent.js:254 @@ -163,188 +164,188 @@ msgstr "" #: static/scripts/app/views/fileupload.js:382 #: static/scripts/app/views/myhome-sub-repos.js:152 msgid "Just now" -msgstr "" +msgstr "Nettopp" #: static/scripts/app/views/dir.js:380 msgid "Only an extension there, please input a name." -msgstr "" +msgstr "Dette er bare en endelse - vennligst tast inn et navn." #: static/scripts/app/views/dir.js:538 static/scripts/app/views/dir.js.c:667 msgid "Processing..." -msgstr "" +msgstr "Behandler..." #: static/scripts/app/views/dir.js:574 msgid "Successfully deleted %(name)s." -msgstr "" +msgstr "Vellykket sletting av %(name)s." #: static/scripts/app/views/dir.js:576 msgid "Successfully deleted %(name)s and 1 other item." -msgstr "" +msgstr "Vellykket sletting av %(name)s og 1 annet element." #: static/scripts/app/views/dir.js:578 msgid "Successfully deleted %(name)s and %(amount)s other items." -msgstr "" +msgstr "Vellykket sletting av %(name)s og %(amount)s andre elementer." #: static/scripts/app/views/dir.js:585 msgid "Failed to delete %(name)s." -msgstr "" +msgstr "Kunne ikke slette %(name)s." #: static/scripts/app/views/dir.js:587 msgid "Failed to delete %(name)s and 1 other item." -msgstr "" +msgstr "Mislykket sletting av %(name)s og 1 annet element." #: static/scripts/app/views/dir.js:589 msgid "Failed to delete %(name)s and %(amount)s other items." -msgstr "" +msgstr "Mislykket sletting av %(name)s og %(amount)s øvrige elementer." #: static/scripts/app/views/dir.js:602 msgid "Delete Items" -msgstr "" +msgstr "Slett elementer" #: static/scripts/app/views/dir.js:603 msgid "Are you sure you want to delete these selected items?" -msgstr "" +msgstr "Er du sikker på at du ønsker å slette de valgte elementene?" #: static/scripts/app/views/dir.js:617 msgid "Move selected item(s) to:" -msgstr "" +msgstr "Flytt valgte element(er) til:" #: static/scripts/app/views/dir.js:617 msgid "Copy selected item(s) to:" -msgstr "" +msgstr "Kopiere valgte element(er) til:" #: static/scripts/app/views/dir.js:662 static/scripts/app/views/dirent.js:331 msgid "Invalid destination path" -msgstr "" +msgstr "Ugyldig destinasjonsbane" #: static/scripts/app/views/dir.js:710 msgid "Successfully moved %(name)s." -msgstr "" +msgstr "Vellykket flytting av %(name)s." #: static/scripts/app/views/dir.js:712 msgid "Successfully moved %(name)s and 1 other item." -msgstr "" +msgstr "Vellykket sletting av %(name)s samt 1 annet element." #: static/scripts/app/views/dir.js:714 msgid "Successfully moved %(name)s and %(amount)s other items." -msgstr "" +msgstr "Vellykket flytting av %(name)s og %(amount)s øvrige elementer." #: static/scripts/app/views/dir.js:718 msgid "Successfully copied %(name)s." -msgstr "" +msgstr "Vellykket kopiering av %(name)s." #: static/scripts/app/views/dir.js:720 msgid "Successfully copied %(name)s and 1 other item." -msgstr "" +msgstr "Vellykket kopiering av %(name)s og 1 øvrig element." #: static/scripts/app/views/dir.js:722 msgid "Successfully copied %(name)s and %(amount)s other items." -msgstr "" +msgstr "Vellykket kopiering av %(name)s og %(amount)s øvrige elementer." #: static/scripts/app/views/dir.js:734 msgid "Internal error. Failed to move %(name)s and %(amount)s other item(s)." -msgstr "" +msgstr "Intern feil. Mislykket flytting av %(name)s og %(amount)s øvrige elementer." #: static/scripts/app/views/dir.js:736 msgid "Internal error. Failed to move %(name)s." -msgstr "" +msgstr "Intern feil. Mislykket forsøk på flytting av %(name)s." #: static/scripts/app/views/dir.js:740 msgid "Internal error. Failed to copy %(name)s and %(amount)s other item(s)." -msgstr "" +msgstr "Intern feil. Mislykket forsøk på kopiering av %(name)s og %(amount)s øvrige elementer." #: static/scripts/app/views/dir.js:742 msgid "Internal error. Failed to copy %(name)s." -msgstr "" +msgstr "Intern feil. Mislykket forsøk på kopiering av %(name)s." #: static/scripts/app/views/dir.js:782 msgid "Moving file %(index)s of %(total)s" -msgstr "" +msgstr "Flytter fil %(index)s av %(total)s" #: static/scripts/app/views/dir.js:782 msgid "Copying file %(index)s of %(total)s" -msgstr "" +msgstr "Kopierer fil %(index)s av %(total)s" #: static/scripts/app/views/dir.js:804 msgid "Failed to move %(name)s" -msgstr "" +msgstr "Kunne ikke flytte %(name)s" #: static/scripts/app/views/dir.js:804 msgid "Failed to copy %(name)s" -msgstr "" +msgstr "Kunne ikke kopiere %(name)s" #: static/scripts/app/views/dir.js:864 static/scripts/app/views/dirent.js:395 #: static/scripts/app/views/dirent.js:423 msgid "Canceled." -msgstr "" +msgstr "Avbrutt." #: static/scripts/app/views/dirent.js:47 msgid "locked by {placeholder}" -msgstr "" +msgstr "låst av {placeholder}" #: static/scripts/app/views/dirent.js:203 msgid "Successfully deleted %(name)s" -msgstr "" +msgstr "Vellykket sletting av %(name)s" #: static/scripts/app/views/dirent.js:216 msgid "Rename Directory" -msgstr "" +msgstr "Omdøpe mappe" #: static/scripts/app/views/dirent.js:216 msgid "Rename File" -msgstr "" +msgstr "Omdøpe fil" #: static/scripts/app/views/dirent.js:239 msgid "You have not renamed it." -msgstr "" +msgstr "Du har ikke omdøpt den." #: static/scripts/app/views/dirent.js:291 msgid "Move {placeholder} to:" -msgstr "" +msgstr "Flytt {placeholder} til:" #: static/scripts/app/views/dirent.js:291 msgid "Copy {placeholder} to:" -msgstr "" +msgstr "Kopiere {placeholder} til:" #: static/scripts/app/views/dirent.js:365 msgid "Moving %(name)s" -msgstr "" +msgstr "Flytter %(name)s" #: static/scripts/app/views/dirent.js:365 msgid "Copying %(name)s" -msgstr "" +msgstr "Kopierer %(name)s" #: static/scripts/app/views/dirent.js:381 msgid "Saving..." -msgstr "" +msgstr "Lagrer..." #: static/scripts/app/views/dirent.js:395 msgid "Failed." -msgstr "" +msgstr "Mislyktes." #: static/scripts/app/views/fileupload.js:10 msgid "File is too big" -msgstr "" +msgstr "Filen er for stor" #: static/scripts/app/views/fileupload.js:11 msgid "File is too small" -msgstr "" +msgstr "Filen er for liten" #: static/scripts/app/views/fileupload.js:12 msgid "Filetype not allowed" -msgstr "" +msgstr "Filtypen tillates ikke" #: static/scripts/app/views/fileupload.js:13 msgid "Max number of files exceeded" -msgstr "" +msgstr "Maksimalt antall filer er overskredet" #: static/scripts/app/views/fileupload.js:14 msgid "Uploaded bytes exceed file size" -msgstr "" +msgstr "Opplastet antall byte overskrider filstørrelsen" #: static/scripts/app/views/fileupload.js:15 msgid "Empty file upload result" -msgstr "" +msgstr "Resultatet er en tom filopplasting" #: static/scripts/app/views/fileupload.js:17 #: static/scripts/app/views/group.js:108 @@ -353,81 +354,81 @@ msgstr "" #: static/scripts/app/views/myhome-sub-repos.js:75 #: static/scripts/app/views/organization.js:120 msgid "Error" -msgstr "" +msgstr "Feil" #: static/scripts/app/views/fileupload.js:18 msgid "uploaded" -msgstr "" +msgstr "opplastet" #: static/scripts/app/views/fileupload.js:19 msgid "canceled" -msgstr "" +msgstr "avbrutt" #: static/scripts/app/views/fileupload.js:20 msgid "Start" -msgstr "" +msgstr "Start" #: static/scripts/app/views/fileupload.js:21 msgid "Cancel" -msgstr "" +msgstr "Avbryt" #: static/scripts/app/views/fileupload.js:22 msgid "Delete" -msgstr "" +msgstr "Slett" #: static/scripts/app/views/fileupload.js:46 msgid "File Uploading..." -msgstr "" +msgstr "Filen lastes opp..." #: static/scripts/app/views/fileupload.js:47 msgid "File Upload complete" -msgstr "" +msgstr "Filopplastingen er utført" #: static/scripts/app/views/fileupload.js:48 msgid "File Upload canceled" -msgstr "" +msgstr "Filoppastingen ble avbrutt" #: static/scripts/app/views/fileupload.js:49 msgid "File Upload failed" -msgstr "" +msgstr "Mislykket filopplasting" #: static/scripts/app/views/fileupload.js:187 msgid "Failed to get upload url" -msgstr "" +msgstr "Lykkes ikke med å få en opplastingslenke" #: static/scripts/app/views/fileupload.js:220 msgid "Failed to get update url" -msgstr "" +msgstr "Lykkes ikke med å få en oppdateringslenke" #: static/scripts/app/views/fileupload.js:231 msgid "Replace file {filename}?" -msgstr "" +msgstr "Erstatt fil {filename}?" #: static/scripts/app/views/fileupload.js:257 msgid "File is locked" -msgstr "" +msgstr "Filen er låst" #: static/scripts/app/views/folder-perm.js:49 msgid "Set {placeholder}'s permission" -msgstr "" +msgstr "Sett {placeholder}'s tillatelse" #: static/scripts/app/views/folder-perm.js:101 #: static/scripts/app/views/share.js:521 msgid "Select groups" -msgstr "" +msgstr "Velg frupper" #: static/scripts/app/views/folder-share-item.js:77 msgid "Edit failed" -msgstr "" +msgstr "Redigering feilet" #: static/scripts/app/views/folder-share-item.js:113 msgid "Delete failed" -msgstr "" +msgstr "Sletting feilet" #: static/scripts/app/views/group-repo.js:50 #: static/scripts/app/views/organization-repo.js:49 msgid "Successfully unshared {placeholder}" -msgstr "" +msgstr "Vellykket oppheving av deling for {placeholder}" #: static/scripts/app/views/group.js:106 #: static/scripts/app/views/myhome-repos.js:87 @@ -435,72 +436,72 @@ msgstr "" #: static/scripts/app/views/myhome-sub-repos.js:73 #: static/scripts/app/views/organization.js:118 msgid "Permission error" -msgstr "" +msgstr "Tilgangsfeil" #: static/scripts/app/views/myhome-sub-repos.js:115 msgid "You don't have any library at present." -msgstr "" +msgstr "Du har ingen biblioteker for øyeblikket." #: static/scripts/app/views/myhome-sub-repos.js:134 msgid "Please choose a directory" -msgstr "" +msgstr "Vennligst velg en mappe" #: static/scripts/app/views/repo.js:48 static/scripts/app/views/sub-lib.js:46 msgid "Really want to delete {lib_name}?" -msgstr "" +msgstr "Ønsker du virkelig å slette {lib_name}?" #: static/scripts/app/views/repo.js:71 static/scripts/app/views/sub-lib.js:69 msgid "Delete succeeded." -msgstr "" +msgstr "Vellykket sletting." #: static/scripts/app/views/share.js:63 msgid "Share {placeholder}" -msgstr "" +msgstr "Dele {placeholder}" #: static/scripts/app/views/share.js:132 msgid "Expired" -msgstr "" +msgstr "Utgått" #: static/scripts/app/views/share.js:160 static/scripts/app/views/share.js:176 msgid "Hide" -msgstr "" +msgstr "Skjul" #: static/scripts/app/views/share.js:173 msgid "Show" -msgstr "" +msgstr "Vis" #: static/scripts/app/views/share.js:228 msgid "Please enter days." -msgstr "" +msgstr "Tast inn antall dager." #: static/scripts/app/views/share.js:232 msgid "Please enter valid days" -msgstr "" +msgstr "Vennligst tast inn gyldige dager" #: static/scripts/app/views/share.js:319 msgid "Please input at least an email." -msgstr "" +msgstr "Vennligst legg inn minst en e-postadresse" #: static/scripts/app/views/share.js:336 msgid "Successfully sent to {placeholder}" -msgstr "" +msgstr "Vellykket sending til {placeholder}" #: static/scripts/app/views/share.js:340 msgid "Failed to send to {placeholder}" -msgstr "" +msgstr "Mislykket sending til {placeholder}" #: static/scripts/app/views/share.js:605 msgid "Failed to share to {placeholder}" -msgstr "" +msgstr "Mislykket deling til {placeholder}" #: static/scripts/app/views/share.js:613 static/scripts/app/views/share.js:671 msgid "Share failed" -msgstr "" +msgstr "Mislykket deling" #: static/scripts/app/views/shared-repo.js:26 msgid "Success" -msgstr "" +msgstr "Vellykket" #: static/scripts/app/views/starred-file-item.js:45 msgid "Successfully unstared {placeholder}" -msgstr "" +msgstr "Vellykket fjerning av stjernemarkering av {placeholder}" diff --git a/locale/nl_NL/LC_MESSAGES/djangojs.po b/locale/nl_NL/LC_MESSAGES/djangojs.po index f0a9dcedac..5c23aead77 100644 --- a/locale/nl_NL/LC_MESSAGES/djangojs.po +++ b/locale/nl_NL/LC_MESSAGES/djangojs.po @@ -3,13 +3,14 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Jorgen vd Meulen , 2015 msgid "" msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:04+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-14 08:14+0000\n" +"Last-Translator: Jorgen vd Meulen \n" "Language-Team: Dutch (Netherlands) (http://www.transifex.com/haiwen/seahub/language/nl_NL/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -35,35 +36,35 @@ msgstr "" #: static/scripts/app/views/share.js:615 static/scripts/app/views/share.js:673 #: static/scripts/app/views/sub-lib.js:79 msgid "Failed. Please check the network." -msgstr "" +msgstr "Mislukt. Controleer de netwerkverbinding." #: static/scripts/common.js:513 msgid "Search users or enter emails" -msgstr "" +msgstr "Zoek gebruikers of voer emails in" #: static/scripts/common.js:523 msgid "Please enter 1 or more character" -msgstr "" +msgstr "Voer 1 of meer tekens in." #: static/scripts/common.js:524 msgid "No matches" -msgstr "" +msgstr "Niet gevonden" #: static/scripts/common.js:525 msgid "Searching..." -msgstr "" +msgstr "Aan het zoeken..." #: static/scripts/common.js:526 msgid "Loading failed" -msgstr "" +msgstr "Ophalen mislukt" #: static/scripts/app/models/repo.js:30 msgid "Name is required" -msgstr "" +msgstr "Naam is verplicht" #: static/scripts/app/models/repo.js:33 static/scripts/app/views/share.js:203 msgid "Please enter password" -msgstr "" +msgstr "Voer het wachtwoord in" #: static/scripts/app/models/repo.js:34 static/scripts/app/views/share.js:211 msgid "Please enter the password again" @@ -71,7 +72,7 @@ msgstr "" #: static/scripts/app/models/repo.js:36 static/scripts/app/views/share.js:207 msgid "Password is too short" -msgstr "" +msgstr "Wachtwoord is te kort" #: static/scripts/app/models/repo.js:38 static/scripts/app/views/share.js:215 msgid "Passwords don't match" @@ -79,11 +80,11 @@ msgstr "" #: static/scripts/app/views/details.js:47 msgid "New files" -msgstr "" +msgstr "Nieuwe bestanden" #: static/scripts/app/views/details.js:53 msgid "Deleted files" -msgstr "" +msgstr "Verwijderde bestanden" #: static/scripts/app/views/details.js:59 msgid "Renamed or Moved files" @@ -99,12 +100,12 @@ msgstr "" #: static/scripts/app/views/details.js:77 msgid "Deleted directories" -msgstr "" +msgstr "Verwijderde mappen" #: static/scripts/app/views/dir.js:45 #: static/scripts/app/views/starred-file.js:29 msgid "Close (Esc)" -msgstr "" +msgstr "Sluiten (Esc)" #: static/scripts/app/views/dir.js:46 #: static/scripts/app/views/starred-file.js:30 @@ -114,22 +115,22 @@ msgstr "" #: static/scripts/app/views/dir.js:49 #: static/scripts/app/views/starred-file.js:33 msgid "Previous (Left arrow key)" -msgstr "" +msgstr "Vorige (linker pijltjestoets)" #: static/scripts/app/views/dir.js:50 #: static/scripts/app/views/starred-file.js:34 msgid "Next (Right arrow key)" -msgstr "" +msgstr "Volgende (rechter pijltjestoets)" #: static/scripts/app/views/dir.js:51 #: static/scripts/app/views/starred-file.js:35 msgid "%curr% of %total%" -msgstr "" +msgstr "%curr% van %total%" #: static/scripts/app/views/dir.js:57 #: static/scripts/app/views/starred-file.js:41 msgid "Open in New Tab" -msgstr "" +msgstr "Open in nieuw tabblad" #: static/scripts/app/views/dir.js:60 #: static/scripts/app/views/starred-file.js:44 @@ -288,11 +289,11 @@ msgstr "" #: static/scripts/app/views/dirent.js:216 msgid "Rename Directory" -msgstr "" +msgstr "Hernoem map" #: static/scripts/app/views/dirent.js:216 msgid "Rename File" -msgstr "" +msgstr "Hernoem Bestand" #: static/scripts/app/views/dirent.js:239 msgid "You have not renamed it." @@ -340,7 +341,7 @@ msgstr "" #: static/scripts/app/views/fileupload.js:14 msgid "Uploaded bytes exceed file size" -msgstr "" +msgstr "Geüploadde bytes overtreft bestandsgrootte" #: static/scripts/app/views/fileupload.js:15 msgid "Empty file upload result" @@ -499,7 +500,7 @@ msgstr "" #: static/scripts/app/views/shared-repo.js:26 msgid "Success" -msgstr "" +msgstr "Gelukt" #: static/scripts/app/views/starred-file-item.js:45 msgid "Successfully unstared {placeholder}" diff --git a/locale/pl/LC_MESSAGES/django.po b/locale/pl/LC_MESSAGES/django.po index c61c3ba795..65b8dcf6a4 100644 --- a/locale/pl/LC_MESSAGES/django.po +++ b/locale/pl/LC_MESSAGES/django.po @@ -13,8 +13,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:40+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-17 15:44+0000\n" +"Last-Translator: K.S. \n" "Language-Team: Polish (http://www.transifex.com/haiwen/seahub/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1319,7 +1319,7 @@ msgstr "Nazwa grupy jest zbyt długa (maksymalnie 255 znaków)" #: seahub/group/forms.py:37 msgid "" "Group name can only contain letters, numbers, blank, hyphen or underscore" -msgstr "" +msgstr "Nazwa grupy może zawierać wyłącznie litery, liczby, znaki puste, łączniki lub podkreślenia" #: seahub/group/forms.py:47 msgid "Verification message can't be empty" @@ -1376,7 +1376,7 @@ msgstr "Pomyślnie opuszczono grupę" msgid "" "Failed to rename group, group name can only contain letters, numbers, blank," " hyphen or underscore" -msgstr "" +msgstr "Nie udało się zmienić nazwy grupy, nazwa ta może zawierać tylko litery, liczby, znaki puste, łączniki lub podkreślenia" #: seahub/group/views.py:332 #, python-format @@ -4819,7 +4819,7 @@ msgstr "Katalog" #: seahub/templates/repo_recycle_view.html:19 #, python-format msgid "%(repo_name)s Trash" -msgstr "" +msgstr "Kosz %(repo_name)s" #: seahub/templates/repo_recycle_view.html:10 #: seahub/templates/repo_recycle_view.html:100 @@ -5126,7 +5126,7 @@ msgstr "wykryj automatycznie" msgid "" "You can use IE 10 or other browsers, for example, Firefox, to view it " "online." -msgstr "" +msgstr "Możesz skorzystać z IE 10 lub innej przeglądarki, np. Firefoksa, aby wyświetlić zawartość online." #: seahub/templates/view_file_svg.html:7 #: seahub/templates/snippets/file_content_js.html:63 @@ -5318,7 +5318,7 @@ msgstr "Dodaj ochronę hasła" #: seahub/templates/snippets/file_share_popup.html:70 #, python-format msgid "(at least %(share_link_password_min_length)s characters)" -msgstr "" +msgstr "(min. %(share_link_password_min_length)s znaków)" #: seahub/templates/js/templates.html:367 #: seahub/templates/js/templates.html:422 @@ -5406,7 +5406,7 @@ msgstr "Wybierz biblioteki do udostępnienia" #: seahub/templates/js/templates.html:837 msgid "Deleted library" -msgstr "" +msgstr "Usunięta biblioteka" #: seahub/templates/js/templates.html:844 #: seahub/templates/snippets/list_commit_detail.html:11 @@ -5724,7 +5724,7 @@ msgstr "Niepowodzenie konwersji dokumentu" #: seahub/templates/snippets/office_convert_js.html:76 msgid "Failed to load this page." -msgstr "" +msgstr "Nie udało się wczytać tej strony." #: seahub/templates/snippets/password_strength_js.html:42 msgid "too weak" @@ -6441,7 +6441,7 @@ msgstr "Nowa wersja %(v)s serwera jest dostępna." #: seahub/templates/sysadmin/sys_useradmin.html:99 #: seahub/templates/sysadmin/sys_useradmin.html:112 msgid "Hide" -msgstr "" +msgstr "Ukryj" #: seahub/templates/sysadmin/sys_useradmin_admins.html:21 msgid "Add admin" @@ -7152,11 +7152,11 @@ msgstr "Pomyślnie przeniesiono." #: seahub/views/sysadmin.py:1548 msgid "Successfully deleted." -msgstr "" +msgstr "Pomyślnie usunięto." #: seahub/views/sysadmin.py:1551 msgid "Failed to delete, please try again later." -msgstr "" +msgstr "Nie udało się usunąć, proszę spróbować później." #: seahub/views/sysadmin.py:1582 #, python-format diff --git a/locale/pl/LC_MESSAGES/djangojs.po b/locale/pl/LC_MESSAGES/djangojs.po index 7b46a3ec1a..4101d246d8 100644 --- a/locale/pl/LC_MESSAGES/djangojs.po +++ b/locale/pl/LC_MESSAGES/djangojs.po @@ -9,8 +9,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:04+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-17 15:37+0000\n" +"Last-Translator: K.S. \n" "Language-Team: Polish (http://www.transifex.com/haiwen/seahub/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -80,27 +80,27 @@ msgstr "Hasła nie są identyczne" #: static/scripts/app/views/details.js:47 msgid "New files" -msgstr "" +msgstr "Nowe pliki" #: static/scripts/app/views/details.js:53 msgid "Deleted files" -msgstr "" +msgstr "Usunięte pliki" #: static/scripts/app/views/details.js:59 msgid "Renamed or Moved files" -msgstr "" +msgstr "Pliki przeniesione lub o zmienionej nazwie" #: static/scripts/app/views/details.js:65 msgid "Modified files" -msgstr "" +msgstr "Zmodyfikowane pliki" #: static/scripts/app/views/details.js:71 msgid "New directories" -msgstr "" +msgstr "Nowe katalogi" #: static/scripts/app/views/details.js:77 msgid "Deleted directories" -msgstr "" +msgstr "Usunięte katalogi" #: static/scripts/app/views/dir.js:45 #: static/scripts/app/views/starred-file.js:29 diff --git a/locale/ru/LC_MESSAGES/django.po b/locale/ru/LC_MESSAGES/django.po index 6200020e12..906aaec9f0 100644 --- a/locale/ru/LC_MESSAGES/django.po +++ b/locale/ru/LC_MESSAGES/django.po @@ -24,8 +24,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:40+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-14 10:14+0000\n" +"Last-Translator: Vladimir \n" "Language-Team: Russian (http://www.transifex.com/haiwen/seahub/language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1334,7 +1334,7 @@ msgstr "Название группы слишком длинное (макси #: seahub/group/forms.py:37 msgid "" "Group name can only contain letters, numbers, blank, hyphen or underscore" -msgstr "" +msgstr "Имя группы может содержать только буквы, цифры, пробелы, дефисы или подчеркивания" #: seahub/group/forms.py:47 msgid "Verification message can't be empty" @@ -1391,7 +1391,7 @@ msgstr "Группа успешно удалена." msgid "" "Failed to rename group, group name can only contain letters, numbers, blank," " hyphen or underscore" -msgstr "" +msgstr "Не удалось переименовать группу, имя группы можно содержать только буквы, цифры, пробелы, дефисы или подчеркивания" #: seahub/group/views.py:332 #, python-format @@ -4836,7 +4836,7 @@ msgstr "Каталог" #: seahub/templates/repo_recycle_view.html:19 #, python-format msgid "%(repo_name)s Trash" -msgstr "" +msgstr "%(repo_name)s Корзина" #: seahub/templates/repo_recycle_view.html:10 #: seahub/templates/repo_recycle_view.html:100 @@ -5143,7 +5143,7 @@ msgstr "автоопределение" msgid "" "You can use IE 10 or other browsers, for example, Firefox, to view it " "online." -msgstr "" +msgstr "Вы можете использовать IE 10 или другие браузеры, например, Firefox, для просмотра онлайн." #: seahub/templates/view_file_svg.html:7 #: seahub/templates/snippets/file_content_js.html:63 @@ -5335,7 +5335,7 @@ msgstr "Защитить паролем" #: seahub/templates/snippets/file_share_popup.html:70 #, python-format msgid "(at least %(share_link_password_min_length)s characters)" -msgstr "" +msgstr "(не менее %(share_link_password_min_length)s символов)" #: seahub/templates/js/templates.html:367 #: seahub/templates/js/templates.html:422 @@ -5423,7 +5423,7 @@ msgstr "Выберите библиотеки, общий доступ к кот #: seahub/templates/js/templates.html:837 msgid "Deleted library" -msgstr "" +msgstr "Удаленная библиотека" #: seahub/templates/js/templates.html:844 #: seahub/templates/snippets/list_commit_detail.html:11 @@ -5741,7 +5741,7 @@ msgstr "Ошибка конвертации." #: seahub/templates/snippets/office_convert_js.html:76 msgid "Failed to load this page." -msgstr "" +msgstr "Не удалось загрузить страницу." #: seahub/templates/snippets/password_strength_js.html:42 msgid "too weak" @@ -5968,7 +5968,7 @@ msgstr "Журналы" #: seahub/templates/sysadmin/base.html:43 msgid "Virus Scan" -msgstr "" +msgstr "Антивирус" #: seahub/templates/sysadmin/repo_transfer_form.html:3 msgid "Transfer Library" @@ -6458,7 +6458,7 @@ msgstr "Новая версия сервера %(v)s доступна." #: seahub/templates/sysadmin/sys_useradmin.html:99 #: seahub/templates/sysadmin/sys_useradmin.html:112 msgid "Hide" -msgstr "" +msgstr "Скрыть" #: seahub/templates/sysadmin/sys_useradmin_admins.html:21 msgid "Add admin" @@ -6482,15 +6482,15 @@ msgstr "Пожалуйста, введите или выберите email." #: seahub/templates/sysadmin/sys_virus_scan_records.html:8 msgid "Virus Scan Records" -msgstr "" +msgstr "Отчеты антивируса" #: seahub/templates/sysadmin/sys_virus_scan_records.html:13 msgid "Virus File" -msgstr "" +msgstr "Файл вируса" #: seahub/templates/sysadmin/sys_virus_scan_records.html:25 msgid "Handled" -msgstr "" +msgstr "Обработано" #: seahub/templates/sysadmin/user_activation_email.html:12 #, python-format @@ -7169,11 +7169,11 @@ msgstr "Успешно передана." #: seahub/views/sysadmin.py:1548 msgid "Successfully deleted." -msgstr "" +msgstr "Успешно удалено." #: seahub/views/sysadmin.py:1551 msgid "Failed to delete, please try again later." -msgstr "" +msgstr "Не удалось удалить, пожалуйста, попробуйте позже." #: seahub/views/sysadmin.py:1582 #, python-format diff --git a/locale/ru/LC_MESSAGES/djangojs.po b/locale/ru/LC_MESSAGES/djangojs.po index 59f542a166..cb67baf506 100644 --- a/locale/ru/LC_MESSAGES/djangojs.po +++ b/locale/ru/LC_MESSAGES/djangojs.po @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: seahub\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-14 15:39+0800\n" -"PO-Revision-Date: 2015-09-14 07:04+0000\n" -"Last-Translator: zheng xie \n" +"PO-Revision-Date: 2015-09-14 09:56+0000\n" +"Last-Translator: Vladimir \n" "Language-Team: Russian (http://www.transifex.com/haiwen/seahub/language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -82,27 +82,27 @@ msgstr "Пароли не совпадают" #: static/scripts/app/views/details.js:47 msgid "New files" -msgstr "" +msgstr "Новые файлы" #: static/scripts/app/views/details.js:53 msgid "Deleted files" -msgstr "" +msgstr "Удаленные файлы" #: static/scripts/app/views/details.js:59 msgid "Renamed or Moved files" -msgstr "" +msgstr "Переименованные или перемещенные файлы" #: static/scripts/app/views/details.js:65 msgid "Modified files" -msgstr "" +msgstr "Измененные файлы" #: static/scripts/app/views/details.js:71 msgid "New directories" -msgstr "" +msgstr "Новые каталоги" #: static/scripts/app/views/details.js:77 msgid "Deleted directories" -msgstr "" +msgstr "Удаленные каталоги" #: static/scripts/app/views/dir.js:45 #: static/scripts/app/views/starred-file.js:29 diff --git a/media/js/base.js b/media/js/base.js index 97c1184147..c2c7b82540 100644 --- a/media/js/base.js +++ b/media/js/base.js @@ -73,7 +73,9 @@ $(function() { var link_href = $(this).attr('href'); $.ajax({ url: notice_list.data('url') + '?notice_id=' + e(notice_id), - dataType:'json', + type: 'POST', + dataType: 'json', + beforeSend: prepareCSRFToken, success: function(data) { location.href = link_href; }, @@ -116,7 +118,9 @@ $(function() { var url = $(this).data('url'); $.ajax({ url: url, + type: 'POST', dataType: 'json', + beforeSend: prepareCSRFToken, success: function() { $('.num', msg_ct).html(0).addClass('hide'); document.title = orig_doc_title; diff --git a/seahub/api2/endpoints/account.py b/seahub/api2/endpoints/account.py new file mode 100644 index 0000000000..61d4f2129e --- /dev/null +++ b/seahub/api2/endpoints/account.py @@ -0,0 +1,230 @@ +import logging + +from django.utils import timezone +from rest_framework import status +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAdminUser +from rest_framework.response import Response +from rest_framework.reverse import reverse +from rest_framework.throttling import UserRateThrottle +from rest_framework.views import APIView +import seaserv +from seaserv import seafile_api, ccnet_threaded_rpc + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.serializers import AccountSerializer +from seahub.api2.utils import api_error, to_python_boolean +from seahub.base.accounts import User +from seahub.profile.models import Profile +from seahub.utils import is_valid_username +from seahub.views import get_owned_repo_list + +logger = logging.getLogger(__name__) +json_content_type = 'application/json; charset=utf-8' + + +class Account(APIView): + """Query/Add/Delete a specific account. + Administator permission is required. + """ + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAdminUser, ) + throttle_classes = (UserRateThrottle, ) + + def get(self, request, email, format=None): + if not is_valid_username(email): + return api_error(status.HTTP_404_NOT_FOUND, 'User not found.') + + # query account info + try: + user = User.objects.get(email=email) + except User.DoesNotExist: + return api_error(status.HTTP_404_NOT_FOUND, 'User not found.') + + info = {} + info['email'] = user.email + info['id'] = user.id + info['is_staff'] = user.is_staff + info['is_active'] = user.is_active + info['create_time'] = user.ctime + info['total'] = seafile_api.get_user_quota(email) + info['usage'] = seafile_api.get_user_self_usage(email) + + return Response(info) + + def _update_account_profile(self, request, email): + name = request.DATA.get("name", None) + note = request.DATA.get("note", None) + + if name is None and note is None: + return + + profile = Profile.objects.get_profile_by_user(email) + if profile is None: + profile = Profile(user=email) + + if name is not None: + # if '/' in name: + # return api_error(status.HTTP_400_BAD_REQUEST, "Nickname should not include '/'") + profile.nickname = name + + if note is not None: + profile.intro = note + + profile.save() + + def _update_account_quota(self, request, email): + storage = request.DATA.get("storage", None) + sharing = request.DATA.get("sharing", None) + + if storage is None and sharing is None: + return + + if storage is not None: + seafile_api.set_user_quota(email, int(storage)) + + if sharing is not None: + seafile_api.set_user_share_quota(email, int(sharing)) + + def _create_account(self, request, email): + copy = request.DATA.copy() + copy['email'] = email + serializer = AccountSerializer(data=copy) + if serializer.is_valid(): + try: + user = User.objects.create_user(serializer.object['email'], + serializer.object['password'], + serializer.object['is_staff'], + serializer.object['is_active']) + except User.DoesNotExist as e: + logger.error(e) + return api_error(status.HTTP_403_FORBIDDEN, + 'Fail to add user.') + + self._update_account_profile(request, user.username) + + resp = Response('success', status=status.HTTP_201_CREATED) + resp['Location'] = reverse('api2-account', args=[email]) + return resp + else: + return api_error(status.HTTP_400_BAD_REQUEST, serializer.errors) + + def _update_account(self, request, user): + password = request.DATA.get("password", None) + is_staff = request.DATA.get("is_staff", None) + if is_staff is not None: + try: + is_staff = to_python_boolean(is_staff) + except ValueError: + return api_error(status.HTTP_400_BAD_REQUEST, + '%s is not a valid value' % is_staff) + + is_active = request.DATA.get("is_active", None) + if is_active is not None: + try: + is_active = to_python_boolean(is_active) + except ValueError: + return api_error(status.HTTP_400_BAD_REQUEST, + '%s is not a valid value' % is_active) + + if password is not None: + user.set_password(password) + + if is_staff is not None: + user.is_staff = is_staff + + if is_active is not None: + user.is_active = is_active + + result_code = user.save() + if result_code == -1: + return api_error(status.HTTP_403_FORBIDDEN, + 'Fail to update user.') + + self._update_account_profile(request, user.username) + + try: + self._update_account_quota(request, user.username) + except SearpcError as e: + logger.error(e) + return api_error(HTTP_520_OPERATION_FAILED, 'Failed to set account quota') + + is_trial = request.DATA.get("is_trial", None) + if is_trial is not None: + try: + from seahub_extra.trialaccount.models import TrialAccount + except ImportError: + pass + else: + try: + is_trial = to_python_boolean(is_trial) + except ValueError: + return api_error(status.HTTP_400_BAD_REQUEST, + '%s is not a valid value' % is_trial) + + if is_trial is True: + expire_date = timezone.now() + relativedelta(days=7) + TrialAccount.object.create_or_update(user.username, + expire_date) + else: + TrialAccount.objects.filter(user_or_org=user.username).delete() + + return Response('success') + + def post(self, request, email, format=None): + # migrate an account's repos and groups to an exist account + if not is_valid_username(email): + return api_error(status.HTTP_404_NOT_FOUND, 'User not found.') + + op = request.DATA.get('op', '').lower() + if op == 'migrate': + from_user = email + to_user = request.DATA.get('to_user', '') + if not is_valid_username(to_user): + return api_error(status.HTTP_400_BAD_REQUEST, '%s is not valid email.' % to_user) + + try: + user2 = User.objects.get(email=to_user) + except User.DoesNotExist: + return api_error(status.HTTP_400_BAD_REQUEST, '%s does not exist.' % to_user) + + # transfer owned repos to new user + for r in seafile_api.get_owned_repo_list(from_user): + seafile_api.set_repo_owner(r.id, user2.username) + + # transfer joined groups to new user + for g in seaserv.get_personal_groups_by_user(from_user): + if not seaserv.is_group_user(g.id, user2.username): + # add new user to the group on behalf of the group creator + ccnet_threaded_rpc.group_add_member(g.id, g.creator_name, + to_user) + + if from_user == g.creator_name: + ccnet_threaded_rpc.set_group_creator(g.id, to_user) + + return Response("success") + else: + return api_error(status.HTTP_400_BAD_REQUEST, 'Op can only be migrate') + + def put(self, request, email, format=None): + if not is_valid_username(email): + return api_error(status.HTTP_404_NOT_FOUND, 'User not found.') + + try: + user = User.objects.get(email=email) + return self._update_account(request, user) + except User.DoesNotExist: + return self._create_account(request, email) + + def delete(self, request, email, format=None): + if not is_valid_username(email): + return api_error(status.HTTP_404_NOT_FOUND, 'User not found.') + + # delete account + try: + user = User.objects.get(email=email) + user.delete() + return Response("success") + except User.DoesNotExist: + resp = Response("success", status=status.HTTP_202_ACCEPTED) + return resp diff --git a/seahub/api2/urls.py b/seahub/api2/urls.py index d0a5fb978a..2b128f1886 100644 --- a/seahub/api2/urls.py +++ b/seahub/api2/urls.py @@ -4,7 +4,7 @@ from .views import * from .views_misc import ServerInfoView from .views_auth import LogoutDeviceView, ClientLoginTokenView from .endpoints.dir_shared_items import DirSharedItemsEndpoint - +from .endpoints.account import Account urlpatterns = patterns('', url(r'^ping/$', Ping.as_view()), diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 015f3870c3..0ed7bfc139 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -227,176 +227,6 @@ class Accounts(APIView): return Response(accounts_json) -class Account(APIView): - """Query/Add/Delete a specific account. - Administator permission is required. - """ - authentication_classes = (TokenAuthentication, ) - permission_classes = (IsAdminUser, ) - throttle_classes = (UserRateThrottle, ) - - def get(self, request, email, format=None): - if not is_valid_username(email): - return api_error(status.HTTP_404_NOT_FOUND, 'User not found.') - - # query account info - try: - user = User.objects.get(email=email) - except User.DoesNotExist: - return api_error(status.HTTP_404_NOT_FOUND, 'User not found.') - - info = {} - info['email'] = user.email - info['id'] = user.id - info['is_staff'] = user.is_staff - info['is_active'] = user.is_active - info['create_time'] = user.ctime - info['total'] = seafile_api.get_user_quota(email) - info['usage'] = seafile_api.get_user_self_usage(email) - - return Response(info) - - def _update_account_profile(self, request, email): - name = request.DATA.get("name", None) - note = request.DATA.get("note", None) - - if name is None and note is None: - return - - profile = Profile.objects.get_profile_by_user(email) - if profile is None: - profile = Profile(user=email) - - if name is not None: - # if '/' in name: - # return api_error(status.HTTP_400_BAD_REQUEST, "Nickname should not include '/'") - profile.nickname = name - - if note is not None: - profile.intro = note - - profile.save() - - def _update_account_quota(self, request, email): - storage = request.DATA.get("storage", None) - sharing = request.DATA.get("sharing", None) - - if storage is None and sharing is None: - return - - if storage is not None: - seafile_api.set_user_quota(email, int(storage)) - - if sharing is not None: - seafile_api.set_user_share_quota(email, int(sharing)) - - def _create_account(self, request, email): - copy = request.DATA.copy() - copy['email'] = email - serializer = AccountSerializer(data=copy) - if serializer.is_valid(): - try: - user = User.objects.create_user(serializer.object['email'], - serializer.object['password'], - serializer.object['is_staff'], - serializer.object['is_active']) - except User.DoesNotExist as e: - logger.error(e) - return api_error(status.HTTP_403_FORBIDDEN, - 'Fail to add user.') - - self._update_account_profile(request, user.username) - - resp = Response('success', status=status.HTTP_201_CREATED) - resp['Location'] = reverse('api2-account', args=[email]) - return resp - else: - return api_error(status.HTTP_400_BAD_REQUEST, serializer.errors) - - def _update_account(self, request, user): - password = request.DATA.get("password", None) - is_staff = request.DATA.get("is_staff", None) - if is_staff is not None: - try: - is_staff = to_python_boolean(is_staff) - except ValueError: - return api_error(status.HTTP_400_BAD_REQUEST, - '%s is not a valid value' % is_staff) - - is_active = request.DATA.get("is_active", None) - if is_active is not None: - try: - is_active = to_python_boolean(is_active) - except ValueError: - return api_error(status.HTTP_400_BAD_REQUEST, - '%s is not a valid value' % is_active) - - if password is not None: - user.set_password(password) - - if is_staff is not None: - user.is_staff = is_staff - - if is_active is not None: - user.is_active = is_active - - result_code = user.save() - if result_code == -1: - return api_error(status.HTTP_403_FORBIDDEN, - 'Fail to update user.') - - self._update_account_profile(request, user.username) - - try: - self._update_account_quota(request, user.username) - except SearpcError as e: - logger.error(e) - return api_error(HTTP_520_OPERATION_FAILED, 'Failed to set account quota') - - is_trial = request.DATA.get("is_trial", None) - if is_trial is not None: - try: - from seahub_extra.trialaccount.models import TrialAccount - except ImportError: - pass - else: - try: - is_trial = to_python_boolean(is_trial) - except ValueError: - return api_error(status.HTTP_400_BAD_REQUEST, - '%s is not a valid value' % is_trial) - - if is_trial is True: - expire_date = timezone.now() + relativedelta(days=7) - TrialAccount.object.create_or_update(user.username, - expire_date) - else: - TrialAccount.objects.filter(user_or_org=user.username).delete() - - return Response('success') - - def put(self, request, email, format=None): - if not is_valid_username(email): - return api_error(status.HTTP_404_NOT_FOUND, 'User not found.') - - try: - user = User.objects.get(email=email) - return self._update_account(request, user) - except User.DoesNotExist: - return self._create_account(request, email) - - def delete(self, request, email, format=None): - if not is_valid_username(email): - return api_error(status.HTTP_404_NOT_FOUND, 'User not found.') - - # delete account - try: - user = User.objects.get(email=email) - user.delete() - return Response("success") - except User.DoesNotExist: - resp = Response("success", status=status.HTTP_202_ACCEPTED) - return resp class AccountInfo(APIView): """ Show account info. @@ -1321,7 +1151,34 @@ class UpdateBlksLinkView(APIView): url = gen_file_upload_url(token, 'update-blks-api') return Response(url) -def get_dir_entrys_by_id(request, repo, path, dir_id): +def get_dir_recursively(username, repo_id, path, all_dirs): + path_id = seafile_api.get_dir_id_by_path(repo_id, path) + dirs = seafserv_threaded_rpc.list_dir_with_perm(repo_id, path, + path_id, username, -1, -1) + + for dirent in dirs: + if stat.S_ISDIR(dirent.mode): + entry = {} + entry["type"] = 'dir' + entry["parent_dir"] = path + entry["id"] = dirent.obj_id + entry["name"] = dirent.obj_name + entry["mtime"] = dirent.mtime + entry["permission"] = dirent.permission + all_dirs.append(entry) + + sub_path = posixpath.join(path, dirent.obj_name) + get_dir_recursively(username, repo_id, sub_path, all_dirs) + + return all_dirs + +def get_dir_entrys_by_id(request, repo, path, dir_id, request_type=None): + """ Get dirents in a dir + + if request_type is 'f', only return file list, + if request_type is 'd', only return dir list, + else, return both. + """ username = request.user.username try: dirs = seafserv_threaded_rpc.list_dir_with_perm(repo.id, path, dir_id, @@ -1366,7 +1223,13 @@ def get_dir_entrys_by_id(request, repo, path, dir_id): dir_list.sort(lambda x, y: cmp(x['name'].lower(), y['name'].lower())) file_list.sort(lambda x, y: cmp(x['name'].lower(), y['name'].lower())) - dentrys = dir_list + file_list + + if request_type == 'f': + dentrys = file_list + elif request_type == 'd': + dentrys = dir_list + else: + dentrys = dir_list + file_list response = HttpResponse(json.dumps(dentrys), status=200, content_type=json_content_type) @@ -1401,10 +1264,12 @@ def get_shared_link(request, repo_id, path): settings.SITE_ROOT, token) return Response(file_shared_link) -def get_repo_file(request, repo_id, file_id, file_name, op): +def get_repo_file(request, repo_id, file_id, file_name, op, use_onetime=True): if op == 'download': token = seafile_api.get_fileserver_access_token(repo_id, file_id, op, - request.user.username) + request.user.username, + use_onetime) + redirect_url = gen_file_get_url(token, file_name) response = HttpResponse(json.dumps(redirect_url), status=200, content_type=json_content_type) @@ -1736,10 +1601,18 @@ class FileView(APIView): file_name = os.path.basename(path) op = request.GET.get('op', 'download') - return get_repo_file(request, repo_id, file_id, file_name, op) + + reuse = request.GET.get('reuse', '0') + if reuse not in ('1', '0'): + return api_error(status.HTTP_400_BAD_REQUEST, + "If you want to reuse file server access token for download file, you should set 'reuse' argument as '1'.") + + use_onetime = False if reuse == '1' else True + return get_repo_file(request, repo_id, file_id, + file_name, op, use_onetime) def post(self, request, repo_id, format=None): - # rename, move or create file + # rename, move, copy or create file repo = get_repo(repo_id) if not repo: return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.') @@ -1848,6 +1721,57 @@ class FileView(APIView): uri = reverse('FileView', args=[dst_repo_id], request=request) resp['Location'] = uri + '?p=' + quote(dst_dir_utf8) + quote(new_filename_utf8) return resp + + elif operation.lower() == 'copy': + src_repo_id = repo_id + src_dir = os.path.dirname(path) + src_dir_utf8 = src_dir.encode('utf-8') + dst_repo_id = request.POST.get('dst_repo', '') + dst_dir = request.POST.get('dst_dir', '') + dst_dir_utf8 = dst_dir.encode('utf-8') + + if dst_dir[-1] != '/': # Append '/' to the end of directory if necessary + dst_dir += '/' + + if not (dst_repo_id and dst_dir): + return api_error(status.HTTP_400_BAD_REQUEST, 'Missing arguments.') + + if src_repo_id == dst_repo_id and src_dir == dst_dir: + return Response('success', status=status.HTTP_200_OK) + + # check src folder permission + if check_folder_permission(request, repo_id, path) is None: + return api_error(status.HTTP_403_FORBIDDEN, + 'You do not have permission to copy file.') + + # check dst folder permission + if check_folder_permission(request, dst_repo_id, dst_dir) != 'rw': + return api_error(status.HTTP_403_FORBIDDEN, + 'You do not have permission to copy file.') + + filename = os.path.basename(path) + filename_utf8 = filename.encode('utf-8') + new_filename_utf8 = check_filename_with_rename_utf8(dst_repo_id, + dst_dir, + filename) + try: + seafile_api.copy_file(src_repo_id, src_dir_utf8, + filename_utf8, dst_repo_id, + dst_dir_utf8, new_filename_utf8, + username, 0, synchronous=1) + except SearpcError as e: + logger.error(e) + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, + "SearpcError:" + e.msg) + + if request.GET.get('reloaddir', '').lower() == 'true': + return reloaddir(request, dst_repo, dst_dir) + else: + resp = Response('success', status=status.HTTP_200_OK) + uri = reverse('FileView', args=[dst_repo_id], request=request) + resp['Location'] = uri + '?p=' + quote(dst_dir_utf8) + quote(new_filename_utf8) + return resp + elif operation.lower() == 'create': if check_folder_permission(request, repo_id, parent_dir) != 'rw': return api_error(status.HTTP_403_FORBIDDEN, @@ -2240,7 +2164,28 @@ class DirView(APIView): response["oid"] = dir_id return response else: - return get_dir_entrys_by_id(request, repo, path, dir_id) + request_type = request.GET.get('t', None) + if request_type and request_type not in ('f', 'd'): + return api_error(status.HTTP_400_BAD_REQUEST, + "'t'(type) should be 'f' or 'd'.") + + if request_type == 'd': + recursive = request.GET.get('recursive', '0') + if recursive not in ('1', '0'): + return api_error(status.HTTP_400_BAD_REQUEST, + "If you want to get recursive dir entries, you should set 'recursive' argument as '1'.") + + if recursive == '1': + username = request.user.username + dir_list = get_dir_recursively(username, repo_id, path, []) + dir_list.sort(lambda x, y: cmp(x['name'].lower(), y['name'].lower())) + response = HttpResponse(json.dumps(dir_list), status=200, + content_type=json_content_type) + response["oid"] = dir_id + response["dir_perm"] = seafile_api.check_permission_by_path(repo_id, path, username) + return response + + return get_dir_entrys_by_id(request, repo, path, dir_id, request_type) def post(self, request, repo_id, format=None): # new dir diff --git a/seahub/auth/views.py b/seahub/auth/views.py index f279ed783d..ec337c4e28 100644 --- a/seahub/auth/views.py +++ b/seahub/auth/views.py @@ -82,20 +82,20 @@ def _incr_login_faied_attempts(username=None, ip=None): Returns new value of failed attempts. """ timeout = settings.LOGIN_ATTEMPT_TIMEOUT - username_attempts = 0 - ip_attempts = 0 + username_attempts = 1 + ip_attempts = 1 if username: try: username_attempts = cache.incr(LOGIN_ATTEMPT_PREFIX + username) except ValueError: - cache.set(LOGIN_ATTEMPT_PREFIX + username, 0, timeout) + cache.set(LOGIN_ATTEMPT_PREFIX + username, 1, timeout) if ip: try: ip_attempts = cache.incr(LOGIN_ATTEMPT_PREFIX + ip) except ValueError: - cache.set(LOGIN_ATTEMPT_PREFIX + ip, 0, timeout) + cache.set(LOGIN_ATTEMPT_PREFIX + ip, 1, timeout) return max(username_attempts, ip_attempts) @@ -123,26 +123,27 @@ def login(request, template_name='registration/login.html', redirect_to = request.REQUEST.get(redirect_field_name, '') ip = get_remote_ip(request) + failed_attempt = _get_login_failed_attempts(ip=ip) if request.method == "POST": - if request.REQUEST.get('captcha_0', '') != '': + username = urlquote(request.REQUEST.get('username', '').strip()) + remember_me = True if request.REQUEST.get('remember_me', + '') == 'on' else False + + if failed_attempt >= settings.LOGIN_ATTEMPT_LIMIT: # have captcha form = CaptchaAuthenticationForm(data=request.POST) if form.is_valid(): # captcha & passwod is valid, log user in - remember_me = True if request.REQUEST.get( - 'remember_me', '') == 'on' else False request.session['remember_me'] = remember_me return log_user_in(request, form.get_user(), redirect_to) else: # show page with captcha and increase failed login attempts - _incr_login_faied_attempts(ip=ip) + _incr_login_faied_attempts(username=username, ip=ip) else: form = authentication_form(data=request.POST) if form.is_valid(): # password is valid, log user in - remember_me = True if request.REQUEST.get( - 'remember_me', '') == 'on' else False request.session['remember_me'] = remember_me return log_user_in(request, form.get_user(), redirect_to) else: @@ -158,7 +159,6 @@ def login(request, template_name='registration/login.html', form = authentication_form(data=request.POST) else: ### GET - failed_attempt = _get_login_failed_attempts(ip=ip) if failed_attempt >= settings.LOGIN_ATTEMPT_LIMIT: logger.warn('Login attempt limit reached, ip: %s, attempts: %d' % (ip, failed_attempt)) @@ -229,10 +229,19 @@ def login_simple_check(request): raise Http404 -def logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name=REDIRECT_FIELD_NAME): +def logout(request, next_page=None, + template_name='registration/logged_out.html', + redirect_field_name=REDIRECT_FIELD_NAME): "Logs out the user and displays 'You are logged out' message." from seahub.auth import logout logout(request) + + if redirect_field_name in request.REQUEST: + next_page = request.REQUEST[redirect_field_name] + # Security check -- don't allow redirection to a different host. + if not is_safe_url(url=next_page, host=request.get_host()): + next_page = request.path + if next_page is None: redirect_to = request.REQUEST.get(redirect_field_name, '') if redirect_to: diff --git a/seahub/base/decorators.py b/seahub/base/decorators.py index ae2faa57aa..88a66175a9 100644 --- a/seahub/base/decorators.py +++ b/seahub/base/decorators.py @@ -80,6 +80,6 @@ def repo_passwd_set_required(func): def require_POST(func): def decorated(request, *args, **kwargs): if request.method != 'POST': - return HttpResponseNotAllowed('Only POST here') + return HttpResponseNotAllowed(['POST']) return func(request, *args, **kwargs) return decorated diff --git a/seahub/group/views.py b/seahub/group/views.py index 2c995c2ec5..4f47a69a4f 100644 --- a/seahub/group/views.py +++ b/seahub/group/views.py @@ -32,7 +32,7 @@ from forms import MessageForm, MessageReplyForm, GroupRecommendForm, \ GroupAddForm, GroupJoinMsgForm, WikiCreateForm from signals import grpmsg_added, grpmsg_reply_added, group_join_request from seahub.auth import REDIRECT_FIELD_NAME -from seahub.base.decorators import sys_staff_required +from seahub.base.decorators import sys_staff_required, require_POST from seahub.base.models import FileDiscuss from seahub.contacts.models import Contact from seahub.contacts.signals import mail_sended @@ -230,6 +230,7 @@ def group_list(request): @login_required @sys_staff_required +@require_POST def group_remove(request, group_id): """ Remove group from groupadmin page. Only system admin can perform this diff --git a/seahub/notifications/templates/notifications/user_notification_list.html b/seahub/notifications/templates/notifications/user_notification_list.html index 551afbc000..1b391be485 100644 --- a/seahub/notifications/templates/notifications/user_notification_list.html +++ b/seahub/notifications/templates/notifications/user_notification_list.html @@ -46,7 +46,9 @@ $('#mark-all-read').click(function() { if (unread_items.length > 0) { $.ajax({ url: '{% url 'set_notices_seen' %}', + type: 'POST', dataType: 'json', + beforeSend: prepareCSRFToken, success: function() { var msg_ct = $("#msg-count"); $('.num', msg_ct).html(0).addClass('hide'); diff --git a/seahub/share/models.py b/seahub/share/models.py index 5f0fcddb0d..55481caf37 100644 --- a/seahub/share/models.py +++ b/seahub/share/models.py @@ -151,6 +151,9 @@ class FileShare(models.Model): else: return False + def is_owner(self, owner): + return owner == self.username + class OrgFileShareManager(models.Manager): def set_org_file_share(self, org_id, file_share): """Set a share link as org share link. @@ -233,6 +236,9 @@ class UploadLinkShare(models.Model): def is_encrypted(self): return True if self.password is not None else False + def is_owner(self, owner): + return owner == self.username + class PrivateFileDirShareManager(models.Manager): def add_private_file_share(self, from_user, to_user, repo_id, path, perm): """ diff --git a/seahub/share/templates/share/links.html b/seahub/share/templates/share/links.html index a373b2a36c..5e1e270102 100644 --- a/seahub/share/templates/share/links.html +++ b/seahub/share/templates/share/links.html @@ -111,9 +111,11 @@ $('.rm-link, .rm-upload-link').click(function() { var _this = $(this); $.ajax({ url: $(this).hasClass('rm-link') ? '{% url 'ajax_remove_shared_link' %}' : '{% url 'ajax_remove_shared_upload_link' %}', + type: 'POST', data: {'t': $(this).attr('data-token')}, cache: false, dataType: 'json', + beforeSend: prepareCSRFToken, success: function() { _this.closest('tr').remove(); }, diff --git a/seahub/share/urls.py b/seahub/share/urls.py index 744bc3f59c..aa5f06da84 100644 --- a/seahub/share/urls.py +++ b/seahub/share/urls.py @@ -11,19 +11,12 @@ urlpatterns = patterns('', url(r'^add/$', share_repo, name='share_repo'), url(r'^remove/$', repo_remove_share, name='repo_remove_share'), - url(r'^link/get/$', get_shared_link, name='get_shared_link'), - url(r'^link/remove/$', remove_shared_link, name='remove_shared_link'), - url(r'^ajax/link/remove/$', ajax_remove_shared_link, name='ajax_remove_shared_link'), url(r'^link/send/$', send_shared_link, name='send_shared_link'), url(r'^link/save/$', save_shared_link, name='save_shared_link'), - url(r'^upload_link/get/$', get_shared_upload_link, name='get_shared_upload_link'), - url(r'^upload_link/remove/$', remove_shared_upload_link, name='remove_shared_upload_link'), - url(r'^ajax/upload_link/remove/$', ajax_remove_shared_upload_link, name='ajax_remove_shared_upload_link'), - url(r'^upload_link/send/$', send_shared_upload_link, name='send_shared_upload_link'), url(r'^permission_admin/$', share_permission_admin, name='share_permission_admin'), diff --git a/seahub/share/views.py b/seahub/share/views.py index 01c3aadeeb..80205b4441 100644 --- a/seahub/share/views.py +++ b/seahub/share/views.py @@ -32,7 +32,7 @@ from seahub.share.signals import share_repo_to_user_successful # from tokens import anon_share_token_generator from seahub.auth.decorators import login_required, login_required_ajax from seahub.base.accounts import User -from seahub.base.decorators import user_mods_check +from seahub.base.decorators import user_mods_check, require_POST from seahub.contacts.models import Contact from seahub.contacts.signals import mail_sended from seahub.signals import share_file_to_user_successful @@ -272,15 +272,16 @@ def share_repo(request): return HttpResponseRedirect(next) @login_required_ajax +@require_POST def ajax_repo_remove_share(request): """ - Remove repo share if this repo is shared to user/group/public + Remove repo shared to user/group/public """ - - repo_id = request.GET.get('repo_id', None) - share_type = request.GET.get('share_type', None) content_type = 'application/json; charset=utf-8' + repo_id = request.POST.get('repo_id', None) + share_type = request.POST.get('share_type', None) + if not seafile_api.get_repo(repo_id): return HttpResponse(json.dumps({'error': _(u'Library does not exist')}), status=400, content_type=content_type) @@ -289,7 +290,7 @@ def ajax_repo_remove_share(request): if share_type == 'personal': - from_email = request.GET.get('from', None) + from_email = request.POST.get('from', None) if not is_valid_username(from_email): return HttpResponse(json.dumps({'error': _(u'Invalid argument')}), status=400, content_type=content_type) @@ -304,12 +305,12 @@ def ajax_repo_remove_share(request): elif share_type == 'group': - from_email = request.GET.get('from', None) + from_email = request.POST.get('from', None) if not is_valid_username(from_email): return HttpResponse(json.dumps({'error': _(u'Invalid argument')}), status=400, content_type=content_type) - group_id = request.GET.get('group_id', None) + group_id = request.POST.get('group_id', None) group = seaserv.get_group(group_id) if not group: return HttpResponse(json.dumps({'error': _(u"Group does not exist")}), status=400, @@ -341,7 +342,7 @@ def ajax_repo_remove_share(request): return HttpResponse(json.dumps({'success': True}), status=200, content_type=content_type) else: - return HttpResponse(json.dumps({'error': _(u'Permission denied')}), status=400, + return HttpResponse(json.dumps({'error': _(u'Permission denied')}), status=403, content_type=content_type) else: @@ -351,7 +352,7 @@ def ajax_repo_remove_share(request): return HttpResponse(json.dumps({'success': True}), status=200, content_type=content_type) else: - return HttpResponse(json.dumps({'error': _(u'Permission denied')}), status=400, + return HttpResponse(json.dumps({'error': _(u'Permission denied')}), status=403, content_type=content_type) else: return HttpResponse(json.dumps({'error': _(u'Invalid argument')}), status=400, @@ -535,14 +536,14 @@ def list_shared_links(request): # download links fileshares = FileShare.objects.filter(username=username) - p_fileshares = [] # personal file share + fs_files, fs_dirs = [], [] for fs in fileshares: r = seafile_api.get_repo(fs.repo_id) if not r: fs.delete() continue - if fs.s_type == 'f': + if fs.is_file_share_link(): if seafile_api.get_file_id_by_path(r.id, fs.path) is None: fs.delete() continue @@ -562,7 +563,9 @@ def list_shared_links(request): if fs.expire_date is not None and timezone.now() > fs.expire_date: fs.is_expired = True - p_fileshares.append(fs) + fs_files.append(fs) if fs.is_file_share_link() else fs_dirs.append(fs) + fs_files.sort(lambda x, y: cmp(x.filename, y.filename)) + fs_dirs.sort(lambda x, y: cmp(x.filename, y.filename)) # upload links uploadlinks = UploadLinkShare.objects.filter(username=username) @@ -582,9 +585,10 @@ def list_shared_links(request): link.shared_link = gen_shared_upload_link(link.token) link.repo = r p_uploadlinks.append(link) + p_uploadlinks.sort(lambda x, y: cmp(x.dir_name, y.dir_name)) return render_to_response('share/links.html', { - "fileshares": p_fileshares, + "fileshares": fs_dirs + fs_files, "uploadlinks": p_uploadlinks, }, context_instance=RequestContext(request)) @@ -812,136 +816,59 @@ def share_permission_admin(request): ########## share link @login_required_ajax -def get_shared_link(request): - """ - Handle ajax request to generate file or dir shared link. - """ - content_type = 'application/json; charset=utf-8' - - repo_id = request.GET.get('repo_id', '') - share_type = request.GET.get('type', 'f') # `f` or `d` - path = request.GET.get('p', '') - use_passwd = True if int(request.POST.get('use_passwd', '0')) == 1 else False - passwd = request.POST.get('passwd') if use_passwd else None - - try: - expire_days = int(request.POST.get('expire_days', 0)) - except ValueError: - expire_days = 0 - if expire_days <= 0: - expire_date = None - else: - expire_date = timezone.now() + relativedelta(days=expire_days) - - if not (repo_id and path): - err = _('Invalid arguments') - data = json.dumps({'error': err}) - return HttpResponse(data, status=400, content_type=content_type) - - if share_type != 'f' and path == '/': - err = _('You cannot share the library in this way.') - data = json.dumps({'error': err}) - return HttpResponse(data, status=400, content_type=content_type) - - username = request.user.username - if share_type == 'f': - fs = FileShare.objects.get_file_link_by_path(username, repo_id, path) - if fs is None: - fs = FileShare.objects.create_file_link(username, repo_id, path, - passwd, expire_date) - if is_org_context(request): - org_id = request.user.org.org_id - OrgFileShare.objects.set_org_file_share(org_id, fs) - else: - fs = FileShare.objects.get_dir_link_by_path(username, repo_id, path) - if fs is None: - fs = FileShare.objects.create_dir_link(username, repo_id, path, - passwd, expire_date) - if is_org_context(request): - org_id = request.user.org.org_id - OrgFileShare.objects.set_org_file_share(org_id, fs) - - token = fs.token - shared_link = gen_shared_link(token, fs.s_type) - data = json.dumps({'token': token, 'shared_link': shared_link}) - return HttpResponse(data, status=200, content_type=content_type) - -@login_required -def remove_shared_link(request): - """ - Handle request to remove file shared link. - """ - token = request.GET.get('t') - - FileShare.objects.filter(token=token).delete() - next = request.META.get('HTTP_REFERER', None) - if not next: - next = reverse('share_admin') - - messages.success(request, _(u'Removed successfully')) - - return HttpResponseRedirect(next) - - -@login_required_ajax +@require_POST def ajax_remove_shared_link(request): - + username = request.user.username content_type = 'application/json; charset=utf-8' result = {} - token = request.GET.get('t') - + token = request.POST.get('t') if not token: result = {'error': _(u"Argument missing")} return HttpResponse(json.dumps(result), status=400, content_type=content_type) try: link = FileShare.objects.get(token=token) - link.delete() - result = {'success': True} - return HttpResponse(json.dumps(result), content_type=content_type) - except: + except FileShare.DoesNotExist: result = {'error': _(u"The link doesn't exist")} return HttpResponse(json.dumps(result), status=400, content_type=content_type) + if not link.is_owner(username): + result = {'error': _("Permission denied")} + return HttpResponse(json.dumps(result), status=403, + content_type=content_type) -@login_required -def remove_shared_upload_link(request): - """ - Handle request to remove shared upload link. - """ - token = request.GET.get('t') - - UploadLinkShare.objects.filter(token=token).delete() - next = request.META.get('HTTP_REFERER', None) - if not next: - next = reverse('share_admin') - - messages.success(request, _(u'Removed successfully')) - - return HttpResponseRedirect(next) + link.delete() + result = {'success': True} + return HttpResponse(json.dumps(result), content_type=content_type) @login_required_ajax +@require_POST def ajax_remove_shared_upload_link(request): - + username = request.user.username content_type = 'application/json; charset=utf-8' result = {} - token = request.GET.get('t') + token = request.POST.get('t') if not token: result = {'error': _(u"Argument missing")} return HttpResponse(json.dumps(result), status=400, content_type=content_type) try: upload_link = UploadLinkShare.objects.get(token=token) - upload_link.delete() - result = {'success': True} - return HttpResponse(json.dumps(result), content_type=content_type) - except: + except UploadLinkShare.DoesNotExist: result = {'error': _(u"The link doesn't exist")} return HttpResponse(json.dumps(result), status=400, content_type=content_type) + if not upload_link.is_owner(username): + result = {'error': _("Permission denied")} + return HttpResponse(json.dumps(result), status=403, + content_type=content_type) + upload_link.delete() + result = {'success': True} + return HttpResponse(json.dumps(result), content_type=content_type) + @login_required_ajax def send_shared_link(request): @@ -1044,6 +971,10 @@ def save_shared_link(request): messages.error(request, _(u'Please choose a directory.')) return HttpResponseRedirect(next) + if check_folder_permission(request, dst_repo_id, dst_path) != 'rw': + messages.error(request, _('Permission denied')) + return HttpResponseRedirect(next) + try: fs = FileShare.objects.get(token=token) except FileShare.DoesNotExist: @@ -1060,7 +991,6 @@ def save_shared_link(request): need_progress=0) messages.success(request, _(u'Successfully saved.')) - return HttpResponseRedirect(next) ########## private share @@ -1073,6 +1003,14 @@ def gen_private_file_share(request, repo_id): file_or_dir = os.path.basename(path.rstrip('/')) username = request.user.username + next = request.META.get('HTTP_REFERER', None) + if not next: + next = SITE_ROOT + + if not check_folder_permission(request, repo_id, file_or_dir): + messages.error(request, _('Permission denied')) + return HttpResponseRedirect(next) + for email in [e.strip() for e in emails if e.strip()]: if not is_valid_username(email): continue @@ -1094,9 +1032,6 @@ def gen_private_file_share(request, repo_id): share_file_to_user_successful.send(sender=None, priv_share_obj=pfds) messages.success(request, _('Successfully shared %s.') % file_or_dir) - next = request.META.get('HTTP_REFERER', None) - if not next: - next = SITE_ROOT return HttpResponseRedirect(next) @login_required @@ -1131,6 +1066,10 @@ def save_private_file_share(request, token): Save private share file to someone's library. """ username = request.user.username + next = request.META.get('HTTP_REFERER', None) + if not next: + next = SITE_ROOT + try: pfs = PrivateFileDirShare.objects.get_priv_file_dir_share_by_token(token) except PrivateFileDirShare.DoesNotExist: @@ -1147,18 +1086,18 @@ def save_private_file_share(request, token): dst_repo_id = request.POST.get('dst_repo') dst_path = request.POST.get('dst_path') + if check_folder_permission(request, dst_repo_id, dst_path) != 'rw': + messages.error(request, _('Permission denied')) + return HttpResponseRedirect(next) + new_obj_name = check_filename_with_rename(dst_repo_id, dst_path, obj_name) seafile_api.copy_file(repo_id, src_path, obj_name, dst_repo_id, dst_path, new_obj_name, username, need_progress=0) messages.success(request, _(u'Successfully saved.')) - else: messages.error(request, _("You don't have permission to save %s.") % obj_name) - next = request.META.get('HTTP_REFERER', None) - if not next: - next = SITE_ROOT return HttpResponseRedirect(next) # @login_required @@ -1200,59 +1139,6 @@ def save_private_file_share(request, token): # 'add_to_contacts': add_to_contacts, # }, context_instance=RequestContext(request)) -@login_required_ajax -def get_shared_upload_link(request): - """ - Handle ajax request to generate dir upload link. - """ - content_type = 'application/json; charset=utf-8' - - repo_id = request.GET.get('repo_id', '') - path = request.GET.get('p', '') - use_passwd = True if int(request.POST.get('use_passwd', '0')) == 1 else False - passwd = request.POST.get('passwd') if use_passwd else None - - if not (repo_id and path): - err = _('Invalid arguments') - data = json.dumps({'error': err}) - return HttpResponse(data, status=400, content_type=content_type) - - if path == '/': # can not share root dir - err = _('You cannot share the library in this way.') - data = json.dumps({'error': err}) - return HttpResponse(data, status=400, content_type=content_type) - else: - if path[-1] != '/': # append '/' at end of path - path += '/' - - repo = seaserv.get_repo(repo_id) - if not repo: - messages.error(request, _(u'Library does not exist')) - return HttpResponse(status=400, content_type=content_type) - - user_perm = check_folder_permission(request, repo.id, path) - - if user_perm == 'rw': - l = UploadLinkShare.objects.filter(repo_id=repo_id).filter( - username=request.user.username).filter(path=path) - if len(l) > 0: - upload_link = l[0] - token = upload_link.token - else: - username = request.user.username - uls = UploadLinkShare.objects.create_upload_link_share( - username, repo_id, path, passwd) - token = uls.token - - shared_upload_link = gen_shared_upload_link(token) - - data = json.dumps({'token': token, 'shared_upload_link': shared_upload_link}) - return HttpResponse(data, status=200, content_type=content_type) - else: - return HttpResponse(json.dumps({'error': _(u'Permission denied')}), - status=403, content_type=content_type) - - @login_required_ajax def send_shared_upload_link(request): """ diff --git a/seahub/templates/file_revisions.html b/seahub/templates/file_revisions.html index fc79942087..8219eeb15f 100644 --- a/seahub/templates/file_revisions.html +++ b/seahub/templates/file_revisions.html @@ -73,7 +73,7 @@ {% trans 'Restore' %} {% endif %} {% endif %} - {% trans 'Download' %} + {% trans 'Download' %} {% trans 'View' %} {% if can_compare and not forloop.last %} {% trans 'Diff' %} diff --git a/seahub/templates/libraries.html b/seahub/templates/libraries.html index 1140f603f9..3fea931b11 100644 --- a/seahub/templates/libraries.html +++ b/seahub/templates/libraries.html @@ -89,7 +89,7 @@ -

+

diff --git a/seahub/templates/repo.html b/seahub/templates/repo.html index b1846c25be..6952568c27 100644 --- a/seahub/templates/repo.html +++ b/seahub/templates/repo.html @@ -1303,11 +1303,15 @@ $('#add-new-file').click(function () { // share current dir $('#share-cur-dir').click(function() { - var op = $(this), name, aj_urls, type; - name = $('#cur-dir-name').attr('data-name'); - aj_urls = { 'link': op.data('url'), 'upload-link': op.data('upload-url') }; - type = 'd'; - showSharePopup(op, name, aj_urls, type, cur_path); + var op = $(this), + name = $('#cur-dir-name').attr('data-name'), + aj_data = { + 'repo_id': '{{repo.id}}', + 'p': cur_path, + 'type': 'd' + }, + type = 'd'; + showSharePopup(op, name, aj_data, type, cur_path); }); //select all or not @@ -1647,7 +1651,9 @@ $('.dir-del, .file-del', context).click(function() { $.ajax({ url: url_main + '?parent_dir=' + e(cur_path) + '&name=' + e(dirent_name), + type: 'POST', dataType: 'json', + beforeSend: prepareCSRFToken, success: function(data) { if (data['success']) { dirent.remove(); @@ -1890,20 +1896,20 @@ $('.file-star', context).click(function() { //share $('.file-share, .dir-share', context).click(function() { - var op = $(this), name, aj_urls, type; + var op = $(this), name, aj_data, type; name = op.parents('tr').attr('data-name'); - aj_urls = { - 'link': '{% url 'get_shared_link' %}?repo_id={{ repo.id }}&p=' + e(cur_path) + e(name), - 'upload-link': '{% url 'get_shared_upload_link' %}?repo_id={{ repo.id }}&p=' + e(cur_path) + e(name) + aj_data = { + 'repo_id': "{{repo.id}}", + 'p': cur_path + name }; if (op.hasClass('dir-share')) { - aj_urls['link'] += '&type=d'; + $.extend(aj_data, {'type': 'd'}); type = 'd'; } else { type = 'f'; } - showSharePopup(op, name, aj_urls, type, cur_path); + showSharePopup(op, name, aj_data, type, cur_path); return false; }); diff --git a/seahub/templates/repo_shared_link.html b/seahub/templates/repo_shared_link.html index 9662c99a1f..211f2b986b 100644 --- a/seahub/templates/repo_shared_link.html +++ b/seahub/templates/repo_shared_link.html @@ -97,7 +97,7 @@ {% endblock %} diff --git a/seahub/templates/snippets/repo_dir_data.html b/seahub/templates/snippets/repo_dir_data.html index a945e38cae..04df791eb3 100644 --- a/seahub/templates/snippets/repo_dir_data.html +++ b/seahub/templates/snippets/repo_dir_data.html @@ -45,7 +45,7 @@ {% if path != '/' %} {% if not repo.encrypted %} - + {% endif %} {% endif %}
diff --git a/seahub/templates/snippets/shared_link_js.html b/seahub/templates/snippets/shared_link_js.html index 55790ea798..c27823d107 100644 --- a/seahub/templates/snippets/shared_link_js.html +++ b/seahub/templates/snippets/shared_link_js.html @@ -18,7 +18,7 @@ $(function () { }); }); -function showSharePopup(op, name, aj_urls, type, cur_path) { +function showSharePopup(op, name, aj_data, type, cur_path) { var path; if (op.attr('id') == 'share-cur-dir') { path = cur_path.substr(0, cur_path.length - 1); // rm the last '/' as seafile_api treats '/xx' and '/xx/' as different @@ -87,7 +87,7 @@ function showSharePopup(op, name, aj_urls, type, cur_path) { $('#gen-link-btn, #link-options').removeClass('hide'); $('#share-link-body').addClass('hide'); } - $('#gen-link-btn').data('url', aj_urls['link']).data('obj', op); + $('#gen-link-btn').data('aj_data', aj_data).data('obj', op); $('#rm-shared-link').data('obj', op); $('input[name="file_shared_name"]').val(name); $('input[name="file_shared_type"]').val(type); @@ -107,7 +107,7 @@ function showSharePopup(op, name, aj_urls, type, cur_path) { $('#gen-upload-link-btn, #upload-link-options').removeClass('hide'); $('#share-upload-link-body').addClass('hide'); } - $('#gen-upload-link-btn').data('url', aj_urls['upload-link']).data('obj', op); + $('#gen-upload-link-btn').data('aj_data', aj_data).data('obj', op); $('#rm-shared-upload-link').data('obj', op); }); {% endif %} @@ -282,13 +282,13 @@ $('#gen-link-btn').click(function() { } $.ajax({ - url: gen_link_btn.data('url'), + url: '{% url 'ajax_get_download_link' %}', type: 'POST', dataType: 'json', beforeSend: prepareCSRFToken, - data: post_data, + data: $.extend(post_data, $(this).data('aj_data')), success: function(data) { - var link = data['shared_link']; + var link = data['download_link']; // hide gen-link button, and show link gen_link_btn.addClass('hide'); @@ -314,9 +314,12 @@ $('#rm-shared-link').click(function() { token = obj.attr('data-token'); $.ajax({ - url: '{% url 'ajax_remove_shared_link' %}?t=' + token, + url: '{% url 'ajax_remove_shared_link' %}', + type: 'POST', + data: {'t': token}, dataType: 'json', cache: false, + beforeSend: prepareCSRFToken, success: function(data) { $('#share-link-body').addClass('hide'); $('#link-options, #gen-link-btn').removeClass('hide'); @@ -441,13 +444,16 @@ $('#gen-upload-link-btn').click(function() { } $.ajax({ - url: gen_upload_link_btn.data('url'), + url: '{% url 'ajax_get_upload_link' %}', type: 'POST', dataType: 'json', beforeSend: prepareCSRFToken, - data: post_data, + data: $.extend(post_data, { + 'repo_id': $(this).data('aj_data').repo_id, + 'p': $(this).data('aj_data').p + }), success: function(data) { - var upload_link = data['shared_upload_link']; + var upload_link = data['upload_link']; gen_upload_link_btn.addClass('hide'); $('#upload-link-options, #upload-link-options .error').addClass('hide'); @@ -474,9 +480,12 @@ $('#rm-shared-upload-link').click(function() { token = obj.attr('data-upload-token'); $.ajax({ - url: '{% url 'ajax_remove_shared_upload_link' %}?t=' + token, + url: '{% url 'ajax_remove_shared_upload_link' %}', + type: 'POST', + data: {'t': token}, dataType: 'json', cache: false, + beforeSend: prepareCSRFToken, success: function(data) { $('#share-upload-link-body').addClass('hide'); $('#upload-link-options, #gen-upload-link-btn').removeClass('hide'); diff --git a/seahub/templates/sysadmin/org_admin_table.html b/seahub/templates/sysadmin/org_admin_table.html new file mode 100644 index 0000000000..74aaa0ace9 --- /dev/null +++ b/seahub/templates/sysadmin/org_admin_table.html @@ -0,0 +1,28 @@ +{% load seahub_tags i18n %} + + + + + + + + + {% for org in orgs %} + + + + + + + + {% endfor %} +
{% trans "Name" %}{% trans "Creator" %}{% trans "Space Used" %}{% trans "Created At / Expiration" %}{% trans "Operations" %}
+ {{ org.org_name }} + {% if org.trial_info %} +

(Trial  X)

+ {% endif %} +
{{ org.creator }} + {{ org.quota_usage|filesizeformat }} {% if org.total_quota > 0 %} / {{ org.total_quota|filesizeformat }} {% endif %} + {{ org.ctime|tsstr_sec }}
+ {% if org.expiration %}{{ org.expiration|date:'Y-m-d H:i:s' }}{% else %}--{% endif %} +
{% trans "Delete" %}
diff --git a/seahub/templates/sysadmin/repoadmin_js.html b/seahub/templates/sysadmin/repoadmin_js.html index 9d2dcf7b96..06fc9867bd 100644 --- a/seahub/templates/sysadmin/repoadmin_js.html +++ b/seahub/templates/sysadmin/repoadmin_js.html @@ -38,4 +38,9 @@ $('#repo-transfer-form').submit(function() { }); $('#main-panel').removeClass('ovhd'); -{% include 'snippets/repo_del_js.html' %} + +addConfirmTo($('.repo-delete-btn'), { + 'title': "{% trans "Delete Library" %}", + 'con': "{% trans "Are you sure you want to delete %s ?" %}", + 'post': true +}); diff --git a/seahub/templates/sysadmin/repoadmin_table.html b/seahub/templates/sysadmin/repoadmin_table.html index 5be6e76adb..f413703c4c 100644 --- a/seahub/templates/sysadmin/repoadmin_table.html +++ b/seahub/templates/sysadmin/repoadmin_table.html @@ -25,7 +25,7 @@ diff --git a/seahub/templates/sysadmin/sys_admin_group_info.html b/seahub/templates/sysadmin/sys_admin_group_info.html index 98bddfb8c5..d261b3a725 100644 --- a/seahub/templates/sysadmin/sys_admin_group_info.html +++ b/seahub/templates/sysadmin/sys_admin_group_info.html @@ -56,7 +56,7 @@ {{ repo.size|filesizeformat }} {{ repo.user }} -
+
{% endfor %} @@ -83,12 +83,15 @@ -{% include 'snippets/repo_del_popup.html' %} {% endblock %} {% block extra_script %} {% endblock %} diff --git a/seahub/templates/sysadmin/sys_group_admin.html b/seahub/templates/sysadmin/sys_group_admin.html index fc39646f57..36fe602e9b 100644 --- a/seahub/templates/sysadmin/sys_group_admin.html +++ b/seahub/templates/sysadmin/sys_group_admin.html @@ -42,7 +42,8 @@ {% endblock %} diff --git a/seahub/templates/sysadmin/sys_org_admin.html b/seahub/templates/sysadmin/sys_org_admin.html index 85bc412250..a32072eccd 100644 --- a/seahub/templates/sysadmin/sys_org_admin.html +++ b/seahub/templates/sysadmin/sys_org_admin.html @@ -1,11 +1,21 @@ {% extends "sysadmin/base.html" %} {% load seahub_tags i18n %} - {% block cur_org %}tab-cur{% endblock %} +{% block left_panel %}{{block.super}} +
+ +
+{% endblock %} + {% block right_panel %} -
-

{% trans "All Organizations" %}

+
+
@@ -29,38 +39,16 @@ {% if orgs %} - - - - - - - - - {% for org in orgs %} - - - - - - - - - {% endfor %} -
{% trans "Name" %}{% trans "Url Prefix" %}{% trans "Creator" %}{% trans "Space Used" %}{% trans "Created At" %}
- {{ org.org_name }} - {% if org.trial_info %} -

(Trial  X)

- {% endif %} -
{{ org.url_prefix }}{{ org.creator }} - {{ org.quota_usage|filesizeformat }} {% if org.total_quota > 0 %} / {{ org.total_quota|filesizeformat }} {% endif %} - {{ org.ctime|tsstr_sec }}
-{% include "snippets/admin_paginator.html" %} +{% include "sysadmin/org_admin_table.html" %} + {% if not hide_paginator %} + {% include "snippets/admin_paginator.html" %} + {% endif %} {% else %}

{% trans "None." %}

{% endif %} + {% endblock %} {% block extra_script %} @@ -70,6 +58,12 @@ $('#add-btn').click(function() { $('#simplemodal-container').css({'width':'auto'}); }); +addConfirmTo($('.remove-btn'), { + 'title':"{% trans "Delete org" %}", + 'con':"{% trans "Are you sure you want to delete %s ?" %}", + 'post': true // post request +}); + $('#add-org-form').submit(function() { var form = $(this), form_id = form.attr('id'), diff --git a/seahub/templates/sysadmin/sys_org_info_library.html b/seahub/templates/sysadmin/sys_org_info_library.html index d03f6d305e..2c4062901b 100644 --- a/seahub/templates/sysadmin/sys_org_info_library.html +++ b/seahub/templates/sysadmin/sys_org_info_library.html @@ -39,7 +39,7 @@ @@ -51,7 +51,6 @@
{% endif %} -{% include 'snippets/repo_del_popup.html' %} {% include 'sysadmin/repo_transfer_form.html' %} {% endblock %} @@ -59,7 +58,6 @@ {% block extra_script %} {% endblock %} diff --git a/seahub/templates/sysadmin/sys_org_search.html b/seahub/templates/sysadmin/sys_org_search.html new file mode 100644 index 0000000000..58d3fb8c55 --- /dev/null +++ b/seahub/templates/sysadmin/sys_org_search.html @@ -0,0 +1,33 @@ +{% extends "sysadmin/base.html" %} +{% load seahub_tags i18n staticfiles %} +{% block cur_org %}tab-cur{% endblock %} + +{% block right_panel %} +

{% trans "Search Org"%}

+ +
+

{% trans "Tip: you can search by keyword in name or creator or both." %}

+
+
+ +
+ +

{% trans "Result"%}

+{% if orgs %} +{% include "sysadmin/org_admin_table.html" %} +{% else %} +

{% trans "No result" %}

+{% endif %} + +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/seahub/templates/sysadmin/sys_publink_admin.html b/seahub/templates/sysadmin/sys_publink_admin.html index 768360f36f..40dcd86006 100644 --- a/seahub/templates/sysadmin/sys_publink_admin.html +++ b/seahub/templates/sysadmin/sys_publink_admin.html @@ -22,7 +22,7 @@ {{ publink.ctime|translate_seahub_time }} {{ publink.view_cnt }} - {% trans "Remove" %} + {% trans "Remove" %} {% endfor %} diff --git a/seahub/templates/sysadmin/sys_useradmin.html b/seahub/templates/sysadmin/sys_useradmin.html index 5e02644760..c5bafce2e9 100644 --- a/seahub/templates/sysadmin/sys_useradmin.html +++ b/seahub/templates/sysadmin/sys_useradmin.html @@ -17,6 +17,9 @@
  • {% trans "LDAP(imported)" %}
  • {% endif %}
  • {% trans "Admins" %}
  • + {% if enable_user_plan %} +
  • {% trans "Paid" %}
  • + {% endif %}
    diff --git a/seahub/templates/sysadmin/sys_useradmin_paid.html b/seahub/templates/sysadmin/sys_useradmin_paid.html new file mode 100644 index 0000000000..1b8e4b95c8 --- /dev/null +++ b/seahub/templates/sysadmin/sys_useradmin_paid.html @@ -0,0 +1,39 @@ +{% extends "sysadmin/base.html" %} +{% load seahub_tags i18n %} +{% block cur_users %}tab-cur{% endblock %} + +{% block left_panel %}{{block.super}} +
    + +
    +{% endblock %} + +{% block right_panel %} +
    + +
    + +{% with is_admin_page=False%} +{% include "sysadmin/useradmin_table.html" %} +{% endwith %} + +
    +

    {% trans "Activating..., please wait" %}

    +
    +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/seahub/templates/sysadmin/useradmin_table.html b/seahub/templates/sysadmin/useradmin_table.html index 714c4373c0..8d7c376906 100644 --- a/seahub/templates/sysadmin/useradmin_table.html +++ b/seahub/templates/sysadmin/useradmin_table.html @@ -79,12 +79,12 @@ {% if not user.is_self %} - {% trans "Delete" %} + {% trans "Delete" %} {% if user.source == "DB" %} - {% trans "ResetPwd" %} + {% trans "ResetPwd" %} {% endif %} {% if is_admin_page %} - {% trans "Revoke Admin" %} + {% trans "Revoke Admin" %} {% endif %} {% endif %} diff --git a/seahub/templates/sysadmin/userinfo.html b/seahub/templates/sysadmin/userinfo.html index a3a2b2846e..3bc91cb770 100644 --- a/seahub/templates/sysadmin/userinfo.html +++ b/seahub/templates/sysadmin/userinfo.html @@ -93,7 +93,7 @@ {{ repo.size|filesizeformat }} {{ repo.last_modify|translate_seahub_time }} -
    +
    {% endfor %} @@ -161,7 +161,7 @@ {% trans "Download" %} {{ link.view_cnt }} - {% trans "Remove"%} + {% trans "Remove"%} {% else %} {% trans @@ -170,7 +170,7 @@ {% trans "Upload" %} {{ link.view_cnt }} - {% trans "Remove"%} + {% trans "Remove"%} {% endif %} @@ -239,6 +239,11 @@ $('#set-quota-form .submit').click(function() { return false; }); -{% include 'snippets/repo_del_js.html' %} +addConfirmTo($('.repo-delete-btn'), { + 'title': "{% trans "Delete Library" %}", + 'con': "{% trans "Are you sure you want to delete %s ?" %}", + 'post': true +}); + {% endblock %} diff --git a/seahub/templates/view_file_base.html b/seahub/templates/view_file_base.html index 69890de656..f447b5155f 100644 --- a/seahub/templates/view_file_base.html +++ b/seahub/templates/view_file_base.html @@ -115,10 +115,14 @@ $('#share').click(function() { var op = $(this), name = "{{filename|escapejs}}", path = "{{path|escapejs}}"; - aj_urls = { 'link': '{% url 'get_shared_link' %}?repo_id={{ repo.id }}&p=' + e(path) }, + aj_data = { + 'repo_id': "{{ repo.id }}", + 'p': path, + 'type': 'f' + }, type = 'f', cur_path = path.substr(0, path.length - name.length); - showSharePopup(op, name, aj_urls, type, cur_path); + showSharePopup(op, name, aj_data, type, cur_path); return false; }); diff --git a/seahub/test_utils.py b/seahub/test_utils.py index fab49d5d1d..b93213ad21 100644 --- a/seahub/test_utils.py +++ b/seahub/test_utils.py @@ -5,6 +5,7 @@ from django.core.urlresolvers import reverse from django.test import TestCase from exam.decorators import fixture from exam.cases import Exam +import seaserv from seaserv import seafile_api, ccnet_threaded_rpc from seahub.base.accounts import User @@ -24,7 +25,9 @@ class Fixtures(Exam): @fixture def repo(self): - r = seafile_api.get_repo(self.create_repo()) + r = seafile_api.get_repo(self.create_repo(name='test-repo', desc='', + username=self.user.username, + passwd=None)) return r @fixture @@ -56,14 +59,18 @@ class Fixtures(Exam): return User.objects.create_user(password='secret', **kwargs) - def remove_user(self, email=None, source="DB"): + def remove_user(self, email=None): if not email: email = self.user.username - ccnet_threaded_rpc.remove_emailuser(email, source) + try: + User.objects.get(email).delete() + except User.DoesNotExist: + pass + for g in seaserv.get_personal_groups_by_user(email): + ccnet_threaded_rpc.remove_group(g.id, email) def create_repo(self, **kwargs): - repo_id = seafile_api.create_repo('test-repo', '', - self.user.username, None) + repo_id = seafile_api.create_repo(**kwargs) return repo_id def remove_repo(self, repo_id=None): diff --git a/seahub/thumbnail/utils.py b/seahub/thumbnail/utils.py index fd410481ee..e46bebca69 100644 --- a/seahub/thumbnail/utils.py +++ b/seahub/thumbnail/utils.py @@ -34,6 +34,9 @@ def allow_generate_thumbnail(request, repo_id, path): # get file size file_id = get_file_id_by_path(repo_id, path) + if not file_id: + return False + repo = get_repo(repo_id) file_size = get_file_size(repo.store_id, repo.version, file_id) @@ -81,6 +84,9 @@ def generate_thumbnail(request, repo_id, size, path): os.makedirs(thumbnail_dir) file_id = get_file_id_by_path(repo_id, path) + if not file_id: + return False + thumbnail_file = os.path.join(thumbnail_dir, file_id) if os.path.exists(thumbnail_file): diff --git a/seahub/urls.py b/seahub/urls.py index 51cbff2dd3..c8a8c44d7b 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -58,11 +58,14 @@ urlpatterns = patterns( # url(r'^home/public/reply/(?P[\d]+)/$', innerpub_msg_reply, name='innerpub_msg_reply'), # url(r'^home/owner/(?P[^/]+)/$', ownerhome, name='ownerhome'), + # revert file/dir/repo + url(r'^repo/revert_file/(?P[-0-9a-f]{36})/$', repo_revert_file, name='repo_revert_file'), + url(r'^repo/revert_dir/(?P[-0-9a-f]{36})/$', repo_revert_dir, name='repo_revert_dir'), + url(r'^repo/history/revert/(?P[-0-9a-f]{36})/$', repo_history_revert, name='repo_revert_history'), + (r'^repo/upload_check/$', validate_filename), url(r'^repo/unsetinnerpub/(?P[-0-9a-f]{36})/$', unsetinnerpub, name='unsetinnerpub'), url(r'^repo/set_password/$', repo_set_password, name="repo_set_password"), - url(r'^repo/revert_file/(?P[-0-9a-f]{36})/$', repo_revert_file, name='repo_revert_file'), - url(r'^repo/revert_dir/(?P[-0-9a-f]{36})/$', repo_revert_dir, name='repo_revert_dir'), url(r'^repo/download_dir/(?P[-0-9a-f]{36})/$', repo_download_dir, name='repo_download_dir'), (r'^repo/upload_error/(?P[-0-9a-f]{36})/$', upload_file_error), (r'^repo/update_error/(?P[-0-9a-f]{36})/$', update_file_error), @@ -70,7 +73,6 @@ urlpatterns = patterns( url(r'^repo/text_diff/(?P[-0-9a-f]{36})/$', text_diff, name='text_diff'), url(r'^repo/(?P[-0-9a-f]{36})/$', repo, name='repo'), url(r'^repo/history/(?P[-0-9a-f]{36})/$', repo_history, name='repo_history'), - url(r'^repo/history/revert/(?P[-0-9a-f]{36})/$', repo_history_revert, name='repo_history_revert'), url(r'^repo/history/view/(?P[-0-9a-f]{36})/$', repo_history_view, name='repo_history_view'), url(r'^repo/recycle/(?P[-0-9a-f]{36})/$', repo_recycle_view, name='repo_recycle_view'), url(r'^repo/(?P[-0-9a-f]{36})/online_gc/$', repo_online_gc, name='repo_online_gc'), @@ -80,8 +82,6 @@ urlpatterns = patterns( url(r'^repo/(?P[-0-9a-f]{36})/trash/files/$', view_trash_file, name="view_trash_file"), url(r'^repo/(?P[-0-9a-f]{36})/snapshot/files/$', view_snapshot_file, name="view_snapshot_file"), url(r'^repo/(?P[-0-9a-f]{36})/file/edit/$', file_edit, name='file_edit'), - url(r'^repo/(?P[-0-9a-f]{36})/privshare/$', gen_private_file_share, name='gen_private_file_share'), - url(r'^repo/(?P[-0-9a-f]{36})/(?P[0-9a-f]{40})/$', repo_access_file, name='repo_access_file'), url(r'^repo/(?P[-0-9a-f]{36})/(?P[0-9a-f]{40})/download/$', download_file, name='download_file'), url(r'^repo/(?P[-0-9a-f]{36})/settings/$', repo_basic_info, name='repo_basic_info'), url(r'^repo/(?P[-0-9a-f]{36})/settings/transfer-owner/$', repo_transfer_owner, name='repo_transfer_owner'), @@ -99,9 +99,10 @@ urlpatterns = patterns( # url(r'^home/my/lib/(?P[-0-9a-f]{36})/dir/(?P.*)$', myhome_lib, name='myhome_lib'), ### share file/dir, upload link ### - url(r'^s/f/(?P[a-f0-9]{10})/$', view_priv_shared_file, name="view_priv_shared_file"), - url(r'^s/f/(?P[a-f0-9]{10})/rm/$', rm_private_file_share, name="rm_private_file_share"), - url(r'^s/f/(?P[a-f0-9]{10})/save/$', save_private_file_share, name='save_private_file_share'), +# url(r'^repo/(?P[-0-9a-f]{36})/privshare/$', gen_private_file_share, name='gen_private_file_share'), +# url(r'^s/f/(?P[a-f0-9]{10})/$', view_priv_shared_file, name="view_priv_shared_file"), +# url(r'^s/f/(?P[a-f0-9]{10})/rm/$', rm_private_file_share, name="rm_private_file_share"), +# url(r'^s/f/(?P[a-f0-9]{10})/save/$', save_private_file_share, name='save_private_file_share'), url(r'^f/(?P[a-f0-9]{10})/$', view_shared_file, name='view_shared_file'), url(r'^f/(?P[a-f0-9]{10})/raw/(?P[0-9a-f]{40})/(?P.*)', view_raw_shared_file, name='view_raw_shared_file'), url(r'^d/(?P[a-f0-9]{10})/$', view_shared_dir, name='view_shared_dir'), @@ -213,6 +214,7 @@ urlpatterns = patterns( url(r'^sys/seafadmin/repo-trash/(?P[-0-9a-f]{36})/restore/$', sys_repo_trash_restore, name="sys_repo_trash_restore"), url(r'^sys/seafadmin/search/$', sys_repo_search, name='sys_repo_search'), url(r'^sys/seafadmin/transfer/$', sys_repo_transfer, name='sys_repo_transfer'), + url(r'^sys/seafadmin/delete/(?P[-0-9a-f]{36})/$', sys_repo_delete, name='sys_repo_delete'), url(r'^sys/useradmin/$', sys_user_admin, name='sys_useradmin'), url(r'^sys/useradmin/ldap/$', sys_user_admin_ldap, name='sys_useradmin_ldap'), url(r'^sys/useradmin/ldap/imported$', sys_user_admin_ldap_imported, name='sys_useradmin_ldap_imported'), @@ -220,14 +222,18 @@ urlpatterns = patterns( url(r'^sys/groupadmin/$', sys_group_admin, name='sys_group_admin'), url(r'^sys/groupadmin/(?P\d+)/$', sys_admin_group_info, name='sys_admin_group_info'), url(r'^sys/orgadmin/$', sys_org_admin, name='sys_org_admin'), + url(r'^sys/orgadmin/search/$', sys_org_search, name='sys_org_search'), url(r'^sys/orgadmin/(?P\d+)/set_quota/$', sys_org_set_quota, name='sys_org_set_quota'), url(r'^sys/orgadmin/(?P\d+)/rename/$', sys_org_rename, name='sys_org_rename'), + url(r'^sys/orgadmin/(?P\d+)/remove/$', sys_org_remove, name='sys_org_remove'), url(r'^sys/orgadmin/(?P\d+)/set_member_quota/$', sys_org_set_member_quota, name='sys_org_set_member_quota'), url(r'^sys/orgadmin/(?P\d+)/user/$', sys_org_info_user, name='sys_org_info_user'), url(r'^sys/orgadmin/(?P\d+)/group/$', sys_org_info_group, name='sys_org_info_group'), url(r'^sys/orgadmin/(?P\d+)/library/$', sys_org_info_library, name='sys_org_info_library'), url(r'^sys/orgadmin/(?P\d+)/setting/$', sys_org_info_setting, name='sys_org_info_setting'), url(r'^sys/publinkadmin/$', sys_publink_admin, name='sys_publink_admin'), + url(r'^sys/publink/remove/$', sys_publink_remove, name='sys_publink_remove'), + url(r'^sys/uploadlink/remove/$', sys_upload_link_remove, name='sys_upload_link_remove'), url(r'^sys/notificationadmin/', notification_list, name='notification_list'), url(r'^sys/sudo/', sys_sudo_mode, name='sys_sudo_mode'), url(r'^useradmin/add/$', user_add, name="user_add"), diff --git a/seahub/utils/__init__.py b/seahub/utils/__init__.py index 0af0eab5e8..0ce7458494 100644 --- a/seahub/utils/__init__.py +++ b/seahub/utils/__init__.py @@ -188,13 +188,13 @@ def gen_token(max_length=5): return uuid.uuid4().hex[:max_length] -def normalize_cache_key(value, prefix=None, token=None): +def normalize_cache_key(value, prefix=None, token=None, max_length=200): """Returns a cache key consisten of ``value`` and ``prefix`` and ``token``. Cache key must not include control characters or whitespace. """ key = value if prefix is None else prefix + value key = key if token is None else key + '_' + token - return urlquote(key) + return urlquote(key)[:max_length] def get_repo_last_modify(repo): """ Get last modification time for a repo. diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index 8f4b1ba7f2..85c0399a72 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -35,7 +35,7 @@ from seahub.auth.decorators import login_required, login_required_ajax from seahub.auth import login as auth_login from seahub.auth import get_backends from seahub.base.accounts import User -from seahub.base.decorators import user_mods_check +from seahub.base.decorators import user_mods_check, require_POST from seahub.base.models import UserStarredFiles, ClientLoginToken from seahub.contacts.models import Contact from seahub.options.models import UserOptions, CryptoOptionNotSetError @@ -623,7 +623,7 @@ def repo_basic_info(request, repo_id): @login_required def repo_transfer_owner(request, repo_id): - """Transfer repo owner. + """Show transfer repo owner page. """ username = request.user.username can_access, repo = can_access_repo_setting(request, repo_id, username) @@ -648,7 +648,7 @@ def repo_transfer_success(request, repo_id): @login_required def repo_change_password(request, repo_id): - """Change library password. + """Show change library password page. """ username = request.user.username can_access, repo = can_access_repo_setting(request, repo_id, username) @@ -1196,6 +1196,7 @@ def devices(request): }, context_instance=RequestContext(request)) @login_required_ajax +@require_POST def unlink_device(request): content_type = 'application/json; charset=utf-8' @@ -1294,63 +1295,6 @@ def repo_set_access_property(request, repo_id): return HttpResponseRedirect(reverse('repo', args=[repo_id])) -@login_required -def repo_del_file(request, repo_id): - if check_repo_access_permission(repo_id, request.user) != 'rw': - return render_permission_error(request, _('Failed to delete file.')) - - parent_dir = request.GET.get("p", "/") - file_name = request.GET.get("file_name") - user = request.user.username - try: - seafserv_threaded_rpc.del_file(repo_id, parent_dir, file_name, user) - messages.success(request, _(u'%s successfully deleted.') % file_name) - except: - messages.error(request, _(u'Internal error. Failed to delete %s.') % file_name) - - url = reverse('repo', args=[repo_id]) + ('?p=%s' % urllib2.quote(parent_dir.encode('utf-8'))) - return HttpResponseRedirect(url) - -def repo_access_file(request, repo_id, obj_id): - """Delete or download file. - TODO: need to be rewrite. - - **NOTE**: download file is moved to file.py::download_file - """ - repo = get_repo(repo_id) - if not repo: - raise Http404 - - password_set = False - if repo.props.encrypted: - try: - ret = seafserv_rpc.is_passwd_set(repo_id, request.user.username) - if ret == 1: - password_set = True - except SearpcError, e: - return render_error(request, e.msg) - - if repo.props.encrypted and not password_set: - return HttpResponseRedirect(reverse('repo', args=[repo_id])) - - op = request.GET.get('op', 'view') - file_name = request.GET.get('file_name', '') - - if op == 'del': - return repo_del_file(request, repo_id) - - username = request.user.username - path = request.GET.get('p', '') - if check_repo_access_permission(repo_id, request.user) or \ - get_file_access_permission(repo_id, path, username): - # Get a token to access file - token = seafile_api.get_fileserver_access_token(repo_id, obj_id, op, username) - else: - return render_permission_error(request, _(u'Unable to access file')) - - redirect_url = gen_file_get_url(token, file_name) - return HttpResponseRedirect(redirect_url) - @login_required def file_upload_progress_page(request): ''' @@ -1568,9 +1512,8 @@ def repo_revert_dir(request, repo_id): @login_required def file_revisions(request, repo_id): - if request.method != 'GET': - return render_error(request) - + """List file revisions in file version history page. + """ repo = get_repo(repo_id) if not repo: raise Http404 @@ -1579,43 +1522,7 @@ def file_revisions(request, repo_id): if check_repo_access_permission(repo.id, request.user) is None: raise Http404 - op = request.GET.get('op') - if not op: - return render_file_revisions(request, repo_id) - elif op != 'download': - return render_error(request) - - commit_id = request.GET.get('commit') - path = request.GET.get('p') - - if not (commit_id and path): - return render_error(request) - - if op == 'download': - def handle_download(): - parent_dir = os.path.dirname(path) - file_name = os.path.basename(path) - seafdir = seafile_api.list_dir_by_commit_and_path (repo_id, - commit_id, - parent_dir) - if not seafdir: - return render_error(request) - - # for ... else ... - for dirent in seafdir: - if dirent.obj_name == file_name: - break - else: - return render_error(request) - - url = reverse('repo_access_file', args=[repo_id, dirent.obj_id]) - url += '?file_name=%s&op=download' % urllib2.quote(file_name.encode('utf-8')) - return HttpResponseRedirect(url) - - try: - return handle_download() - except Exception, e: - return render_error(request, str(e)) + return render_file_revisions(request, repo_id) def demo(request): """ @@ -1781,10 +1688,15 @@ def i18n(request): # language code is not supported, use default. lang = settings.LANGUAGE_CODE - # set language code to user profile - p = Profile.objects.get_profile_by_user(request.user.username) - if p is not None: - p.set_lang_code(lang) + # set language code to user profile if user is logged in + if not request.user.is_anonymous(): + p = Profile.objects.get_profile_by_user(request.user.username) + if p is not None: + # update exist record + p.set_lang_code(lang) + else: + # add new record + Profile.objects.add_or_update(request.user.username, '', '', lang) # set language code to client res = HttpResponseRedirect(next) diff --git a/seahub/views/ajax.py b/seahub/views/ajax.py index f66bed1f8a..b3578871ff 100644 --- a/seahub/views/ajax.py +++ b/seahub/views/ajax.py @@ -24,6 +24,7 @@ from seaserv import seafile_api, seafserv_rpc, is_passwd_set, \ from pysearpc import SearpcError from seahub.auth.decorators import login_required_ajax +from seahub.base.decorators import require_POST from seahub.contacts.models import Contact from seahub.forms import RepoNewDirentForm, RepoRenameDirentForm, \ RepoCreateForm, SharedRepoCreateForm, RepoSettingForm @@ -657,6 +658,7 @@ def rename_dirent(request, repo_id): content_type=content_type) @login_required_ajax +@require_POST def delete_dirent(request, repo_id): """ Delete a file/dir with ajax. @@ -706,6 +708,7 @@ def delete_dirent(request, repo_id): status=500, content_type=content_type) @login_required_ajax +@require_POST def delete_dirents(request, repo_id): """ Delete multi files/dirs with ajax. @@ -791,9 +794,9 @@ def copy_move_common(): content_type=content_type) # Leave src folder/file permission checking to corresponding - # views, only need to check folder permission when perform 'move' - # operation, 1), if move file, check parent dir perm, 2), if move - # folder, check that folder perm. + # views. + # For 'move', check has read-write perm to src folder; + # For 'cp', check has read perm to src folder. return view_method(request, repo_id, path, dst_repo_id, dst_path, obj_name) @@ -845,12 +848,18 @@ def cp_file(request, src_repo_id, src_path, dst_repo_id, dst_path, obj_name): content_type = 'application/json; charset=utf-8' username = request.user.username + # check parent dir perm + if not check_folder_permission(request, src_repo_id, src_path): + result['error'] = _('Permission denied') + return HttpResponse(json.dumps(result), status=403, + content_type=content_type) + new_obj_name = check_filename_with_rename(dst_repo_id, dst_path, obj_name) try: res = seafile_api.copy_file(src_repo_id, src_path, obj_name, dst_repo_id, dst_path, new_obj_name, username, need_progress=1) - except SearpcError, e: + except SearpcError as e: res = None if not res: @@ -916,6 +925,12 @@ def cp_dir(request, src_repo_id, src_path, dst_repo_id, dst_path, obj_name): content_type = 'application/json; charset=utf-8' username = request.user.username + # check src dir perm + if not check_folder_permission(request, src_repo_id, src_path): + result['error'] = _('Permission denied') + return HttpResponse(json.dumps(result), status=403, + content_type=content_type) + src_dir = posixpath.join(src_path, obj_name) if dst_path.startswith(src_dir): error_msg = _(u'Can not copy directory %(src)s to its subdirectory %(des)s') \ @@ -1069,6 +1084,11 @@ def cp_dirents(request, src_repo_id, src_path, dst_repo_id, dst_path, obj_file_n content_type = 'application/json; charset=utf-8' username = request.user.username + if check_folder_permission(request, src_repo_id, src_path) is None: + error_msg = _(u'You do not have permission to copy files/dirs in this directory') + result['error'] = error_msg + return HttpResponse(json.dumps(result), status=403, content_type=content_type) + for obj_name in obj_dir_names: src_dir = posixpath.join(src_path, obj_name) if dst_path.startswith(src_dir): @@ -1480,6 +1500,7 @@ def get_popup_notices(request): }), content_type=content_type) @login_required_ajax +@require_POST def set_notices_seen(request): """Set user's notices seen: @@ -1504,6 +1525,7 @@ def set_notices_seen(request): return HttpResponse(json.dumps({'success': True}), content_type=content_type) @login_required_ajax +@require_POST def set_notice_seen_by_id(request): """ @@ -1521,23 +1543,20 @@ def set_notice_seen_by_id(request): return HttpResponse(json.dumps({'success': True}), content_type=content_type) @login_required_ajax +@require_POST def repo_remove(request, repo_id): ct = 'application/json; charset=utf-8' result = {} - if get_system_default_repo_id() == repo_id: - result['error'] = _(u'System library can not be deleted.') - return HttpResponse(json.dumps(result), status=403, content_type=ct) - repo = get_repo(repo_id) username = request.user.username if is_org_context(request): - # Remove repo in org context, only (sys admin/repo owner/org staff) can - # perform this operation. + # Remove repo in org context, only (repo owner/org staff) can perform + # this operation. org_id = request.user.org.org_id is_org_staff = request.user.org.is_staff org_repo_owner = seafile_api.get_org_repo_owner(repo_id) - if request.user.is_staff or is_org_staff or org_repo_owner == username: + if is_org_staff or org_repo_owner == username: # Must get related useres before remove the repo usernames = get_related_users_by_org_repo(org_id, repo_id) seafile_api.remove_repo(repo_id) @@ -1554,9 +1573,9 @@ def repo_remove(request, repo_id): result['error'] = _(u'Permission denied.') return HttpResponse(json.dumps(result), status=403, content_type=ct) else: - # Remove repo in personal context, only (repo owner/sys admin) can - # perform this operation. - if validate_owner(request, repo_id) or request.user.is_staff: + # Remove repo in personal context, only (repo owner) can perform this + # operation. + if validate_owner(request, repo_id): usernames = get_related_users_by_repo(repo_id) seafile_api.remove_repo(repo_id) if repo: # send delete signal only repo is valid @@ -2681,6 +2700,7 @@ def toggle_personal_modules(request): content_type=content_type) @login_required_ajax +@require_POST def ajax_unset_inner_pub_repo(request, repo_id): """ Unshare repos in organization. @@ -2695,7 +2715,7 @@ def ajax_unset_inner_pub_repo(request, repo_id): return HttpResponse(json.dumps(result), status=400, content_type=content_type) - perm = request.GET.get('permission', None) + perm = request.POST.get('permission', None) if perm is None: result["error"] = _(u'Argument missing') return HttpResponse(json.dumps(result), diff --git a/seahub/views/sysadmin.py b/seahub/views/sysadmin.py index 79240ceba9..ada7d88f26 100644 --- a/seahub/views/sysadmin.py +++ b/seahub/views/sysadmin.py @@ -14,6 +14,7 @@ from django.contrib import messages from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseNotAllowed from django.shortcuts import render_to_response from django.template import RequestContext +from django.utils import timezone from django.utils.translation import ugettext as _ from seaserv import ccnet_threaded_rpc, seafserv_threaded_rpc, get_emailusers, \ @@ -36,8 +37,11 @@ from seahub.utils.licenseparse import parse_license from seahub.utils.sysinfo import get_platform_name from seahub.views import get_system_default_repo_id +from seahub.views.ajax import (get_related_users_by_org_repo, + get_related_users_by_repo) from seahub.forms import SetUserQuotaForm, AddUserForm, BatchAddUserForm from seahub.profile.models import Profile, DetailedProfile +from seahub.signals import repo_deleted from seahub.share.models import FileShare, UploadLinkShare import seahub.settings as settings from seahub.settings import INIT_PASSWD, SITE_NAME, CONSTANCE_CONFIG, \ @@ -362,6 +366,37 @@ def _populate_user_quota_usage(user): def sys_user_admin(request): """List all users from database. """ + try: + from seahub_extra.plan.models import UserPlan + enable_user_plan = True + except ImportError: + enable_user_plan = False + + if enable_user_plan and request.GET.get('filter', '') == 'paid': + # show paid users + users = [] + ups = UserPlan.objects.all() + for up in ups: + try: + u = User.objects.get(up.username) + except User.DoesNotExist: + continue + + _populate_user_quota_usage(u) + users.append(u) + + last_logins = UserLastLogin.objects.filter(username__in=[x.username for x in users]) + for u in users: + for e in last_logins: + if e.username == u.username: + u.last_login = e.last_login + + return render_to_response('sysadmin/sys_useradmin_paid.html', { + 'users': users, + 'enable_user_plan': enable_user_plan, + }, context_instance=RequestContext(request)) + + ### List all users # Make sure page request is an int. If not, deliver first page. try: current_page = int(request.GET.get('page', '1')) @@ -426,6 +461,7 @@ def sys_user_admin(request): 'guest_user': GUEST_USER, 'is_pro': is_pro_version(), 'pro_server': pro_server, + 'enable_user_plan': enable_user_plan, }, context_instance=RequestContext(request)) @login_required @@ -1148,27 +1184,67 @@ def sys_org_admin(request): current_page = 1 per_page = 25 + try: + from seahub_extra.plan.models import OrgPlan + enable_org_plan = True + except ImportError: + enable_org_plan = False + + if enable_org_plan and request.GET.get('filter', '') == 'paid': + orgs = [] + ops = OrgPlan.objects.all() + for e in ops: + o = ccnet_threaded_rpc.get_org_by_id(e.org_id) + if not o: + continue + + o.quota_usage = seafserv_threaded_rpc.get_org_quota_usage(o.org_id) + o.total_quota = seafserv_threaded_rpc.get_org_quota(o.org_id) + o.expiration = e.expire_date + o.is_expired = True if e.expire_date < timezone.now() else False + orgs.append(o) + + return render_to_response('sysadmin/sys_org_admin.html', { + 'orgs': orgs, + 'enable_org_plan': enable_org_plan, + 'hide_paginator': True, + 'paid_page': True, + }, context_instance=RequestContext(request)) + orgs_plus_one = ccnet_threaded_rpc.get_all_orgs(per_page * (current_page - 1), per_page + 1) + if len(orgs_plus_one) == per_page + 1: + page_next = True + else: + page_next = False + orgs = orgs_plus_one[:per_page] if ENABLE_TRIAL_ACCOUNT: trial_orgs = TrialAccount.objects.filter(user_or_org__in=[x.org_id for x in orgs]) else: trial_orgs = [] + for org in orgs: org.quota_usage = seafserv_threaded_rpc.get_org_quota_usage(org.org_id) org.total_quota = seafserv_threaded_rpc.get_org_quota(org.org_id) + from seahub_extra.organizations.settings import ORG_TRIAL_DAYS + if ORG_TRIAL_DAYS > 0: + from datetime import timedelta + org.expiration = datetime.datetime.fromtimestamp(org.ctime / 1e6) + timedelta(days=ORG_TRIAL_DAYS) + org.trial_info = None for trial_org in trial_orgs: if trial_org.user_or_org == str(org.org_id): org.trial_info = {'expire_date': trial_org.expire_date} + if trial_org.expire_date: + org.expiration = trial_org.expire_date - if len(orgs_plus_one) == per_page + 1: - page_next = True - else: - page_next = False + if org.expiration: + org.is_expired = True if org.expiration < timezone.now() else False + else: + org.is_expired = False return render_to_response('sysadmin/sys_org_admin.html', { 'orgs': orgs, @@ -1177,8 +1253,43 @@ def sys_org_admin(request): 'next_page': current_page+1, 'per_page': per_page, 'page_next': page_next, + 'enable_org_plan': enable_org_plan, + 'all_page': True, }, context_instance=RequestContext(request)) +@login_required +@sys_staff_required +def sys_org_search(request): + org_name = request.GET.get('name', '').lower() + creator = request.GET.get('creator', '').lower() + if not org_name and not creator: + return HttpResponseRedirect(reverse('sys_org_admin')) + + orgs = [] + orgs_all = ccnet_threaded_rpc.get_all_orgs(-1, -1) + + if org_name and creator: + for o in orgs_all: + if org_name in o.org_name.lower() and creator in o.creator.lower(): + orgs.append(o) + else: + if org_name: + for o in orgs_all: + if org_name in o.org_name.lower(): + orgs.append(o) + + if creator: + for o in orgs_all: + if creator in o.creator.lower(): + orgs.append(o) + + return render_to_response( + 'sysadmin/sys_org_search.html', { + 'orgs': orgs, + 'name': org_name, + 'creator': creator, + }, context_instance=RequestContext(request)) + @login_required @sys_staff_required def sys_org_rename(request, org_id): @@ -1200,6 +1311,38 @@ def sys_org_rename(request, org_id): return HttpResponseRedirect(next) +@login_required +@require_POST +@sys_staff_required +def sys_org_remove(request, org_id): + """Remove an org and all members/repos/groups. + + Arguments: + - `request`: + - `org_id`: + """ + org_id = int(org_id) + org = ccnet_threaded_rpc.get_org_by_id(org_id) + users = ccnet_threaded_rpc.get_org_emailusers(org.url_prefix, -1, -1) + for u in users: + ccnet_threaded_rpc.remove_org_user(org_id, u.email) + + groups = ccnet_threaded_rpc.get_org_groups(org.org_id, -1, -1) + for g in groups: + ccnet_threaded_rpc.remove_org_group(org_id, g.gid) + + # remove org repos + seafserv_threaded_rpc.remove_org_repo_by_org_id(org_id) + + # remove org + ccnet_threaded_rpc.remove_org(org_id) + + messages.success(request, _(u'Successfully deleted.')) + + referer = request.META.get('HTTP_REFERER', None) + next = reverse('sys_org_admin') if referer is None else referer + return HttpResponseRedirect(next) + @login_required_ajax @sys_staff_required def sys_org_set_member_quota(request, org_id): @@ -1369,6 +1512,36 @@ def sys_publink_admin(request): }, context_instance=RequestContext(request)) +@login_required +@sys_staff_required +def sys_publink_remove(request): + """Remove share links. + """ + token = request.GET.get('t') + + FileShare.objects.filter(token=token).delete() + next = request.META.get('HTTP_REFERER', None) + if not next: + next = reverse('share_admin') + + messages.success(request, _(u'Removed successfully')) + return HttpResponseRedirect(next) + +@login_required +@sys_staff_required +def sys_upload_link_remove(request): + """Remove shared upload links. + """ + token = request.GET.get('t') + + UploadLinkShare.objects.filter(token=token).delete() + next = request.META.get('HTTP_REFERER', None) + if not next: + next = reverse('share_admin') + + messages.success(request, _(u'Removed successfully')) + return HttpResponseRedirect(next) + @login_required @sys_staff_required def user_search(request): @@ -1453,9 +1626,44 @@ def sys_repo_transfer(request): pass seafile_api.set_repo_owner(repo_id, new_owner) + messages.success(request, _(u'Successfully transfered.')) return HttpResponseRedirect(next) +@login_required +@sys_staff_required +@require_POST +def sys_repo_delete(request, repo_id): + """Delete a repo. + """ + next = request.META.get('HTTP_REFERER', None) + if not next: + next = reverse(sys_repo_admin) + + if get_system_default_repo_id() == repo_id: + messages.error(request, _('System library can not be deleted.')) + return HttpResponseRedirect(next) + + repo = seafile_api.get_repo(repo_id) + repo_name = repo.name + + if MULTI_TENANCY: + org_id = seafserv_threaded_rpc.get_org_id_by_repo_id(repo_id) + usernames = get_related_users_by_org_repo(org_id, repo_id) + repo_owner = seafile_api.get_org_repo_owner(repo_id) + else: + org_id = -1 + usernames = get_related_users_by_repo(repo_id) + repo_owner = seafile_api.get_repo_owner(repo_id) + + seafile_api.remove_repo(repo_id) + repo_deleted.send(sender=None, org_id=org_id, usernames=usernames, + repo_owner=repo_owner, repo_id=repo_id, + repo_name=repo_name) + + messages.success(request, _(u'Successfully deleted.')) + return HttpResponseRedirect(next) + @login_required @sys_staff_required def sys_traffic_admin(request): diff --git a/static/scripts/app/views/add-repo.js b/static/scripts/app/views/add-repo.js index 6a36da6b37..e4ae2afabb 100644 --- a/static/scripts/app/views/add-repo.js +++ b/static/scripts/app/views/add-repo.js @@ -43,8 +43,8 @@ define([ // Generate the attributes for a new GroupRepo item. newAttributes: function() { return { - name: $('input[name=repo_name]', this.$el).val().trim(), - encrypted: $('#encrypt-switch', this.$el).parent().hasClass('checkbox-checked'), + name: $.trim($('input[name=repo_name]', this.$el).val()), + encrypted: $('#encrypt-switch').prop('checked'), passwd1: $('input[name=passwd]', this.$el).val(), passwd2: $('input[name=passwd_again]', this.$el).val(), passwd: $('input[name=passwd]', this.$el).val() @@ -59,6 +59,7 @@ define([ addRepo: function(e) { e.preventDefault(); + Common.disableButton(this.$('[type="submit"]')); var repos = this.repos; repos.create(this.newAttributes(), { wait: true, @@ -80,11 +81,11 @@ define([ }, togglePasswdInput: function(e) { - var $parent = $(e.target).parent(); - $parent.toggleClass('checkbox-checked'); + var $checkbox = $('#encrypt-switch'); + var pwd_input = this.$('input[type="password"]'); - var pwd_input = $('input[type="password"]', $('.repo-create-encryption')); - if ($parent.hasClass('checkbox-checked')) { + $checkbox.parent().toggleClass('checkbox-checked'); + if ($checkbox.prop('checked')) { pwd_input.attr('disabled', false).removeClass('input-disabled'); } else { pwd_input.attr('disabled', true).addClass('input-disabled'); diff --git a/static/scripts/app/views/dir.js b/static/scripts/app/views/dir.js index 184dc70c3b..6f60f847d8 100644 --- a/static/scripts/app/views/dir.js +++ b/static/scripts/app/views/dir.js @@ -117,6 +117,7 @@ define([ dir.setPath(category, repo_id, path); var _this = this; dir.fetch({ + cache: false, reset: true, data: {'p': path}, success: function (collection, response, opts) { @@ -911,6 +912,7 @@ define([ _this = this; dir.last_start = start; dir.fetch({ + cache: false, remove: false, data: { 'p': dir.path, diff --git a/static/scripts/app/views/dirent.js b/static/scripts/app/views/dirent.js index 503ff5faf2..dd9ff653dd 100644 --- a/static/scripts/app/views/dirent.js +++ b/static/scripts/app/views/dirent.js @@ -190,7 +190,9 @@ define([ $.ajax({ url: Common.getUrl(options) + '?parent_dir=' + encodeURIComponent(dir.path) + '&name=' + encodeURIComponent(dirent_name), + type: 'POST', dataType: 'json', + beforeSend: Common.prepareCSRFToken, success: function(data) { dir.remove(model); var msg = gettext("Successfully deleted %(name)s") diff --git a/static/scripts/app/views/group.js b/static/scripts/app/views/group.js index 8aa882a09f..cd51ffa4e3 100644 --- a/static/scripts/app/views/group.js +++ b/static/scripts/app/views/group.js @@ -93,6 +93,7 @@ define([ var _this = this; this.repos.setGroupID(group_id); this.repos.fetch({ + cache: false, reset: true, data: {from: 'web'}, success: function (collection, response, opts) { diff --git a/static/scripts/app/views/myhome-repos.js b/static/scripts/app/views/myhome-repos.js index 19a0049464..2396dd484a 100644 --- a/static/scripts/app/views/myhome-repos.js +++ b/static/scripts/app/views/myhome-repos.js @@ -75,6 +75,7 @@ define([ $loadingTip.show(); var _this = this; this.repos.fetch({ + cache: false, // for IE reset: true, success: function (collection, response, opts) { }, diff --git a/static/scripts/app/views/organization-repo.js b/static/scripts/app/views/organization-repo.js index d67649845f..f31be36dfe 100644 --- a/static/scripts/app/views/organization-repo.js +++ b/static/scripts/app/views/organization-repo.js @@ -36,20 +36,23 @@ define([ removeShare: function() { var el = this.$el; var lib_name = this.model.get('name'); - Common.ajaxGet({ - get_url: Common.getUrl({ + $.ajax({ + url: Common.getUrl({ name: 'ajax_unset_inner_pub_repo', repo_id: this.model.get('id') }), + type: 'POST', data: { 'permission': this.model.get('permission') }, - after_op_success: function () { + beforeSend: Common.prepareCSRFToken, + dataType: 'json', + success: function () { el.remove(); var msg = gettext('Successfully unshared {placeholder}').replace('{placeholder}', '' + Common.HTMLescape(lib_name) + ''); Common.feedback(msg, 'success', Common.SUCCESS_TIMOUT); }, - after_op_error: function(xhr) { + error: function(xhr) { Common.ajaxErrorHandler(xhr); } }); diff --git a/static/scripts/app/views/organization.js b/static/scripts/app/views/organization.js index 6557f3e56d..f5073c4a77 100644 --- a/static/scripts/app/views/organization.js +++ b/static/scripts/app/views/organization.js @@ -106,6 +106,7 @@ define([ $loadingTip.show(); var _this = this; this.repos.fetch({ + cache: false, reset: true, success: function (collection, response, opts) { }, diff --git a/static/scripts/app/views/repo.js b/static/scripts/app/views/repo.js index 434ab32031..2caa2fb4d9 100644 --- a/static/scripts/app/views/repo.js +++ b/static/scripts/app/views/repo.js @@ -65,7 +65,9 @@ define([ $('.yes', confirm_popup).click(function() { $.ajax({ url: Common.getUrl({'name':'repo_del', 'repo_id': _this.model.get('id')}), + type: 'POST', dataType: 'json', + beforeSend: Common.prepareCSRFToken, success: function(data) { _this.remove(); Common.feedback(gettext("Delete succeeded."), 'success'); diff --git a/static/scripts/app/views/shared-repo.js b/static/scripts/app/views/shared-repo.js index a2b22828ed..3eb8dbaa2c 100644 --- a/static/scripts/app/views/shared-repo.js +++ b/static/scripts/app/views/shared-repo.js @@ -32,14 +32,17 @@ define([ }; }; - Common.ajaxGet({ - 'get_url': Common.getUrl({name: 'ajax_repo_remove_share'}), - 'data': { - 'repo_id': this.model.get('id'), - 'from': this.model.get('owner'), - 'share_type': this.model.get('share_type') - }, - 'after_op_success': success_callback + $.ajax({ + url: Common.getUrl({name: 'ajax_repo_remove_share'}), + type: 'POST', + beforeSend: Common.prepareCSRFToken, + data: { + 'repo_id': this.model.get('id'), + 'from': this.model.get('owner'), + 'share_type': this.model.get('share_type') + }, + dataType: 'json', + success: success_callback }); }, diff --git a/static/scripts/app/views/sub-lib.js b/static/scripts/app/views/sub-lib.js index 4ae7dc4a5c..3a23abfe68 100644 --- a/static/scripts/app/views/sub-lib.js +++ b/static/scripts/app/views/sub-lib.js @@ -63,7 +63,9 @@ define([ $('.yes', confirm_popup).click(function() { $.ajax({ url: Common.getUrl({'name':'repo_del', 'repo_id': _this.model.get('id')}), + type: 'POST', dataType: 'json', + beforeSend: Common.prepareCSRFToken, success: function(data) { _this.remove(); Common.feedback(gettext("Delete succeeded."), 'success'); diff --git a/static/scripts/common.js b/static/scripts/common.js index 8862eaac9d..1cf352db64 100644 --- a/static/scripts/common.js +++ b/static/scripts/common.js @@ -36,7 +36,7 @@ require.config({ 'jquery.magnific-popup': 'lib/jquery.magnific-popup', - simplemodal: 'lib/jquery.simplemodal.1.4.4.min', + simplemodal: 'lib/jquery.simplemodal', jstree: 'lib/jstree.1.0', select2: 'lib/select2-3.5.2', @@ -363,13 +363,14 @@ define([ } }); - _this = this; + var _this = this; $(document).click(function(e) { _this.closePopup(e, $('#user-info-popup'), $('#my-info')); }); }, initNoticePopup: function() { + var _this = this; var msg_ct = $("#msg-count"); // for login page, and pages without 'header' such as 'file view' page. @@ -427,7 +428,9 @@ define([ var link_href = $(this).attr('href'); $.ajax({ url: _this.getUrl({name: 'set_notice_seen_by_id'}) + '?notice_id=' + encodeURIComponent(notice_id), - dataType:'json', + type: 'POST', + dataType: 'json', + beforeSend: _this.prepareCSRFToken, success: function(data) { location.href = link_href; }, @@ -468,7 +471,9 @@ define([ // set all unread notice to be read $.ajax({ url: _this.getUrl({name: 'set_notices_seen'}), + type: 'POST', dataType: 'json', + beforeSend: _this.prepareCSRFToken, success: function() { $('.num', msg_ct).html(0).addClass('hide'); document.title = orig_doc_title; diff --git a/static/scripts/lib/jquery.simplemodal.1.4.4.min.js b/static/scripts/lib/jquery.simplemodal.1.4.4.min.js deleted file mode 100644 index 382c7367f5..0000000000 --- a/static/scripts/lib/jquery.simplemodal.1.4.4.min.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SimpleModal 1.4.4 - jQuery Plugin - * http://simplemodal.com/ - * Copyright (c) 2013 Eric Martin - * Licensed under MIT and GPL - * Date: Sun, Jan 20 2013 15:58:56 -0800 - */ -(function(b){"function"===typeof define&&define.amd?define(["jquery"],b):b(jQuery)})(function(b){var j=[],n=b(document),k=navigator.userAgent.toLowerCase(),l=b(window),g=[],o=null,p=/msie/.test(k)&&!/opera/.test(k),q=/opera/.test(k),m,r;m=p&&/msie 6./.test(k)&&"object"!==typeof window.XMLHttpRequest;r=p&&/msie 7.0/.test(k);b.modal=function(a,h){return b.modal.impl.init(a,h)};b.modal.close=function(){b.modal.impl.close()};b.modal.focus=function(a){b.modal.impl.focus(a)};b.modal.setContainerDimensions= -function(){b.modal.impl.setContainerDimensions()};b.modal.setPosition=function(){b.modal.impl.setPosition()};b.modal.update=function(a,h){b.modal.impl.update(a,h)};b.fn.modal=function(a){return b.modal.impl.init(this,a)};b.modal.defaults={appendTo:"body",focus:!0,opacity:50,overlayId:"simplemodal-overlay",overlayCss:{},containerId:"simplemodal-container",containerCss:{},dataId:"simplemodal-data",dataCss:{},minHeight:null,minWidth:null,maxHeight:null,maxWidth:null,autoResize:!1,autoPosition:!0,zIndex:1E3, -close:!0,closeHTML:'',closeClass:"simplemodal-close",escClose:!0,overlayClose:!1,fixed:!0,position:null,persist:!1,modal:!0,onOpen:null,onShow:null,onClose:null};b.modal.impl={d:{},init:function(a,h){if(this.d.data)return!1;o=p&&!b.support.boxModel;this.o=b.extend({},b.modal.defaults,h);this.zIndex=this.o.zIndex;this.occb=!1;if("object"===typeof a){if(a=a instanceof b?a:b(a),this.d.placeholder=!1,0").attr("id", -"simplemodal-placeholder").css({display:"none"})),this.d.placeholder=!0,this.display=a.css("display"),!this.o.persist))this.d.orig=a.clone(!0)}else if("string"===typeof a||"number"===typeof a)a=b("
    ").html(a);else return alert("SimpleModal Error: Unsupported data type: "+typeof a),this;this.create(a);this.open();b.isFunction(this.o.onShow)&&this.o.onShow.apply(this,[this.d]);return this},create:function(a){this.getDimensions();if(this.o.modal&&m)this.d.iframe=b('').css(b.extend(this.o.iframeCss, -{display:"none",opacity:0,position:"fixed",height:g[0],width:g[1],zIndex:this.o.zIndex,top:0,left:0})).appendTo(this.o.appendTo);this.d.overlay=b("
    ").attr("id",this.o.overlayId).addClass("simplemodal-overlay").css(b.extend(this.o.overlayCss,{display:"none",opacity:this.o.opacity/100,height:this.o.modal?j[0]:0,width:this.o.modal?j[1]:0,position:"fixed",left:0,top:0,zIndex:this.o.zIndex+1})).appendTo(this.o.appendTo);this.d.container=b("
    ").attr("id",this.o.containerId).addClass("simplemodal-container").css(b.extend({position:this.o.fixed? -"fixed":"absolute"},this.o.containerCss,{display:"none",zIndex:this.o.zIndex+2})).append(this.o.close&&this.o.closeHTML?b(this.o.closeHTML).addClass(this.o.closeClass):"").appendTo(this.o.appendTo);this.d.wrap=b("
    ").attr("tabIndex",-1).addClass("simplemodal-wrap").css({height:"100%",outline:0,width:"100%"}).appendTo(this.d.container);this.d.data=a.attr("id",a.attr("id")||this.o.dataId).addClass("simplemodal-data").css(b.extend(this.o.dataCss,{display:"none"})).appendTo("body");this.setContainerDimensions(); -this.d.data.appendTo(this.d.wrap);(m||o)&&this.fixIE()},bindEvents:function(){var a=this;b("."+a.o.closeClass).bind("click.simplemodal",function(b){b.preventDefault();a.close()});a.o.modal&&a.o.close&&a.o.overlayClose&&a.d.overlay.bind("click.simplemodal",function(b){b.preventDefault();a.close()});n.bind("keydown.simplemodal",function(b){a.o.modal&&9===b.keyCode?a.watchTab(b):a.o.close&&a.o.escClose&&27===b.keyCode&&(b.preventDefault(),a.close())});l.bind("resize.simplemodal orientationchange.simplemodal", -function(){a.getDimensions();a.o.autoResize?a.setContainerDimensions():a.o.autoPosition&&a.setPosition();m||o?a.fixIE():a.o.modal&&(a.d.iframe&&a.d.iframe.css({height:g[0],width:g[1]}),a.d.overlay.css({height:j[0],width:j[1]}))})},unbindEvents:function(){b("."+this.o.closeClass).unbind("click.simplemodal");n.unbind("keydown.simplemodal");l.unbind(".simplemodal");this.d.overlay.unbind("click.simplemodal")},fixIE:function(){var a=this.o.position;b.each([this.d.iframe||null,!this.o.modal?null:this.d.overlay, -"fixed"===this.d.container.css("position")?this.d.container:null],function(b,e){if(e){var f=e[0].style;f.position="absolute";if(2>b)f.removeExpression("height"),f.removeExpression("width"),f.setExpression("height",'document.body.scrollHeight > document.body.clientHeight ? document.body.scrollHeight : document.body.clientHeight + "px"'),f.setExpression("width",'document.body.scrollWidth > document.body.clientWidth ? document.body.scrollWidth : document.body.clientWidth + "px"');else{var c,d;a&&a.constructor=== -Array?(c=a[0]?"number"===typeof a[0]?a[0].toString():a[0].replace(/px/,""):e.css("top").replace(/px/,""),c=-1===c.indexOf("%")?c+' + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"':parseInt(c.replace(/%/,""))+' * ((document.documentElement.clientHeight || document.body.clientHeight) / 100) + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"',a[1]&&(d="number"===typeof a[1]? -a[1].toString():a[1].replace(/px/,""),d=-1===d.indexOf("%")?d+' + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"':parseInt(d.replace(/%/,""))+' * ((document.documentElement.clientWidth || document.body.clientWidth) / 100) + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"')):(c='(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"', -d='(document.documentElement.clientWidth || document.body.clientWidth) / 2 - (this.offsetWidth / 2) + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"');f.removeExpression("top");f.removeExpression("left");f.setExpression("top",c);f.setExpression("left",d)}}})},focus:function(a){var h=this,a=a&&-1!==b.inArray(a,["first","last"])?a:"first",e=b(":input:enabled:visible:"+a,h.d.wrap);setTimeout(function(){0c?c:bc?c:this.o.minHeight&&"auto"!==i&&ed?d:ad?d:this.o.minWidth&&"auto"!==c&&fb||f>a?"auto":"visible"});this.o.autoPosition&&this.setPosition()},setPosition:function(){var a,b;a=g[0]/2-this.d.container.outerHeight(!0)/2;b=g[1]/2-this.d.container.outerWidth(!0)/2;var e="fixed"!==this.d.container.css("position")?l.scrollTop():0;this.o.position&&"[object Array]"===Object.prototype.toString.call(this.o.position)?(a=e+(this.o.position[0]||a),b=this.o.position[1]||b): -a=e+a;this.d.container.css({left:b,top:a})},watchTab:function(a){if(0my data
    ').modal({options}); + * @example $('#myDiv').modal({options}); + * @example jQueryObject.modal({options}); + * + * 2) As a stand-alone function, like $.modal(data). The data parameter + * is required and an optional options object can be passed as a second + * parameter. This method provides more flexibility in the types of data + * that are allowed. The data could be a DOM object, a jQuery object, HTML + * or a string. + * + * @example $.modal('
    my data
    ', {options}); + * @example $.modal('my data', {options}); + * @example $.modal($('#myDiv'), {options}); + * @example $.modal(jQueryObject, {options}); + * @example $.modal(document.getElementById('myDiv'), {options}); + * + * A SimpleModal call can contain multiple elements, but only one modal + * dialog can be created at a time. Which means that all of the matched + * elements will be displayed within the modal container. + * + * SimpleModal internally sets the CSS needed to display the modal dialog + * properly in all browsers, yet provides the developer with the flexibility + * to easily control the look and feel. The styling for SimpleModal can be + * done through external stylesheets, or through SimpleModal, using the + * overlayCss, containerCss, and dataCss options. + * + * SimpleModal has been tested in the following browsers: + * - IE 6+ + * - Firefox 2+ + * - Opera 9+ + * - Safari 3+ + * - Chrome 1+ + * + * @name SimpleModal + * @type jQuery + * @requires jQuery v1.3 + * @cat Plugins/Windows and Overlays + * @author Eric Martin (http://ericmmartin.com) + * @version @VERSION + */ + +;(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else { + // Browser globals + factory(jQuery); + } +} +(function ($) { + var d = [], + doc = $(document), + ua = navigator.userAgent.toLowerCase(), + wndw = $(window), + w = []; + + var browser = { + ieQuirks: null, + msie: /msie/.test(ua) && !/opera/.test(ua), + opera: /opera/.test(ua) + }; + browser.ie6 = browser.msie && /msie 6./.test(ua) && typeof window['XMLHttpRequest'] !== 'object'; + browser.ie7 = browser.msie && /msie 7.0/.test(ua); + browser.boxModel = (document.compatMode === "CSS1Compat"); + + /* + * Create and display a modal dialog. + * + * @param {string, object} data A string, jQuery object or DOM object + * @param {object} [options] An optional object containing options overrides + */ + $.modal = function (data, options) { + return $.modal.impl.init(data, options); + }; + + /* + * Close the modal dialog. + */ + $.modal.close = function () { + $.modal.impl.close(); + }; + + /* + * Set focus on first or last visible input in the modal dialog. To focus on the last + * element, call $.modal.focus('last'). If no input elements are found, focus is placed + * on the data wrapper element. + */ + $.modal.focus = function (pos) { + $.modal.impl.focus(pos); + }; + + /* + * Determine and set the dimensions of the modal dialog container. + * setPosition() is called if the autoPosition option is true. + */ + $.modal.setContainerDimensions = function () { + $.modal.impl.setContainerDimensions(); + }; + + /* + * Re-position the modal dialog. + */ + $.modal.setPosition = function () { + $.modal.impl.setPosition(); + }; + + /* + * Update the modal dialog. If new dimensions are passed, they will be used to determine + * the dimensions of the container. + * + * setContainerDimensions() is called, which in turn calls setPosition(), if enabled. + * Lastly, focus() is called is the focus option is true. + */ + $.modal.update = function (height, width) { + $.modal.impl.update(height, width); + }; + + /* + * Chained function to create a modal dialog. + * + * @param {object} [options] An optional object containing options overrides + */ + $.fn.modal = function (options) { + return $.modal.impl.init(this, options); + }; + + /* + * SimpleModal default options + * + * appendTo: (String:'body') The jQuery selector to append the elements to. For .NET, use 'form'. + * focus: (Boolean:true) Focus in the first visible, enabled element? + * opacity: (Number:50) The opacity value for the overlay div, from 0 - 100 + * overlayId: (String:'simplemodal-overlay') The DOM element id for the overlay div + * overlayCss: (Object:{}) The CSS styling for the overlay div + * containerId: (String:'simplemodal-container') The DOM element id for the container div + * containerCss: (Object:{}) The CSS styling for the container div + * dataId: (String:'simplemodal-data') The DOM element id for the data div + * dataCss: (Object:{}) The CSS styling for the data div + * minHeight: (Number:null) The minimum height for the container + * minWidth: (Number:null) The minimum width for the container + * maxHeight: (Number:null) The maximum height for the container. If not specified, the window height is used. + * maxWidth: (Number:null) The maximum width for the container. If not specified, the window width is used. + * autoResize: (Boolean:false) Automatically resize the container if it exceeds the browser window dimensions? + * autoPosition: (Boolean:true) Automatically position the container upon creation and on window resize? + * zIndex: (Number: 1000) Starting z-index value + * close: (Boolean:true) If true, closeHTML, escClose and overClose will be used if set. + If false, none of them will be used. + * closeHTML: (String:'') The HTML for the default close link. + SimpleModal will automatically add the closeClass to this element. + * closeClass: (String:'simplemodal-close') The CSS class used to bind to the close event + * escClose: (Boolean:true) Allow Esc keypress to close the dialog? + * overlayClose: (Boolean:false) Allow click on overlay to close the dialog? + * fixed: (Boolean:true) If true, the container will use a fixed position. If false, it will use a + absolute position (the dialog will scroll with the page) + * position: (Array:null) Position of container [top, left]. Can be number of pixels or percentage + * persist: (Boolean:false) Persist the data across modal calls? Only used for existing + DOM elements. If true, the data will be maintained across modal calls, if false, + the data will be reverted to its original state. + * modal: (Boolean:true) User will be unable to interact with the page below the modal or tab away from the dialog. + If false, the overlay, iframe, and certain events will be disabled allowing the user to interact + with the page below the dialog. + * onOpen: (Function:null) The callback function used in place of SimpleModal's open + * onShow: (Function:null) The callback function used after the modal dialog has opened + * onClose: (Function:null) The callback function used in place of SimpleModal's close + */ + $.modal.defaults = { + appendTo: 'body', + focus: true, + opacity: 50, + overlayId: 'simplemodal-overlay', + overlayCss: {}, + containerId: 'simplemodal-container', + containerCss: {}, + dataId: 'simplemodal-data', + dataCss: {}, + minHeight: null, + minWidth: null, + maxHeight: null, + maxWidth: null, + autoResize: false, + autoPosition: true, + zIndex: 1000, + close: true, + closeHTML: '', + closeClass: 'simplemodal-close', + escClose: true, + overlayClose: false, + fixed: true, + position: null, + persist: false, + modal: true, + onOpen: null, + onShow: null, + onClose: null + }; + + /* + * Main modal object + * o = options + */ + $.modal.impl = { + /* + * Contains the modal dialog elements and is the object passed + * back to the callback (onOpen, onShow, onClose) functions + */ + d: {}, + /* + * Initialize the modal dialog + */ + init: function (data, options) { + var s = this; + + // don't allow multiple calls + if (s.d.data) { + return false; + } + + browser.ieQuirks = browser.msie && !browser.boxModel; + + // merge defaults and user options + s.o = $.extend({}, $.modal.defaults, options); + + // keep track of z-index + s.zIndex = s.o.zIndex; + + // set the onClose callback flag + s.occb = false; + + // determine how to handle the data based on its type + if (typeof data === 'object') { + // convert DOM object to a jQuery object + data = data instanceof $ ? data : $(data); + s.d.placeholder = false; + + // if the object came from the DOM, keep track of its parent + if (data.parent().parent().size() > 0) { + data.before($('') + .attr('id', 'simplemodal-placeholder') + .css({display: 'none'})); + + s.d.placeholder = true; + s.display = data.css('display'); + + // persist changes? if not, make a clone of the element + if (!s.o.persist) { + s.d.orig = data.clone(true); + } + } + } + else if (typeof data === 'string' || typeof data === 'number') { + // just insert the data as innerHTML + data = $('
    ').html(data); + } + else { + // unsupported data type! + alert('SimpleModal Error: Unsupported data type: ' + typeof data); + return s; + } + + // create the modal overlay, container and, if necessary, iframe + s.create(data); + data = null; + + // display the modal dialog + s.open(); + + // useful for adding events/manipulating data in the modal dialog + if ($.isFunction(s.o.onShow)) { + s.o.onShow.apply(s, [s.d]); + } + + // don't break the chain =) + return s; + }, + /* + * Create and add the modal overlay and container to the page + */ + create: function (data) { + var s = this; + + // get the window properties + s.getDimensions(); + + // add an iframe to prevent select options from bleeding through + if (s.o.modal && browser.ie6) { + s.d.iframe = $('') + .css($.extend(s.o.iframeCss, { + display: 'none', + opacity: 0, + position: 'fixed', + height: w[0], + width: w[1], + zIndex: s.o.zIndex, + top: 0, + left: 0 + })) + .appendTo(s.o.appendTo); + } + + // create the overlay + s.d.overlay = $('
    ') + .attr('id', s.o.overlayId) + .addClass('simplemodal-overlay') + .css($.extend(s.o.overlayCss, { + display: 'none', + opacity: s.o.opacity / 100, + height: s.o.modal ? d[0] : 0, + width: s.o.modal ? d[1] : 0, + position: 'fixed', + left: 0, + top: 0, + zIndex: s.o.zIndex + 1 + })) + .appendTo(s.o.appendTo); + + // create the container + s.d.container = $('
    ') + .attr('id', s.o.containerId) + .addClass('simplemodal-container') + .css($.extend( + {position: s.o.fixed ? 'fixed' : 'absolute'}, + s.o.containerCss, + {display: 'none', zIndex: s.o.zIndex + 2} + )) + .append(s.o.close && s.o.closeHTML + ? $(s.o.closeHTML).addClass(s.o.closeClass) + : '') + .appendTo(s.o.appendTo); + + s.d.wrap = $('
    ') + .attr('tabIndex', -1) + .addClass('simplemodal-wrap') + .css({height: '100%', outline: 0, width: '100%'}) + .appendTo(s.d.container); + + // add styling and attributes to the data + // append to body to get correct dimensions, then move to wrap + s.d.data = data + .attr('id', data.attr('id') || s.o.dataId) + .addClass('simplemodal-data') + .css($.extend(s.o.dataCss, { + display: 'none' + })) + .appendTo('body'); + data = null; + + s.setContainerDimensions(); + s.d.data.appendTo(s.d.wrap); + + // fix issues with IE + if (browser.ie6 || browser.ieQuirks) { + s.fixIE(); + } + }, + /* + * Bind events + */ + bindEvents: function () { + var s = this; + + // bind the close event to any element with the closeClass class + $('.' + s.o.closeClass).bind('click.simplemodal', function (e) { + e.preventDefault(); + s.close(); + }); + + // bind the overlay click to the close function, if enabled + if (s.o.modal && s.o.close && s.o.overlayClose) { + s.d.overlay.bind('click.simplemodal', function (e) { + e.preventDefault(); + s.close(); + }); + } + + // bind keydown events + doc.bind('keydown.simplemodal', function (e) { + if (s.o.modal && e.keyCode === 9) { // TAB + s.watchTab(e); + } + else if ((s.o.close && s.o.escClose) && e.keyCode === 27) { // ESC + e.preventDefault(); + s.close(); + } + }); + + // update window size + wndw.bind('resize.simplemodal orientationchange.simplemodal', function () { + // redetermine the window width/height + s.getDimensions(); + + // reposition the dialog + s.o.autoResize ? s.setContainerDimensions() : s.o.autoPosition && s.setPosition(); + + if (browser.ie6 || browser.ieQuirks) { + s.fixIE(); + } + else if (s.o.modal) { + // update the iframe & overlay + s.d.iframe && s.d.iframe.css({height: w[0], width: w[1]}); + s.d.overlay.css({height: d[0], width: d[1]}); + } + }); + }, + /* + * Unbind events + */ + unbindEvents: function () { + $('.' + this.o.closeClass).unbind('click.simplemodal'); + doc.unbind('keydown.simplemodal'); + wndw.unbind('.simplemodal'); + this.d.overlay.unbind('click.simplemodal'); + }, + /* + * Fix issues in IE6 and IE7 in quirks mode + */ + fixIE: function () { + var s = this, p = s.o.position; + + // simulate fixed position - adapted from BlockUI + $.each([s.d.iframe || null, !s.o.modal ? null : s.d.overlay, s.d.container.css('position') === 'fixed' ? s.d.container : null], function (i, el) { + if (el) { + var bch = 'document.body.clientHeight', bcw = 'document.body.clientWidth', + bsh = 'document.body.scrollHeight', bsl = 'document.body.scrollLeft', + bst = 'document.body.scrollTop', bsw = 'document.body.scrollWidth', + ch = 'document.documentElement.clientHeight', cw = 'document.documentElement.clientWidth', + sl = 'document.documentElement.scrollLeft', st = 'document.documentElement.scrollTop', + s = el[0].style; + + s.position = 'absolute'; + if (i < 2) { + s.removeExpression('height'); + s.removeExpression('width'); + s.setExpression('height','' + bsh + ' > ' + bch + ' ? ' + bsh + ' : ' + bch + ' + "px"'); + s.setExpression('width','' + bsw + ' > ' + bcw + ' ? ' + bsw + ' : ' + bcw + ' + "px"'); + } + else { + var te, le; + if (p && p.constructor === Array) { + var top = p[0] + ? typeof p[0] === 'number' ? p[0].toString() : p[0].replace(/px/, '') + : el.css('top').replace(/px/, ''); + te = top.indexOf('%') === -1 + ? top + ' + (t = ' + st + ' ? ' + st + ' : ' + bst + ') + "px"' + : parseInt(top.replace(/%/, '')) + ' * ((' + ch + ' || ' + bch + ') / 100) + (t = ' + st + ' ? ' + st + ' : ' + bst + ') + "px"'; + + if (p[1]) { + var left = typeof p[1] === 'number' ? p[1].toString() : p[1].replace(/px/, ''); + le = left.indexOf('%') === -1 + ? left + ' + (t = ' + sl + ' ? ' + sl + ' : ' + bsl + ') + "px"' + : parseInt(left.replace(/%/, '')) + ' * ((' + cw + ' || ' + bcw + ') / 100) + (t = ' + sl + ' ? ' + sl + ' : ' + bsl + ') + "px"'; + } + } + else { + te = '(' + ch + ' || ' + bch + ') / 2 - (this.offsetHeight / 2) + (t = ' + st + ' ? ' + st + ' : ' + bst + ') + "px"'; + le = '(' + cw + ' || ' + bcw + ') / 2 - (this.offsetWidth / 2) + (t = ' + sl + ' ? ' + sl + ' : ' + bsl + ') + "px"'; + } + s.removeExpression('top'); + s.removeExpression('left'); + s.setExpression('top', te); + s.setExpression('left', le); + } + } + }); + }, + /* + * Place focus on the first or last visible input + */ + focus: function (pos) { + var s = this, p = pos && $.inArray(pos, ['first', 'last']) !== -1 ? pos : 'first'; + + // focus on dialog or the first visible/enabled input element + var input = $(':input:enabled:visible:' + p, s.d.wrap); + setTimeout(function () { + input.length > 0 ? input.focus() : s.d.wrap.focus(); + }, 10); + }, + getDimensions: function () { + // fix a jQuery bug with determining the window height - use innerHeight if available + var s = this, + h = typeof window.innerHeight === 'undefined' ? wndw.height() : window.innerHeight; + + d = [doc.height(), doc.width()]; + w = [h, wndw.width()]; + }, + getVal: function (v, d) { + return v ? (typeof v === 'number' ? v + : v === 'auto' ? 0 + : v.indexOf('%') > 0 ? ((parseInt(v.replace(/%/, '')) / 100) * (d === 'h' ? w[0] : w[1])) + : parseInt(v.replace(/px/, ''))) + : null; + }, + /* + * Update the container. Set new dimensions, if provided. + * Focus, if enabled. Re-bind events. + */ + update: function (height, width) { + var s = this; + + // prevent update if dialog does not exist + if (!s.d.data) { + return false; + } + + // reset orig values + s.d.origHeight = s.getVal(height, 'h'); + s.d.origWidth = s.getVal(width, 'w'); + + // hide data to prevent screen flicker + s.d.data.hide(); + height && s.d.container.css('height', height); + width && s.d.container.css('width', width); + s.setContainerDimensions(); + s.d.data.show(); + s.o.focus && s.focus(); + + // rebind events + s.unbindEvents(); + s.bindEvents(); + }, + setContainerDimensions: function () { + var s = this, + badIE = browser.ie6 || browser.ie7; + + // get the dimensions for the container and data + var ch = s.d.origHeight ? s.d.origHeight : browser.opera ? s.d.container.height() : s.getVal(badIE ? s.d.container[0].currentStyle['height'] : s.d.container.css('height'), 'h'), + cw = s.d.origWidth ? s.d.origWidth : browser.opera ? s.d.container.width() : s.getVal(badIE ? s.d.container[0].currentStyle['width'] : s.d.container.css('width'), 'w'), + dh = s.d.data.outerHeight(true), dw = s.d.data.outerWidth(true); + + s.d.origHeight = s.d.origHeight || ch; + s.d.origWidth = s.d.origWidth || cw; + + // mxoh = max option height, mxow = max option width + var mxoh = s.o.maxHeight ? s.getVal(s.o.maxHeight, 'h') : null, + mxow = s.o.maxWidth ? s.getVal(s.o.maxWidth, 'w') : null, + mh = mxoh && mxoh < w[0] ? mxoh : w[0], + mw = mxow && mxow < w[1] ? mxow : w[1]; + + // moh = min option height + var moh = s.o.minHeight ? s.getVal(s.o.minHeight, 'h') : 'auto'; + if (!ch) { + if (!dh) {ch = moh;} + else { + if (dh > mh) {ch = mh;} + else if (s.o.minHeight && moh !== 'auto' && dh < moh) {ch = moh;} + else {ch = dh;} + } + } + else { + ch = s.o.autoResize && ch > mh ? mh : ch < moh ? moh : ch; + } + + // mow = min option width + var mow = s.o.minWidth ? s.getVal(s.o.minWidth, 'w') : 'auto'; + if (!cw) { + if (!dw) {cw = mow;} + else { + if (dw > mw) {cw = mw;} + else if (s.o.minWidth && mow !== 'auto' && dw < mow) {cw = mow;} + else {cw = dw;} + } + } + else { + cw = s.o.autoResize && cw > mw ? mw : cw < mow ? mow : cw; + } + + s.d.container.css({height: ch, width: cw}); + s.d.wrap.css({overflow: (dh > ch || dw > cw) ? 'auto' : 'visible'}); + s.o.autoPosition && s.setPosition(); + }, + setPosition: function () { + var s = this, top, left, + hc = (w[0]/2) - (s.d.container.outerHeight(true)/2), + vc = (w[1]/2) - (s.d.container.outerWidth(true)/2), + st = s.d.container.css('position') !== 'fixed' ? wndw.scrollTop() : 0; + + if (s.o.position && Object.prototype.toString.call(s.o.position) === '[object Array]') { + top = st + (s.o.position[0] || hc); + left = s.o.position[1] || vc; + } else { + top = st + hc; + left = vc; + } + s.d.container.css({left: left, top: top}); + }, + watchTab: function (e) { + var s = this; + + if ($(e.target).parents('.simplemodal-container').length > 0) { + // save the list of inputs + s.inputs = $(':input:enabled:visible:first, :input:enabled:visible:last', s.d.data[0]); + + // if it's the first or last tabbable element, refocus + if ((!e.shiftKey && e.target === s.inputs[s.inputs.length -1]) || + (e.shiftKey && e.target === s.inputs[0]) || + s.inputs.length === 0) { + e.preventDefault(); + var pos = e.shiftKey ? 'last' : 'first'; + s.focus(pos); + } + } + else { + // might be necessary when custom onShow callback is used + e.preventDefault(); + s.focus(); + } + }, + /* + * Open the modal dialog elements + * - Note: If you use the onOpen callback, you must "show" the + * overlay and container elements manually + * (the iframe will be handled by SimpleModal) + */ + open: function () { + var s = this; + // display the iframe + s.d.iframe && s.d.iframe.show(); + + if ($.isFunction(s.o.onOpen)) { + // execute the onOpen callback + s.o.onOpen.apply(s, [s.d]); + } + else { + // display the remaining elements + s.d.overlay.show(); + s.d.container.show(); + s.d.data.show(); + } + + s.o.focus && s.focus(); + + // bind default events + s.bindEvents(); + }, + /* + * Close the modal dialog + * - Note: If you use an onClose callback, you must remove the + * overlay, container and iframe elements manually + * + * @param {boolean} external Indicates whether the call to this + * function was internal or external. If it was external, the + * onClose callback will be ignored + */ + close: function () { + var s = this; + + // prevent close when dialog does not exist + if (!s.d.data) { + return false; + } + + // remove the default events + s.unbindEvents(); + + if ($.isFunction(s.o.onClose) && !s.occb) { + // set the onClose callback flag + s.occb = true; + + // execute the onClose callback + s.o.onClose.apply(s, [s.d]); + } + else { + // if the data came from the DOM, put it back + if (s.d.placeholder) { + var ph = $('#simplemodal-placeholder'); + // save changes to the data? + if (s.o.persist) { + // insert the (possibly) modified data back into the DOM + ph.replaceWith(s.d.data.removeClass('simplemodal-data').css('display', s.display)); + } + else { + // remove the current and insert the original, + // unmodified data back into the DOM + s.d.data.hide().remove(); + ph.replaceWith(s.d.orig); + } + } + else { + // otherwise, remove it + s.d.data.hide().remove(); + } + + // remove the remaining elements + s.d.container.hide().remove(); + s.d.overlay.hide(); + s.d.iframe && s.d.iframe.hide().remove(); + s.d.overlay.remove(); + + // reset the dialog object + s.d = {}; + } + } + }; +})); diff --git a/tests/api/endpoints/test_account.py b/tests/api/endpoints/test_account.py new file mode 100644 index 0000000000..d4e502ca06 --- /dev/null +++ b/tests/api/endpoints/test_account.py @@ -0,0 +1,176 @@ +import json + +from django.core.urlresolvers import reverse +import seaserv +from seaserv import seafile_api + +from seahub.base.accounts import User +from seahub.profile.models import Profile +from seahub.test_utils import BaseTestCase +from tests.common.utils import randstring + +class AccountTest(BaseTestCase): + def setUp(self): + self.user1 = self.create_user('user_%s@test.com' % randstring(4), + is_staff=False) + self.user2 = self.create_user('user_%s@test.com' % randstring(4), + is_staff=False) + + def tearDown(self): + self.remove_user(self.user1.username) + self.remove_user(self.user2.username) + + def _do_create(self): + resp = self.client.put( + reverse('api2-account', args=['new_user@test.com']), + 'password=123456&is_staff=1&is_active=1', + 'application/x-www-form-urlencoded', + ) + # manually remove this account + self.remove_user(email='new_user@test.com') + return resp + + def _do_get_info(self): + return self.client.get(reverse('api2-account', args=[self.user1.email])) + + def _do_migrate(self): + return self.client.post( + reverse('api2-account', args=[self.user1.username]), { + 'op': 'migrate', + 'to_user': self.user2.username, + } + ) + + def _do_update(self): + return self.client.put( + reverse('api2-account', args=[self.user1.username]), + 'password=654321&is_staff=1&is_active=0&name=user1¬e=this_is_user1&storage=102400', + 'application/x-www-form-urlencoded', + ) + + def _do_delete(self): + return self.client.delete( + reverse('api2-account', args=[self.user1.username]) + ) + + def test_permission_error(self): + self.login_as(self.user) + + resp = self._do_create() + self.assertEqual(403, resp.status_code) + + resp = self._do_get_info() + self.assertEqual(403, resp.status_code) + + resp = self._do_update() + self.assertEqual(403, resp.status_code) + + resp = self._do_migrate() + self.assertEqual(403, resp.status_code) + + resp = self._do_delete() + self.assertEqual(403, resp.status_code) + + def test_get_info(self): + self.login_as(self.admin) + + resp = self._do_get_info() + json_resp = json.loads(resp.content) + assert len(json_resp) == 7 + assert json_resp['email'] == self.user1.username + assert json_resp['is_staff'] is False + assert json_resp['is_active'] is True + assert json_resp['usage'] == 0 + + def test_create(self): + self.login_as(self.admin) + + resp = self._do_create() + self.assertEqual(201, resp.status_code) + + def test_update(self): + self.login_as(self.admin) + + resp = self._do_update() + self.assertEqual(200, resp.status_code) + + self.assertTrue(User.objects.get(self.user1.username).check_password( + '654321')) + self.assertTrue(User.objects.get(self.user1.username).is_staff) + self.assertFalse(User.objects.get(self.user1.username).is_active) + self.assertEqual(Profile.objects.get_profile_by_user( + self.user1.username).nickname, 'user1') + self.assertEqual(Profile.objects.get_profile_by_user( + self.user1.username).intro, 'this_is_user1') + self.assertEqual(seafile_api.get_user_quota( + self.user1.username), 102400) + + def test_migrate(self): + self.login_as(self.admin) + + # user1 created a repo + user1_repo = self.create_repo(name='user1-repo', desc='', + username=self.user1.username, + passwd=None) + user1_repos = seafile_api.get_owned_repo_list(self.user1.username) + self.assertEqual(len(user1_repos), 1) + self.assertEqual(user1_repos[0].id, user1_repo) + + # user1 created a group and joined a group created by the other + user1_group = self.create_group(group_name='test_group', + username=self.user1.username) + other_group = self.create_group(group_name='other_group', + username=self.user.username) + seaserv.ccnet_threaded_rpc.group_add_member(other_group.id, + self.user.username, + self.user1.username) + + user1_groups = seaserv.get_personal_groups_by_user(self.user1.username) + self.assertEqual(len(user1_groups), 2) + self.assertEqual(user1_groups[0].id, user1_group.id) + self.assertEqual(user1_groups[0].creator_name, self.user1.username) + self.assertEqual(user1_groups[1].id, other_group.id) + self.assertEqual(user1_groups[1].creator_name, self.user.username) + + # user2 had no repos + user2_repos = seafile_api.get_owned_repo_list(self.user2.username) + self.assertEqual(len(user2_repos), 0) + # user2 had no groups + user2_groups = seaserv.get_personal_groups_by_user(self.user2.username) + self.assertEqual(len(user2_groups), 0) + + # admin migrate account user1 to account user2 + resp = self._do_migrate() + self.assertEqual(200, resp.status_code) + + ### Verify ### + # user1 should have no repos + new_user1_repos = seafile_api.get_owned_repo_list(self.user1.username) + self.assertEqual(len(new_user1_repos), 0) + # user1 should still in two groups, except not the creator anymore in + # the first group, but second group should remain the same + user1_groups = seaserv.get_personal_groups_by_user(self.user1.username) + self.assertEqual(len(user1_groups), 2) + self.assertEqual(user1_groups[0].id, user1_group.id) + self.assertNotEqual(user1_groups[0].creator_name, self.user1.username) + self.assertEqual(user1_groups[1].id, other_group.id) + self.assertEqual(user1_groups[1].creator_name, self.user.username) + + # user2 should have the repo used to be user1's + new_user2_repos = seafile_api.get_owned_repo_list(self.user2.username) + self.assertEqual(len(new_user2_repos), 1) + self.assertEqual(new_user2_repos[0].id, user1_repo) + # user2 should be in two groups, and is the creator of first group, + # but second group should remain the same + user2_groups = seaserv.get_personal_groups_by_user(self.user2.username) + self.assertEqual(len(user2_groups), 2) + self.assertEqual(user2_groups[0].id, user1_group.id) + self.assertEqual(user2_groups[0].creator_name, self.user2.username) + self.assertEqual(user2_groups[1].id, other_group.id) + self.assertEqual(user2_groups[1].creator_name, self.user.username) + + def test_delete(self): + self.login_as(self.admin) + + resp = self._do_delete() + self.assertEqual(200, resp.status_code) diff --git a/tests/api/test_files.py b/tests/api/test_files.py index 15b9c396c9..53d9cb39b8 100644 --- a/tests/api/test_files.py +++ b/tests/api/test_files.py @@ -3,14 +3,14 @@ Test file/dir operations. """ -import random -import re +import posixpath import pytest -from urllib import urlencode, quote, quote +import urllib +from urllib import urlencode, quote +import urlparse from tests.common.utils import randstring, urljoin -from tests.api.urls import DEFAULT_REPO_URL, REPOS_URL -from tests.api.apitestbase import ApiTestBase, USERNAME +from tests.api.apitestbase import ApiTestBase class FilesApiTest(ApiTestBase): def test_rename_file(self): @@ -43,40 +43,73 @@ class FilesApiTest(ApiTestBase): def test_copy_file(self): with self.get_tmp_repo() as repo: - fname, _ = self.create_file(repo) # TODO: create another repo here, and use it as dst_repo + + # create sub folder(dpath) dpath, _ = self.create_dir(repo) - fopurl = urljoin(repo.repo_url, 'fileops/copy/') + '?p=/' - data = { - 'file_names': fname, - 'dst_repo': repo.repo_id, - 'dst_dir': dpath, - } - res = self.post(fopurl, data=data) - self.assertEqual(res.text, '"success"') # create tmp file in sub folder(dpath) tmp_file = 'tmp_file.txt' - furl = repo.get_filepath_url(dpath + '/' + tmp_file) + file_path = dpath + '/' + tmp_file + furl = repo.get_filepath_url(file_path) data = {'operation': 'create'} res = self.post(furl, data=data, expected=201) - # copy tmp file(in dpath) to dst dir - fopurl = urljoin(repo.repo_url, 'fileops/copy/') + '?p=' + quote(dpath) + # copy tmp file from sub folder(dpath) to dst dir('/') data = { - 'file_names': tmp_file, 'dst_repo': repo.repo_id, - 'dst_dir': dpath, + 'dst_dir': '/', + 'operation': 'copy', } - res = self.post(fopurl, data=data) + u = urlparse.urlparse(furl) + parsed_furl = urlparse.urlunparse((u.scheme, u.netloc, u.path, '', '', '')) + res = self.post(parsed_furl+ '?p=' + quote(file_path), data=data) self.assertEqual(res.text, '"success"') + # get info of copied file in dst dir('/') + fdurl = repo.file_url + u'detail/?p=/%s' % quote(tmp_file) + detail = self.get(fdurl).json() + self.assertIsNotNone(detail) + self.assertIsNotNone(detail['id']) + def test_download_file(self): with self.get_tmp_repo() as repo: fname, furl = self.create_file(repo) res = self.get(furl) self.assertRegexpMatches(res.text, '"http(.*)/%s"' % quote(fname)) + def test_download_file_without_reuse_token(self): + with self.get_tmp_repo() as repo: + fname, furl = self.create_file(repo) + res = self.get(furl) + self.assertRegexpMatches(res.text, '"http(.*)/%s"' % quote(fname)) + + # download for the first time + url = urllib.urlopen(res.text.strip('"')) + code = url.getcode() + self.assertEqual(code, 200) + + # download for the second time + url = urllib.urlopen(res.text.strip('"')) + code = url.getcode() + self.assertEqual(code, 400) + + def test_download_file_with_reuse_token(self): + with self.get_tmp_repo() as repo: + fname, furl = self.create_file(repo) + res = self.get(furl + '&reuse=1') + self.assertRegexpMatches(res.text, '"http(.*)/%s"' % quote(fname)) + + # download for the first time + url = urllib.urlopen(res.text.strip('"')) + code = url.getcode() + self.assertEqual(code, 200) + + # download for the second time + url = urllib.urlopen(res.text.strip('"')) + code = url.getcode() + self.assertEqual(code, 200) + def test_download_file_from_history(self): with self.get_tmp_repo() as repo: fname, _ = self.create_file(repo) @@ -179,7 +212,30 @@ class FilesApiTest(ApiTestBase): res = self.get(update_blks_url) self.assertRegexpMatches(res.text, r'"http(.*)/update-blks-api/[^/]+"') - def test_list_dir(self): + def test_only_list_dir(self): + with self.get_tmp_repo() as repo: + self.create_file(repo) + self.create_dir(repo) + dirents = self.get(repo.dir_url + '?t=d').json() + self.assertHasLen(dirents, 1) + for dirent in dirents: + self.assertIsNotNone(dirent['id']) + self.assertIsNotNone(dirent['name']) + self.assertEqual(dirent['type'], 'dir') + + def test_only_list_file(self): + with self.get_tmp_repo() as repo: + self.create_file(repo) + self.create_dir(repo) + dirents = self.get(repo.dir_url + '?t=f').json() + self.assertHasLen(dirents, 1) + for dirent in dirents: + self.assertIsNotNone(dirent['id']) + self.assertIsNotNone(dirent['name']) + self.assertIsNotNone(dirent['size']) + self.assertEqual(dirent['type'], 'file') + + def test_list_dir_and_file(self): with self.get_tmp_repo() as repo: self.create_file(repo) self.create_dir(repo) @@ -192,6 +248,25 @@ class FilesApiTest(ApiTestBase): if dirent['type'] == 'file': self.assertIsNotNone(dirent['size']) + def test_list_recursive_dir(self): + with self.get_tmp_repo() as repo: + + # create test dir + data = {'operation': 'mkdir'} + dir_list = ['/1/', '/1/2/', '/1/2/3/', '/4/', '/4/5/', '/6/'] + for dpath in dir_list: + durl = repo.get_dirpath_url(dpath) + self.post(durl, data=data, expected=201) + + # get recursive dir + dirents = self.get(repo.dir_url + '?t=d&recursive=1').json() + self.assertHasLen(dirents, len(dir_list)) + for dirent in dirents: + self.assertIsNotNone(dirent['id']) + self.assertEqual(dirent['type'], 'dir') + full_path = posixpath.join(dirent['parent_dir'], dirent['name']) + '/' + self.assertIn(full_path, dir_list) + def test_remove_dir(self): with self.get_tmp_repo() as repo: _, durl = self.create_dir(repo) diff --git a/tests/seahub/views/test_i18n.py b/tests/seahub/views/test_i18n.py new file mode 100644 index 0000000000..d1fd933d2e --- /dev/null +++ b/tests/seahub/views/test_i18n.py @@ -0,0 +1,44 @@ +from django.core.urlresolvers import reverse +from django.http.cookie import parse_cookie + +from seahub.test_utils import BaseTestCase +from seahub.profile.models import Profile + +class I18nTest(BaseTestCase): + + def test_can_set_to_zh(self): + resp = self.client.get(reverse('i18n') + '?lang=zh-cn') + self.assertEqual(302, resp.status_code) + assert parse_cookie(resp.cookies)['django_language'] == 'zh-cn' + + def test_wrong_lang_code(self): + resp = self.client.get(reverse('i18n') + '?lang=zh_CN') + self.assertEqual(302, resp.status_code) + assert parse_cookie(resp.cookies)['django_language'] == 'en' + + def test_anonymous_user_profile(self): + # Should not add profile record when user is anonymous. + resp = self.client.get(reverse('i18n') + '?lang=zh-cn') + self.assertEqual(302, resp.status_code) + assert len(Profile.objects.all()) == 0 + + def test_add_new_user_profile(self): + # Should add new profile record. + self.login_as(self.user) + + resp = self.client.get(reverse('i18n') + '?lang=zh-cn') + self.assertEqual(302, resp.status_code) + assert len(Profile.objects.all()) == 1 + assert Profile.objects.get_user_language(self.user.username) == 'zh-cn' + + def test_update_user_profile(self): + # Should update exist profile record. + self.login_as(self.user) + + Profile.objects.add_or_update(self.user.username, 'nickname', 'intro') + assert len(Profile.objects.all()) == 1 + + resp = self.client.get(reverse('i18n') + '?lang=zh-cn') + self.assertEqual(302, resp.status_code) + assert len(Profile.objects.all()) == 1 + assert Profile.objects.get_user_language(self.user.username) == 'zh-cn' diff --git a/tests/seahub/views/test_shared_file.py b/tests/seahub/views/test_shared_file.py index 7fdb907213..6a41740460 100644 --- a/tests/seahub/views/test_shared_file.py +++ b/tests/seahub/views/test_shared_file.py @@ -176,47 +176,47 @@ class FileViaSharedDirTest(TestCase, Fixtures): assert '8082/files/' in resp.get('location') -class PrivateSharedFileTest(TestCase, Fixtures): - def setUp(self): - self.user2 = self.create_user('test2@test.com') - share_file_info = { - 'from_user': self.user.username, - 'to_user': self.user2.username, - 'repo_id': self.repo.id, - 'path': self.file, - } - self.fs = PrivateFileDirShare.objects.add_read_only_priv_file_share( - **share_file_info) +# class PrivateSharedFileTest(TestCase, Fixtures): +# def setUp(self): +# self.user2 = self.create_user('test2@test.com') +# share_file_info = { +# 'from_user': self.user.username, +# 'to_user': self.user2.username, +# 'repo_id': self.repo.id, +# 'path': self.file, +# } +# self.fs = PrivateFileDirShare.objects.add_read_only_priv_file_share( +# **share_file_info) - def tearDown(self): - self.remove_repo() - self.remove_user(self.user.username) - self.remove_user(self.user2.username) +# def tearDown(self): +# self.remove_repo() +# self.remove_user(self.user.username) +# self.remove_user(self.user2.username) - def test_can_render(self): - self.client.post( - reverse('auth_login'), {'username': self.user2.username, - 'password': 'secret'} - ) +# def test_can_render(self): +# self.client.post( +# reverse('auth_login'), {'username': self.user2.username, +# 'password': 'secret'} +# ) - resp = self.client.get( - reverse('view_priv_shared_file', args=[self.fs.token]) - ) - self.assertEqual(200, resp.status_code) - self.assertTemplateUsed(resp, 'shared_file_view.html') - self.assertContains(resp, os.path.basename(self.file)) +# resp = self.client.get( +# reverse('view_priv_shared_file', args=[self.fs.token]) +# ) +# self.assertEqual(200, resp.status_code) +# self.assertTemplateUsed(resp, 'shared_file_view.html') +# self.assertContains(resp, os.path.basename(self.file)) - dl_url_tag = '' - self.assertContains(resp, dl_url_tag) +# dl_url_tag = '' +# self.assertContains(resp, dl_url_tag) - def test_can_download(self): - self.client.post( - reverse('auth_login'), {'username': self.user2.username, - 'password': 'secret'} - ) +# def test_can_download(self): +# self.client.post( +# reverse('auth_login'), {'username': self.user2.username, +# 'password': 'secret'} +# ) - dl_url = reverse('view_priv_shared_file', args=[self.fs.token]) + \ - '?p=%s&dl=1' % self.file - resp = self.client.get(dl_url) - self.assertEqual(302, resp.status_code) - assert '8082/files/' in resp.get('location') +# dl_url = reverse('view_priv_shared_file', args=[self.fs.token]) + \ +# '?p=%s&dl=1' % self.file +# resp = self.client.get(dl_url) +# self.assertEqual(302, resp.status_code) +# assert '8082/files/' in resp.get('location')