From 6a259b96cff47e1fd64cbfd6f3d1e1d8e8b6486c Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Mon, 3 Oct 2011 16:41:17 +0200 Subject: [PATCH 1/1] nearly working version --- apps/catalogue/forms.py | 2 + apps/catalogue/helpers.py | 59 +- .../catalogue/locale/pl/LC_MESSAGES/django.mo | Bin 6633 -> 5437 bytes .../catalogue/locale/pl/LC_MESSAGES/django.po | 644 ++++++++++-------- .../management/commands/import_wl.py | 40 +- .../management/commands/merge_books.py | 136 ++-- apps/catalogue/managers.py | 5 + apps/catalogue/migrations/0001_initial.py | 31 +- apps/catalogue/migrations/0002_stages.py | 122 ++++ .../{0002_from_hg.py => 0003_from_hg.py} | 19 +- apps/catalogue/models/__init__.py | 9 + apps/catalogue/{models.py => models/book.py} | 320 +++++---- apps/catalogue/models/chunk.py | 123 ++++ apps/catalogue/models/listeners.py | 48 ++ apps/catalogue/models/publish_log.py | 39 ++ apps/catalogue/signals.py | 3 + apps/catalogue/tasks.py | 11 + .../templates/catalogue/activity.html | 7 + apps/catalogue/templates/catalogue/base.html | 6 +- .../templates/catalogue/book_list/book.html | 34 + .../catalogue/book_list/book_list.html | 81 +++ .../templates/catalogue/book_list/chunk.html | 25 + .../templates/catalogue/document_list.html | 142 +--- .../templates/catalogue/my_page.html | 24 + .../templates/catalogue/user_page.html | 15 + apps/catalogue/templates/catalogue/wall.html | 4 +- apps/catalogue/templatetags/book_list.py | 134 ++++ apps/catalogue/templatetags/catalogue.py | 113 +-- apps/catalogue/templatetags/wall.py | 125 ++++ apps/catalogue/tests.py | 19 - apps/catalogue/urls.py | 1 + apps/catalogue/views.py | 80 +-- apps/catalogue/xml_tools.py | 2 +- apps/dvcs/locale/pl/LC_MESSAGES/django.mo | Bin 0 -> 1555 bytes apps/dvcs/locale/pl/LC_MESSAGES/django.po | 115 ++++ apps/dvcs/models.py | 121 ++-- apps/dvcs/settings.py | 3 - apps/dvcs/signals.py | 4 + apps/dvcs/tests.py | 164 ----- apps/dvcs/tests/__init__.py | 158 +++++ apps/dvcs/urls.py | 6 - apps/dvcs/views.py | 21 - apps/wiki/tests.py | 19 - apps/wiki/views.py | 3 +- redakcja.wsgi.template | 1 + redakcja/settings/common.py | 14 +- redakcja/settings/compress.py | 10 +- redakcja/settings/test.py | 4 +- redakcja/static/js/catalogue/catalogue.js | 29 + redakcja/templates/pagination/pagination.html | 26 + requirements-test.txt | 2 +- requirements.txt | 2 + 52 files changed, 1945 insertions(+), 1180 deletions(-) create mode 100644 apps/catalogue/managers.py create mode 100644 apps/catalogue/migrations/0002_stages.py rename apps/catalogue/migrations/{0002_from_hg.py => 0003_from_hg.py} (92%) create mode 100755 apps/catalogue/models/__init__.py rename apps/catalogue/{models.py => models/book.py} (63%) mode change 100644 => 100755 create mode 100755 apps/catalogue/models/chunk.py create mode 100755 apps/catalogue/models/listeners.py create mode 100755 apps/catalogue/models/publish_log.py create mode 100644 apps/catalogue/signals.py create mode 100644 apps/catalogue/tasks.py create mode 100755 apps/catalogue/templates/catalogue/activity.html create mode 100755 apps/catalogue/templates/catalogue/book_list/book.html create mode 100755 apps/catalogue/templates/catalogue/book_list/book_list.html create mode 100755 apps/catalogue/templates/catalogue/book_list/chunk.html create mode 100755 apps/catalogue/templates/catalogue/my_page.html create mode 100755 apps/catalogue/templates/catalogue/user_page.html create mode 100755 apps/catalogue/templatetags/book_list.py create mode 100755 apps/catalogue/templatetags/wall.py delete mode 100644 apps/catalogue/tests.py mode change 100755 => 100644 apps/catalogue/xml_tools.py create mode 100644 apps/dvcs/locale/pl/LC_MESSAGES/django.mo create mode 100644 apps/dvcs/locale/pl/LC_MESSAGES/django.po delete mode 100755 apps/dvcs/settings.py create mode 100755 apps/dvcs/signals.py delete mode 100644 apps/dvcs/tests.py create mode 100755 apps/dvcs/tests/__init__.py delete mode 100644 apps/dvcs/urls.py delete mode 100644 apps/dvcs/views.py delete mode 100644 apps/wiki/tests.py create mode 100755 redakcja/static/js/catalogue/catalogue.js create mode 100755 redakcja/templates/pagination/pagination.html diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py index 6a56e764..f6b2dc98 100644 --- a/apps/catalogue/forms.py +++ b/apps/catalogue/forms.py @@ -44,6 +44,8 @@ class DocumentsUploadForm(forms.Form): Form used for uploading new documents. """ file = forms.FileField(required=True, label=_('ZIP file')) + dirs = forms.BooleanField(label=_('Directories are documents in chunks'), + widget = forms.CheckboxInput(attrs={'disabled':'disabled'})) def clean(self): file = self.cleaned_data['file'] diff --git a/apps/catalogue/helpers.py b/apps/catalogue/helpers.py index c9dc0bd9..7bc24819 100644 --- a/apps/catalogue/helpers.py +++ b/apps/catalogue/helpers.py @@ -16,50 +16,15 @@ def active_tab(tab): return wrapper -class ChunksList(object): - def __init__(self, chunk_qs): - self.chunk_qs = chunk_qs.annotate( - book_length=Count('book__chunk')).select_related( - 'book', 'stage__name', - 'user') - - self.book_qs = chunk_qs.values('book_id') - - def __getitem__(self, key): - if isinstance(key, slice): - return self.get_slice(key) - elif isinstance(key, int): - return self.get_slice(slice(key, key+1))[0] - else: - raise TypeError('Unsupported list index. Must be a slice or an int.') - - def __len__(self): - return self.book_qs.count() - - def get_slice(self, slice_): - book_ids = [x['book_id'] for x in self.book_qs[slice_]] - chunk_qs = self.chunk_qs.filter(book__in=book_ids) - - chunks_list = [] - book = None - for chunk in chunk_qs: - if chunk.book != book: - book = chunk.book - chunks_list.append(ChoiceChunks(book, [chunk], chunk.book_length)) - else: - chunks_list[-1].chunks.append(chunk) - return chunks_list - - -class ChoiceChunks(object): - """ - Associates the given chunks iterable for a book. - """ - - chunks = None - - def __init__(self, book, chunks, book_length): - self.book = book - self.chunks = chunks - self.book_length = book_length - +def cached_in_field(field_name): + def decorator(f): + @property + @wraps(f) + def wrapped(self, *args, **kwargs): + value = getattr(self, field_name) + if value is None: + value = f(self, *args, **kwargs) + type(self)._default_manager.filter(pk=self.pk).update(**{field_name: value}) + return value + return wrapped + return decorator diff --git a/apps/catalogue/locale/pl/LC_MESSAGES/django.mo b/apps/catalogue/locale/pl/LC_MESSAGES/django.mo index f841945b2f770acbdbc63e6e93d5659c3ac0f72c..4eb727192cfe2b883aa68ef35a7eb15813c12a0c 100644 GIT binary patch literal 5437 zcmai$Ym6OL9l(!>6jns37GEF-p|ut6cDDsA+iqdI?b_OIcijiC;D@s_XYV~bb7!ve z=;clbF{PF8K|_c}D~WzE7!wntq6umvAJ~mC5@IwYD$yvKD3QchBqkd0_n(=2cS~zH zx##|7&TIa!^FQaen=k&nqAXCark=e>sZYZD-ohV6Z&Kd(AAdXC0;PW| zyciyV*TCa&4-DaM_ypvqe#PJA@I@%={}sx*uR>Yx5|j{L1x3zna0Tv#qQ}>ujQi2&^8UNq9{UVGZ<2Kd20*d}yAVbvk5R<5#Q0#LXl=Y56S!Wi? z{5$LRPt@&8Q0BL4W>DsT3d;P);NRb*)ED4R-XFnuGXDEe)?I^Q&!6%q^86Z#9Djyl zr++{he+kBUKimvyQo~T}Iu0eS=AhW417+T);SKOhP}X}I%DkVzcfeo58{tb(^x8tB zjK3OggA?#}*n+rJeI1G%PeS?5)AjrF@V&hM9?JUvfg;xsMtBHb3uXRSp~&$~DC3`k z{M66+lkfi#O5FSf%09bvb0w#SAK-ly?t-&W)_Vv}!Oz0o@OdclcNxlKYPGrMwNT`} z2}=I~DEb|RVz&fJ{5}drzHij;--BZBpVjRzLw@QN{zTp(loUO$e{hPeak?St#>ftobJ>^Iops z|5?BP8_K-bAg)oDGgmn;qcs^?#>P%4x=0?AGFhRNXNKpSsfVeezm#dJ z=yY6OD2FPPc~~CjsgnDoh^!KCk|RV8DWZ=QP5mHsFZCwshp8g_KI%5Al7pdO)qocb~9F{+eXsJE6+#5Uv@ zDvRuuIqFvGF!eSnAz79&{>29p-xAwWB!(BL2dVq15-a_(n};dtho~Q=UPqN8ev_Ek zL1o*P`vO#D>vnHPs+Zz8Q0tGWn(#7r!eu=*;rmjx*UF=Sr?!jKL>Q`xPRB-mHL-rq z;(luu>o{xKWY8V<^s>9(rs@D6(y7fd7d3gy!$AA7m$z+{r8@6~vGLWUiFDf}fnJUi z-N~0im$n$LCPjqx$<=GuWi6exT&mMBZ)y`J*7!Yb?{{gIs>!%5OT#3wUWO1MLQFCy zvx5(-gHAf*#MxAvWW6KRE~*qy)gj+yYKrMPZN+(r=2kB*Of~js8+oyB{o%4V)h%Rt80}-X{z1AHKgG?%P>D zqE=?3)WwmSVpVL`Kbq)}*MLFST8!jlTEDtS+s6j@lwawaTy-^P~u#VUu$8 za`^dLbRDu|Ki7=nL@YNePWDVDij~i4OU+pi&uLQ!q67nN2#SfUIAb0|t9fe@uO)tx z5Z0N?#KH5exT{;v_wjy~9(6gCZtae5P5x0!v#T2)zd*VFN zp*UX5Lg!#ZDRG$$ReV}6nbdiMPMu~+?qzvWciJYJW{GRF!J9fW&2o-x@pBqQN>z$l z3y5{RIPovkmX?kDCvkuy`eG@sWg>#I0y{m;Y$9JI>gz@yO1dfk`=;oB=|YWZH8fRa zWXK=UV%r@OSQJNAMR|M4CaS~kCL2`H{l+T8m?i8qu)0cdg>ITKv`J4#rfrqv7nLF> zkD1BxRAr9lGUOD}MsYVPcVr%|FOx?DJ7Y<|O``#ke~=p>fe(O=u+?GUH{YS|2CsE!(7VNHR8IKBo69xol`Q z%o7tfrbvM4n2tK-YdXGrFQ1j3@$J$0_^7^Jk7&BLj*jdf-=(*0DS?dg{co+FS#R^>jnAab$gT_qI(1hsh$FM2 zi?%aSPc0JXU1uX_HjFu(Ct2K;L((^06FGSb$^-nFC%bB#jEdS&{!i-f?Ak#k+mNT!J#*Qqk41R`6krepf?Jib!LaR4?CZNV0g`+l;)usT2- zobd-1yHI+OJe1-G-DM9K2L0tUb=|yOp7!W~_NML1L%usOPF~%7hDljs9EpJ-UORH~k{-_KZw*KLz+L5hL{l}VGG2{Auq|E*Tjx)AkBNs6{sxO!-a~f;2&(}_I-3aplB zvdg$AROyMFd!7tc4~gr15e8j@2W*#NYc99MFISOH&LxM7kO|}}!UZ5fJm*y}h@>78p@~1w@&riWmh5Fxt_fmci%Ftg61X-*QTR#tR@g%M?mE01em>Oy9z0I@Irt9v=TQ2+0%gDd8u&j@)^`t$KMC)HRH+`6 z@jeKp{jtEcz>h=emqWjfP`(a#ss9|5b$&0j--R-y{~;*-4?~*N{ZRCN6v{j&0?$C1 z|1x|xyb7h?7`_uuAb;w!{7Cy3p@v_FGS6Q_ndg;I{%0t5`7d}L{xAF`xPmesfMY2B z^96{?)fb_B{}t%{0`jN6!H@L24W<3hpzPz@kRjDS2EGdCDZdWI-bdJ+%zqY&Ue=&| ze>Idp0maXE;9+GuKnTR)`K6QTWYsAMbZ@1czMFHrXL8kGHrQN(}lfili} zp~S@zDBrC>{#2hInSTgnoDmd%{}hz<+zjO}!(X8MO~}&KccH|~e?yj}4l|k9>0v12 zpMX-o1f~CFNLMw0vW^;xoqiX}`ksd}{vX0$gx`k00$+pj-AR-x89{CQGb^Ryg&sR#nchM+%d;s1DkHVjZD^T|JF(~WK zpv>DqS=XoGXW(Z;`Cgnx#<>s5_zyx^_bDj-9}m0$Wq((o=qrJu_coM&J(RxyF|E28 z>c0l>r+f>Zfd2qxKlid&AIDJUIU0Bpo};`3MZdoZAAx@aWxnr1+25;B^!UBNyHJ{p z_qM=yL+Sqzl>Wz{%=>8I1t{zJ2t*a?b5Qp6RVd@W2t_a74CQY@S4pMST1UvVh1wKZ0x_vQ~NIEW3tW zMJ^zhkm>UGIfop4-cP|I zV#||=#OzUI6_Mw32k(ROdJd6%DUZah#L`Elz_W&kjekxGJRd`T)xTj!_;`4~94LMz zwwFgEk0BDvV&6U@&nEJuf9rmRK>S^N;vyox^eFOi6Pa*OQkOVo8oI+ycEyyxLXfjzGi|A;l&Qsn-Nv@W% zOfBWPt4x)+yi`l$vCU&WaE>0`d*$}uw5xV(F>B5my`4N``K&CHVQynxIX$v!sjhZh z5%6%S?Z_mVI#XwQ?P)nvIT`CN@aS-298IWW4NKoGFvoi*^S1gu=;6Rlzx{| zk=e>JyDJJCRWb_OSZ4gn&eo`wYg+Sa`k^{&%cw{O78RO&XiMF=T8~U@=k}XWkYlvS7&J6>aG5{-YISK$i+6EQ_@=M9rLuM z(dN3WqsW%!cAcUAPKll3yP3xh6}qCCepsdnc8qmgTTxH$^l2IRvAJhP+3Dl{6@^x! zd6%nmg&XOpE=tUIjv4fmmoBO&f*#NJFI}81`n=7?>O6*EF15PtRS`|~s*TQ)ZS>Sc zvbD+#?J=yIiK>%oPOau8TG3|adD9y9fd5=HWuOsT3Z^uJ%&NqoMnd(HWjLQ}&irmDf{Es$OWk+Ldlk0Ke zh6VnmHi@oiCje8$_;NSbW^k!aQ=11vvo0~2`29l+@TLZaXN;MHZo0aNa1z{6ZPp`J z)u{fH{h5yYhM12D0@Ww$*n=_PMpoORa78(%F|A+8fp-6u_{jIiL^lu##pv8lNM*ss z+HH4`Fd^iZaABgp$(}6lcf=J8r^ehJ|_BRHVbj zBi#qtfe@PRsqC6^Lx_pL%CgioN5pdyc)Ge$Ori&-Orn|N5WqzpRdr#zMmxliRgsK1 zL57;(_SJH3ys?tA#UCW{Q>sDXQqtFSF{;a<_+m-^mQk>mlAu?9fs(QFN_EhAJsObZ zOWAUWd+ob*3B&)Es`3WmSa#(KZ!&j|vlcb4N~QJ%|Mxq2bbf(;lI(i@!sI2ZaK)m&%z={X<<{(`MxQQB>}Z+HMZV&3 zw&tSNmbn?btW3?ED~5C9>@mj4hc$_|*SBW0sLzq+OtfnVkLd?`!+)VqZ-=%YJQy3k zw0db}UsVfp^M{sQ&UwM2n=!i=tsW;aB>AI~O^ec2AM#0Xzn^Sw+ZMeQIn(f&MSW_J zREIbbW3$`+>ZrYQbMH%cextjXaIO@xdUdsIo3ct1i{aX4oRkgy_paZ$5w$uklRG!| zZl!l_?$^cc;5&|;+ud2mL|tuCmz!PPjuMkM(=kmhhrS-=OvBxU5M1fWl|>sV7)-^{%??+T!IG_HN|~<4+QsrB3$4LFn%%VSBpORWtJU znZ%CtZoPL)vsuR6wX7~`8~+iZSrzI+mZW*IdrX@_qu*D!Bkd%~@7!QeV!##&8{cqD zI*~1r%}U1DV%le;*5!`2abYIxZOk}{+NvfmzfiQZwX3DHBJ1R)`_ypGC1tD22>~)y zP**i}d>L~zw9SO+BFi2*#Lv!#s?}AQ-DtVjs12sme3P&%4x7ZX;MB#gQ=Rt>PBmGR zZXK6{z3YuvZD$2JA=(DLC-rFBZR|)jY?sSfDWHyhN5mvyU`3l3TvxhULhX!@|-`%*SrsII5#f)3QQaw&5*`WHn90n1Yb268H^;eEYj`NVGux7++|>PT%C(2IF8*6*cDQ%lb!Nuv z&k#~K|6w|*R8)1dBx!u_;zb!M!{|6UX&7oPq+}K2UT>9 zrP#e^@dQsM@nJ8HOtz7%(!F+3KuCq1IFsD6R2TX}E1C#%i2PRU=_h`xC5 zC5@AWsc@mGyNWK7?kZFnwp|*6-td1=X1tzixhb6u*+g?;3CW>;V`|V7e{tsvU=B+Eyq`9Rb1zQ%Nx~is(SCbcVEdh zXqq5Ve2gi)9rmu97(4q-&C=T5t)?Po@+4})&biDCBvvNkvsiRR0UUGp?oY0_aXM`M@x{BNVG&bq;zT>`Fb%D;L8%dr#o-l+crJ&Vp9 diff --git a/apps/catalogue/locale/pl/LC_MESSAGES/django.po b/apps/catalogue/locale/pl/LC_MESSAGES/django.po index c760f3a7..d7566e38 100644 --- a/apps/catalogue/locale/pl/LC_MESSAGES/django.po +++ b/apps/catalogue/locale/pl/LC_MESSAGES/django.po @@ -7,472 +7,534 @@ msgid "" msgstr "" "Project-Id-Version: Platforma Redakcyjna\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-06-21 13:41+0200\n" -"PO-Revision-Date: 2011-06-21 13:46+0100\n" +"POT-Creation-Date: 2011-10-03 15:56+0200\n" +"PO-Revision-Date: 2011-10-03 15:56+0100\n" "Last-Translator: Radek Czajka \n" "Language-Team: Fundacja Nowoczesna Polska \n" -"Language: \n" +"Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" -#: forms.py:32 -msgid "Publishable" -msgstr "Gotowe do publikacji" - -#: forms.py:68 +#: forms.py:46 msgid "ZIP file" msgstr "Plik ZIP" +#: forms.py:47 +msgid "Directories are documents in chunks" +msgstr "Katalogi zawierają dokumenty w częściach" + +#: forms.py:85 #: forms.py:99 -#: forms.py:137 -msgid "Author" -msgstr "Autor" - -#: forms.py:100 -#: forms.py:138 -msgid "Your name" -msgstr "Imię i nazwisko" - -#: forms.py:105 -#: forms.py:143 -msgid "Author's email" -msgstr "E-mail autora" - -#: forms.py:106 -#: forms.py:144 -msgid "Your email address, so we can show a gravatar :)" -msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)" - -#: forms.py:112 -#: forms.py:150 -msgid "Your comments" -msgstr "Twój komentarz" - -#: forms.py:113 -msgid "Describe changes you made." -msgstr "Opisz swoje zmiany" - -#: forms.py:119 -msgid "Completed" -msgstr "Ukończono" - -#: forms.py:120 -msgid "If you completed a life cycle stage, select it." -msgstr "Jeśli został ukończony etap prac, wskaż go." - -#: forms.py:151 -msgid "Describe the reason for reverting." -msgstr "Opisz powód przywrócenia." - -#: forms.py:176 -#: forms.py:190 msgid "Chunk with this slug already exists" msgstr "Część z tym slugiem już istnieje" -#: forms.py:202 +#: forms.py:109 msgid "Append to" msgstr "Dołącz do" -#: models.py:25 +#: views.py:137 +#, python-format +msgid "Slug already used for %s" +msgstr "Slug taki sam jak dla pliku %s" + +#: views.py:139 +msgid "Slug already used in repository." +msgstr "Dokument o tym slugu już istnieje w repozytorium." + +#: views.py:145 +msgid "File should be UTF-8 encoded." +msgstr "Plik powinien mieć kodowanie UTF-8." + +#: models/book.py:20 +#: models/chunk.py:24 msgid "title" msgstr "tytuł" -#: models.py:26 +#: models/book.py:21 +#: models/chunk.py:23 msgid "slug" -msgstr "" +msgstr "slug" -#: models.py:27 +#: models/book.py:22 msgid "scan gallery name" msgstr "nazwa galerii skanów" -#: models.py:29 +#: models/book.py:25 msgid "parent" msgstr "rodzic" -#: models.py:30 +#: models/book.py:26 msgid "parent number" msgstr "numeracja rodzica" -#: models.py:40 +#: models/book.py:43 +#: models/chunk.py:21 +#: models/publish_log.py:17 msgid "book" msgstr "książka" -#: models.py:41 +#: models/book.py:44 msgid "books" msgstr "książki" -#: models.py:206 -msgid "name" -msgstr "nazwa" +#: models/book.py:45 +msgid "Can mark for publishing" +msgstr "Oznacza do publikacji" -#: models.py:210 -msgid "theme" -msgstr "motyw" +#: models/chunk.py:22 +msgid "number" +msgstr "numer" -#: models.py:211 -msgid "themes" -msgstr "motywy" +#: models/chunk.py:39 +msgid "chunk" +msgstr "część" -#: views.py:241 -#, python-format -msgid "Slug already used for %s" -msgstr "Slug taki sam jak dla pliku %s" +#: models/chunk.py:40 +msgid "chunks" +msgstr "części" -#: views.py:243 -msgid "Slug already used in repository." -msgstr "Dokument o tym slugu już istnieje w repozytorium." +#: models/publish_log.py:18 +msgid "time" +msgstr "czas" -#: views.py:249 -msgid "File should be UTF-8 encoded." -msgstr "Plik powinien mieć kodowanie UTF-8." +#: models/publish_log.py:19 +msgid "user" +msgstr "użytkownik" -#: views.py:655 -msgid "Tag added" -msgstr "Dodano tag" +#: models/publish_log.py:24 +#: models/publish_log.py:33 +msgid "book publish record" +msgstr "zapis publikacji książki" -#: views.py:677 -msgid "Revision marked" -msgstr "Wersja oznaczona" +#: models/publish_log.py:25 +msgid "book publish records" +msgstr "zapisy publikacji książek" -#: views.py:679 -msgid "Nothing changed" -msgstr "Nic nie uległo zmianie" +#: models/publish_log.py:34 +msgid "change" +msgstr "zmiana" -#: templates/wiki/base.html:9 +#: models/publish_log.py:38 +msgid "chunk publish record" +msgstr "zapis publikacji części" + +#: models/publish_log.py:39 +msgid "chunk publish records" +msgstr "zapisy publikacji części" + +#: templates/catalogue/base.html:9 msgid "Platforma Redakcyjna" -msgstr "" +msgstr "Platforma Redakcyjna" -#: templates/wiki/book_append_to.html:8 +#: templates/catalogue/book_append_to.html:9 msgid "Append book" msgstr "Dołącz książkę" -#: templates/wiki/book_detail.html:6 -#: templates/wiki/book_detail.html.py:46 +#: templates/catalogue/book_detail.html:6 +#: templates/catalogue/book_detail.html:46 msgid "edit" msgstr "edytuj" -#: templates/wiki/book_detail.html:16 +#: templates/catalogue/book_detail.html:16 msgid "add basic document structure" msgstr "dodaj podstawową strukturę dokumentu" -#: templates/wiki/book_detail.html:20 +#: templates/catalogue/book_detail.html:20 msgid "change master tag to" msgstr "zmień tak master na" -#: templates/wiki/book_detail.html:24 +#: templates/catalogue/book_detail.html:24 msgid "add begin trimming tag" msgstr "dodaj początkowy ogranicznik" -#: templates/wiki/book_detail.html:28 +#: templates/catalogue/book_detail.html:28 msgid "add end trimming tag" msgstr "dodaj końcowy ogranicznik" -#: templates/wiki/book_detail.html:34 +#: templates/catalogue/book_detail.html:34 msgid "unstructured text" msgstr "tekst bez struktury" -#: templates/wiki/book_detail.html:38 +#: templates/catalogue/book_detail.html:38 msgid "unknown XML" msgstr "nieznany XML" -#: templates/wiki/book_detail.html:42 +#: templates/catalogue/book_detail.html:42 msgid "broken document" msgstr "uszkodzony dokument" -#: templates/wiki/book_detail.html:60 +#: templates/catalogue/book_detail.html:61 msgid "Apply fixes" msgstr "Wykonaj zmiany" -#: templates/wiki/book_detail.html:66 +#: templates/catalogue/book_detail.html:67 msgid "Append to other book" msgstr "Dołącz do innej książki" -#: templates/wiki/book_detail.html:68 +#: templates/catalogue/book_detail.html:69 msgid "Last published" msgstr "Ostatnio opublikowano" -#: templates/wiki/book_detail.html:72 +#: templates/catalogue/book_detail.html:73 msgid "Full XML" msgstr "Pełny XML" -#: templates/wiki/book_detail.html:73 +#: templates/catalogue/book_detail.html:74 msgid "HTML version" msgstr "Wersja HTML" -#: templates/wiki/book_detail.html:74 +#: templates/catalogue/book_detail.html:75 msgid "TXT version" msgstr "Wersja TXT" -#: templates/wiki/book_detail.html:76 -msgid "EPUB version" -msgstr "Wersja EPUB" - -#: templates/wiki/book_detail.html:77 -msgid "PDF version" -msgstr "Wersja PDF" - -#: templates/wiki/book_detail.html:90 -#: templates/wiki/tabs/summary_view.html:30 +#: templates/catalogue/book_detail.html:92 msgid "Publish" msgstr "Opublikuj" -#: templates/wiki/book_detail.html:94 +#: templates/catalogue/book_detail.html:96 msgid "This book cannot be published yet" msgstr "Ta książka nie może jeszcze zostać opublikowana" -#: templates/wiki/book_edit.html:8 -#: templates/wiki/chunk_edit.html:8 -#: templates/wiki/document_details_base.html:35 -#: templates/wiki/pubmark_dialog.html:15 -#: templates/wiki/tag_dialog.html:15 +#: templates/catalogue/book_edit.html:9 +#: templates/catalogue/chunk_edit.html:9 msgid "Save" msgstr "Zapisz" -#: templates/wiki/chunk_add.html:8 +#: templates/catalogue/chunk_add.html:9 msgid "Add chunk" msgstr "Dodaj część" -#: templates/wiki/diff_table.html:5 -msgid "Old version" -msgstr "Stara wersja" - -#: templates/wiki/diff_table.html:6 -msgid "New version" -msgstr "Nowa wersja" - -#: templates/wiki/document_create_missing.html:8 +#: templates/catalogue/document_create_missing.html:9 msgid "Create document" msgstr "Utwórz dokument" -#: templates/wiki/document_details.html:32 -msgid "Click to open/close gallery" -msgstr "Kliknij, aby (ro)zwinąć galerię" - -#: templates/wiki/document_details_base.html:31 -msgid "Help" -msgstr "Pomoc" - -#: templates/wiki/document_details_base.html:33 -msgid "Version" -msgstr "Wersja" - -#: templates/wiki/document_details_base.html:33 -msgid "Unknown" -msgstr "nieznana" - -#: templates/wiki/document_details_base.html:36 -msgid "Save attempt in progress" -msgstr "Trwa zapisywanie" - -#: templates/wiki/document_details_base.html:37 -msgid "There is a newer version of this document!" -msgstr "Istnieje nowsza wersja tego dokumentu!" - -#: templates/wiki/document_list.html:31 -msgid "Clear filter" -msgstr "Wyczyść filtr" - -#: templates/wiki/document_list.html:46 -msgid "No books found." -msgstr "Nie znaleziono książek." - -#: templates/wiki/document_list.html:89 -msgid "Your last edited documents" -msgstr "Twoje ostatnie edycje" - -#: templates/wiki/document_upload.html:8 +#: templates/catalogue/document_upload.html:8 msgid "Bulk documents upload" msgstr "Hurtowe dodawanie dokumentów" -#: templates/wiki/document_upload.html:11 +#: templates/catalogue/document_upload.html:11 msgid "Please submit a ZIP with UTF-8 encoded XML files. Files not ending with .xml will be ignored." msgstr "Proszę wskazać archiwum ZIP z plikami XML w kodowaniu UTF-8. Pliki nie kończące się na .xml zostaną zignorowane." -#: templates/wiki/document_upload.html:16 -#: templatetags/wiki.py:36 +#: templates/catalogue/document_upload.html:17 +#: templatetags/catalogue.py:34 msgid "Upload" msgstr "Załaduj" -#: templates/wiki/document_upload.html:23 +#: templates/catalogue/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:24 +#: templates/catalogue/document_upload.html:25 msgid "Offending files" msgstr "Błędne pliki" -#: templates/wiki/document_upload.html:32 +#: templates/catalogue/document_upload.html:33 msgid "Correct files" msgstr "Poprawne pliki" -#: templates/wiki/document_upload.html:43 +#: templates/catalogue/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:44 +#: templates/catalogue/document_upload.html:45 msgid "Uploaded files" msgstr "Dodane pliki" -#: templates/wiki/document_upload.html:54 +#: templates/catalogue/document_upload.html:55 msgid "Skipped files" msgstr "Pominięte pliki" -#: templates/wiki/document_upload.html:55 +#: templates/catalogue/document_upload.html:56 msgid "Files skipped due to no .xml extension" msgstr "Pliki pominięte z powodu braku rozszerzenia .xml." -#: templates/wiki/pubmark_dialog.html:16 -#: templates/wiki/revert_dialog.html:39 -#: templates/wiki/tag_dialog.html:16 -msgid "Cancel" -msgstr "Anuluj" +#: templates/catalogue/my_page.html:13 +msgid "Your last edited documents" +msgstr "Twoje ostatnie edycje" -#: templates/wiki/revert_dialog.html:38 -msgid "Revert" -msgstr "Przywróć" +#: templates/catalogue/my_page.html:22 +#: templates/catalogue/user_page.html:13 +msgid "Recent activity for" +msgstr "Ostatnia aktywność dla:" -#: templates/wiki/user_list.html:7 -#: templatetags/wiki.py:33 +#: templates/catalogue/user_list.html:7 +#: templatetags/catalogue.py:32 msgid "Users" msgstr "Użytkownicy" -#: templates/wiki/tabs/annotations_view.html:9 -msgid "all" -msgstr "wszystkie" +#: templates/catalogue/book_list/book.html:6 +#: templates/catalogue/book_list/book.html:25 +msgid "Book settings" +msgstr "Ustawienia książki" -#: templates/wiki/tabs/annotations_view_item.html:3 -msgid "Annotations" -msgstr "Przypisy" +#: templates/catalogue/book_list/book.html:7 +#: templates/catalogue/book_list/chunk.html:5 +msgid "Chunk settings" +msgstr "Ustawienia części" -#: templates/wiki/tabs/gallery_view.html:7 -msgid "Previous" -msgstr "Poprzednie" +#: templates/catalogue/book_list/book_list.html:19 +msgid "Show hidden books" +msgstr "Pokaż ukryte książki" -#: templates/wiki/tabs/gallery_view.html:13 -msgid "Next" -msgstr "Następne" +#: templates/catalogue/book_list/book_list.html:24 +msgid "Search in book titles" +msgstr "Szukaj w tytułach książek" -#: templates/wiki/tabs/gallery_view.html:15 -msgid "Zoom in" -msgstr "Powiększ" +#: templates/catalogue/book_list/book_list.html:29 +msgid "stage" +msgstr "etap" -#: templates/wiki/tabs/gallery_view.html:16 -msgid "Zoom out" -msgstr "Zmniejsz" +#: templates/catalogue/book_list/book_list.html:31 +#: templates/catalogue/book_list/book_list.html:42 +msgid "none" +msgstr "brak" -#: templates/wiki/tabs/gallery_view_item.html:3 -msgid "Gallery" -msgstr "Galeria" +#: templates/catalogue/book_list/book_list.html:40 +msgid "editor" +msgstr "redaktor" -#: templates/wiki/tabs/history_view.html:5 -msgid "Compare versions" -msgstr "Porównaj wersje" +#: templates/catalogue/book_list/book_list.html:51 +msgid "status" +msgstr "status" -#: templates/wiki/tabs/history_view.html:7 -msgid "Mark for publishing" -msgstr "Oznacz do publikacji" +#: templates/catalogue/book_list/book_list.html:75 +#, python-format +msgid "%(c)s book" +msgid_plural "%(c)s books" +msgstr[0] "%(c)s książka" +msgstr[1] "%(c)s książki" +msgstr[2] "%(c)s książek" -#: templates/wiki/tabs/history_view.html:9 -msgid "Revert document" -msgstr "Przywróć wersję" +#: templates/catalogue/book_list/book_list.html:80 +msgid "No books found." +msgstr "Nie znaleziono książek." -#: templates/wiki/tabs/history_view.html:12 -msgid "View version" -msgstr "Zobacz wersję" +#: templatetags/book_list.py:81 +msgid "publishable" +msgstr "do publikacji" -#: templates/wiki/tabs/history_view_item.html:3 -msgid "History" -msgstr "Historia" +#: templatetags/book_list.py:82 +msgid "changed" +msgstr "zmienione" -#: templates/wiki/tabs/search_view.html:3 -#: templates/wiki/tabs/search_view.html:5 -msgid "Search" -msgstr "Szukaj" +#: templatetags/book_list.py:83 +msgid "published" +msgstr "opublikowane" -#: templates/wiki/tabs/search_view.html:8 -msgid "Replace with" -msgstr "Zamień na" +#: templatetags/book_list.py:84 +msgid "unpublished" +msgstr "nie opublikowane" -#: templates/wiki/tabs/search_view.html:10 -msgid "Replace" -msgstr "Zamień" +#: templatetags/book_list.py:85 +msgid "empty" +msgstr "puste" -#: templates/wiki/tabs/search_view.html:13 -msgid "Options" -msgstr "Opcje" +#: templatetags/catalogue.py:28 +msgid "My page" +msgstr "Moja strona" -#: templates/wiki/tabs/search_view.html:15 -msgid "Case sensitive" -msgstr "Rozróżniaj wielkość liter" +#: templatetags/catalogue.py:30 +msgid "Activity" +msgstr "Aktywność" -#: templates/wiki/tabs/search_view.html:17 -msgid "From cursor" -msgstr "Zacznij od kursora" +#: templatetags/catalogue.py:31 +msgid "All" +msgstr "Wszystkie" -#: templates/wiki/tabs/search_view_item.html:3 -msgid "Search and replace" -msgstr "Znajdź i zamień" +#: templatetags/catalogue.py:33 +msgid "Add" +msgstr "Dodaj" -#: templates/wiki/tabs/source_editor_item.html:5 -msgid "Source code" -msgstr "Kod źródłowy" +#: templatetags/catalogue.py:37 +msgid "Admin" +msgstr "Administracja" -#: templates/wiki/tabs/summary_view.html:9 -msgid "Title" -msgstr "Tytuł" +#: templatetags/wall.py:43 +msgid "Related edit" +msgstr "Powiązana zmiana" -#: templates/wiki/tabs/summary_view.html:14 -msgid "Document ID" -msgstr "ID dokumentu" +#: templatetags/wall.py:45 +msgid "Edit" +msgstr "Zmiana" -#: templates/wiki/tabs/summary_view.html:18 -msgid "Current version" -msgstr "Aktualna wersja" +#: templatetags/wall.py:67 +msgid "Publication" +msgstr "Publikacja" -#: templates/wiki/tabs/summary_view.html:21 -msgid "Last edited by" -msgstr "Ostatnio edytowane przez" +#: templatetags/wall.py:84 +msgid "Comment" +msgstr "Komentarz" -#: templates/wiki/tabs/summary_view.html:25 -msgid "Link to gallery" -msgstr "Link do galerii" +#~ msgid "Author" +#~ msgstr "Autor" -#: templates/wiki/tabs/summary_view_item.html:3 -msgid "Summary" -msgstr "Podsumowanie" +#~ msgid "Your name" +#~ msgstr "Imię i nazwisko" -#: templates/wiki/tabs/wysiwyg_editor.html:9 -msgid "Insert theme" -msgstr "Wstaw motyw" +#~ msgid "Author's email" +#~ msgstr "E-mail autora" -#: templates/wiki/tabs/wysiwyg_editor.html:12 -msgid "Insert annotation" -msgstr "Wstaw przypis" +#~ msgid "Your email address, so we can show a gravatar :)" +#~ msgstr "Adres e-mail, żebyśmy mogli pokazać gravatar :)" -#: templates/wiki/tabs/wysiwyg_editor_item.html:3 -msgid "Visual editor" -msgstr "Edytor wizualny" +#~ msgid "Describe changes you made." +#~ msgstr "Opisz swoje zmiany" -#: templatetags/wiki.py:30 -msgid "Assigned to me" -msgstr "Przypisane do mnie" +#~ msgid "Completed" +#~ msgstr "Ukończono" -#: templatetags/wiki.py:32 -msgid "Unassigned" -msgstr "Nie przypisane" +#~ msgid "If you completed a life cycle stage, select it." +#~ msgstr "Jeśli został ukończony etap prac, wskaż go." -#: templatetags/wiki.py:34 -msgid "All" -msgstr "Wszystkie" +#~ msgid "Describe the reason for reverting." +#~ msgstr "Opisz powód przywrócenia." -#: templatetags/wiki.py:35 -msgid "Add" -msgstr "Dodaj" +#~ msgid "name" +#~ msgstr "nazwa" -#: templatetags/wiki.py:39 -msgid "Admin" -msgstr "Administracja" +#~ msgid "theme" +#~ msgstr "motyw" + +#~ msgid "themes" +#~ msgstr "motywy" + +#~ msgid "Tag added" +#~ msgstr "Dodano tag" + +#~ msgid "Revision marked" +#~ msgstr "Wersja oznaczona" + +#~ msgid "EPUB version" +#~ msgstr "Wersja EPUB" + +#~ msgid "PDF version" +#~ msgstr "Wersja PDF" + +#~ msgid "Old version" +#~ msgstr "Stara wersja" + +#~ msgid "New version" +#~ msgstr "Nowa wersja" + +#~ msgid "Click to open/close gallery" +#~ msgstr "Kliknij, aby (ro)zwinąć galerię" + +#~ msgid "Help" +#~ msgstr "Pomoc" + +#~ msgid "Version" +#~ msgstr "Wersja" + +#~ msgid "Unknown" +#~ msgstr "nieznana" + +#~ msgid "Save attempt in progress" +#~ msgstr "Trwa zapisywanie" + +#~ msgid "There is a newer version of this document!" +#~ msgstr "Istnieje nowsza wersja tego dokumentu!" + +#~ msgid "Clear filter" +#~ msgstr "Wyczyść filtr" + +#~ msgid "Cancel" +#~ msgstr "Anuluj" + +#~ msgid "Revert" +#~ msgstr "Przywróć" + +#~ msgid "all" +#~ msgstr "wszystkie" + +#~ msgid "Annotations" +#~ msgstr "Przypisy" + +#~ msgid "Previous" +#~ msgstr "Poprzednie" + +#~ msgid "Next" +#~ msgstr "Następne" + +#~ msgid "Zoom in" +#~ msgstr "Powiększ" + +#~ msgid "Zoom out" +#~ msgstr "Zmniejsz" + +#~ msgid "Gallery" +#~ msgstr "Galeria" + +#~ msgid "Compare versions" +#~ msgstr "Porównaj wersje" + +#~ msgid "Revert document" +#~ msgstr "Przywróć wersję" + +#~ msgid "View version" +#~ msgstr "Zobacz wersję" + +#~ msgid "History" +#~ msgstr "Historia" + +#~ msgid "Search" +#~ msgstr "Szukaj" + +#~ msgid "Replace with" +#~ msgstr "Zamień na" + +#~ msgid "Replace" +#~ msgstr "Zamień" + +#~ msgid "Options" +#~ msgstr "Opcje" + +#~ msgid "Case sensitive" +#~ msgstr "Rozróżniaj wielkość liter" + +#~ msgid "From cursor" +#~ msgstr "Zacznij od kursora" + +#~ msgid "Search and replace" +#~ msgstr "Znajdź i zamień" + +#~ msgid "Source code" +#~ msgstr "Kod źródłowy" + +#~ msgid "Title" +#~ msgstr "Tytuł" + +#~ msgid "Document ID" +#~ msgstr "ID dokumentu" + +#~ msgid "Current version" +#~ msgstr "Aktualna wersja" + +#~ msgid "Last edited by" +#~ msgstr "Ostatnio edytowane przez" + +#~ msgid "Link to gallery" +#~ msgstr "Link do galerii" + +#~ msgid "Summary" +#~ msgstr "Podsumowanie" + +#~ msgid "Insert theme" +#~ msgstr "Wstaw motyw" + +#~ msgid "Insert annotation" +#~ msgstr "Wstaw przypis" + +#~ msgid "Visual editor" +#~ msgstr "Edytor wizualny" + +#~ msgid "Assigned to me" +#~ msgstr "Przypisane do mnie" + +#~ msgid "Unassigned" +#~ msgstr "Nie przypisane" #~ msgid "First correction" #~ msgstr "Autokorekta" diff --git a/apps/catalogue/management/commands/import_wl.py b/apps/catalogue/management/commands/import_wl.py index 6836d366..4f15cef8 100755 --- a/apps/catalogue/management/commands/import_wl.py +++ b/apps/catalogue/management/commands/import_wl.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from collections import defaultdict import json from optparse import make_option import urllib2 @@ -35,9 +36,9 @@ class Command(BaseCommand): transaction.managed(True) if verbose: - print 'Reading currently managed files.' - slugs = {} - for b in Book.objects.all(): + print 'Reading currently managed files (skipping hidden ones).' + slugs = defaultdict(list) + for b in Book.objects.exclude(slug__startswith='.').all(): if verbose: print b.slug text = b.materialize().encode('utf-8') @@ -46,28 +47,49 @@ class Command(BaseCommand): except (ParseError, ValidationError): pass else: - slugs[info.slug] = b + slugs[info.slug].append(b) + + #~ conflicts = [] + #~ for slug, book_list in slugs.items(): + #~ if len(book_list) > 1: + #~ conflicts.append((slug, book_list)) + #~ if conflicts: + #~ print self.style.ERROR("There is more than one book " + #~ "with the same slug in dc:url. " + #~ "Merge or hide them before proceeding.") + #~ for slug, book_list in sorted(conflicts): + #~ print slug + #~ print "\n".join(b.slug for b in book_list) + #~ print + #~ return book_count = 0 commit_args = { "author_name": 'Platforma', - "description": 'Import from WL', + "description": 'Automatycznie zaimportowane z Wolnych Lektur', + "publishable": True, } if verbose: print 'Opening books list' - for book in json.load(urllib2.urlopen(WL_API)): + for book in json.load(urllib2.urlopen(WL_API))[:10]: book_detail = json.load(urllib2.urlopen(book['href'])) xml_text = urllib2.urlopen(book_detail['xml']).read() info = BookInfo.from_string(xml_text) - previous_book = slugs.get(info.slug, None) - if previous_book: + previous_books = slugs.get(info.slug) + if previous_books: + if len(previous_books) > 1: + print self.style.ERROR("There is more than one book " + "with slug %s:"), + previous_book = previous_books[0] comm = previous_book.slug else: + previous_book = None comm = '*' print book_count, info.slug , '-->', comm Book.import_xml_text(xml_text, title=info.title, - slug=info.slug, previous_book=slugs.get(info.slug, None)) + slug=info.slug, previous_book=previous_book, + commit_args=commit_args) book_count += 1 # Print results diff --git a/apps/catalogue/management/commands/merge_books.py b/apps/catalogue/management/commands/merge_books.py index 4747591c..00014dfd 100755 --- a/apps/catalogue/management/commands/merge_books.py +++ b/apps/catalogue/management/commands/merge_books.py @@ -24,51 +24,6 @@ def common_prefix(texts): return "".join(common) -def print_guess(dry_run=True): - from collections import defaultdict - from pipes import quote - import re - - def read_slug(slug): - res = [] - res.append((re.compile(ur'__?(przedmowa)$'), -1)) - res.append((re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P\d*)$'), None)) - res.append((re.compile(ur'__?(rozdzialy__?)?(?P\d*)-'), None)) - - for r, default in res: - m = r.search(slug) - if m: - start = m.start() - try: - return int(m.group('n')), slug[:start] - except IndexError: - return default, slug[:start] - return None, slug - - def file_to_title(fname): - """ Returns a title-like version of a filename. """ - parts = (p.replace('_', ' ').title() for p in fname.split('__')) - return ' / '.join(parts) - - merges = defaultdict(list) - for b in Book.objects.all(): - n, ns = read_slug(b.slug) - if n is not None: - merges[ns].append((n, b)) - - for slug in sorted(merges.keys()): - merge_list = sorted(merges[slug]) - if len(merge_list) < 2: - continue - - title = file_to_title(slug) - print "./manage.py merge_books %s--title=%s --slug=%s \\\n %s\n" % ( - '--dry-run ' if dry_run else '', - quote(title), slug, - " \\\n ".join(b.slug for i, b in merge_list) - ) - - class Command(BaseCommand): option_list = BaseCommand.option_list + ( make_option('-s', '--slug', dest='new_slug', metavar='SLUG', @@ -81,14 +36,79 @@ class Command(BaseCommand): help='Try to guess what merges are needed (but do not apply them).'), make_option('-d', '--dry-run', action='store_true', dest='dry_run', default=False, help='Dry run: do not actually change anything.'), + make_option('-f', '--force', action='store_true', dest='force', default=False, + help='On slug conflict, hide the original book to archive.'), ) help = 'Merges multiple books into one.' args = '[slug]...' + + def print_guess(self, dry_run=True, force=False): + from collections import defaultdict + from pipes import quote + import re + + def read_slug(slug): + res = [] + res.append((re.compile(ur'__?(przedmowa)$'), -1)) + res.append((re.compile(ur'__?(cz(esc)?|ksiega|rozdzial)__?(?P\d*)$'), None)) + res.append((re.compile(ur'__?(rozdzialy__?)?(?P\d*)-'), None)) + + for r, default in res: + m = r.search(slug) + if m: + start = m.start() + try: + return int(m.group('n')), slug[:start] + except IndexError: + return default, slug[:start] + return None, slug + + def file_to_title(fname): + """ Returns a title-like version of a filename. """ + parts = (p.replace('_', ' ').title() for p in fname.split('__')) + return ' / '.join(parts) + + merges = defaultdict(list) + slugs = [] + for b in Book.objects.all(): + slugs.append(b.slug) + n, ns = read_slug(b.slug) + if n is not None: + merges[ns].append((n, b)) + + conflicting_slugs = [] + for slug in sorted(merges.keys()): + merge_list = sorted(merges[slug]) + if len(merge_list) < 2: + continue + + merge_slugs = [b.slug for i, b in merge_list] + if slug in slugs and slug not in merge_slugs: + conflicting_slugs.append(slug) + + title = file_to_title(slug) + print "./manage.py merge_books %s%s--title=%s --slug=%s \\\n %s\n" % ( + '--dry-run ' if dry_run else '', + '--force ' if force else '', + quote(title), slug, + " \\\n ".join(merge_slugs) + ) + + if conflicting_slugs: + if force: + print self.style.NOTICE('# These books will be archived:') + else: + print self.style.ERROR('# ERROR: Conflicting slugs:') + for slug in conflicting_slugs: + print '#', slug + + def handle(self, *slugs, **options): self.style = color_style() + force = options.get('force') guess = options.get('guess') dry_run = options.get('dry_run') new_slug = options.get('new_slug') @@ -100,19 +120,17 @@ class Command(BaseCommand): print "Please specify either slugs, or --guess." return else: - print_guess(dry_run) + self.print_guess(dry_run, force) return if not slugs: print "Please specify some book slugs" return - # Start transaction management. transaction.commit_unless_managed() transaction.enter_transaction_management() transaction.managed(True) - books = [Book.objects.get(slug=slug) for slug in slugs] common_slug = common_prefix(slugs) common_title = common_prefix([b.title for b in books]) @@ -127,6 +145,10 @@ class Command(BaseCommand): elif common_slug.startswith(new_slug): common_slug = new_slug + if slugs[0] != new_slug and Book.objects.filter(slug=new_slug).exists(): + self.style.ERROR('Book already exists, skipping!') + + if dry_run and verbose: print self.style.NOTICE('DRY RUN: nothing will be changed.') print @@ -161,6 +183,24 @@ class Command(BaseCommand): print if not dry_run: + try: + conflict = Book.objects.get(slug=new_slug) + except Book.DoesNotExist: + conflict = None + else: + if conflict == books[0]: + conflict = None + + if conflict: + if force: + # FIXME: there still may be a conflict + conflict.slug = '.' + conflict.slug + conflict.save() + print self.style.NOTICE('Book with slug "%s" moved to "%s".' % (new_slug, conflict.slug)) + else: + print self.style.ERROR('ERROR: Book with slug "%s" exists.' % new_slug) + return + if i: books[0].append(books[i], slugs=chunk_slugs, titles=chunk_titles) else: diff --git a/apps/catalogue/managers.py b/apps/catalogue/managers.py new file mode 100644 index 00000000..4f804b84 --- /dev/null +++ b/apps/catalogue/managers.py @@ -0,0 +1,5 @@ +from django.db import models + +class VisibleManager(models.Manager): + def get_query_set(self): + return super(VisibleManager, self).get_query_set().exclude(_hidden=True) diff --git a/apps/catalogue/migrations/0001_initial.py b/apps/catalogue/migrations/0001_initial.py index 8bd220ad..dccd9b7b 100644 --- a/apps/catalogue/migrations/0001_initial.py +++ b/apps/catalogue/migrations/0001_initial.py @@ -4,7 +4,6 @@ from south.db import db from south.v2 import SchemaMigration from django.db import models - class Migration(SchemaMigration): def forwards(self, orm): @@ -17,6 +16,10 @@ class Migration(SchemaMigration): ('gallery', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='children', null=True, to=orm['catalogue.Book'])), ('parent_number', self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True)), + ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('_single', self.gf('django.db.models.fields.NullBooleanField')(db_index=True, null=True, blank=True)), + ('_new_publishable', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('_published', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), )) db.send_create_signal('catalogue', ['Book']) @@ -29,6 +32,9 @@ class Migration(SchemaMigration): ('number', self.gf('django.db.models.fields.IntegerField')()), ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)), ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('_short_html', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('_hidden', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), + ('_changed', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), ('stage', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ChunkTag'], null=True, blank=True)), ('head', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['catalogue.ChunkChange'], null=True, blank=True)), )) @@ -55,7 +61,6 @@ class Migration(SchemaMigration): ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)), ('author_name', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), ('author_email', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), - ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)), ('revision', self.gf('django.db.models.fields.IntegerField')(db_index=True)), ('parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='children', null=True, blank=True, to=orm['catalogue.ChunkChange'])), ('merge_parent', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='merge_children', null=True, blank=True, to=orm['catalogue.ChunkChange'])), @@ -63,6 +68,7 @@ class Migration(SchemaMigration): ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, db_index=True)), ('publishable', self.gf('django.db.models.fields.BooleanField')(default=False)), ('tree', self.gf('django.db.models.fields.related.ForeignKey')(related_name='change_set', to=orm['catalogue.Chunk'])), + ('data', self.gf('django.db.models.fields.files.FileField')(max_length=100)), )) db.send_create_signal('catalogue', ['ChunkChange']) @@ -80,7 +86,7 @@ class Migration(SchemaMigration): # Adding model 'BookPublishRecord' db.create_table('catalogue_bookpublishrecord', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Book'])), + ('book', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.Book'])), ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), )) @@ -90,14 +96,10 @@ class Migration(SchemaMigration): db.create_table('catalogue_chunkpublishrecord', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('book_record', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.BookPublishRecord'])), - ('change', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.ChunkChange'])), + ('change', self.gf('django.db.models.fields.related.ForeignKey')(related_name='publish_log', to=orm['catalogue.ChunkChange'])), )) db.send_create_signal('catalogue', ['ChunkPublishRecord']) - if not db.dry_run: - from django.core.management import call_command - call_command("loaddata", "stages.json") - def backwards(self, orm): @@ -164,6 +166,10 @@ class Migration(SchemaMigration): }, 'catalogue.book': { 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), @@ -173,21 +179,24 @@ class Migration(SchemaMigration): }, 'catalogue.bookpublishrecord': { 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) }, 'catalogue.chunk': { 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}), 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'number': ('django.db.models.fields.IntegerField', [], {}), 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) }, 'catalogue.chunkchange': { @@ -209,7 +218,7 @@ class Migration(SchemaMigration): 'catalogue.chunkpublishrecord': { 'Meta': {'object_name': 'ChunkPublishRecord'}, 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkChange']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'catalogue.chunktag': { diff --git a/apps/catalogue/migrations/0002_stages.py b/apps/catalogue/migrations/0002_stages.py new file mode 100644 index 00000000..71554570 --- /dev/null +++ b/apps/catalogue/migrations/0002_stages.py @@ -0,0 +1,122 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + + from django.core.management import call_command + call_command("loaddata", "stages.json") + + + def backwards(self, orm): + "Write your backwards methods here." + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'catalogue.book': { + 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + 'catalogue.bookpublishrecord': { + 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'catalogue.chunk': { + 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}), + 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'number': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'catalogue.chunkchange': { + 'Meta': {'ordering': "('created_at',)", 'unique_together': "(['tree', 'revision'],)", 'object_name': 'ChunkChange'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'author_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}), + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'merge_parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'merge_children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'blank': 'True', 'to': "orm['catalogue.ChunkChange']"}), + 'publishable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'change_set'", 'symmetrical': 'False', 'to': "orm['catalogue.ChunkTag']"}), + 'tree': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_set'", 'to': "orm['catalogue.Chunk']"}) + }, + 'catalogue.chunkpublishrecord': { + 'Meta': {'object_name': 'ChunkPublishRecord'}, + 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'catalogue.chunktag': { + 'Meta': {'ordering': "['ordering']", 'object_name': 'ChunkTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'ordering': ('django.db.models.fields.IntegerField', [], {}), + 'slug': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['catalogue'] diff --git a/apps/catalogue/migrations/0002_from_hg.py b/apps/catalogue/migrations/0003_from_hg.py similarity index 92% rename from apps/catalogue/migrations/0002_from_hg.py rename to apps/catalogue/migrations/0003_from_hg.py index 2a64819e..1816af90 100644 --- a/apps/catalogue/migrations/0002_from_hg.py +++ b/apps/catalogue/migrations/0003_from_hg.py @@ -89,7 +89,7 @@ def migrate_file_from_hg(orm, fname, entry): gallery_link = None # this will fail if directory exists - os.makedirs(os.path.join(settings.DVCS_REPO_PATH, str(chunk.pk))) + os.makedirs(os.path.join(settings.CATALOGUE_REPO_PATH, str(chunk.pk))) for rev in xrange(maxrev + 1): fctx = entry.filectx(rev) @@ -135,7 +135,7 @@ def migrate_file_from_hg(orm, fname, entry): ) path = "%d/%d" % (chunk.pk, head.pk) - abs_path = os.path.join(settings.DVCS_REPO_PATH, path) + abs_path = os.path.join(settings.CATALOGUE_REPO_PATH, path) f = open(abs_path, 'wb') f.write(compress(data)) f.close() @@ -164,7 +164,7 @@ class Migration(DataMigration): repo = hg.repository(ui.ui(), hg_path) tip = repo['tip'] for fname in tip: - if fname.startswith('.') or not fname.startswith('a'): + if fname.startswith('.'): continue migrate_file_from_hg(orm, fname, tip[fname]) @@ -206,6 +206,10 @@ class Migration(DataMigration): }, 'catalogue.book': { 'Meta': {'ordering': "['parent_number', 'title']", 'object_name': 'Book'}, + '_new_publishable': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '_single': ('django.db.models.fields.NullBooleanField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), 'gallery': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), @@ -215,21 +219,24 @@ class Migration(DataMigration): }, 'catalogue.bookpublishrecord': { 'Meta': {'ordering': "['-timestamp']", 'object_name': 'BookPublishRecord'}, - 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.Book']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) }, 'catalogue.chunk': { 'Meta': {'ordering': "['number']", 'unique_together': "[['book', 'number'], ['book', 'slug']]", 'object_name': 'Chunk'}, + '_changed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), + '_short_html': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'created_documents'", 'null': 'True', 'to': "orm['auth.User']"}), 'head': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['catalogue.ChunkChange']", 'null': 'True', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'number': ('django.db.models.fields.IntegerField', [], {}), 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), 'stage': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkTag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) }, 'catalogue.chunkchange': { @@ -251,7 +258,7 @@ class Migration(DataMigration): 'catalogue.chunkpublishrecord': { 'Meta': {'object_name': 'ChunkPublishRecord'}, 'book_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.BookPublishRecord']"}), - 'change': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ChunkChange']"}), + 'change': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publish_log'", 'to': "orm['catalogue.ChunkChange']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) }, 'catalogue.chunktag': { diff --git a/apps/catalogue/models/__init__.py b/apps/catalogue/models/__init__.py new file mode 100755 index 00000000..d9a11dde --- /dev/null +++ b/apps/catalogue/models/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from catalogue.models.chunk import Chunk +from catalogue.models.publish_log import BookPublishRecord, ChunkPublishRecord +from catalogue.models.book import Book +from catalogue.models.listeners import * diff --git a/apps/catalogue/models.py b/apps/catalogue/models/book.py old mode 100644 new mode 100755 similarity index 63% rename from apps/catalogue/models.py rename to apps/catalogue/models/book.py index 00e1d300..9f809b86 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models/book.py @@ -3,20 +3,16 @@ # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -from django.contrib.auth.models import User -from django.core.urlresolvers import reverse from django.db import models +from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ -from django.db.utils import IntegrityError - from slughifi import slughifi - -from dvcs import models as dvcs_models +from catalogue.helpers import cached_in_field +from catalogue.models import BookPublishRecord, ChunkPublishRecord +from catalogue.signals import post_publish +from catalogue.tasks import refresh_instance from catalogue.xml_tools import compile_text, split_xml -import logging -logger = logging.getLogger("fnp.catalogue") - class Book(models.Model): """ A document edited on the wiki """ @@ -25,33 +21,79 @@ class Book(models.Model): slug = models.SlugField(_('slug'), max_length=128, unique=True, db_index=True) gallery = models.CharField(_('scan gallery name'), max_length=255, blank=True) + #wl_slug = models.CharField(_('title'), max_length=255, null=True, db_index=True, editable=False) parent = models.ForeignKey('self', null=True, blank=True, verbose_name=_('parent'), related_name="children") parent_number = models.IntegerField(_('parent number'), null=True, blank=True, db_index=True) + # Cache + _short_html = models.TextField(null=True, blank=True, editable=False) + _single = models.NullBooleanField(editable=False, db_index=True) + _new_publishable = models.NullBooleanField(editable=False) + _published = models.NullBooleanField(editable=False) + + # Managers + objects = models.Manager() + class NoTextError(BaseException): pass class Meta: + app_label = 'catalogue' ordering = ['parent_number', 'title'] verbose_name = _('book') verbose_name_plural = _('books') permissions = [('can_pubmark', 'Can mark for publishing')] + + # Representing + # ============ + + def __iter__(self): + return iter(self.chunk_set.all()) + + def __getitem__(self, chunk): + return self.chunk_set.all()[chunk] + + def __len__(self): + return self.chunk_set.count() + + def __nonzero__(self): + """ + Necessary so that __len__ isn't used for bool evaluation. + """ + return True + def __unicode__(self): return self.title + @models.permalink def get_absolute_url(self): - return reverse("catalogue_book", args=[self.slug]) + return ("catalogue_book", [self.slug]) + + + # Creating & manipulating + # ======================= @classmethod - def import_xml_text(cls, text=u'', creator=None, previous_book=None, - *args, **kwargs): + def create(cls, creator, text, *args, **kwargs): + b = cls.objects.create(*args, **kwargs) + b.chunk_set.all().update(creator=creator) + b[0].commit(text, author=creator) + return b + def add(self, *args, **kwargs): + """Add a new chunk at the end.""" + return self.chunk_set.reverse()[0].split(*args, **kwargs) + + @classmethod + def import_xml_text(cls, text=u'', previous_book=None, + commit_args=None, **kwargs): + """Imports a book from XML, splitting it into chunks as necessary.""" texts = split_xml(text) if previous_book: instance = previous_book else: - instance = cls(*args, **kwargs) + instance = cls(**kwargs) instance.save() # if there are more parts, set the rest to empty strings @@ -72,85 +114,12 @@ class Book(models.Model): chunk.title = title chunk.save() else: - chunk = instance.add(slug, title, creator, adjust_slug=True) + chunk = instance.add(slug, title, adjust_slug=True) - chunk.commit(text, author=creator) + chunk.commit(text, **commit_args) return instance - @classmethod - def create(cls, creator=None, text=u'', *args, **kwargs): - """ - >>> Book.create(slug='x', text='abc').materialize() - 'abc' - """ - instance = cls(*args, **kwargs) - instance.save() - instance[0].commit(text, author=creator) - return instance - - def __iter__(self): - return iter(self.chunk_set.all()) - - def __getitem__(self, chunk): - return self.chunk_set.all()[chunk] - - def __len__(self): - return self.chunk_set.count() - - def __nonzero__(self): - """ - Necessary so that __len__ isn't used for bool evaluation. - """ - return True - - def get_current_changes(self, publishable=True): - """ - Returns a list containing one Change for every Chunk in the Book. - Takes the most recent revision (publishable, if set). - Throws an error, if a proper revision is unavailable for a Chunk. - """ - if publishable: - changes = [chunk.publishable() for chunk in self] - else: - changes = [chunk.head for chunk in self if chunk.head is not None] - if None in changes: - raise self.NoTextError('Some chunks have no available text.') - return changes - - def materialize(self, publishable=False, changes=None): - """ - Get full text of the document compiled from chunks. - Takes the current versions of all texts - or versions most recently tagged for publishing, - or a specified iterable changes. - """ - if changes is None: - changes = self.get_current_changes(publishable) - return compile_text(change.materialize() for change in changes) - - def publishable(self): - if not self.chunk_set.exists(): - return False - for chunk in self: - if not chunk.publishable(): - return False - return True - - def publish(self, user): - """ - Publishes a book on behalf of a (local) user. - """ - from apiclient import api_call - - changes = self.get_current_changes(publishable=True) - book_xml = book.materialize(changes=changes) - #api_call(user, "books", {"book_xml": book_xml}) - # record the publish - br = BookPublishRecord.objects.create(book=self, user=user) - for c in changes: - ChunkPublishRecord.objects.create(book_record=br, change=c) - def make_chunk_slug(self, proposed): """ Finds a chunk slug not yet used in the book. @@ -208,91 +177,114 @@ class Book(models.Model): number += 1 other.delete() - def add(self, *args, **kwargs): - """Add a new chunk at the end.""" - return self.chunk_set.reverse()[0].split(*args, **kwargs) - - @staticmethod - def listener_create(sender, instance, created, **kwargs): - if created: - instance.chunk_set.create(number=1, slug='1') -models.signals.post_save.connect(Book.listener_create, sender=Book) + # State & cache + # ============= + def last_published(self): + try: + return self.publish_log.all()[0].timestamp + except IndexError: + return None -class Chunk(dvcs_models.Document): - """ An editable chunk of text. Every Book text is divided into chunks. """ + def publishable(self): + if not self.chunk_set.exists(): + return False + for chunk in self: + if not chunk.publishable(): + return False + return True - book = models.ForeignKey(Book, editable=False) - number = models.IntegerField() - slug = models.SlugField() - title = models.CharField(max_length=255, blank=True) + def hidden(self): + return self.slug.startswith('.') - class Meta: - unique_together = [['book', 'number'], ['book', 'slug']] - ordering = ['number'] + def is_new_publishable(self): + """Checks if book is ready for publishing. - def __unicode__(self): - return "%d:%d: %s" % (self.book_id, self.number, self.title) + Returns True if there is a publishable version newer than the one + already published. - def get_absolute_url(self): - return reverse("wiki_editor", args=[self.book.slug, self.slug]) + """ + new_publishable = False + if not self.chunk_set.exists(): + return False + for chunk in self: + change = chunk.publishable() + if not change: + return False + if not new_publishable and not change.publish_log.exists(): + new_publishable = True + return new_publishable + new_publishable = cached_in_field('_new_publishable')(is_new_publishable) + + def is_published(self): + return self.publish_log.exists() + published = cached_in_field('_published')(is_published) + + def is_single(self): + return len(self) == 1 + single = cached_in_field('_single')(is_single) + + @cached_in_field('_short_html') + def short_html(self): + return render_to_string('catalogue/book_list/book.html', {'book': self}) + + def touch(self): + update = { + "_new_publishable": self.is_new_publishable(), + "_published": self.is_published(), + "_single": self.is_single(), + "_short_html": None, + } + Book.objects.filter(pk=self.pk).update(**update) + refresh_instance(self) + + def refresh(self): + """This should be done offline.""" + self.short_html + self.single + self.new_publishable + self.published + + # Materializing & publishing + # ========================== - @classmethod - def get(cls, slug, chunk=None): - if chunk is None: - return cls.objects.get(book__slug=slug, number=1) + def get_current_changes(self, publishable=True): + """ + Returns a list containing one Change for every Chunk in the Book. + Takes the most recent revision (publishable, if set). + Throws an error, if a proper revision is unavailable for a Chunk. + """ + if publishable: + changes = [chunk.publishable() for chunk in self] else: - return cls.objects.get(book__slug=slug, slug=chunk) - - def pretty_name(self, book_length=None): - title = self.book.title - if self.title: - title += ", %s" % self.title - if book_length > 1: - title += " (%d/%d)" % (self.number, book_length) - return title - - def split(self, slug, title='', creator=None, adjust_slug=False): - """ Create an empty chunk after this one """ - self.book.chunk_set.filter(number__gt=self.number).update( - number=models.F('number')+1) - new_chunk = None - while not new_chunk: - new_slug = self.book.make_chunk_slug(slug) - try: - new_chunk = self.book.chunk_set.create(number=self.number+1, - creator=creator, slug=new_slug, title=title) - except IntegrityError: - pass - return new_chunk - - @staticmethod - def listener_saved(sender, instance, created, **kwargs): - if instance.book: - # save book so that its _list_html is reset - instance.book.save() - -models.signals.post_save.connect(Chunk.listener_saved, sender=Chunk) - - -class BookPublishRecord(models.Model): - """ - A record left after publishing a Book. - """ - - book = models.ForeignKey(Book) - timestamp = models.DateTimeField(auto_now_add=True) - user = models.ForeignKey(User) - - class Meta: - ordering = ['-timestamp'] + changes = [chunk.head for chunk in self if chunk.head is not None] + if None in changes: + raise self.NoTextError('Some chunks have no available text.') + return changes + def materialize(self, publishable=False, changes=None): + """ + Get full text of the document compiled from chunks. + Takes the current versions of all texts + or versions most recently tagged for publishing, + or a specified iterable changes. + """ + if changes is None: + changes = self.get_current_changes(publishable) + return compile_text(change.materialize() for change in changes) -class ChunkPublishRecord(models.Model): - """ - BookPublishRecord details for each Chunk. - """ + def publish(self, user): + """ + Publishes a book on behalf of a (local) user. + """ + from apiclient import api_call - book_record = models.ForeignKey(BookPublishRecord) - change = models.ForeignKey(Chunk.change_model) + changes = self.get_current_changes(publishable=True) + book_xml = self.materialize(changes=changes) + #api_call(user, "books", {"book_xml": book_xml}) + # record the publish + br = BookPublishRecord.objects.create(book=self, user=user) + for c in changes: + ChunkPublishRecord.objects.create(book_record=br, change=c) + post_publish.send(sender=br) diff --git a/apps/catalogue/models/chunk.py b/apps/catalogue/models/chunk.py new file mode 100755 index 00000000..e68b1c17 --- /dev/null +++ b/apps/catalogue/models/chunk.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.conf import settings +from django.db import models +from django.db.utils import IntegrityError +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _ +from catalogue.helpers import cached_in_field +from catalogue.managers import VisibleManager +from catalogue.tasks import refresh_instance +from dvcs import models as dvcs_models + + +class Chunk(dvcs_models.Document): + """ An editable chunk of text. Every Book text is divided into chunks. """ + REPO_PATH = settings.CATALOGUE_REPO_PATH + + book = models.ForeignKey('Book', editable=False, verbose_name=_('book')) + number = models.IntegerField(_('number')) + slug = models.SlugField(_('slug')) + title = models.CharField(_('title'), max_length=255, blank=True) + + # cache + _short_html = models.TextField(null=True, blank=True, editable=False) + _hidden = models.NullBooleanField(editable=False) + _changed = models.NullBooleanField(editable=False) + + # managers + objects = models.Manager() + visible_objects = VisibleManager() + + class Meta: + app_label = 'catalogue' + unique_together = [['book', 'number'], ['book', 'slug']] + ordering = ['number'] + verbose_name = _('chunk') + verbose_name_plural = _('chunks') + + # Representing + # ============ + + def __unicode__(self): + return "%d:%d: %s" % (self.book_id, self.number, self.title) + + @models.permalink + def get_absolute_url(self): + return ("wiki_editor", [self.book.slug, self.slug]) + + def pretty_name(self, book_length=None): + title = self.book.title + if self.title: + title += ", %s" % self.title + if book_length > 1: + title += " (%d/%d)" % (self.number, book_length) + return title + + + # Creating and manipulation + # ========================= + + def split(self, slug, title='', adjust_slug=False, **kwargs): + """ Create an empty chunk after this one """ + self.book.chunk_set.filter(number__gt=self.number).update( + number=models.F('number')+1) + new_chunk = None + while not new_chunk: + new_slug = self.book.make_chunk_slug(slug) + try: + new_chunk = self.book.chunk_set.create(number=self.number+1, + slug=new_slug, title=title, **kwargs) + except IntegrityError: + pass + return new_chunk + + @classmethod + def get(cls, book_slug, chunk_slug=None): + if chunk_slug is None: + return cls.objects.get(book__slug=book_slug, number=1) + else: + return cls.objects.get(book__slug=book_slug, slug=chunk_slug) + + + # State & cache + # ============= + + def new_publishable(self): + change = self.publishable() + if not change: + return False + return change.publish_log.exists() + + def is_changed(self): + if self.head is None: + return False + return not self.head.publishable + changed = cached_in_field('_changed')(is_changed) + + def is_hidden(self): + return self.book.hidden() + hidden = cached_in_field('_hidden')(is_hidden) + + @cached_in_field('_short_html') + def short_html(self): + return render_to_string( + 'catalogue/book_list/chunk.html', {'chunk': self}) + + def touch(self): + update = { + "_changed": self.is_changed(), + "_hidden": self.is_hidden(), + "_short_html": None, + } + Chunk.objects.filter(pk=self.pk).update(**update) + refresh_instance(self) + + def refresh(self): + """This should be done offline.""" + self.changed + self.hidden + self.short_html diff --git a/apps/catalogue/models/listeners.py b/apps/catalogue/models/listeners.py new file mode 100755 index 00000000..7848974f --- /dev/null +++ b/apps/catalogue/models/listeners.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.contrib.auth.models import User +from django.db import models +from catalogue.models import Book, Chunk +from catalogue.signals import post_publish +from dvcs.signals import post_publishable + + +def book_changed(sender, instance, created, **kwargs): + instance.touch() + for c in instance: + c.touch() +models.signals.post_save.connect(book_changed, sender=Book) + + +def chunk_changed(sender, instance, created, **kwargs): + instance.book.touch() + instance.touch() +models.signals.post_save.connect(chunk_changed, sender=Chunk) + + +def user_changed(sender, instance, *args, **kwargs): + books = set() + for c in instance.chunk_set.all(): + books.add(c.book) + c.touch() + for b in books: + b.touch() +models.signals.post_save.connect(user_changed, sender=User) + + +def publish_listener(sender, *args, **kwargs): + sender.book.touch() + for c in sender.book: + c.touch() +post_publish.connect(publish_listener) + + +def listener_create(sender, instance, created, **kwargs): + if created: + instance.chunk_set.create(number=1, slug='1') +models.signals.post_save.connect(listener_create, sender=Book) + + diff --git a/apps/catalogue/models/publish_log.py b/apps/catalogue/models/publish_log.py new file mode 100755 index 00000000..f422e377 --- /dev/null +++ b/apps/catalogue/models/publish_log.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# +# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.contrib.auth.models import User +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from catalogue.models import Chunk + + +class BookPublishRecord(models.Model): + """ + A record left after publishing a Book. + """ + + book = models.ForeignKey('Book', verbose_name=_('book'), related_name='publish_log') + timestamp = models.DateTimeField(_('time'), auto_now_add=True) + user = models.ForeignKey(User, verbose_name=_('user')) + + class Meta: + app_label = 'catalogue' + ordering = ['-timestamp'] + verbose_name = _('book publish record') + verbose_name = _('book publish records') + + +class ChunkPublishRecord(models.Model): + """ + BookPublishRecord details for each Chunk. + """ + + book_record = models.ForeignKey(BookPublishRecord, verbose_name=_('book publish record')) + change = models.ForeignKey(Chunk.change_model, related_name='publish_log', verbose_name=_('change')) + + class Meta: + app_label = 'catalogue' + verbose_name = _('chunk publish record') + verbose_name = _('chunk publish records') diff --git a/apps/catalogue/signals.py b/apps/catalogue/signals.py new file mode 100644 index 00000000..62ca5145 --- /dev/null +++ b/apps/catalogue/signals.py @@ -0,0 +1,3 @@ +from django.dispatch import Signal + +post_publish = Signal() diff --git a/apps/catalogue/tasks.py b/apps/catalogue/tasks.py new file mode 100644 index 00000000..e9b8cf9b --- /dev/null +++ b/apps/catalogue/tasks.py @@ -0,0 +1,11 @@ +from celery.task import task + + +@task +def refresh_by_pk(cls, pk): + cls._default_manager.get(pk=pk).refresh() + + +def refresh_instance(instance): + refresh_by_pk.delay(type(instance), instance.pk) + diff --git a/apps/catalogue/templates/catalogue/activity.html b/apps/catalogue/templates/catalogue/activity.html new file mode 100755 index 00000000..4354b337 --- /dev/null +++ b/apps/catalogue/templates/catalogue/activity.html @@ -0,0 +1,7 @@ +{% extends "catalogue/base.html" %} + +{% load wall %} + +{% block leftcolumn %} + {% wall %} +{% endblock leftcolumn %} diff --git a/apps/catalogue/templates/catalogue/base.html b/apps/catalogue/templates/catalogue/base.html index 4ba8a0ea..9b32fe7e 100644 --- a/apps/catalogue/templates/catalogue/base.html +++ b/apps/catalogue/templates/catalogue/base.html @@ -4,7 +4,7 @@ - {% compressed_css 'listing' %} + {% compressed_css 'catalogue' %} {% block title %}{% trans "Platforma Redakcyjna" %}{% endblock title %} @@ -42,7 +42,9 @@ -{% compressed_js 'listing' %} + + +{% compressed_js 'catalogue' %} {% block extrabody %} {% endblock %} diff --git a/apps/catalogue/templates/catalogue/book_list/book.html b/apps/catalogue/templates/catalogue/book_list/book.html new file mode 100755 index 00000000..a45a3578 --- /dev/null +++ b/apps/catalogue/templates/catalogue/book_list/book.html @@ -0,0 +1,34 @@ +{% load i18n %} + +{% if book.single %} + {% with book.0 as chunk %} + + [B] + [c] + + {{ book.title }} + {% if chunk.stage %} + {{ chunk.stage }} + {% else %}– + {% endif %} + {% if chunk.user %}{{ chunk.user.first_name }} {{ chunk.user.last_name }}{% endif %} + + {% if chunk.published %}P{% endif %} + {% if book.new_publishable %}p{% endif %} + {% if chunk.changed %}+{% endif %} + + + {% endwith %} +{% else %} + + [B] + + {{ book.title }} + + + {% if book.published %}P{% endif %} + {% if book.new_publishable %}p{% endif %} + + +{% endif %} diff --git a/apps/catalogue/templates/catalogue/book_list/book_list.html b/apps/catalogue/templates/catalogue/book_list/book_list.html new file mode 100755 index 00000000..73811cab --- /dev/null +++ b/apps/catalogue/templates/catalogue/book_list/book_list.html @@ -0,0 +1,81 @@ +{% load i18n %} +{% load pagination_tags %} + + +
+ + +{% if not viewed_user %} + +{% endif %} + + +
+ + + + + + + + + {% if not viewed_user %} + + {% endif %} + + + + + + {% with cnt=books|length %} + {% autopaginate books 100 %} + + {% for item in books %} + {% with item.book as book %} + {{ book.short_html|safe }} + {% if not book.single %} + {% for chunk in item.chunks %} + {{ chunk.short_html|safe }} + {% endfor %} + {% endif %} + {% endwith %} + {% endfor %} + + + {% endwith %} +
+ + +
+ +
+
+ {% paginate %} + {% blocktrans count c=cnt %}{{c}} book{% plural %}{{c}} books{% endblocktrans %}
+{% if not books %} +

