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.
 
   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
 
  13 class DocumentCreateForm(forms.ModelForm):
 
  15         Form used for creating new documents.
 
  17     file = forms.FileField(required=False)
 
  18     text = forms.CharField(required=False, widget=forms.Textarea)
 
  19     docx = forms.FileField(required=False)
 
  23         exclude = ['parent', 'parent_number', 'project']
 
  25     def __init__(self, *args, **kwargs):
 
  26         super(DocumentCreateForm, self).__init__(*args, **kwargs)
 
  27         self.fields['slug'].widget.attrs={'class': 'autoslug'}
 
  28         self.fields['slug'].required = False
 
  29         self.fields['gallery'].widget.attrs={'class': 'autoslug'}
 
  30         self.fields['title'].widget.attrs={'class': 'autoslug-source'}
 
  31         self.fields['title'].required = False
 
  34         super(DocumentCreateForm, self).clean()
 
  35         file = self.cleaned_data['file']
 
  39                 self.cleaned_data['text'] = file.read().decode('utf-8')
 
  40             except UnicodeDecodeError:
 
  41                 raise forms.ValidationError(_("Text file must be UTF-8 encoded."))
 
  43         docx = self.cleaned_data['docx']
 
  46                 text, meta = xml_from_docx(docx)
 
  47             except Exception as e:
 
  48                 raise forms.ValidationError(e)
 
  50                 self.cleaned_data['text'] = text
 
  51                 if not self.cleaned_data['title']:
 
  52                     self.cleaned_data['title'] = meta.get('title', '')
 
  53                 if not self.cleaned_data['slug']:
 
  54                     self.cleaned_data['slug'] = slugify(meta.get('title', ''))
 
  56         if not self.cleaned_data["title"]:
 
  57             self._errors["title"] = self.error_class([_("Title not set")])
 
  59         if not self.cleaned_data["slug"]:
 
  60             self._errors["slug"] = self.error_class([_("Slug not set")])
 
  62         if not self.cleaned_data["text"]:
 
  63             self._errors["text"] = self.error_class([_("You must either enter text or upload a file")])
 
  65         return self.cleaned_data
 
  68 class DocumentsUploadForm(forms.Form):
 
  70         Form used for uploading new documents.
 
  72     file = forms.FileField(required=True, label=_('ZIP file'))
 
  73     dirs = forms.BooleanField(label=_('Directories are documents in chunks'),
 
  74             widget = forms.CheckboxInput(attrs={'disabled':'disabled'}))
 
  77         file = self.cleaned_data['file']
 
  81             z = self.cleaned_data['zip'] = zipfile.ZipFile(file)
 
  82         except zipfile.BadZipfile:
 
  83             raise forms.ValidationError("Should be a ZIP file.")
 
  85             raise forms.ValidationError("ZIP file corrupt.")
 
  87         return self.cleaned_data
 
  90 class ChunkForm(forms.ModelForm):
 
  92         Form used for editing a chunk.
 
  94     user = forms.ModelChoiceField(queryset=
 
  95         User.objects.annotate(count=Count('chunk')).
 
  96         order_by('last_name', 'first_name'), required=False,
 
  97         label=_('Assigned to')) 
 
 101         fields = ['title', 'slug', 'gallery_start', 'user', 'stage']
 
 104     def __init__(self, *args, **kwargs):
 
 105         super(ChunkForm, self).__init__(*args, **kwargs)
 
 106         self.fields['gallery_start'].widget.attrs={'class': 'number-input'}
 
 107         self.fields['slug'].widget.attrs={'class': 'autoslug'}
 
 108         self.fields['title'].widget.attrs={'class': 'autoslug-source'}
 
 110     def clean_slug(self):
 
 111         slug = self.cleaned_data['slug']
 
 113             chunk = Chunk.objects.get(book=self.instance.book, slug=slug)
 
 114         except Chunk.DoesNotExist:
 
 116         if chunk == self.instance:
 
 118         raise forms.ValidationError(_('Chunk with this slug already exists'))
 
 121 class ChunkAddForm(ChunkForm):
 
 123         Form used for adding a chunk to a document.
 
 126     def clean_slug(self):
 
 127         slug = self.cleaned_data['slug']
 
 129             user = Chunk.objects.get(book=self.instance.book, slug=slug)
 
 130         except Chunk.DoesNotExist:
 
 132         raise forms.ValidationError(_('Chunk with this slug already exists'))
 
 135 class BookAppendForm(forms.Form):
 
 137         Form for appending a book to another book.
 
 138         It means moving all chunks from book A to book B and deleting A.
 
 140     append_to = forms.ModelChoiceField(queryset=Book.objects.all(),
 
 141             label=_("Append to"))
 
 143     def __init__(self, book, *args, **kwargs):
 
 144         ret =  super(BookAppendForm, self).__init__(*args, **kwargs)
 
 145         self.fields['append_to'].queryset = Book.objects.exclude(pk=book.pk)
 
 149 class BookForm(forms.ModelForm):
 
 150     """Form used for editing a Book."""
 
 154         exclude = ['project', 'cover', 'legimi_id']
 
 156     def __init__(self, *args, **kwargs):
 
 157         ret = super(BookForm, self).__init__(*args, **kwargs)
 
 158         self.fields['slug'].widget.attrs.update({"class": "autoslug"})
 
 159         self.fields['title'].widget.attrs.update({"class": "autoslug-source"})
 
 162     def save(self, **kwargs):
 
 163         orig_instance = Book.objects.get(pk=self.instance.pk)
 
 164         old_gallery = orig_instance.gallery
 
 165         new_gallery = self.cleaned_data['gallery']
 
 166         if new_gallery and old_gallery and new_gallery != old_gallery:
 
 169             from django.conf import settings
 
 170             shutil.move(orig_instance.gallery_path(),
 
 171                         os.path.join(settings.MEDIA_ROOT, settings.IMAGE_DIR, new_gallery))
 
 172         super(BookForm, self).save(**kwargs)
 
 175 class ReadonlyBookForm(BookForm):
 
 176     """Form used for not editing a Book."""
 
 178     def __init__(self, *args, **kwargs):
 
 179         ret = super(ReadonlyBookForm, self).__init__(*args, **kwargs)
 
 180         for field in self.fields.values():
 
 181             field.widget.attrs.update({"disabled": "disabled"})
 
 185 class ChooseMasterForm(forms.Form):
 
 187         Form used for fixing the chunks in a book.
 
 190     master = forms.ChoiceField(choices=((m, m) for m in MASTERS))
 
 193 class ImageForm(forms.ModelForm):
 
 194     """Form used for editing an Image."""
 
 195     user = forms.ModelChoiceField(queryset=
 
 196         User.objects.annotate(count=Count('chunk')).
 
 197         order_by('-count', 'last_name', 'first_name'), required=False,
 
 198         label=_('Assigned to')) 
 
 202         fields = ['title', 'slug', 'user', 'stage']
 
 204     def __init__(self, *args, **kwargs):
 
 205         super(ImageForm, self).__init__(*args, **kwargs)
 
 206         self.fields['slug'].widget.attrs={'class': 'autoslug'}
 
 207         self.fields['title'].widget.attrs={'class': 'autoslug-source'}
 
 210 class ReadonlyImageForm(ImageForm):
 
 211     """Form used for not editing an Image."""
 
 213     def __init__(self, *args, **kwargs):
 
 214         super(ReadonlyImageForm, self).__init__(*args, **kwargs)
 
 215         for field in self.fields.values():
 
 216             field.widget.attrs.update({"disabled": "disabled"})
 
 219 class MarkFinalForm(forms.Form):
 
 220     username = forms.CharField(initial=settings.LITERARY_DIRECTOR_USERNAME)
 
 221     comment = forms.CharField(initial=u'Ostateczna akceptacja merytoryczna przez kierownika literackiego.')
 
 222     books = forms.CharField(widget=forms.Textarea, help_text=u'linki do książek w redakcji, po jednym na wiersz')
 
 224     def clean_books(self):
 
 225         books_value = self.cleaned_data['books']
 
 226         slugs = [line.strip().strip('/').split('/')[-1] for line in books_value.split('\n') if line.strip()]
 
 227         books = Book.objects.filter(slug__in=slugs)
 
 228         if len(books) != len(slugs):
 
 229             raise forms.ValidationError(
 
 230                 'Incorrect slug(s): %s' % ' '.join(slug for slug in slugs if not Book.objects.filter(slug=slug)))
 
 233     def clean_username(self):
 
 234         username = self.cleaned_data['username']
 
 235         if not User.objects.filter(username=username):
 
 236             raise forms.ValidationError('Invalid username')
 
 240         for book in self.cleaned_data['books']:
 
 241             for chunk in book.chunk_set.all():
 
 242                 src = chunk.head.materialize()
 
 245                     author=User.objects.get(username=self.cleaned_data['username']),
 
 246                     description=self.cleaned_data['comment'],
 
 247                     tags=[Chunk.tag_model.objects.get(slug='editor-proofreading')],
 
 252 class PublishOptionsForm(forms.Form):
 
 253     days = forms.IntegerField(label=u'po ilu dniach udostępnienić (0 = od razu)', min_value=0, initial=0)
 
 254     hidden = forms.BooleanField(label='tekst ukryty przed wyszukiwaniem', required=False, initial=False)
 
 255     beta = forms.BooleanField(label=u'Opublikuj na wersji testowej', required=False)