ptrad
[redakcja.git] / src / documents / forms.py
1 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 from django.db.models import Count
5 from django import forms
6 from django.utils.translation import gettext_lazy as _
7 from django.conf import settings
8 from slugify import slugify
9 from .constants import MASTERS
10 from .models import Book, Chunk, Image, User
11 from .docx import xml_from_docx
12
13 class DocumentCreateForm(forms.ModelForm):
14     """
15         Form used for creating new documents.
16     """
17     file = forms.FileField(required=False)
18     text = forms.CharField(required=False, widget=forms.Textarea)
19     docx = forms.FileField(required=False)
20
21     class Meta:
22         model = Book
23         fields = [
24             'title', 'slug', 'public', 'gallery'
25         ]
26
27     def __init__(self, *args, **kwargs):
28         super(DocumentCreateForm, self).__init__(*args, **kwargs)
29         self.fields['slug'].widget.attrs={'class': 'autoslug'}
30         self.fields['slug'].required = False
31         self.fields['gallery'].widget.attrs={'class': 'autoslug'}
32         self.fields['title'].widget.attrs={'class': 'autoslug-source'}
33         self.fields['title'].required = False
34
35     def clean(self):
36         super(DocumentCreateForm, self).clean()
37         file = self.cleaned_data['file']
38
39         if file is not None:
40             try:
41                 self.cleaned_data['text'] = file.read().decode('utf-8')
42             except UnicodeDecodeError:
43                 raise forms.ValidationError(_("Text file must be UTF-8 encoded."))
44
45         docx = self.cleaned_data['docx']
46         if docx is not None:
47             try:
48                 text, meta = xml_from_docx(docx)
49             except Exception as e:
50                 raise forms.ValidationError(e)
51             else:
52                 self.cleaned_data['text'] = text
53                 if not self.cleaned_data['title']:
54                     self.cleaned_data['title'] = meta.get('title', '')
55                 if not self.cleaned_data['slug']:
56                     self.cleaned_data['slug'] = slugify(meta.get('title', ''))
57
58         if not self.cleaned_data["title"]:
59             self._errors["title"] = self.error_class([_("Title not set")])
60
61         if not self.cleaned_data["slug"]:
62             self._errors["slug"] = self.error_class([_("Slug not set")])
63
64         if not self.cleaned_data["text"]:
65             self._errors["text"] = self.error_class([_("You must either enter text or upload a file")])
66
67         return self.cleaned_data
68
69
70 class DocumentsUploadForm(forms.Form):
71     """
72         Form used for uploading new documents.
73     """
74     file = forms.FileField(required=True, label=_('ZIP file'))
75     dirs = forms.BooleanField(label=_('Directories are documents in chunks'),
76             widget = forms.CheckboxInput(attrs={'disabled':'disabled'}))
77
78     def clean(self):
79         file = self.cleaned_data['file']
80
81         import zipfile
82         try:
83             z = self.cleaned_data['zip'] = zipfile.ZipFile(file)
84         except zipfile.BadZipfile:
85             raise forms.ValidationError("Should be a ZIP file.")
86         if z.testzip():
87             raise forms.ValidationError("ZIP file corrupt.")
88
89         return self.cleaned_data
90
91
92 class ChunkForm(forms.ModelForm):
93     """
94         Form used for editing a chunk.
95     """
96     user = forms.ModelChoiceField(queryset=
97         User.objects.annotate(count=Count('chunk')).
98         order_by('last_name', 'first_name'), required=False,
99         label=_('Assigned to')) 
100
101     class Meta:
102         model = Chunk
103         fields = ['title', 'slug', 'gallery_start', 'user', 'stage']
104         exclude = ['number']
105
106     def __init__(self, *args, **kwargs):
107         super(ChunkForm, self).__init__(*args, **kwargs)
108         self.fields['gallery_start'].widget.attrs={'class': 'number-input'}
109         self.fields['slug'].widget.attrs={'class': 'autoslug'}
110         self.fields['title'].widget.attrs={'class': 'autoslug-source'}
111
112     def clean_slug(self):
113         slug = self.cleaned_data['slug']
114         try:
115             chunk = Chunk.objects.get(book=self.instance.book, slug=slug)
116         except Chunk.DoesNotExist:
117             return slug
118         if chunk == self.instance:
119             return slug
120         raise forms.ValidationError(_('Chunk with this slug already exists'))
121
122
123 class ChunkAddForm(ChunkForm):
124     """
125         Form used for adding a chunk to a document.
126     """
127
128     def clean_slug(self):
129         slug = self.cleaned_data['slug']
130         try:
131             user = Chunk.objects.get(book=self.instance.book, slug=slug)
132         except Chunk.DoesNotExist:
133             return slug
134         raise forms.ValidationError(_('Chunk with this slug already exists'))
135
136
137 class BookAppendForm(forms.Form):
138     """
139         Form for appending a book to another book.
140         It means moving all chunks from book A to book B and deleting A.
141     """
142     append_to = forms.ModelChoiceField(queryset=Book.objects.all(),
143             label=_("Append to"))
144
145     def __init__(self, book, *args, **kwargs):
146         ret =  super(BookAppendForm, self).__init__(*args, **kwargs)
147         self.fields['append_to'].queryset = Book.objects.exclude(pk=book.pk)
148         return ret
149
150
151 class BookForm(forms.ModelForm):
152     """Form used for editing a Book."""
153
154     class Meta:
155         model = Book
156         exclude = ['project', 'cover']
157
158     def __init__(self, *args, **kwargs):
159         ret = super(BookForm, self).__init__(*args, **kwargs)
160         self.fields['slug'].widget.attrs.update({"class": "autoslug"})
161         self.fields['title'].widget.attrs.update({"class": "autoslug-source"})
162         return ret
163
164     def save(self, **kwargs):
165         orig_instance = Book.objects.get(pk=self.instance.pk)
166         old_gallery = orig_instance.gallery
167         new_gallery = self.cleaned_data['gallery']
168         if new_gallery and old_gallery and new_gallery != old_gallery:
169             import shutil
170             import os.path
171             from django.conf import settings
172             shutil.move(orig_instance.gallery_path(),
173                         os.path.join(settings.MEDIA_ROOT, settings.IMAGE_DIR, new_gallery))
174         super(BookForm, self).save(**kwargs)
175
176
177 class ReadonlyBookForm(BookForm):
178     """Form used for not editing a Book."""
179
180     def __init__(self, *args, **kwargs):
181         ret = super(ReadonlyBookForm, self).__init__(*args, **kwargs)
182         for field in self.fields.values():
183             field.widget.attrs.update({"disabled": "disabled"})
184         return ret
185
186
187 class ChooseMasterForm(forms.Form):
188     """
189         Form used for fixing the chunks in a book.
190     """
191
192     master = forms.ChoiceField(choices=((m, m) for m in MASTERS))
193
194
195 class ImageForm(forms.ModelForm):
196     """Form used for editing an Image."""
197     user = forms.ModelChoiceField(queryset=
198         User.objects.annotate(count=Count('chunk')).
199         order_by('-count', 'last_name', 'first_name'), required=False,
200         label=_('Assigned to')) 
201
202     class Meta:
203         model = Image
204         fields = ['title', 'slug', 'user', 'stage']
205
206     def __init__(self, *args, **kwargs):
207         super(ImageForm, self).__init__(*args, **kwargs)
208         self.fields['slug'].widget.attrs={'class': 'autoslug'}
209         self.fields['title'].widget.attrs={'class': 'autoslug-source'}
210
211
212 class ReadonlyImageForm(ImageForm):
213     """Form used for not editing an Image."""
214
215     def __init__(self, *args, **kwargs):
216         super(ReadonlyImageForm, self).__init__(*args, **kwargs)
217         for field in self.fields.values():
218             field.widget.attrs.update({"disabled": "disabled"})
219
220
221 class MarkFinalForm(forms.Form):
222     username = forms.CharField(initial=settings.LITERARY_DIRECTOR_USERNAME)
223     comment = forms.CharField(initial=u'Ostateczna akceptacja merytoryczna przez kierownika literackiego.')
224     books = forms.CharField(widget=forms.Textarea, help_text=u'linki do książek w redakcji, po jednym na wiersz')
225
226     def clean_books(self):
227         books_value = self.cleaned_data['books']
228         slugs = [line.strip().strip('/').split('/')[-1] for line in books_value.split('\n') if line.strip()]
229         books = Book.objects.filter(slug__in=slugs)
230         if len(books) != len(slugs):
231             raise forms.ValidationError(
232                 'Incorrect slug(s): %s' % ' '.join(slug for slug in slugs if not Book.objects.filter(slug=slug)))
233         return books
234
235     def clean_username(self):
236         username = self.cleaned_data['username']
237         if not User.objects.filter(username=username):
238             raise forms.ValidationError('Invalid username')
239         return username
240
241     def save(self):
242         for book in self.cleaned_data['books']:
243             for chunk in book.chunk_set.all():
244                 src = chunk.head.materialize()
245                 chunk.commit(
246                     text=src,
247                     author=User.objects.get(username=self.cleaned_data['username']),
248                     description=self.cleaned_data['comment'],
249                     tags=[Chunk.tag_model.objects.get(slug='editor-proofreading')],
250                     publishable=True
251                 )
252
253
254 class PublishOptionsForm(forms.Form):
255     days = forms.IntegerField(label=u'po ilu dniach udostępnienić (0 = od razu)', min_value=0, initial=0)
256     hidden = forms.BooleanField(label='tekst ukryty przed wyszukiwaniem', required=False, initial=False)
257     beta = forms.BooleanField(label=u'Opublikuj na wersji testowej', required=False)