{% trans "No books found." %}

+{% endif %} diff --git a/apps/catalogue/templates/catalogue/book_list/chunk.html b/apps/catalogue/templates/catalogue/book_list/chunk.html new file mode 100755 index 00000000..3897d78b --- /dev/null +++ b/apps/catalogue/templates/catalogue/book_list/chunk.html @@ -0,0 +1,25 @@ +{% load i18n %} + + + + [c] + + {{ chunk.number }}. + {{ chunk.title }} + {% if chunk.stage %} + {{ chunk.stage }} + {% else %} + – + {% endif %} + {% if chunk.user %} + + {{ chunk.user.first_name }} {{ chunk.user.last_name }} + {% else %} + + {% endif %} + + + {% if chunk.new_publishable %}p{% endif %} + {% if chunk.changed %}+{% endif %} + + diff --git a/apps/catalogue/templates/catalogue/document_list.html b/apps/catalogue/templates/catalogue/document_list.html index 01e5a24c..d5343a7d 100644 --- a/apps/catalogue/templates/catalogue/document_list.html +++ b/apps/catalogue/templates/catalogue/document_list.html @@ -1,147 +1,9 @@ {% extends "catalogue/base.html" %} {% load i18n %} -{% load pagination_tags %} -{% load catalogue %} +{% load catalogue book_list %} -{% block extrabody %} -{{ block.super }} - -{% endblock %} {% block leftcolumn %} - -
- - - -
- - - - - - - - - {% if not viewed_user %} - - {% endif %} - - - - - {% autopaginate books 100 %} - {% if not books %} - - {% endif %} - {% for item in books %} - {% with item.book as book %} - - {% ifequal item.book_length 1 %} - {% with item.chunks.0 as chunk %} - - - - - - - - {% endwith %} - {% else %} - - - - - - {% for chunk in item.chunks %} - - - - - - {% if not viewed_user %} - - {% endif %} - - {% endfor %} - {% endifequal %} - {% endwith %} - {% endfor %} - - -
-
- -
-
{% trans "No books found." %}
[B][c] - {{ book.title }}{% if chunk.stage %} - ({{ chunk.stage }}) - {% else %}– - {% endif %}{% if chunk.user %}{{ chunk.user.first_name }} {{ chunk.user.last_name }}{% endif %}
[B]{{ book.title }}
[c] - {{ chunk.number }}. - {{ chunk.title }}{% if chunk.stage %} - {{ chunk.stage }} - {% else %} - – - {% endif %}{% if chunk.user %} - - {{ chunk.user.first_name }} {{ chunk.user.last_name }} - {% else %} - - {% endif %}
{% paginate %}
+ {% book_list %} {% endblock leftcolumn %} - -{% block rightcolumn %} -
-

