Make static URL consistent
[Booktype.git] / lib / booktypecontrol / forms.py
blobe54c422e5190fb05b6dd37c322b714cad5dd66ff
1 import os
2 import shutil
3 import logging
5 from django import forms
6 from django import template
7 from django.conf import settings
8 from django.utils import timezone
9 from django.template.base import Context
10 from django.contrib.auth.models import User
11 from django.core.urlresolvers import reverse_lazy
12 from django.utils.translation import ugettext_lazy as _
13 from django.core.validators import RegexValidator, MinLengthValidator
15 from booktype.utils import config, misc
16 from booktype.convert import loader as convert_loader
17 from booktype.apps.convert import utils as convert_utils
18 from booktype.apps.account.models import UserProfile
19 from booktype.apps.core.forms import BaseBooktypeForm
20 from booktype.apps.core.models import Role, Permission, BookSkeleton
21 from booktype.apps.core.widgets import GroupedCheckboxSelectMultiple
22 from booktype.apps.portal.forms import GroupCreateForm
23 from booktype.apps.portal.widgets import RemovableImageWidget
24 from booki.editor.models import License, Book, BookiGroup, Language, METADATA_FIELDS
25 from booktype.utils.book import create_book, rename_book, check_book_availability
26 from . import widgets as cc_widgets
29 logger = logging.getLogger('booktype.controlcenter')
32 class BaseControlForm(BaseBooktypeForm):
33 """Base class for Control Center forms"""
35 success_message = None
36 success_url = None
37 cancel_url = reverse_lazy('control_center:settings')
39 @classmethod
40 def initial_data(cls):
41 return None
43 @classmethod
44 def extra_context(cls):
45 return {}
47 def get_cancel_url(self):
48 return self.cancel_url
50 def save_settings(self, request):
51 pass
54 class SiteDescriptionForm(BaseControlForm, forms.Form):
55 title = forms.CharField(
56 label=_("Site title"),
57 required=False,
58 error_messages={'required': _('Site title is required.')},
59 max_length=200
61 tagline = forms.CharField(
62 label=_("Tagline"),
63 required=False,
64 max_length=200
66 frontpage_url = forms.CharField(
67 label=_("Frontpage URL"),
68 required=False,
69 max_length=2048,
70 help_text=_('Can be a full absolute or relative url')
72 favicon = forms.FileField(
73 label=_("Favicon"),
74 required=False,
75 help_text=_("Upload .ico file")
78 @classmethod
79 def initial_data(cls):
80 return {
81 'title': config.get_configuration('BOOKTYPE_SITE_NAME'),
82 'tagline': config.get_configuration('BOOKTYPE_SITE_TAGLINE'),
83 'frontpage_url': config.get_configuration('BOOKTYPE_FRONTPAGE_URL')
86 def save_settings(self, request):
87 from uuid import uuid4
89 config.set_configuration(
90 'BOOKTYPE_SITE_NAME', self.cleaned_data['title'])
91 config.set_configuration(
92 'BOOKTYPE_SITE_TAGLINE', self.cleaned_data['tagline'])
93 config.set_configuration(
94 'BOOKTYPE_FRONTPAGE_URL', self.cleaned_data['frontpage_url'])
96 if 'favicon' in self.files:
97 # just check for any kind of silly error
98 try:
99 prev_favicon = config.get_configuration('BOOKTYPE_SITE_FAVICON', None)
100 fh, fname = misc.save_uploaded_as_file(self.files['favicon'])
101 rand_name = 'favicon.%s.ico' % uuid4().hex[:8]
102 shutil.move(fname, '{}/{}'.format(settings.STATIC_ROOT, rand_name))
104 config.set_configuration(
105 'BOOKTYPE_SITE_FAVICON',
106 '{}{}'.format(settings.STATIC_URL, rand_name)
109 # delete prev icon to avoid garbage
110 if prev_favicon:
111 try:
112 prev_name = prev_favicon.rsplit('/', 1)[-1]
113 os.remove('{}/{}'.format(settings.STATIC_ROOT, prev_name))
114 except Exception as err:
115 logger.exception("Unable to remove previous favicon. Msg: %s" % err)
116 except:
117 pass
119 try:
120 config.save_configuration()
121 except config.ConfigurationError as err:
122 raise err
125 class AppearanceForm(BaseControlForm, forms.Form):
126 css = forms.CharField(
127 label=_('CSS'),
128 required=False,
129 widget=forms.Textarea(attrs={'rows': 20, 'cols': 40})
132 @classmethod
133 def initial_data(cls):
134 try:
135 f = open('%s/css/_user.css' % settings.STATIC_ROOT, 'r')
136 css_content = unicode(f.read(), 'utf8')
137 f.close()
138 except IOError:
139 css_content = ''
141 return dict(css=css_content)
143 def save_settings(self, request):
144 try:
145 # should really save it in a safe way
146 f = open('%s/css/_user.css' % settings.STATIC_ROOT, 'w')
147 f.write(self.cleaned_data['css'].encode('utf8'))
148 f.close()
149 except IOError as err:
150 raise err
153 class FrontpageForm(BaseControlForm, forms.Form):
154 use_anonymous_page = forms.BooleanField(
155 label=_('Use anonymous page'),
156 required=False,
157 help_text=_('Show a message, contact email or image to anonymous visitors.')
159 anonymous_message = forms.CharField(
160 label=_('Anonymous page message'),
161 required=False,
162 widget=forms.Textarea(attrs={'rows': 8, 'cols': 40}),
163 help_text=_('Message for displaying on anonymous page.')
165 anonymous_email = forms.EmailField(
166 label=_('Anonymous page email'),
167 required=False,
168 help_text=_('Email for displaying on anonymous page.')
170 anonymous_image = forms.ImageField(
171 label=_("Anonymous page image"),
172 required=False,
173 help_text=_("Use image/png files."),
174 widget=RemovableImageWidget(attrs={
175 'label_class': 'checkbox-inline',
176 'input_class': 'group-image-removable'
180 def clean_anonymous_image(self):
181 data = self.cleaned_data['anonymous_image']
182 if data and data.content_type not in ['image/png']:
183 raise forms.ValidationError("Wrong file content type.")
184 return data
186 @classmethod
187 def initial_data(cls):
188 _dict = {}
189 try:
190 f = open(
191 '%s/templates/portal/welcome_message.html'
192 % settings.BOOKTYPE_ROOT,
195 _dict['description'] = unicode(f.read(), 'utf8')
196 f.close()
197 except IOError:
198 _dict['description'] = ''
200 _dict['use_anonymous_page'] = config.get_configuration('BOOKTYPE_FRONTPAGE_USE_ANONYMOUS_PAGE')
201 _dict['anonymous_message'] = config.get_configuration('BOOKTYPE_FRONTPAGE_ANONYMOUS_MESSAGE')
202 _dict['anonymous_email'] = config.get_configuration('BOOKTYPE_FRONTPAGE_ANONYMOUS_EMAIL')
203 _dict['anonymous_image'] = config.get_configuration('BOOKTYPE_FRONTPAGE_ANONYMOUS_IMAGE')
205 return _dict
207 def save_settings(self, request):
208 static_root = settings.BOOKTYPE_ROOT
210 config.set_configuration('BOOKTYPE_FRONTPAGE_USE_ANONYMOUS_PAGE', self.cleaned_data['use_anonymous_page'])
211 config.set_configuration('BOOKTYPE_FRONTPAGE_ANONYMOUS_MESSAGE', self.cleaned_data['anonymous_message'])
212 config.set_configuration('BOOKTYPE_FRONTPAGE_ANONYMOUS_EMAIL', self.cleaned_data['anonymous_email'])
214 # anonymous page image
215 destination_filename = 'anonymous_image.png'
216 destination_dir = '{0}/portal/frontpage/'.format(settings.MEDIA_ROOT)
217 destination_file_path = '{dir}{filename}'.format(dir=destination_dir, filename=destination_filename)
219 if 'anonymous_image_remove' in request.POST:
220 os.remove(destination_file_path)
221 config.del_configuration('BOOKTYPE_FRONTPAGE_ANONYMOUS_IMAGE')
222 elif 'anonymous_image' in self.files:
223 try:
224 fh, fname = misc.save_uploaded_as_file(self.files['anonymous_image'])
226 if not os.path.exists(destination_dir):
227 os.makedirs(destination_dir)
229 shutil.move(fname, destination_file_path)
230 config.set_configuration('BOOKTYPE_FRONTPAGE_ANONYMOUS_IMAGE',
231 '{0}portal/frontpage/anonymous_image.png'.format(settings.MEDIA_URL))
232 except:
233 pass
235 # welcome message
236 if not os.path.exists('%s/templates/portal/' % static_root):
237 os.makedirs('%s/templates/portal/' % static_root)
239 try:
240 f = open(
241 '%s/templates/portal/welcome_message.html' % static_root, 'w')
243 text_data = self.cleaned_data.get('description', '')
244 for ch in ['{%', '%}', '{{', '}}']:
245 text_data = text_data.replace(ch, '')
247 f.write(text_data.encode('utf8'))
248 f.close()
249 config.save_configuration()
250 except IOError as err:
251 raise err
252 except config.ConfigurationError as err:
253 raise err
256 class LicenseForm(BaseControlForm, forms.ModelForm):
257 abbrevation = forms.CharField(
258 label=_("Abbreviation"),
259 required=True,
260 error_messages={'required': _('Abbreviation is required.')},
261 max_length=30
263 name = forms.CharField(
264 label=_("Name"),
265 required=True,
266 error_messages={'required': _('License name is required.')},
267 max_length=100
269 url = forms.URLField(
270 label=_("License URL"),
271 required=True,
272 error_messages={'required': _('License name is required.')},
273 max_length=200
276 success_message = _('Successfully created new license.')
277 success_url = "#license"
279 class Meta:
280 model = License
281 fields = '__all__'
283 @classmethod
284 def extra_context(cls):
285 return dict(licenses=License.objects.all().order_by("name"))
287 def get_cancel_url(self):
288 return "{0}{1}".format(self.cancel_url, self.success_url)
290 def save_settings(self, request):
291 return self.save()
294 class BookSettingsForm(BaseControlForm, forms.Form):
295 hlp_visible = _(
296 "Default: visible. If this box is unchecked, create/import wizard will suggest to mark book as hidden by "
297 "default.")
299 hlp_track = _(
300 "If it is turned on then track changes will be enabled for all the users.")
302 create_book_visible = forms.BooleanField(
303 label=_('Visible by default'),
304 required=False,
305 help_text=_(hlp_visible))
307 track_changes = forms.BooleanField(
308 label=_('Track changes'),
309 required=False,
310 help_text=_(hlp_track))
312 create_book_license = forms.ModelChoiceField(
313 label=_('Default License'),
314 queryset=License.objects.all().order_by("name"),
315 required=False,
316 help_text=_("Default license for newly created books."))
318 create_book_language = forms.ModelChoiceField(
319 label=_('Default Language'),
320 queryset=Language.objects.all(),
321 required=False,
322 help_text=_("Default language for newly created books."))
324 create_book_metadata = forms.MultipleChoiceField(
325 label=_('Metadata to be filled'),
326 choices=[],
327 required=False,
328 widget=forms.CheckboxSelectMultiple(),
329 help_text=_("Select the metadata fields to be filled in book creation wizard (selected fields will be optional)")
332 def __init__(self, *args, **kwargs):
333 super(BookSettingsForm, self).__init__(*args, **kwargs)
335 for name, field in self.fields.items():
336 if name == 'create_book_metadata':
337 metadata_choices = []
339 for meta_field, label, standard in METADATA_FIELDS:
340 metadata_choices.append((
341 "%s.%s" % (standard, meta_field), label))
343 field.choices = metadata_choices
345 @classmethod
346 def initial_data(cls):
347 lic = config.get_configuration('CREATE_BOOK_LICENSE')
348 if lic and lic != '':
349 try:
350 license = License.objects.get(abbrevation=lic)
351 except License.DoesNotExist:
352 license = None
353 else:
354 license = None
356 lang = config.get_configuration('CREATE_BOOK_LANGUAGE')
357 if lang and lang != '':
358 try:
359 language = Language.objects.get(abbrevation=lang)
360 except License.DoesNotExist:
361 language = None
362 else:
363 language = None
365 create_book_metadata = config.get_configuration('CREATE_BOOK_METADATA', [])
367 return {
368 'create_book_visible': config.get_configuration('CREATE_BOOK_VISIBLE'),
369 'create_book_license': license,
370 'create_book_language': language,
371 'create_book_metadata': create_book_metadata,
372 'track_changes': config.get_configuration('BOOK_TRACK_CHANGES')
375 def save_settings(self, request):
376 config.set_configuration(
377 'CREATE_BOOK_VISIBLE', self.cleaned_data['create_book_visible'])
379 config.set_configuration(
380 'BOOK_TRACK_CHANGES', self.cleaned_data['track_changes'])
382 license = self.cleaned_data.get('create_book_license')
383 if license is not None:
384 config.set_configuration(
385 'CREATE_BOOK_LICENSE', license.abbrevation)
386 else:
387 config.set_configuration('CREATE_BOOK_LICENSE', '')
389 lang = self.cleaned_data.get('create_book_language')
390 if lang is not None:
391 config.set_configuration(
392 'CREATE_BOOK_LANGUAGE', lang.abbrevation)
393 else:
394 config.set_configuration('CREATE_BOOK_LANGUAGE', '')
396 # let's save metadata fields to be used
397 create_book_metadata = self.cleaned_data.get('create_book_metadata', [])
398 config.set_configuration('CREATE_BOOK_METADATA', create_book_metadata)
400 try:
401 config.save_configuration()
402 except config.ConfigurationError as err:
403 raise err
406 class PrivacyForm(BaseControlForm, forms.Form):
407 user_register = forms.BooleanField(
408 label=_('Anyone can register'),
409 required=False,
410 help_text=_('Anyone can register on the site and create an account')
412 create_books = forms.BooleanField(
413 label=_('Only the superuser can create books'),
414 required=False
416 import_books = forms.BooleanField(
417 label=_('Only the superuser can import books'),
418 required=False
421 @classmethod
422 def initial_data(cls):
423 return {
424 'user_register': config.get_configuration('FREE_REGISTRATION'),
425 'create_books': config.get_configuration('ADMIN_CREATE_BOOKS'),
426 'import_books': config.get_configuration('ADMIN_IMPORT_BOOKS')
429 def save_settings(self, request):
430 config.set_configuration(
431 'FREE_REGISTRATION', self.cleaned_data['user_register'])
432 config.set_configuration(
433 'ADMIN_CREATE_BOOKS', self.cleaned_data['create_books'])
434 config.set_configuration(
435 'ADMIN_IMPORT_BOOKS', self.cleaned_data['import_books'])
437 try:
438 config.save_configuration()
439 except config.ConfigurationError as err:
440 raise err
443 class AddPersonForm(BaseControlForm, forms.ModelForm):
444 username = forms.CharField(
445 label=_('Username'),
446 required=True,
447 error_messages={
448 'required': _('Username is required.'),
449 'ivalid': _("Illegal characters in username.")
451 max_length=100,
452 validators=[
453 RegexValidator(
454 r"^[\w\d\@\.\+\-\_]+$",
455 message=_("Illegal characters in username.")
457 MinLengthValidator(3)
460 first_name = forms.CharField(
461 label=_('Full name'),
462 required=True,
463 error_messages={'required': _('Full name is required.')},
464 max_length=32
466 email = forms.EmailField(
467 label=_('Email'),
468 required=True,
469 error_messages={'required': _('Email is required.')},
470 max_length=100
472 description = forms.CharField(
473 label=_("User description"),
474 required=False,
475 widget=forms.Textarea
477 password1 = forms.CharField(
478 label=_('Password'),
479 required=True,
480 error_messages={'required': _('Password is required.')},
481 max_length=100,
482 widget=forms.PasswordInput
484 password2 = forms.CharField(
485 label=_('Password confirmation'),
486 required=True,
487 error_messages={'required': _('Password is required.')},
488 max_length=100,
489 widget=forms.PasswordInput,
490 help_text=_("Enter the same password as above, for verification.")
492 send_email = forms.BooleanField(
493 label=_('Notify person by email'),
494 required=False
497 is_superuser = forms.BooleanField(
498 label=_("This person is a superuser"),
499 required=False
502 success_message = _('Successfully created new account.')
503 success_url = reverse_lazy('control_center:people_list')
505 def __init__(self, *args, **kwargs):
506 super(AddPersonForm, self).__init__(*args, **kwargs)
507 self.fields.keyOrder = ['username', 'first_name', 'email',
508 'description', 'password1', 'password2',
509 'send_email', 'is_superuser']
511 class Meta:
512 model = User
513 exclude = [
514 'password', 'last_login', 'groups',
515 'user_permissions', 'date_joined',
516 'is_staff', 'last_name', 'is_active'
519 def get_cancel_url(self):
520 return self.success_url
522 def clean_username(self):
523 try:
524 User.objects.get(username=self.cleaned_data['username'])
525 except User.DoesNotExist:
526 pass
527 else:
528 raise forms.ValidationError(_("That username is already taken."))
530 return self.cleaned_data['username']
532 def clean_password2(self):
533 if self.cleaned_data['password2'] != self.cleaned_data['password1']:
534 raise forms.ValidationError(_("Passwords do not match."))
536 return self.cleaned_data['password2']
538 def save_settings(self, request):
539 user_data = dict(
540 username=self.cleaned_data['username'],
541 email=self.cleaned_data['email'],
542 password=self.cleaned_data['password2'],
544 if self.cleaned_data.get('is_superuser', False):
545 user = User.objects.create_superuser(**user_data)
546 else:
547 user = User.objects.create_user(**user_data)
549 user.first_name = self.cleaned_data['first_name']
550 user.save()
552 profile = UserProfile.objects.get_or_create(user=user)[0]
553 profile.description = self.cleaned_data['description']
554 profile.save()
556 if self.cleaned_data.get('send_email', False):
557 t = template.loader.get_template(
558 'booktypecontrol/new_person_email.html')
559 content = t.render({
560 "username": self.cleaned_data['username'],
561 "password": self.cleaned_data['password2'],
562 "server": settings.BOOKTYPE_URL
565 from django.core.mail import EmailMultiAlternatives
566 emails = [self.cleaned_data['email']]
567 site_name = config.get_configuration('BOOKTYPE_SITE_NAME', 'Booktype')
569 msg = EmailMultiAlternatives(
570 _('You have a new %s account') % site_name,
571 content, settings.DEFAULT_FROM_EMAIL, emails
573 msg.attach_alternative(content, "text/html")
574 try:
575 msg.send(fail_silently=False)
576 except Exception as e:
577 logger.error(
578 '[CCENTER] Unable to send invite email to %s msg: %s' %
579 (self.cleaned_data['email'], e)
582 return user
585 class ArchivedUsersForm(BaseControlForm, forms.Form):
586 pass
588 @classmethod
589 def extra_context(cls):
590 return {
591 'archived_people': User.objects.filter(is_active=False).order_by("username")
595 class EditPersonInfoForm(BaseControlForm, forms.ModelForm):
596 username = forms.CharField(
597 label=_('Username'),
598 required=True,
599 max_length=100,
600 error_messages={
601 'required': _('Username is required.'),
602 'ivalid': _("Illegal characters in username.")
604 validators=[
605 RegexValidator(
606 r"^[\w\d\@\.\+\-\_]+$",
607 message=_("Illegal characters in username.")
609 MinLengthValidator(3)
612 first_name = forms.CharField(
613 label=_('Full name'),
614 required=True,
615 error_messages={'required': _('Full name is required.')},
616 max_length=32
618 email = forms.EmailField(
619 label=_('Email'),
620 required=True,
621 error_messages={'required': _('Email is required.')},
622 max_length=100
624 profile = forms.ImageField(
625 label=_('Profile picture'),
626 required=False,
627 widget=RemovableImageWidget(attrs={
628 'label_class': 'checkbox-inline',
629 'input_class': 'group-image-removable'
632 description = forms.CharField(
633 label=_("User description"),
634 required=False,
635 widget=forms.Textarea
638 is_superuser = forms.BooleanField(
639 label=_("This person is a superuser"),
640 required=False
642 is_active = forms.BooleanField(
643 label=_("Active account"),
644 required=False,
645 help_text=_("Indicate whether user's account is archived or active.")
648 def __init__(self, *args, **kwargs):
649 super(EditPersonInfoForm, self).__init__(*args, **kwargs)
650 self.fields.keyOrder = ['username', 'first_name', 'email',
651 'description', 'is_superuser']
653 class Meta:
654 model = User
655 exclude = [
656 'password', 'last_login', 'groups',
657 'user_permissions', 'date_joined',
658 'is_staff', 'last_name'
661 def get_cancel_url(self):
662 return reverse_lazy('control_center:people_list')
665 class PasswordForm(BaseControlForm, forms.Form):
666 error_messages = {
667 'password_mismatch': _("The two password fields didn't match."),
670 password1 = forms.CharField(
671 label=_('Password'),
672 required=True,
673 error_messages={'required': _('Password is required.')},
674 max_length=100,
675 widget=forms.PasswordInput
677 password2 = forms.CharField(
678 label=_('Password confirmation'),
679 required=True,
680 max_length=100,
681 error_messages={'required': _('Password is required.')},
682 widget=forms.PasswordInput,
683 help_text=_("Enter the same password as above, for verification.")
685 send_login_data = forms.BooleanField(
686 label=_('Send login data'),
687 required=False,
688 help_text=_('Send new login data to the user via email.'),
689 initial=True
691 short_message = forms.CharField(
692 label=_('Short message'),
693 required=False,
694 help_text=_('Will be added in the bottom of email message text, after new user credentials.'),
695 widget=forms.Textarea(attrs={'rows': 4}),
696 initial=_('You can change password in your profile settings page.')
699 def __init__(self, user, *args, **kwargs):
700 self.user = user
701 super(PasswordForm, self).__init__(*args, **kwargs)
703 def clean_password2(self):
704 password1 = self.cleaned_data.get('password1')
705 password2 = self.cleaned_data.get('password2')
706 if password1 and password2:
707 if password1 != password2:
708 raise forms.ValidationError(
709 self.error_messages['password_mismatch'])
710 return password2
712 def save(self, commit=True):
713 self.user.set_password(self.cleaned_data['password1'])
714 if commit:
715 self.user.save()
716 return self.user
719 class AddBookForm(BaseControlForm, forms.Form):
720 title = forms.CharField(
721 label=_("Title"),
722 error_messages={'required': _('Title is required.')},
723 required=True,
724 max_length=100
726 description = forms.CharField(
727 label=_('Description'),
728 required=False,
729 widget=forms.Textarea
731 owner = forms.ModelChoiceField(
732 label=_('Owner'),
733 error_messages={'required': _('Book owner is required.')},
734 queryset=User.objects.all().order_by("username"),
735 required=True
737 license = forms.ModelChoiceField(
738 label=_('License'),
739 queryset=License.objects.all().order_by("name"),
740 error_messages={'required': _('License is required.')},
741 required=True
743 is_hidden = forms.BooleanField(
744 label=_('Hide this book from other people'),
745 required=False
747 cover = forms.ImageField(
748 label=_('Book image'),
749 required=False
752 success_message = _('Successfully created new book.')
754 def clean_title(self):
755 if not check_book_availability(self.cleaned_data['title']):
756 raise forms.ValidationError(_("That book already exists."))
757 return self.cleaned_data['title']
759 def save_settings(self, request):
760 book = create_book(
761 self.cleaned_data['owner'],
762 self.cleaned_data['title']
764 book.license = self.cleaned_data['license']
765 book.description = self.cleaned_data['description']
766 book.hidden = self.cleaned_data['is_hidden']
767 book.save()
769 if 'cover' in self.files:
770 try:
771 fh, fname = misc.save_uploaded_as_file(self.files['cover'])
772 book.set_cover(fname)
773 os.unlink(fname)
774 except:
775 pass
777 book.save()
779 return book
782 class ListOfBooksForm(BaseControlForm, forms.Form):
783 pass
785 @classmethod
786 def extra_context(cls):
787 return {
788 'books': Book.objects.all().order_by("title")
792 class BookRenameForm(BaseControlForm, forms.ModelForm):
793 title = forms.CharField(
794 label=_("Title"),
795 required=True,
796 error_messages={'required': _('Title is required.')},
797 max_length=200
799 url_title = forms.SlugField(
800 label=_("URL title"),
801 required=False,
802 max_length=200,
803 error_messages={'invalid': _("Illegal characters in URL title.")},
804 help_text=_("If you leave this field empty, a URL\
805 title will be assigned automatically.")
808 class Meta:
809 model = Book
810 exclude = [
811 'status', 'language',
812 'version', 'group',
813 'created', 'published',
814 'permission', 'cover',
815 'description', 'hidden',
816 'owner', 'license'
818 fields = ['title', 'url_title']
820 def get_cancel_url(self):
821 return "{0}#list-of-books".format(self.cancel_url)
823 def save(self, *args, **kwargs):
824 if self.instance.pk and self.has_changed():
825 book = Book.objects.get(url_title__iexact=self.initial['url_title'])
826 rename_book(
827 book,
828 self.cleaned_data['title'],
829 self.cleaned_data['url_title']
831 return super(BookRenameForm, self).save(*args, **kwargs)
833 def clean_url_title(self):
834 url_title = self.cleaned_data['url_title']
835 if not url_title:
836 return misc.booktype_slugify(self.cleaned_data['title'])
837 return url_title
840 class PublishingForm(BaseControlForm, forms.Form):
841 """Dinamically added fields based on converters modules"""
843 def __init__(self, *args, **kwargs):
844 super(PublishingForm, self).__init__(*args, **kwargs)
846 # if we don't have external (additional) converters enabled,
847 # we must hide this options from form
848 converters = self.get_converters()
849 labels_map = {}
851 # first we need to order converters alphabetically
852 for key, conv_klass in converters.items():
853 verbose_name = getattr(conv_klass, 'verbose_name', None)
854 if not verbose_name:
855 logger.warn(
856 "`{0}` module doesn't have verbose_name attribute specified.".format(
857 conv_klass.__name__))
858 verbose_name = key
860 # using interpolation to avoid getting the __proxy__ object from ugettext_lazy
861 labels_map["%s" % verbose_name] = key
863 sorted_labels = sorted(labels_map.keys())
864 for verbose_key in sorted_labels:
865 conv_name = labels_map[verbose_key]
866 conv_klass = converters[conv_name]
868 self.fields['publish_{0}'.format(conv_name)] = forms.BooleanField(
869 label=verbose_key, required=False)
871 @staticmethod
872 def get_converters():
873 return convert_loader.find_all(module_names=convert_utils.get_converter_module_names())
875 @classmethod
876 def initial_data(cls):
877 publish_options = config.get_configuration('PUBLISH_OPTIONS')
878 values_map = {}
880 for key in cls.get_converters().keys():
881 values_map['publish_{0}'.format(key)] = key in publish_options
883 return values_map
885 def save_settings(self, request):
886 opts = []
887 for _opt in self.get_converters().keys():
888 if self.cleaned_data.get('publish_{0}'.format(_opt)):
889 opts.append(_opt)
891 config.set_configuration('PUBLISH_OPTIONS', opts)
893 try:
894 config.save_configuration()
895 except config.ConfigurationError as err:
896 raise err
899 class PublishingDefaultsForm(BaseControlForm, forms.Form):
900 book_css = forms.CharField(
901 label=_('Book CSS'),
902 required=False,
903 widget=forms.Textarea(attrs={
904 'rows': 30,
905 'style': 'max-width: 500px'
908 ebook_css = forms.CharField(
909 label=_('E-Book CSS'),
910 required=False,
911 widget=forms.Textarea(attrs={
912 'rows': 30,
913 'style': 'max-width: 500px'
916 pdf_css = forms.CharField(
917 label=_('PDF CSS'),
918 required=False,
919 widget=forms.Textarea(attrs={
920 'rows': 30,
921 'style': 'max-width: 500px'
924 odt_css = forms.CharField(
925 label=_('ODT CSS'),
926 required=False,
927 widget=forms.Textarea(attrs={
928 'rows': 30,
929 'style': 'max-width: 500px'
933 @classmethod
934 def initial_data(cls):
935 return {
936 'book_css': config.get_configuration('BOOKTYPE_CSS_BOOK', ''),
937 'ebook_css': config.get_configuration('BOOKTYPE_CSS_EBOOK', ''),
938 'pdf_css': config.get_configuration('BOOKTYPE_CSS_PDF', ''),
939 'odt_css': config.get_configuration('BOOKTYPE_CSS_ODT', '')
942 def save_settings(self, request):
943 data = self.__class__.initial_data()
945 if self.cleaned_data['book_css'] != data['book_css']:
946 config.set_configuration(
947 'BOOKTYPE_CSS_BOOK', self.cleaned_data['book_css'])
949 if self.cleaned_data['ebook_css'] != data['ebook_css']:
950 config.set_configuration(
951 'BOOKTYPE_CSS_EBOOK', self.cleaned_data['ebook_css'])
953 if self.cleaned_data['pdf_css'] != data['pdf_css']:
954 config.set_configuration(
955 'BOOKTYPE_CSS_PDF', self.cleaned_data['pdf_css'])
957 if self.cleaned_data['odt_css'] != data['odt_css']:
958 config.set_configuration(
959 'BOOKTYPE_CSS_ODT', self.cleaned_data['odt_css'])
961 try:
962 config.save_configuration()
963 except config.ConfigurationError as err:
964 raise err
967 class ListOfGroupsForm(BaseControlForm, forms.Form):
968 pass
970 @classmethod
971 def extra_context(cls):
972 return {
973 'groups': BookiGroup.objects.all().order_by("name")
977 class AddGroupForm(BaseControlForm, GroupCreateForm, forms.ModelForm):
979 success_message = _('Successfully created new group.')
981 class Meta:
982 model = BookiGroup
983 fields = [
984 'name', 'description', 'owner'
987 def save_settings(self, request):
988 group = self.save(commit=False)
989 group.url_name = misc.booktype_slugify(group.name)
990 group.created = timezone.now()
991 group.save()
993 # auto-join owner as team member
994 group.members.add(request.user)
996 # set group image if exists in post data
997 group_image = self.files.get('group_image', None)
998 if group_image:
999 self.set_group_image(group.pk, group_image)
1001 return group
1004 class ListOfRolesForm(BaseControlForm, forms.Form):
1005 pass
1007 @classmethod
1008 def extra_context(cls):
1009 return {
1010 'roles': Role.objects.all().order_by("name")
1014 class AddRoleForm(BaseControlForm, forms.ModelForm):
1016 success_message = _('Successfully created new role.')
1017 success_url = '#list-of-roles'
1019 def __init__(self, *args, **kwargs):
1020 super(AddRoleForm, self).__init__(*args, **kwargs)
1022 for name, field in self.fields.items():
1023 if name == 'permissions':
1024 field.widget = GroupedCheckboxSelectMultiple(
1025 choices=Permission.objects.all(),
1026 attrs={
1027 'group_by': 'app_name',
1028 'css_class': 'grouped_perms'
1032 class Meta:
1033 model = Role
1034 exclude = ['members']
1035 widgets = {
1036 'description': forms.Textarea
1039 def get_cancel_url(self):
1040 return "{0}{1}".format(self.cancel_url, self.success_url)
1042 def save_settings(self, request):
1043 role = self.save()
1044 return role
1047 class DefaultRolesForm(BaseControlForm, forms.Form):
1049 success_url = '#default-roles'
1050 anonymous = 'anonymous_users'
1051 registered = 'registered_users'
1053 anonymous_users = forms.ChoiceField(
1054 choices=(), required=False,
1055 label=_('Role for anonymous users')
1057 registered_users = forms.ChoiceField(
1058 choices=(), required=False,
1059 label=_('Role for registered users')
1062 def __init__(self, *args, **kwargs):
1063 super(DefaultRolesForm, self).__init__(*args, **kwargs)
1064 new_choices = [('__no_role__', _('None'))] + [(r.name, r.name) for r in Role.objects.all()]
1066 for name, field in self.fields.items():
1067 field.choices = new_choices
1069 @classmethod
1070 def initial_data(cls):
1071 return {
1072 cls.anonymous: config.get_configuration(
1073 'DEFAULT_ROLE_%s' % cls.anonymous,
1074 cls.anonymous
1076 cls.registered: config.get_configuration(
1077 'DEFAULT_ROLE_%s' % cls.registered,
1078 cls.registered
1082 def save_settings(self, request):
1083 config.set_configuration(
1084 'DEFAULT_ROLE_%s' % self.anonymous,
1085 self.cleaned_data[self.anonymous]
1087 config.set_configuration(
1088 'DEFAULT_ROLE_%s' % self.registered,
1089 self.cleaned_data[self.registered]
1092 try:
1093 config.save_configuration()
1094 except config.ConfigurationError as err:
1095 raise err
1098 class ListOfSkeletonsForm(BaseControlForm, forms.ModelForm):
1100 description = forms.CharField(
1101 label=_('Description'),
1102 required=False,
1103 widget=forms.Textarea(attrs={'rows': 5, 'cols': 10})
1106 success_message = _("Successfully created new Book Skeleton")
1107 success_url = "#list-of-skeletons"
1109 class Meta:
1110 model = BookSkeleton
1111 fields = '__all__'
1113 widgets = {
1114 'skeleton_file': cc_widgets.BkFileWidget(attrs={'accept': 'application/epub+zip'})
1117 @classmethod
1118 def extra_context(cls):
1119 return dict(skeletons=BookSkeleton.objects.all().order_by("name"))
1121 def get_cancel_url(self):
1122 return "{0}{1}".format(self.cancel_url, self.success_url)
1124 def save_settings(self, request):
1125 return self.save()
1128 class UserSearchForm(forms.Form):
1129 search = forms.CharField(
1130 label=_('Search users'),
1131 max_length=50,
1132 required=False,
1133 help_text=_('Search by username, email, first name or last name'),
1134 widget=forms.TextInput(attrs={
1135 'placeholder': 'Example: Tolkien',
1136 'class': 'form-control',
1137 'size': '50'