From 1dfd391b507447d7f4a66863e4f6003979777426 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Thu, 12 Aug 2010 12:31:38 +0200 Subject: [PATCH 1/1] Fixes #656: bulk documents upload --- apps/wiki/forms.py | 20 +++++ apps/wiki/locale/pl/LC_MESSAGES/django.mo | Bin 3483 -> 4773 bytes apps/wiki/locale/pl/LC_MESSAGES/django.po | 83 ++++++++++++++---- apps/wiki/models.py | 2 +- apps/wiki/templates/wiki/document_upload.html | 69 +++++++++++++++ apps/wiki/urls.py | 3 + apps/wiki/views.py | 56 +++++++++++- redakcja/static/css/filelist.css | 17 ++++ 8 files changed, 232 insertions(+), 18 deletions(-) create mode 100644 apps/wiki/templates/wiki/document_upload.html diff --git a/apps/wiki/forms.py b/apps/wiki/forms.py index d8f425ab..0a5be8ca 100644 --- a/apps/wiki/forms.py +++ b/apps/wiki/forms.py @@ -42,6 +42,26 @@ class DocumentCreateForm(forms.Form): return self.cleaned_data +class DocumentsUploadForm(forms.Form): + """ + Form used for uploading new documents. + """ + file = forms.FileField(required=True, label=_('ZIP file')) + + def clean(self): + file = self.cleaned_data['file'] + + import zipfile + try: + z = self.cleaned_data['zip'] = zipfile.ZipFile(file) + except zipfile.BadZipfile: + raise forms.ValidationError("Should be a ZIP file.") + if z.testzip(): + raise forms.ValidationError("ZIP file corrupt.") + + return self.cleaned_data + + class DocumentTextSaveForm(forms.Form): """ Form for saving document's text: diff --git a/apps/wiki/locale/pl/LC_MESSAGES/django.mo b/apps/wiki/locale/pl/LC_MESSAGES/django.mo index b16e0a73cf3dce60a052e583ef71c536f17c4420..0712805140d51fb5cbbd764367ab43cd0bbb98b7 100644 GIT binary patch literal 4773 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 RBXNr#GRUZUOI6>6`Y-jF^J4%2 delta 1621 zcmX|>UufJ#5XZ;1CSG!vCReL5Y3t8uv^8FGNo=FBwLNT1qsg6ItRyW)f0(OY{#;6O zNifiy77+`!Lbig$_CciJlhp@{->fgK#5cWe$b;xMG`hj8A70x?ha9@z>}J>l%U}qr;B(%d_4YiBGJhJ@!$nvP zFTn`>1a5{`Jimc;W+nTDK_v@Oyl;iIFbO zhfoQ93YG9zPzikR=YNMW;@d{NDnJdCGrL9N$9U9JYG zggX5CeyBtrfvU`tp2JX=a1<(m6Od-7>Ew42%I}2|gB=VmLOJ-%^Gm3ce(mk6P$&2i z%Hdz0l^nYbyB_M_lZN^P!%zXHq5PDf4)%)Y8K`)rvkds;|BK415~+NRewJ8kq9=jv zwsYNFOI)XEMRi(L>jT~Y^^BF}-Dm>sK=mNqf$A|)iFLtt zqq^A=RY9^3y=!e`c01I4(nEGHx)+t)kLvURs+%v-XWU>Jvs)P4q#5K+R2SnO8Hm2Q z$|`vP-G!>G?WklAdK=m#14+8^vNrcY*-^Kne8{bq{~8|~4rX$bQ~8O4WsVoYlsg(p zyUUR}_d}#FnlFy!>~LYUFfm(jD;1~Q@yd>>)I*P^y1RlWy0ZN}nY3G~Y~P)14_ezg zTictGZOLTCW0^!YHkJ0FQrSdbCUGzo9LjX|^)wyHq`O>0bYM$wD&2ip z?)LWf9vI6vPfR}RE=3pIu~^Zq#-4H)tNxDn7sJUgm?h7VkTjne$+-j71GW8g)1%>t v9Vvt(!^=xSK9~!~^SR}h+!xh@n;(C^IK6z*3i;ezAuNRMYR!UsA>Q~e{1~)E diff --git a/apps/wiki/locale/pl/LC_MESSAGES/django.po b/apps/wiki/locale/pl/LC_MESSAGES/django.po index 58e8d19c..6df7bab4 100644 --- a/apps/wiki/locale/pl/LC_MESSAGES/django.po +++ b/apps/wiki/locale/pl/LC_MESSAGES/django.po @@ -3,15 +3,14 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: Platforma Redakcyjna\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-08-03 12:13+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"POT-Creation-Date: 2010-08-12 12:22+0200\n" +"PO-Revision-Date: 2010-08-12 12:24+0100\n" "Last-Translator: Radek Czajka \n" -"Language-Team: LANGUAGE \n" +"Language-Team: Fundacja Nowoczesna Polska \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -52,35 +51,39 @@ msgstr "Ostateczna redakcja techniczna" msgid "Ready to publish" msgstr "Gotowe do publikacji" -#: forms.py:62 +#: forms.py:49 +msgid "ZIP file" +msgstr "Plik ZIP" + +#: forms.py:82 msgid "Author" msgstr "Autor" -#: forms.py:63 +#: forms.py:83 msgid "Your name" msgstr "Imię i nazwisko" -#: forms.py:68 +#: forms.py:88 msgid "Author's email" msgstr "E-mail autora" -#: forms.py:69 +#: forms.py:89 msgid "Your email address, so we can show a gravatar :)" msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)" -#: forms.py:75 +#: forms.py:95 msgid "Your comments" msgstr "Twój komentarz" -#: forms.py:76 +#: forms.py:96 msgid "Describe changes you made." msgstr "Opisz swoje zmiany" -#: forms.py:82 +#: forms.py:102 msgid "Completed" msgstr "Ukończono" -#: forms.py:83 +#: forms.py:103 msgid "If you completed a life cycle stage, select it." msgstr "Jeśli został ukończony etap prac, wskaż go." @@ -101,7 +104,16 @@ msgstr "motyw" msgid "themes" msgstr "motywy" -#: views.py:279 +#: views.py:165 +#, python-format +msgid "Title already used for %s" +msgstr "Nazwa taka sama jak dla pliku %s" + +#: views.py:167 +msgid "Title already used in repository." +msgstr "Plik o tej nazwie już istnieje w repozytorium." + +#: views.py:339 msgid "Tag added" msgstr "Dodano tag" @@ -144,7 +156,7 @@ msgstr "Zapisz" #: templates/wiki/document_details_base.html:41 msgid "Save attempt in progress" -msgstr "" +msgstr "Trwa zapisywanie" #: templates/wiki/document_list.html:30 msgid "Clear filter" @@ -154,6 +166,46 @@ msgstr "Wyczyść filtr" msgid "Your last edited documents" msgstr "Twoje ostatnie edycje" +#: templates/wiki/document_upload.html:9 +msgid "Bulk documents upload" +msgstr "Hurtowe dodawanie dokumentów" + +#: templates/wiki/document_upload.html:12 +msgid "Please submit a ZIP with XML files. Files not ending with .xml will be ignored." +msgstr "Proszę wskazać archiwum ZIP z plikami XML. Pliki nie kończące się na .xml zostaną zignorowane." + +#: templates/wiki/document_upload.html:17 +msgid "Upload" +msgstr "Dodaj" + +#: templates/wiki/document_upload.html:24 +msgid "There have been some errors. No files have been added to the repository." +msgstr "Wystąpiły błędy. Żadne pliki nie zostały dodane do repozytorium." + +#: templates/wiki/document_upload.html:25 +msgid "Offending files" +msgstr "Błędne pliki" + +#: templates/wiki/document_upload.html:33 +msgid "Correct files" +msgstr "Poprawne pliki" + +#: templates/wiki/document_upload.html:44 +msgid "Files have been successfully uploaded to the repository." +msgstr "Pliki zostały dodane do repozytorium." + +#: templates/wiki/document_upload.html:45 +msgid "Uploaded files" +msgstr "Dodane pliki" + +#: templates/wiki/document_upload.html:55 +msgid "Skipped files" +msgstr "Pominięte pliki" + +#: templates/wiki/document_upload.html:56 +msgid "Files skipped due to no .xml extension" +msgstr "Pliki pominięte z powodu braku rozszerzenia .xml." + #: templates/wiki/tag_dialog.html:16 msgid "Cancel" msgstr "Anuluj" @@ -270,3 +322,4 @@ msgstr "Wstaw przypis" #: templates/wiki/tabs/wysiwyg_editor_item.html:3 msgid "Visual editor" msgstr "Edytor wizualny" + diff --git a/apps/wiki/models.py b/apps/wiki/models.py index 3e892adc..438ec5d3 100644 --- a/apps/wiki/models.py +++ b/apps/wiki/models.py @@ -35,7 +35,7 @@ def normalize_name(name): >>> normalize_name("gąska".decode('utf-8')) u'g\u0105ska' """ - return name.translate(_PCHARS_DICT).lower() + return unicode(name).translate(_PCHARS_DICT).lower() STAGE_TAGS_RE = re.compile(r'^#stage-finished: (.*)$', re.MULTILINE) diff --git a/apps/wiki/templates/wiki/document_upload.html b/apps/wiki/templates/wiki/document_upload.html new file mode 100644 index 00000000..f1e37f76 --- /dev/null +++ b/apps/wiki/templates/wiki/document_upload.html @@ -0,0 +1,69 @@ +{% extends "wiki/base.html" %} +{% load i18n %} +{% load wiki %} + + +{% block leftcolumn %} + + +