{% trans "Your last edited documents" %}

-
    - {% for slugs, item in last_books %} -
  1. {{ item.title }}
    ({{ item.time|date:"H:i:s, d/m/Y" }})
  2. - {% endfor %} -
-
- - {% if viewed_user %} -

{% trans "Recent activity for" %} {{ viewed_user }}

- {% wall viewed_user %} - {% else %} -

{% trans "Recent activity" %}

- {% wall %} - {% endif %} -{% endblock rightcolumn %} diff --git a/apps/catalogue/templates/catalogue/my_page.html b/apps/catalogue/templates/catalogue/my_page.html new file mode 100755 index 00000000..48a21796 --- /dev/null +++ b/apps/catalogue/templates/catalogue/my_page.html @@ -0,0 +1,24 @@ +{% extends "catalogue/base.html" %} + +{% load i18n %} +{% load catalogue book_list wall %} + + +{% block leftcolumn %} + {% book_list request.user %} +{% endblock leftcolumn %} + +{% block rightcolumn %} +
+

{% trans "Your last edited documents" %}

+
    + {% for slugs, item in last_books %} +
  1. {{ item.title }}
    ({{ item.time|date:"H:i:s, d/m/Y" }})
  2. + {% endfor %} +
+
+ +

{% trans "Recent activity for" %} {{ request.user|nice_name }}

+ {% wall request.user 10 %} +{% endblock rightcolumn %} diff --git a/apps/catalogue/templates/catalogue/user_page.html b/apps/catalogue/templates/catalogue/user_page.html new file mode 100755 index 00000000..89b4ecef --- /dev/null +++ b/apps/catalogue/templates/catalogue/user_page.html @@ -0,0 +1,15 @@ +{% extends "catalogue/base.html" %} + +{% load i18n %} +{% load catalogue book_list wall %} + + +{% block leftcolumn %} +

{{ viewed_user|nice_name }}

+ {% book_list viewed_user %} +{% endblock leftcolumn %} + +{% block rightcolumn %} +

{% trans "Recent activity for" %} {{ viewed_user|nice_name }}

+ {% wall viewed_user 10 %} +{% endblock rightcolumn %} diff --git a/apps/catalogue/templates/catalogue/wall.html b/apps/catalogue/templates/catalogue/wall.html index 0e4597e5..2ec3c0ac 100755 --- a/apps/catalogue/templates/catalogue/wall.html +++ b/apps/catalogue/templates/catalogue/wall.html @@ -13,8 +13,8 @@ - {{ item.timestamp }} -
+ {{ item.timestamp }} +

{{ item.header }}

{% if item.user %} {{ item.user.first_name }} {{ item.user.last_name }} diff --git a/apps/catalogue/templatetags/book_list.py b/apps/catalogue/templatetags/book_list.py new file mode 100755 index 00000000..15654be2 --- /dev/null +++ b/apps/catalogue/templatetags/book_list.py @@ -0,0 +1,134 @@ +from __future__ import absolute_import + +from re import split +from django.db.models import Q, Count +from django import template +from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import User +from catalogue.models import Chunk + +register = template.Library() + + +class ChunksList(object): + def __init__(self, chunk_qs): + #self.chunk_qs = chunk_qs#.annotate( + #book_length=Count('book__chunk')).select_related( + #'book')#, 'stage__name', + #'user') + self.chunk_qs = chunk_qs.select_related('book__hidden') + + self.book_qs = chunk_qs.values('book_id') + + def __getitem__(self, key): + if isinstance(key, slice): + return self.get_slice(key) + elif isinstance(key, int): + return self.get_slice(slice(key, key+1))[0] + else: + raise TypeError('Unsupported list index. Must be a slice or an int.') + + def __len__(self): + return self.book_qs.count() + + def get_slice(self, slice_): + book_ids = [x['book_id'] for x in self.book_qs[slice_]] + chunk_qs = self.chunk_qs.filter(book__in=book_ids) + + chunks_list = [] + book = None + for chunk in chunk_qs: + if chunk.book != book: + book = chunk.book + chunks_list.append(ChoiceChunks(book, [chunk])) + else: + chunks_list[-1].chunks.append(chunk) + return chunks_list + + +class ChoiceChunks(object): + """ + Associates the given chunks iterable for a book. + """ + + chunks = None + + def __init__(self, book, chunks): + self.book = book + self.chunks = chunks + + +def foreign_filter(qs, value, filter_field, model, model_field='slug', unset='-'): + if value == unset: + return qs.filter(**{filter_field: None}) + if not value: + return qs + try: + obj = model._default_manager.get(**{model_field: value}) + except model.DoesNotExist: + return qs.none() + else: + return qs.filter(**{filter_field: obj}) + + +def search_filter(qs, value, filter_field): + if not value: + return qs + return qs.filter(**{"%s__icontains" % filter_field: value}) + + +_states = [ + ('publishable', _('publishable'), Q(book___new_publishable=True)), + ('changed', _('changed'), Q(_changed=True)), + ('published', _('published'), Q(book___published=True)), + ('unpublished', _('unpublished'), Q(book___published=False)), + ('empty', _('empty'), Q(head=None)), + ] +_states_options = [s[:2] for s in _states] +_states_dict = dict([(s[0], s[2]) for s in _states]) + + +def document_list_filter(request, **kwargs): + + def arg_or_GET(field): + return kwargs.get(field, request.GET.get(field)) + + if arg_or_GET('all'): + chunks = Chunk.objects.all() + else: + chunks = Chunk.visible_objects.all() + + chunks = chunks.order_by('book__title', 'book', 'number') + + state = arg_or_GET('status') + if state in _states_dict: + chunks = chunks.filter(_states_dict[state]) + + chunks = foreign_filter(chunks, arg_or_GET('user'), 'user', User, 'username') + chunks = foreign_filter(chunks, arg_or_GET('stage'), 'stage', Chunk.tag_model, 'slug') + chunks = search_filter(chunks, arg_or_GET('title'), 'book__title') + return chunks + + +@register.inclusion_tag('catalogue/book_list/book_list.html', takes_context=True) +def book_list(context, user=None): + request = context['request'] + + if user: + filters = {"user": user} + new_context = {"viewed_user": user} + else: + filters = {} + new_context = {"users": User.objects.annotate( + count=Count('chunk')).filter(count__gt=0).order_by( + '-count', 'last_name', 'first_name')} + + new_context.update({ + "request": request, + "books": ChunksList(document_list_filter(request, **filters)), + "stages": Chunk.tag_model.objects.all(), + "states": _states_options, + }) + + return new_context + diff --git a/apps/catalogue/templatetags/catalogue.py b/apps/catalogue/templatetags/catalogue.py index 7d138ffa..bfb900bf 100644 --- a/apps/catalogue/templatetags/catalogue.py +++ b/apps/catalogue/templatetags/catalogue.py @@ -1,14 +1,9 @@ from __future__ import absolute_import -from django.db.models import Count, Q from django.core.urlresolvers import reverse -from django.contrib.comments.models import Comment -from django.template.defaultfilters import stringfilter from django import template from django.utils.translation import ugettext as _ -from catalogue.models import Book, Chunk, BookPublishRecord - register = template.Library() @@ -32,6 +27,7 @@ def main_tabs(context): if user.is_authenticated(): tabs.append(Tab('my', _('My page'), reverse("catalogue_user"))) + tabs.append(Tab('activity', _('Activity'), reverse("catalogue_activity"))) tabs.append(Tab('all', _('All'), reverse("catalogue_document_list"))) tabs.append(Tab('users', _('Users'), reverse("catalogue_users"))) tabs.append(Tab('create', _('Add'), reverse("catalogue_create_missing"))) @@ -43,108 +39,7 @@ def main_tabs(context): return {"tabs": tabs, "active_tab": active} -class WallItem(object): - title = '' - summary = '' - url = '' - timestamp = '' - user = None - email = '' - - def __init__(self, tag): - self.tag = tag - - def get_email(self): - if self.user: - return self.user.email - else: - return self.email - - -def changes_wall(user, max_len): - qs = Chunk.change_model.objects.filter(revision__gt=-1).order_by('-created_at') - qs = qs.select_related('author', 'tree', 'tree__book__title') - if user: - qs = qs.filter(Q(author=user) | Q(tree__user=user)) - qs = qs[:max_len] - for item in qs: - tag = 'stage' if item.tags.count() else 'change' - chunk = item.tree - w = WallItem(tag) - w.title = chunk.pretty_name() - w.summary = item.description - w.url = reverse('wiki_editor', - args=[chunk.book.slug, chunk.slug]) + '?diff=%d' % item.revision - w.timestamp = item.created_at - w.user = item.author - w.email = item.author_email - yield w - - -# TODO: marked for publishing - - -def published_wall(user, max_len): - qs = BookPublishRecord.objects.select_related('book__title') - if user: - # TODO: published my book - qs = qs.filter(Q(user=user)) - qs = qs[:max_len] - for item in qs: - w = WallItem('publish') - w.title = item.book.title - #w.summary = - w.url = chunk.book.get_absolute_url() - yield w - - -def comments_wall(user, max_len): - qs = Comment.objects.filter(is_public=True).select_related().order_by('-submit_date') - if user: - # TODO: comments concerning my books - qs = qs.filter(Q(user=user)) - qs = qs[:max_len] - for item in qs: - w = WallItem('comment') - w.title = item.content_object - w.summary = item.comment - w.url = item.content_object.get_absolute_url() - w.timestamp = item.submit_date - w.user = item.user - w.email = item.user_email - yield w - - -def big_wall(max_len, *args): - """ - Takes some WallItem iterators and zips them into one big wall. - Input iterators must already be sorted by timestamp. - """ - subwalls = [] - for w in args: - try: - subwalls.append([next(w), w]) - except StopIteration: - pass - - while max_len and subwalls: - i, next_item = max(enumerate(subwalls), key=lambda x: x[1][0].timestamp) - yield next_item[0] - max_len -= 1 - try: - next_item[0] = next(next_item[1]) - except StopIteration: - del subwalls[i] - +@register.filter +def nice_name(user): + return user.get_full_name() or user.username -@register.inclusion_tag("catalogue/wall.html", takes_context=True) -def wall(context, user=None, max_len=10): - print user - return { - "request": context['request'], - "STATIC_URL": context['STATIC_URL'], - "wall": big_wall(max_len, - changes_wall(user, max_len), - published_wall(user, max_len), - comments_wall(user, max_len), - )} diff --git a/apps/catalogue/templatetags/wall.py b/apps/catalogue/templatetags/wall.py new file mode 100755 index 00000000..5236eedf --- /dev/null +++ b/apps/catalogue/templatetags/wall.py @@ -0,0 +1,125 @@ +from __future__ import absolute_import + +from django.db.models import Q +from django.core.urlresolvers import reverse +from django.contrib.comments.models import Comment +from django import template +from django.utils.translation import ugettext as _ + +from catalogue.models import Chunk, BookPublishRecord + +register = template.Library() + + +class WallItem(object): + title = '' + summary = '' + url = '' + timestamp = '' + user = None + email = '' + + def __init__(self, tag): + self.tag = tag + + def get_email(self): + if self.user: + return self.user.email + else: + return self.email + + +def changes_wall(user, max_len): + qs = Chunk.change_model.objects.filter(revision__gt=-1).order_by('-created_at') + qs = qs.select_related('author', 'tree', 'tree__book__title') + if user: + qs = qs.filter(Q(author=user) | Q(tree__user=user)) + qs = qs[:max_len] + for item in qs: + tag = 'stage' if item.tags.count() else 'change' + chunk = item.tree + w = WallItem(tag) + if user and item.author != user: + w.header = _('Related edit') + else: + w.header = _('Edit') + w.title = chunk.pretty_name() + w.summary = item.description + w.url = reverse('wiki_editor', + args=[chunk.book.slug, chunk.slug]) + '?diff=%d' % item.revision + w.timestamp = item.created_at + w.user = item.author + w.email = item.author_email + yield w + + +# TODO: marked for publishing + + +def published_wall(user, max_len): + qs = BookPublishRecord.objects.select_related('book__title') + if user: + # TODO: published my book + qs = qs.filter(Q(user=user)) + qs = qs[:max_len] + for item in qs: + w = WallItem('publish') + w.header = _('Publication') + w.title = item.book.title + w.timestamp = item.timestamp + w.url = item.book.get_absolute_url() + w.user = item.user + w.email = item.user.email + yield w + + +def comments_wall(user, max_len): + qs = Comment.objects.filter(is_public=True).select_related().order_by('-submit_date') + if user: + # TODO: comments concerning my books + qs = qs.filter(Q(user=user)) + qs = qs[:max_len] + for item in qs: + w = WallItem('comment') + w.header = _('Comment') + w.title = item.content_object + w.summary = item.comment + w.url = item.content_object.get_absolute_url() + w.timestamp = item.submit_date + w.user = item.user + w.email = item.user_email + yield w + + +def big_wall(max_len, *args): + """ + Takes some WallItem iterators and zips them into one big wall. + Input iterators must already be sorted by timestamp. + """ + subwalls = [] + for w in args: + try: + subwalls.append([next(w), w]) + except StopIteration: + pass + + while max_len and subwalls: + i, next_item = max(enumerate(subwalls), key=lambda x: x[1][0].timestamp) + yield next_item[0] + max_len -= 1 + try: + next_item[0] = next(next_item[1]) + except StopIteration: + del subwalls[i] + + +@register.inclusion_tag("catalogue/wall.html", takes_context=True) +def wall(context, user=None, max_len=100): + return { + "request": context['request'], + "STATIC_URL": context['STATIC_URL'], + "wall": big_wall(max_len, + changes_wall(user, max_len), + published_wall(user, max_len), + comments_wall(user, max_len), + )} diff --git a/apps/catalogue/tests.py b/apps/catalogue/tests.py deleted file mode 100644 index 65777379..00000000 --- a/apps/catalogue/tests.py +++ /dev/null @@ -1,19 +0,0 @@ -from nose.tools import * -import wiki.models as models -import shutil -import tempfile - - -class TestStorageBase: - def setUp(self): - self.dirpath = tempfile.mkdtemp(prefix='nosetest_') - - def tearDown(self): - shutil.rmtree(self.dirpath) - - -class TestDocumentStorage(TestStorageBase): - - def test_storage_empty(self): - storage = models.DocumentStorage(self.dirpath) - eq_(storage.all(), []) diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py index e9e5e893..73fd3ee6 100644 --- a/apps/catalogue/urls.py +++ b/apps/catalogue/urls.py @@ -10,6 +10,7 @@ urlpatterns = patterns('catalogue.views', url(r'^user/$', 'my', name='catalogue_user'), url(r'^user/(?P[^/]+)/$', 'user', name='catalogue_user'), url(r'^users/$', 'users', name='catalogue_users'), + url(r'^activity/$', 'activity', name='catalogue_activity'), url(r'^upload/$', 'upload', name='catalogue_upload'), diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index 81de5ac7..aa214de7 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -7,10 +7,10 @@ from django.contrib import auth from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from django.core.urlresolvers import reverse -from django.db.models import Count +from django.db.models import Count, Q from django import http from django.http import Http404 -from django.shortcuts import get_object_or_404 +from django.shortcuts import get_object_or_404, render from django.utils.http import urlquote_plus from django.utils.translation import ugettext_lazy as _ from django.views.decorators.http import require_POST @@ -33,70 +33,26 @@ from django.views.decorators.cache import never_cache logger = logging.getLogger("fnp.catalogue") -def foreign_filter(qs, value, filter_field, model, model_field='slug', unset='-'): - if value == unset: - return qs.filter(**{filter_field: None}) - if not value: - return qs - try: - obj = model._default_manager.get(**{model_field: value}) - except model.DoesNotExist: - return qs.none() - else: - return qs.filter(**{filter_field: obj}) - - -def search_filter(qs, value, filter_field): - if not value: - return qs - return qs.filter(**{"%s__icontains" % filter_field: value}) - - @active_tab('all') @never_cache -def document_list(request, filters=None): - chunks = Chunk.objects.order_by('book__title', 'book', 'number') - - chunks = foreign_filter(chunks, request.GET.get('user', None), 'user', User, 'username') - chunks = foreign_filter(chunks, request.GET.get('stage', None), 'stage', Chunk.tag_model, 'slug') - chunks = search_filter(chunks, request.GET.get('title', None), 'book__title') - - chunks_list = helpers.ChunksList(chunks) - - users = User.objects.annotate(count=Count('chunk')).filter(count__gt=0).order_by('-count', 'last_name', 'first_name') - #users = User.objects.annotate(count=Count('chunk')).order_by('-count', 'last_name', 'first_name') - - - return direct_to_template(request, 'catalogue/document_list.html', extra_context={ - 'books': chunks_list, - 'last_books': sorted(request.session.get("wiki_last_books", {}).items(), - key=lambda x: x[1]['time'], reverse=True), - 'stages': Chunk.tag_model.objects.all(), - 'users': users, - }) +def document_list(request): + return render(request, 'catalogue/document_list.html') @never_cache -def user(request, username=None): - if username is None: - if request.user.is_authenticated(): - user = request.user - else: - raise Http404 - else: - user = get_object_or_404(User, username=username) +def user(request, username): + user = get_object_or_404(User, username=username) + return render(request, 'catalogue/user_page.html', {"viewed_user": user}) - chunks_list = helpers.ChunksList(Chunk.objects.filter( - user=user).order_by('book__title', 'book', 'number')) - return direct_to_template(request, 'catalogue/document_list.html', extra_context={ - 'books': chunks_list, +@login_required +@active_tab('my') +@never_cache +def my(request): + return render(request, 'catalogue/my_page.html', { 'last_books': sorted(request.session.get("wiki_last_books", {}).items(), key=lambda x: x[1]['time'], reverse=True), - 'viewed_user': user, - 'stages': Chunk.tag_model.objects.all(), - }) -my = login_required(active_tab('my')(user)) + }) @active_tab('users') @@ -107,6 +63,11 @@ def users(request): }) +@active_tab('activity') +def activity(request): + return render(request, 'catalogue/activity.html') + + @never_cache def logout_then_redirect(request): auth.logout(request) @@ -127,11 +88,12 @@ def create_missing(request, slug=None): creator = request.user else: creator = None - book = Book.create(creator=creator, + book = Book.objects.create( slug=form.cleaned_data['slug'], title=form.cleaned_data['title'], - text=form.cleaned_data['text'], ) + book.chunk_set.all().update(creator=creator) + book[0].commit(text=form.cleaned_data['text'], author=creator) return http.HttpResponseRedirect(reverse("wiki_editor", args=[book.slug])) else: diff --git a/apps/catalogue/xml_tools.py b/apps/catalogue/xml_tools.py old mode 100755 new mode 100644 index 522806b6..d6a9333b --- a/apps/catalogue/xml_tools.py +++ b/apps/catalogue/xml_tools.py @@ -230,7 +230,7 @@ def split_xml(text): for a in name_elem.findall('.//' + tag): a.text='' del a[:] - name = etree.tostring(name_elem, method='text', encoding='utf-8') + name = etree.tostring(name_elem, method='text', encoding='utf-8').strip() # in the original, remove everything from the start of the last chapter parent = element.getparent() diff --git a/apps/dvcs/locale/pl/LC_MESSAGES/django.mo b/apps/dvcs/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..4c3a1ffc15dbacdd54a77eecdd650f6c7ef84966 GIT binary patch literal 1555 zcmZ{iO^6&t6vxXX8XYy!_!Y%Ki6om~dhFgc5+*xw$8{4HcavqEjh>X+?n+Nh_f$1i z)wVO_6a){s1w4p?c=4hjcoOlVWbok8gBJ-19>q(%2qOMpPY;`mkAC&5s(xQp^==CKNO}GN$#4JuncrcM;vEI=1&@LF*z?W)Lc`PG z_c%)gQvG+}v;ZE0({tdLWPd(!FroUWFG#2+s{ct^KpvfevHn0go|w2C zN$nRLk4tMY;{z#@HrsJB;p!?Er5_l}VXBSiPWm=`)7Xp)=Txf6wasQW4Y(W$mCqMi zj3kQ(LZ@>$7F#L2u`Cf@utd7ps=_Oy*}ND=C9sjSspN&AqF5cr*hE^TQ`T%2%EI>{L0$)8|#-Z zwYFtptPeI_s*>Qx~s{Dicqn)1olB%S7AQ zv|Z%q5l5#bjvDl(7!Mz| z?$S9vn|4p?Za3nu@Q~y8@hCjsUF0WD)YIkeOEf)6+u?E)@@v=XeXbl`q_2EWaZ~^R literal 0 HcmV?d00001 diff --git a/apps/dvcs/locale/pl/LC_MESSAGES/django.po b/apps/dvcs/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 00000000..64ddfd78 --- /dev/null +++ b/apps/dvcs/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,115 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-10-03 15:35+0200\n" +"PO-Revision-Date: 2011-10-03 15:35+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2)\n" + +#: models.py:19 +msgid "name" +msgstr "nazwa" + +#: models.py:20 +msgid "slug" +msgstr "slug" + +#: models.py:22 +msgid "ordering" +msgstr "kolejność" + +#: models.py:29 +msgid "tag" +msgstr "tag" + +#: models.py:30 models.py:196 +msgid "tags" +msgstr "tagi" + +#: models.py:72 +msgid "author" +msgstr "autor" + +#: models.py:73 +msgid "author name" +msgstr "imię i nazwisko autora" + +#: models.py:75 models.py:79 +msgid "Used if author is not set." +msgstr "Używane, gdy nie jest ustawiony autor." + +#: models.py:77 +msgid "author email" +msgstr "e-mail autora" + +#: models.py:81 +msgid "revision" +msgstr "rewizja" + +#: models.py:85 +msgid "parent" +msgstr "rodzic" + +#: models.py:90 +msgid "merge parent" +msgstr "drugi rodzic" + +#: models.py:93 +msgid "description" +msgstr "opis" + +#: models.py:96 +msgid "publishable" +msgstr "do publikacji" + +#: models.py:102 +msgid "change" +msgstr "zmiana" + +#: models.py:103 +msgid "changes" +msgstr "zmiany" + +#: models.py:195 +msgid "document" +msgstr "dokument" + +#: models.py:197 +msgid "data" +msgstr "dane" + +#: models.py:211 +msgid "stage" +msgstr "etap" + +#: models.py:219 +msgid "head" +msgstr "głowica" + +#: models.py:220 +msgid "This document's current head." +msgstr "Aktualna wersja dokumentu." + +#: models.py:224 +msgid "creator" +msgstr "utworzył" + +#: models.py:239 +msgid "user" +msgstr "użytkownik" + +#: models.py:239 +msgid "Work assignment." +msgstr "Przypisanie pracy użytkownikowi." diff --git a/apps/dvcs/models.py b/apps/dvcs/models.py index 1668dee8..ab5f77d9 100644 --- a/apps/dvcs/models.py +++ b/apps/dvcs/models.py @@ -1,22 +1,21 @@ from datetime import datetime +import os.path +from django.contrib.auth.models import User from django.core.files.base import ContentFile from django.core.files.storage import FileSystemStorage from django.db import models from django.db.models.base import ModelBase -from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ from mercurial import mdiff, simplemerge -from dvcs.fields import GzipFileSystemStorage -from dvcs.settings import REPO_PATH +from django.conf import settings +from dvcs.signals import post_commit +from dvcs.storage import GzipFileSystemStorage class Tag(models.Model): - """ - a tag (e.g. document stage) which can be applied to a change - """ - + """A tag (e.g. document stage) which can be applied to a Change.""" name = models.CharField(_('name'), max_length=64) slug = models.SlugField(_('slug'), unique=True, max_length=64, null=True, blank=True) @@ -27,6 +26,8 @@ class Tag(models.Model): class Meta: abstract = True ordering = ['ordering'] + verbose_name = _("tag") + verbose_name_plural = _("tags") def __unicode__(self): return self.name @@ -57,8 +58,6 @@ class Tag(models.Model): models.signals.pre_save.connect(Tag.listener_changed, sender=Tag) -repo = GzipFileSystemStorage(location=REPO_PATH) - def data_upload_to(instance, filename): return "%d/%d" % (instance.tree.pk, instance.pk) @@ -70,29 +69,38 @@ class Change(models.Model): Data file contains a gzipped text of the document. """ - author = models.ForeignKey(User, null=True, blank=True) - author_name = models.CharField(max_length=128, null=True, blank=True) - author_email = models.CharField(max_length=128, null=True, blank=True) - data = models.FileField(upload_to=data_upload_to, storage=repo) - revision = models.IntegerField(db_index=True) + author = models.ForeignKey(User, null=True, blank=True, verbose_name=_('author')) + author_name = models.CharField(_('author name'), max_length=128, + null=True, blank=True, + help_text=_("Used if author is not set.") + ) + author_email = models.CharField(_('author email'), max_length=128, + null=True, blank=True, + help_text=_("Used if author is not set.") + ) + revision = models.IntegerField(_('revision'), db_index=True) parent = models.ForeignKey('self', null=True, blank=True, default=None, + verbose_name=_('parent'), related_name="children") merge_parent = models.ForeignKey('self', null=True, blank=True, default=None, + verbose_name=_('merge parent'), related_name="merge_children") - description = models.TextField(blank=True, default='') + description = models.TextField(_('description'), blank=True, default='') created_at = models.DateTimeField(editable=False, db_index=True, default=datetime.now) - publishable = models.BooleanField(default=False) + publishable = models.BooleanField(_('publishable'), default=False) class Meta: abstract = True ordering = ('created_at',) unique_together = ['tree', 'revision'] + verbose_name = _("change") + verbose_name_plural = _("changes") def __unicode__(self): return u"Id: %r, Tree %r, Parent %r, Data: %s" % (self.id, self.tree_id, self.parent_id, self.data) @@ -156,58 +164,79 @@ class Change(models.Model): """ commit this version of a doc as new head """ self.tree.commit(text=self.materialize(), **kwargs) + def set_publishable(self, publishable): + self.publishable = publishable + self.save() + post_publishable(sender=self, publishable=publishable).send() + def create_tag_model(model): name = model.__name__ + 'Tag' + + class Meta(Tag.Meta): + app_label = model._meta.app_label + attrs = { '__module__': model.__module__, + 'Meta': Meta, } return type(name, (Tag,), attrs) def create_change_model(model): name = model.__name__ + 'Change' + repo = GzipFileSystemStorage(location=model.REPO_PATH) + + class Meta(Change.Meta): + app_label = model._meta.app_label attrs = { '__module__': model.__module__, - 'tree': models.ForeignKey(model, related_name='change_set'), - 'tags': models.ManyToManyField(model.tag_model, related_name='change_set'), + 'tree': models.ForeignKey(model, related_name='change_set', verbose_name=_('document')), + 'tags': models.ManyToManyField(model.tag_model, verbose_name=_('tags'), related_name='change_set'), + 'data': models.FileField(_('data'), upload_to=data_upload_to, storage=repo), + 'Meta': Meta, } return type(name, (Change,), attrs) - class DocumentMeta(ModelBase): "Metaclass for Document models." def __new__(cls, name, bases, attrs): + model = super(DocumentMeta, cls).__new__(cls, name, bases, attrs) if not model._meta.abstract: # create a real Tag object and `stage' fk model.tag_model = create_tag_model(model) - models.ForeignKey(model.tag_model, + models.ForeignKey(model.tag_model, verbose_name=_('stage'), null=True, blank=True).contribute_to_class(model, 'stage') # create real Change model and `head' fk model.change_model = create_change_model(model) + models.ForeignKey(model.change_model, null=True, blank=True, default=None, + verbose_name=_('head'), help_text=_("This document's current head."), editable=False).contribute_to_class(model, 'head') - return model + models.ForeignKey(User, null=True, blank=True, editable=False, + verbose_name=_('creator'), related_name="created_%s" % name.lower() + ).contribute_to_class(model, 'creator') + return model class Document(models.Model): - """ - File in repository. - """ + """File in repository. Subclass it to use version control in your app.""" + __metaclass__ = DocumentMeta - creator = models.ForeignKey(User, null=True, blank=True, editable=False, - related_name="created_documents") + # default repository path + REPO_PATH = os.path.join(settings.MEDIA_ROOT, 'dvcs') - user = models.ForeignKey(User, null=True, blank=True) + user = models.ForeignKey(User, null=True, blank=True, + verbose_name=_('user'), help_text=_('Work assignment.')) class Meta: abstract = True @@ -215,13 +244,6 @@ class Document(models.Model): def __unicode__(self): return u"{0}, HEAD: {1}".format(self.id, self.head_id) - @models.permalink - def get_absolute_url(self): - return ('dvcs.views.document_data', (), { - 'document_id': self.id, - 'version': self.head_id, - }) - def materialize(self, change=None): if self.head is None: return u'' @@ -231,7 +253,23 @@ class Document(models.Model): change = self.change_set.get(pk=change) return change.materialize() - def commit(self, text, **kwargs): + def commit(self, text, author=None, author_name=None, author_email=None, + publishable=False, **kwargs): + """Commits a new revision. + + This will automatically merge the commit into the main branch, + if parent is not document's head. + + :param unicode text: new version of the document + :param parent: parent revision (head, if not specified) + :type parent: Change or None + :param User author: the commiter + :param unicode author_name: commiter name (if ``author`` not specified) + :param unicode author_email: commiter e-mail (if ``author`` not specified) + :param Tag[] tags: list of tags to apply to the new commit + :param bool publishable: set new commit as ready to publish + :returns: new head + """ if 'parent' not in kwargs: parent = self.head else: @@ -239,9 +277,6 @@ class Document(models.Model): if parent is not None and not isinstance(parent, Change): parent = self.change_set.objects.get(pk=kwargs['parent']) - author = kwargs.get('author', None) - author_name = kwargs.get('author_name', None) - author_email = kwargs.get('author_email', None) tags = kwargs.get('tags', []) if tags: # set stage to next tag after the commited one @@ -251,6 +286,7 @@ class Document(models.Model): author_name=author_name, author_email=author_email, description=kwargs.get('description', ''), + publishable=publishable, parent=parent) change.tags = tags @@ -265,6 +301,9 @@ class Document(models.Model): else: self.head = change self.save() + + post_commit.send(sender=self.head) + return self.head def history(self): @@ -280,8 +319,8 @@ class Document(models.Model): return self.change_set.get(revision=rev) def publishable(self): - changes = self.change_set.filter(publishable=True).order_by('-created_at')[:1] - if changes.count(): - return changes[0] + changes = self.change_set.filter(publishable=True) + if changes.exists(): + return changes.order_by('-created_at')[0] else: return None diff --git a/apps/dvcs/settings.py b/apps/dvcs/settings.py deleted file mode 100755 index d7863bfa..00000000 --- a/apps/dvcs/settings.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.conf import settings - -REPO_PATH = settings.DVCS_REPO_PATH diff --git a/apps/dvcs/signals.py b/apps/dvcs/signals.py new file mode 100755 index 00000000..5da075be --- /dev/null +++ b/apps/dvcs/signals.py @@ -0,0 +1,4 @@ +from django.dispatch import Signal + +post_commit = Signal() +post_publishable = Signal(providing_args=['publishable']) diff --git a/apps/dvcs/tests.py b/apps/dvcs/tests.py deleted file mode 100644 index 0c712957..00000000 --- a/apps/dvcs/tests.py +++ /dev/null @@ -1,164 +0,0 @@ -from django.test import TestCase -from dvcs.models import Change, Document -from django.contrib.auth.models import User - -class DocumentModelTests(TestCase): - - def setUp(self): - self.user = User.objects.create_user("tester", "tester@localhost.local") - - def assertTextEqual(self, given, expected): - return self.assertEqual(given, expected, - "Expected '''%s'''\n differs from text: '''%s'''" % (expected, given) - ) - - def test_empty_file(self): - doc = Document.objects.create(name=u"Sample Document", creator=self.user) - self.assert_(doc.head is not None) - self.assertEqual(doc.materialize(), u"") - - def test_single_commit(self): - doc = Document.objects.create(name=u"Sample Document", creator=self.user) - doc.commit(text=u"Ala ma kota", description="Commit #1", author=self.user) - self.assert_(doc.head is not None) - self.assertEqual(doc.change_set.count(), 2) - self.assertEqual(doc.materialize(), u"Ala ma kota") - - def test_chained_commits(self): - doc = Document.objects.create(name=u"Sample Document", creator=self.user) - c1 = doc.commit(description="Commit #1", text=u""" - Line #1 - Line #2 is cool - """, author=self.user) - c2 = doc.commit(description="Commit #2", text=u""" - Line #1 - Line #2 is hot - """, author=self.user) - c3 = doc.commit(description="Commit #3", text=u""" - Line #1 - ... is hot - Line #3 ate Line #2 - """, author=self.user) - self.assert_(doc.head is not None) - self.assertEqual(doc.change_set.count(), 4) - - self.assertEqual(doc.materialize(), u""" - Line #1 - ... is hot - Line #3 ate Line #2 - """) - self.assertEqual(doc.materialize(version=c3), u""" - Line #1 - ... is hot - Line #3 ate Line #2 - """) - self.assertEqual(doc.materialize(version=c2), u""" - Line #1 - Line #2 is hot - """) - self.assertEqual(doc.materialize(version=c1), """ - Line #1 - Line #2 is cool - """) - - - def test_parallel_commit_noconflict(self): - doc = Document.objects.create(name=u"Sample Document", creator=self.user) - self.assert_(doc.head is not None) - base = doc.head - base = doc.commit(description="Commit #1", text=u""" - Line #1 - Line #2 -""", author=self.user) - - c1 = doc.commit(description="Commit #2", text=u""" - Line #1 is hot - Line #2 -""", parent=base, author=self.user) - self.assertTextEqual(c1.materialize(), u""" - Line #1 is hot - Line #2 -""") - c2 = doc.commit(description="Commit #3", text=u""" - Line #1 - Line #2 - Line #3 -""", parent=base, author=self.user) - self.assertEqual(doc.change_set.count(), 5) - self.assertTextEqual(doc.materialize(), u""" - Line #1 is hot - Line #2 - Line #3 -""") - - def test_parallel_commit_conflict(self): - doc = Document.objects.create(name=u"Sample Document", creator=self.user) - self.assert_(doc.head is not None) - base = doc.head - base = doc.commit(description="Commit #1", text=u""" -Line #1 -Line #2 -Line #3 -""", author=self.user) - - c1 = doc.commit(description="Commit #2", text=u""" -Line #1 -Line #2 is hot -Line #3 -""", parent=base, author=self.user) - c2 = doc.commit(description="Commit #3", text=u""" -Line #1 -Line #2 is cool -Line #3 -""", parent=base, author=self.user) - self.assertEqual(doc.change_set.count(), 5) - self.assertTextEqual(doc.materialize(), u""" -Line #1 -<<<<<<< -Line #2 is hot -======= -Line #2 is cool ->>>>>>> -Line #3 -""") - - def test_multiply_parallel_commits(self): - doc = Document.objects.create(name=u"Sample Document", creator=self.user) - self.assert_(doc.head is not None) - c1 = doc.commit(description="Commit A1", text=u""" -Line #1 - -Line #2 - -Line #3 -""", author=self.user) - c2 = doc.commit(description="Commit A2", text=u""" -Line #1 * - -Line #2 - -Line #3 -""", author=self.user) - c3 = doc.commit(description="Commit B1", text=u""" -Line #1 - -Line #2 ** - -Line #3 -""", parent=c1, author=self.user) - c4 = doc.commit(description="Commit C1", text=u""" -Line #1 * - -Line #2 - -Line #3 *** -""", parent=c2, author=self.user) - self.assertEqual(doc.change_set.count(), 7) - self.assertTextEqual(doc.materialize(), u""" -Line #1 * - -Line #2 ** - -Line #3 *** -""") - diff --git a/apps/dvcs/tests/__init__.py b/apps/dvcs/tests/__init__.py new file mode 100755 index 00000000..de77d991 --- /dev/null +++ b/apps/dvcs/tests/__init__.py @@ -0,0 +1,158 @@ +from nose.tools import * +from django.test import TestCase +from dvcs.models import Document + + +class ADocument(Document): + pass + + +class DocumentModelTests(TestCase): + + def assertTextEqual(self, given, expected): + return self.assertEqual(given, expected, + "Expected '''%s'''\n differs from text: '''%s'''" % (expected, given) + ) + + def test_empty_file(self): + doc = ADocument.objects.create() + self.assertTextEqual(doc.materialize(), u"") + + def test_single_commit(self): + doc = ADocument.objects.create() + doc.commit(text=u"Ala ma kota", description="Commit #1") + self.assertTextEqual(doc.materialize(), u"Ala ma kota") + + def test_chained_commits(self): + doc = ADocument.objects.create() + text1 = u""" + Line #1 + Line #2 is cool + """ + text2 = u""" + Line #1 + Line #2 is hot + """ + text3 = u""" + Line #1 + ... is hot + Line #3 ate Line #2 + """ + + c1 = doc.commit(description="Commit #1", text=text1) + c2 = doc.commit(description="Commit #2", text=text2) + c3 = doc.commit(description="Commit #3", text=text3) + + self.assertTextEqual(doc.materialize(), text3) + self.assertTextEqual(doc.materialize(change=c3), text3) + self.assertTextEqual(doc.materialize(change=c2), text2) + self.assertTextEqual(doc.materialize(change=c1), text1) + + def test_parallel_commit_noconflict(self): + doc = ADocument.objects.create() + text1 = u""" + Line #1 + Line #2 + """ + text2 = u""" + Line #1 is hot + Line #2 + """ + text3 = u""" + Line #1 + Line #2 + Line #3 + """ + text_merged = u""" + Line #1 is hot + Line #2 + Line #3 + """ + + base = doc.commit(description="Commit #1", text=text1) + c1 = doc.commit(description="Commit #2", text=text2) + commits = doc.change_set.count() + c2 = doc.commit(description="Commit #3", text=text3, parent=base) + self.assertEqual(doc.change_set.count(), commits + 2, + u"Parallel commits should create an additional merge commit") + self.assertTextEqual(doc.materialize(), text_merged) + + def test_parallel_commit_conflict(self): + doc = ADocument.objects.create() + text1 = u""" + Line #1 + Line #2 + Line #3 + """ + text2 = u""" + Line #1 + Line #2 is hot + Line #3 + """ + text3 = u""" + Line #1 + Line #2 is cool + Line #3 + """ + text_merged = u""" + Line #1 +<<<<<<< + Line #2 is hot +======= + Line #2 is cool +>>>>>>> + Line #3 + """ + base = doc.commit(description="Commit #1", text=text1) + c1 = doc.commit(description="Commit #2", text=text2) + commits = doc.change_set.count() + c2 = doc.commit(description="Commit #3", text=text3, parent=base) + self.assertEqual(doc.change_set.count(), commits + 2, + u"Parallel commits should create an additional merge commit") + self.assertTextEqual(doc.materialize(), text_merged) + + + def test_multiple_parallel_commits(self): + text_a1 = u""" + Line #1 + + Line #2 + + Line #3 + """ + text_a2 = u""" + Line #1 * + + Line #2 + + Line #3 + """ + text_b1 = u""" + Line #1 + + Line #2 ** + + Line #3 + """ + text_c1 = u""" + Line #1 + + Line #2 + + Line #3 *** + """ + text_merged = u""" + Line #1 * + + Line #2 ** + + Line #3 *** + """ + + + doc = ADocument.objects.create() + c1 = doc.commit(description="Commit A1", text=text_a1) + c2 = doc.commit(description="Commit A2", text=text_a2, parent=c1) + c3 = doc.commit(description="Commit B1", text=text_b1, parent=c1) + c4 = doc.commit(description="Commit C1", text=text_c1, parent=c1) + self.assertTextEqual(doc.materialize(), text_merged) diff --git a/apps/dvcs/urls.py b/apps/dvcs/urls.py deleted file mode 100644 index d1e1e296..00000000 --- a/apps/dvcs/urls.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -from django.conf.urls.defaults import * - -urlpatterns = patterns('dvcs.views', - url(r'^data/(?P[^/]+)/(?P.*)$', 'document_data', name='storage_document_data'), -) diff --git a/apps/dvcs/views.py b/apps/dvcs/views.py deleted file mode 100644 index 7918e96c..00000000 --- a/apps/dvcs/views.py +++ /dev/null @@ -1,21 +0,0 @@ -# Create your views here. -from django.views.generic.simple import direct_to_template -from django import http -from dvcs.models import Document - -def document_list(request, template_name="dvcs/document_list.html"): - return direct_to_template(request, template_name, { - "documents": Document.objects.all(), - }) - -def document_data(request, document_id, version=None): - doc = Document.objects.get(pk=document_id) - return http.HttpResponse(doc.materialize(version or None), content_type="text/plain") - -def document_history(request, docid, template_name="dvcs/document_history.html"): - document = Document.objects.get(pk=docid) - return direct_to_template(request, template_name, { - "document": document, - "changes": document.history(), - }) - diff --git a/apps/wiki/tests.py b/apps/wiki/tests.py deleted file mode 100644 index 65777379..00000000 --- a/apps/wiki/tests.py +++ /dev/null @@ -1,19 +0,0 @@ -from nose.tools import * -import wiki.models as models -import shutil -import tempfile - - -class TestStorageBase: - def setUp(self): - self.dirpath = tempfile.mkdtemp(prefix='nosetest_') - - def tearDown(self): - shutil.rmtree(self.dirpath) - - -class TestDocumentStorage(TestStorageBase): - - def test_storage_empty(self): - storage = models.DocumentStorage(self.dirpath) - eq_(storage.all(), []) diff --git a/apps/wiki/views.py b/apps/wiki/views.py index adffcb76..0455bdde 100644 --- a/apps/wiki/views.py +++ b/apps/wiki/views.py @@ -280,8 +280,7 @@ def pubmark(request, slug, chunk=None): publishable = form.cleaned_data['publishable'] change = doc.at_revision(revision) if publishable != change.publishable: - change.publishable = publishable - change.save() + change.set_publishable(publishable) return JSONResponse({"message": _("Revision marked")}) else: return JSONResponse({"message": _("Nothing changed")}) diff --git a/redakcja.wsgi.template b/redakcja.wsgi.template index ef19f5a7..2d10ac38 100644 --- a/redakcja.wsgi.template +++ b/redakcja.wsgi.template @@ -19,6 +19,7 @@ sys.path = [ ] + sys.path # Run Django +os.environ["CELERY_LOADER"] = "django" os.environ['DJANGO_SETTINGS_MODULE'] = '$PROJECT_NAME.settings' from django.core.handlers.wsgi import WSGIHandler diff --git a/redakcja/settings/common.py b/redakcja/settings/common.py index 9d6e175e..5414246d 100644 --- a/redakcja/settings/common.py +++ b/redakcja/settings/common.py @@ -10,8 +10,6 @@ TEMPLATE_DEBUG = DEBUG MAINTENANCE_MODE = False ADMINS = ( - # (u'Marek Stępniowski', 'marek@stepniowski.com'), - # (u'Łukasz Rekucki', 'lrekucki@gmail.com'), (u'Radek Czajka', 'radoslaw.czajka@nowoczesnapolska.org.pl'), ) @@ -116,6 +114,8 @@ INSTALLED_APPS = ( 'filebrowser', 'pagination', 'gravatar', + 'djcelery', + 'djkombu', 'catalogue', 'dvcs', @@ -137,12 +137,10 @@ FILEBROWSER_DEFAULT_ORDER = "path_relative" IMAGE_DIR = 'images' -WL_API_CONFIG = { - "URL": "http://localhost:7000/api/", - "AUTH_REALM": "WL API", - "AUTH_USER": "platforma", - "AUTH_PASSWD": "platforma", -} +import djcelery +djcelery.setup_loader() + + SHOW_APP_VERSION = False diff --git a/redakcja/settings/compress.py b/redakcja/settings/compress.py index db72c00f..e4b5b085 100644 --- a/redakcja/settings/compress.py +++ b/redakcja/settings/compress.py @@ -13,11 +13,11 @@ COMPRESS_CSS = { ), 'output_filename': 'compressed/detail_styles_?.css', }, - 'listing': { + 'catalogue': { 'source_filenames': ( 'css/filelist.css', ), - 'output_filename': 'compressed/listing_styles_?.css', + 'output_filename': 'compressed/catalogue_styles_?.css', } } @@ -59,12 +59,12 @@ COMPRESS_JS = { ), 'output_filename': 'compressed/detail_scripts_?.js', }, - 'listing': { + 'catalogue': { 'source_filenames': ( - 'js/lib/jquery-1.4.2.min.js', + 'js/catalogue.js', 'js/slugify.js', ), - 'output_filename': 'compressed/listing_scripts_?.js', + 'output_filename': 'compressed/catalogue_scripts_?.js', } } diff --git a/redakcja/settings/test.py b/redakcja/settings/test.py index 118c7ffb..f6e90803 100644 --- a/redakcja/settings/test.py +++ b/redakcja/settings/test.py @@ -11,12 +11,12 @@ DATABASE_NAME = ':memory:' import tempfile -WIKI_REPOSITORY_PATH = tempfile.mkdtemp(prefix='wikirepo') +CATALOGUE_REPO_PATH = tempfile.mkdtemp(prefix='wikirepo') INSTALLED_APPS += ('django_nose',) TEST_RUNNER = 'django_nose.run_tests' -TEST_MODULES = ('wiki', 'toolbar', 'vstorage') +TEST_MODULES = ('catalogue', 'dvcs.tests', 'wiki', 'toolbar') NOSE_ARGS = ( '--tests=' + ','.join(TEST_MODULES), '--cover-package=' + ','.join(TEST_MODULES), diff --git a/redakcja/static/js/catalogue/catalogue.js b/redakcja/static/js/catalogue/catalogue.js new file mode 100755 index 00000000..e8ef5e95 --- /dev/null +++ b/redakcja/static/js/catalogue/catalogue.js @@ -0,0 +1,29 @@ +(function($) { + $(function() { + + + $(function() { + $('.filter').change(function() { + document.filter[this.name].value = this.value; + document.filter.submit(); + }); + + $('.check-filter').change(function() { + document.filter[this.name].value = this.checked ? '1' : ''; + document.filter.submit(); + }); + + $('.text-filter').each(function() { + var inp = this; + $(inp).parent().submit(function() { + document.filter[inp.name].value = inp.value; + document.filter.submit(); + return false; + }); + }); + }); + + + }); +})(jQuery) + diff --git a/redakcja/templates/pagination/pagination.html b/redakcja/templates/pagination/pagination.html new file mode 100755 index 00000000..fe566a86 --- /dev/null +++ b/redakcja/templates/pagination/pagination.html @@ -0,0 +1,26 @@ +{% if is_paginated %} +{% load i18n %} + +{% endif %} diff --git a/requirements-test.txt b/requirements-test.txt index fe7944cf..2ec68c0d 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,3 +1,3 @@ -django-nose==0.0.3 +django-nose==0.1.3 nose nosexcover diff --git a/requirements.txt b/requirements.txt index c5d6b044..5e942547 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,8 @@ sorl-thumbnail>=3.2 django-maintenancemode>=0.9 django-pagination django-gravatar +django-celery +django-kombu # migrations south>=0.6 -- 2.20.1