{% trans "Bulk documents upload" %}

+ +

+{% trans "Please submit a ZIP with XML files. Files not ending with .xml will be ignored." %} +

+ +
+{{ form.as_p }} +

+
+ +
+ +{% if error_list %} + +

{% trans "There have been some errors. No files have been added to the repository." %} +

{% trans "Offending files" %}

+
    + {% for filename, title, error in error_list %} +
  • {{title|wiki_title}} ({{ filename }}): {{ error }}
  • + {% endfor %} +
+ + {% if ok_list %} +

{% trans "Correct files" %}

+
    + {% for filename, title in ok_list %} +
  • {{title|wiki_title}} ({{ filename }})
  • + {% endfor %} +
+ {% endif %} + +{% else %} + + {% if ok_list %} +

{% trans "Files have been successfully uploaded to the repository." %}

+

{% trans "Uploaded files" %}

+ + {% endif %} +{% endif %} + +{% if skipped_list %} +

{% trans "Skipped files" %}

+

{% trans "Files skipped due to no .xml extension" %}

+
    + {% for filename in skipped_list %} +
  • {{ filename }}
  • + {% endfor %} +
+{% endif %} + + +{% endblock leftcolumn %} + + +{% block rightcolumn %} +{% endblock rightcolumn %} diff --git a/apps/wiki/urls.py b/apps/wiki/urls.py index 351ecbc8..f4da5f37 100644 --- a/apps/wiki/urls.py +++ b/apps/wiki/urls.py @@ -20,6 +20,9 @@ urlpatterns = patterns('wiki.views', url(r'^(?P[^/]+)/readonly$', 'editor_readonly', name="wiki_editor_readonly"), + url(r'^upload/$', + 'upload', name='wiki_upload'), + url(r'^create/(?P[^/]+)', 'create_missing', name='wiki_create_missing'), diff --git a/apps/wiki/views.py b/apps/wiki/views.py index b57347c2..7417235b 100644 --- a/apps/wiki/views.py +++ b/apps/wiki/views.py @@ -13,7 +13,7 @@ from wiki.helpers import (JSONResponse, JSONFormInvalid, JSONServerError, from django import http from wiki.models import getstorage, DocumentNotFound, normalize_name, split_name, join_name, Theme -from wiki.forms import DocumentTextSaveForm, DocumentTagForm, DocumentCreateForm +from wiki.forms import DocumentTextSaveForm, DocumentTagForm, DocumentCreateForm, DocumentsUploadForm from datetime import datetime from django.utils.encoding import smart_unicode from django.utils.translation import ugettext_lazy as _ @@ -126,7 +126,7 @@ def create_missing(request, name): form = DocumentCreateForm(request.POST, request.FILES) if form.is_valid(): doc = storage.create_document( - id=form.cleaned_data['id'], + name=form.cleaned_data['id'], text=form.cleaned_data['text'], ) @@ -143,6 +143,58 @@ def create_missing(request, name): }) +def upload(request): + storage = getstorage() + + if request.method == "POST": + form = DocumentsUploadForm(request.POST, request.FILES) + if form.is_valid(): + zip = form.cleaned_data['zip'] + skipped_list = [] + ok_list = [] + error_list = [] + titles = {} + existing = storage.all() + for filename in zip.namelist(): + if filename[-1] == '/': + continue + title = normalize_name(os.path.basename(filename)[:-4]) + if not (title and filename.endswith('.xml')): + skipped_list.append(filename) + elif title in titles: + error_list.append((filename, title, _('Title already used for %s' % titles[title]))) + elif title in existing: + error_list.append((filename, title, _('Title already used in repository.'))) + else: + ok_list.append((filename, title)) + titles[title] = filename + if not error_list: + for filename, title in ok_list: + storage.create_document( + name=title, + text=zip.read(filename) + ) + + return direct_to_template(request, "wiki/document_upload.html", extra_context={ + "form": form, + "ok_list": ok_list, + "skipped_list": skipped_list, + "error_list": error_list, + }) + #doc = storage.create_document( + # name=base, + # text=form.cleaned_data['text'], + + + return http.HttpResponse('\n'.join(yeslist) + '\n\n' + '\n'.join(nolist)) + else: + form = DocumentsUploadForm() + + return direct_to_template(request, "wiki/document_upload.html", extra_context={ + "form": form, + }) + + @never_cache @normalized_name def text(request, name): diff --git a/redakcja/static/css/filelist.css b/redakcja/static/css/filelist.css index b2f51ed2..2ccf33ec 100644 --- a/redakcja/static/css/filelist.css +++ b/redakcja/static/css/filelist.css @@ -70,3 +70,20 @@ a:hover { #loading-overlay { display: none; } + +.error { + color: red; + font-weight: bold; +} + +.success { + color: green; +} + +#error-list { + color: red; +} + +#skipped-list { + color: #666; +} \ No newline at end of file -- 2.20.1