2013-06-03 16:31:39 +00:00
"""
external library imports
"""
import logging
2014-10-13 23:21:30 +00:00
import re
2014-01-31 20:45:02 +00:00
import zipfile
2013-06-03 16:31:39 +00:00
2013-08-09 23:00:54 +00:00
from datetime import timedelta , datetime , date
2013-06-03 16:31:39 +00:00
from decimal import Decimal as D
"""
django imports
"""
2011-10-03 16:36:04 +00:00
from django import forms
2011-11-29 15:48:37 +00:00
from django . conf import settings
2012-05-11 18:13:09 +00:00
from django . conf . global_settings import LANGUAGES
2013-06-03 16:31:39 +00:00
from django . contrib . auth . models import User
2011-12-29 15:40:35 +00:00
from django . core . validators import validate_email
2013-06-03 16:31:39 +00:00
from django . db import models
2012-05-01 14:56:19 +00:00
from django . forms . widgets import RadioSelect
2011-11-21 03:23:51 +00:00
from django . forms . extras . widgets import SelectDateWidget
2013-06-03 16:31:39 +00:00
from django . utils . translation import ugettext_lazy as _
2011-11-18 14:22:21 +00:00
2014-03-12 22:03:50 +00:00
from ckeditor . widgets import CKEditorWidget
2013-06-03 16:31:39 +00:00
from selectable . forms import (
AutoCompleteSelectMultipleWidget ,
AutoCompleteSelectMultipleField ,
AutoCompleteSelectWidget ,
AutoCompleteSelectField
)
2011-11-18 14:22:21 +00:00
2014-01-31 20:45:02 +00:00
from PyPDF2 import PdfFileReader
2013-06-03 16:31:39 +00:00
"""
regluit imports
"""
from regluit . core . models import (
UserProfile ,
RightsHolder ,
Claim ,
Campaign ,
2013-06-17 22:53:21 +00:00
Offer ,
2013-06-03 16:31:39 +00:00
Premium ,
Ebook ,
2013-06-17 22:53:21 +00:00
EbookFile ,
2013-06-03 16:31:39 +00:00
Edition ,
PledgeExtra ,
Work ,
Press ,
2013-07-26 22:30:45 +00:00
Libpref ,
2013-06-03 16:31:39 +00:00
TWITTER ,
FACEBOOK ,
2014-11-07 19:47:12 +00:00
GRAVATAR ,
UNGLUEITAR
2013-06-03 16:31:39 +00:00
)
2013-10-15 20:18:30 +00:00
from regluit . libraryauth . models import Library
2014-01-10 19:51:59 +00:00
from regluit . core . parameters import LIBRARY , REWARDS , BUY2UNGLUE , THANKS
2013-07-17 14:34:01 +00:00
from regluit . core . lookups import (
OwnerLookup ,
WorkLookup ,
PublisherNameLookup ,
2015-01-14 20:07:54 +00:00
EditionLookup ,
SubjectLookup ,
2013-07-17 14:34:01 +00:00
)
2012-03-23 16:29:38 +00:00
from regluit . utils . localdatetime import now
2014-07-05 00:29:42 +00:00
from regluit . utils . fields import EpubFileField , ISBNField
2014-02-05 23:17:26 +00:00
from regluit . mobi import Mobi
2014-02-05 23:23:06 +00:00
from regluit . pyepub import EPUB
2012-03-16 20:24:55 +00:00
2011-11-30 02:02:51 +00:00
logger = logging . getLogger ( __name__ )
2014-08-06 14:59:16 +00:00
nulls = [ False , ' delete ' , ' ' ]
2011-11-30 02:02:51 +00:00
2012-05-11 18:13:09 +00:00
class EditionForm ( forms . ModelForm ) :
2013-08-08 22:21:33 +00:00
add_author = forms . CharField ( max_length = 500 , required = False )
add_subject = forms . CharField ( max_length = 200 , required = False )
2013-04-13 14:59:11 +00:00
publisher_name = AutoCompleteSelectField (
PublisherNameLookup ,
label = ' Publisher Name ' ,
2013-05-13 21:32:30 +00:00
widget = AutoCompleteSelectWidget ( PublisherNameLookup , allow_new = True ) ,
2013-04-13 14:59:11 +00:00
required = False ,
2013-05-13 21:32:30 +00:00
allow_new = True ,
2013-04-13 14:59:11 +00:00
)
2014-07-05 00:29:42 +00:00
isbn = ISBNField (
2012-05-11 18:13:09 +00:00
label = _ ( " ISBN " ) ,
2014-07-05 00:29:42 +00:00
max_length = 17 ,
2013-05-13 21:32:30 +00:00
required = False ,
2012-05-11 18:13:09 +00:00
help_text = _ ( " 13 digits, no dash. " ) ,
error_messages = {
2014-07-05 00:29:42 +00:00
' invalid ' : _ ( " This must be a valid ISBN-13. " ) ,
2012-05-11 18:13:09 +00:00
}
)
2013-04-13 14:59:11 +00:00
goog = forms . RegexField (
label = _ ( " Google Books ID " ) ,
max_length = 12 ,
regex = r ' ^([a-zA-Z0-9 \ -_] {12} |delete)$ ' ,
required = False ,
help_text = _ ( " 12 alphanumeric characters, dash or underscore, case sensitive. " ) ,
error_messages = {
' invalid ' : _ ( " This value must be 12 alphanumeric characters, dash or underscore. " ) ,
}
)
gdrd = forms . RegexField (
label = _ ( " GoodReads ID " ) ,
max_length = 8 ,
regex = r ' ^( \ d+|delete)$ ' ,
required = False ,
help_text = _ ( " 1-8 digits. " ) ,
error_messages = {
' invalid ' : _ ( " This value must be 1-8 digits. " ) ,
}
)
thng = forms . RegexField (
label = _ ( " LibraryThing ID " ) ,
max_length = 8 ,
regex = r ' (^ \ d+|delete)$ ' ,
required = False ,
help_text = _ ( " 1-8 digits. " ) ,
error_messages = {
' invalid ' : _ ( " This value must be 1-8 digits. " ) ,
}
)
oclc = forms . RegexField (
2012-09-25 01:51:57 +00:00
label = _ ( " OCLCnum " ) ,
2013-04-13 14:59:11 +00:00
regex = r ' ^( \ d \ d \ d \ d \ d \ d \ d \ d \ d*|delete)$ ' ,
2012-09-25 01:51:57 +00:00
required = False ,
help_text = _ ( " 8 or more digits. " ) ,
error_messages = {
' invalid ' : _ ( " This value must be 8 or more digits. " ) ,
}
)
2014-10-08 20:23:24 +00:00
http = forms . RegexField (
2014-10-13 23:21:30 +00:00
label = _ ( " HTTP URL " ) ,
# https://mathiasbynens.be/demo/url-regex
2014-11-13 15:19:53 +00:00
regex = re . compile ( r " (https?|ftp)://(- \ .)?([^ \ s/? \ .#]+ \ .?)+(/[^ \ s]*)?$ " ,
2014-10-13 23:21:30 +00:00
flags = re . IGNORECASE | re . S ) ,
2014-10-08 20:23:24 +00:00
required = False ,
help_text = _ ( " no spaces of funny stuff. " ) ,
error_messages = {
' invalid ' : _ ( " This value must be a valid http(s) URL. " ) ,
}
)
2012-05-11 18:13:09 +00:00
language = forms . ChoiceField ( choices = LANGUAGES )
2014-03-11 21:34:27 +00:00
description = forms . CharField ( required = False , widget = forms . Textarea ( attrs = { ' cols ' : 80 , ' rows ' : 10 } ) )
2014-01-31 04:16:06 +00:00
coverfile = forms . ImageField ( required = False )
2013-05-13 21:32:30 +00:00
def clean ( self ) :
2014-08-06 14:59:16 +00:00
has_isbn = self . cleaned_data . get ( " isbn " , False ) not in nulls
has_oclc = self . cleaned_data . get ( " oclc " , False ) not in nulls
has_goog = self . cleaned_data . get ( " goog " , False ) not in nulls
2014-10-08 20:23:24 +00:00
has_http = self . cleaned_data . get ( " http " , False ) not in nulls
if not has_isbn and not has_oclc and not has_goog and not has_http :
2013-05-13 21:32:30 +00:00
raise forms . ValidationError ( _ ( " There must be either an ISBN or an OCLC number. " ) )
return self . cleaned_data
2012-05-11 18:13:09 +00:00
class Meta :
model = Edition
exclude = ' created ' , ' work '
widgets = {
' title ' : forms . TextInput ( attrs = { ' size ' : 40 } ) ,
' add_author ' : forms . TextInput ( attrs = { ' size ' : 30 } ) ,
' add_subject ' : forms . TextInput ( attrs = { ' size ' : 30 } ) ,
2012-09-10 19:18:40 +00:00
' unglued ' : forms . CheckboxInput ( ) ,
2014-01-31 04:16:06 +00:00
' cover_image ' : forms . TextInput ( attrs = { ' size ' : 60 } ) ,
2012-05-11 18:13:09 +00:00
}
2013-08-27 03:54:19 +00:00
2013-06-17 22:53:21 +00:00
class EbookFileForm ( forms . ModelForm ) :
2014-01-31 20:45:02 +00:00
file = forms . FileField ( max_length = 16777216 )
2013-08-27 03:54:19 +00:00
2014-01-15 13:32:55 +00:00
def __init__ ( self , campaign_type = BUY2UNGLUE , * args , * * kwargs ) :
super ( EbookFileForm , self ) . __init__ ( * args , * * kwargs )
self . campaign_type = campaign_type
if campaign_type == BUY2UNGLUE :
2014-01-31 20:45:02 +00:00
self . fields [ ' format ' ] . widget = forms . HiddenInput ( )
if campaign_type == THANKS :
self . fields [ ' format ' ] . widget = forms . Select ( choices = ( ( ' pdf ' , ' PDF ' ) , ( ' epub ' , ' EPUB ' ) , ( ' mobi ' , ' MOBI ' ) ) )
2014-01-15 13:32:55 +00:00
def clean_format ( self ) :
if self . campaign_type is BUY2UNGLUE :
return ' epub '
else :
logger . info ( " EbookFileForm " + self . cleaned_data . get ( ' format ' , ' ' ) )
return self . cleaned_data . get ( ' format ' , ' ' )
2014-01-31 20:45:02 +00:00
def clean ( self ) :
format = self . cleaned_data [ ' format ' ]
the_file = self . cleaned_data . get ( ' file ' , None )
if the_file and the_file . name :
if format == ' epub ' :
2014-02-05 23:23:06 +00:00
try :
book = EPUB ( the_file . file )
except Exception as e :
raise forms . ValidationError ( _ ( ' Are you sure this is an EPUB file?: %s ' % e ) )
2014-01-31 20:45:02 +00:00
elif format == ' mobi ' :
2014-02-05 23:17:26 +00:00
try :
2014-02-05 23:23:06 +00:00
book = Mobi ( the_file . file )
book . parse ( )
2014-02-05 23:17:26 +00:00
except Exception as e :
raise forms . ValidationError ( _ ( ' Are you sure this is a MOBI file?: %s ' % e ) )
2014-01-31 20:45:02 +00:00
elif format == ' pdf ' :
try :
doc = PdfFileReader ( the_file . file )
except Exception , e :
raise forms . ValidationError ( _ ( ' %s is not a valid PDF file ' % the_file . name ) )
return self . cleaned_data
2014-01-15 13:32:55 +00:00
2013-06-17 22:53:21 +00:00
class Meta :
model = EbookFile
2014-01-15 13:32:55 +00:00
widgets = { ' edition ' : forms . HiddenInput }
2014-09-04 22:33:20 +00:00
exclude = { ' created ' , ' asking ' }
2012-05-11 18:13:09 +00:00
2012-02-28 22:28:33 +00:00
class EbookForm ( forms . ModelForm ) :
class Meta :
model = Ebook
2014-09-26 14:40:20 +00:00
exclude = ( ' created ' , ' download_count ' , ' active ' )
2012-02-28 22:28:33 +00:00
widgets = {
' edition ' : forms . HiddenInput ,
' user ' : forms . HiddenInput ,
' provider ' : forms . HiddenInput ,
' url ' : forms . TextInput ( attrs = { ' size ' : 60 } ) ,
}
def clean_provider ( self ) :
2012-04-27 21:14:57 +00:00
new_provider = Ebook . infer_provider ( self . data [ self . prefix + ' -url ' ] )
2012-02-28 22:28:33 +00:00
if not new_provider :
2014-11-17 16:51:36 +00:00
raise forms . ValidationError ( _ ( " At this time, ebook URLs must point at Internet Archive, Wikisources, Wikibooks, Hathitrust, Project Gutenberg, raw files at Github, or Google Books. " ) )
2012-02-28 22:28:33 +00:00
return new_provider
2012-03-05 21:03:57 +00:00
def clean_url ( self ) :
2012-04-27 21:14:57 +00:00
url = self . data [ self . prefix + ' -url ' ]
2012-03-05 21:03:57 +00:00
try :
Ebook . objects . get ( url = url )
except Ebook . DoesNotExist :
return url
raise forms . ValidationError ( _ ( " There ' s already an ebook with that url. " ) )
2011-11-23 16:06:48 +00:00
def UserClaimForm ( user_instance , * args , * * kwargs ) :
class ClaimForm ( forms . ModelForm ) :
2012-04-16 19:28:06 +00:00
i_agree = forms . BooleanField ( error_messages = { ' required ' : ' You must agree to the Terms in order to claim a work. ' } )
2011-11-23 16:06:48 +00:00
rights_holder = forms . ModelChoiceField ( queryset = user_instance . rights_holder . all ( ) , empty_label = None )
class Meta :
model = Claim
exclude = ' status '
widgets = {
' user ' : forms . HiddenInput ,
' work ' : forms . HiddenInput ,
}
def __init__ ( self ) :
super ( ClaimForm , self ) . __init__ ( * args , * * kwargs )
2011-11-15 04:28:55 +00:00
2011-11-23 16:06:48 +00:00
return ClaimForm ( )
2011-11-15 04:28:55 +00:00
class RightsHolderForm ( forms . ModelForm ) :
2011-11-18 14:22:21 +00:00
owner = AutoCompleteSelectField (
OwnerLookup ,
label = ' Owner ' ,
widget = AutoCompleteSelectWidget ( OwnerLookup ) ,
required = True ,
2012-05-08 20:15:23 +00:00
error_messages = { ' required ' : ' Please ensure the owner is a valid Unglue.it account. ' } ,
2011-11-18 14:22:21 +00:00
)
2011-11-21 03:23:51 +00:00
email = forms . EmailField (
label = _ ( " notification email address for rights holder " ) ,
2012-04-16 19:28:06 +00:00
max_length = 100 ,
error_messages = { ' required ' : ' Please enter an email address for the rights holder. ' } ,
2011-11-21 03:23:51 +00:00
)
2011-11-15 04:28:55 +00:00
class Meta :
model = RightsHolder
2011-11-17 19:35:29 +00:00
def clean_rights_holder_name ( self ) :
rights_holder_name = self . data [ " rights_holder_name " ]
try :
RightsHolder . objects . get ( rights_holder_name__iexact = rights_holder_name )
2011-11-18 14:22:21 +00:00
except RightsHolder . DoesNotExist :
2011-11-17 19:35:29 +00:00
return rights_holder_name
raise forms . ValidationError ( _ ( " Another rights holder with that name already exists. " ) )
2013-03-15 01:42:00 +00:00
2011-11-18 14:22:21 +00:00
2011-10-03 16:36:04 +00:00
class ProfileForm ( forms . ModelForm ) :
2011-11-14 18:23:14 +00:00
clear_facebook = forms . BooleanField ( required = False )
clear_twitter = forms . BooleanField ( required = False )
2012-01-24 17:36:45 +00:00
clear_goodreads = forms . BooleanField ( required = False )
2013-03-15 01:42:00 +00:00
2011-10-03 16:36:04 +00:00
class Meta :
model = UserProfile
2013-03-15 01:42:00 +00:00
fields = ' tagline ' , ' librarything_id ' , ' home_url ' , ' clear_facebook ' , ' clear_twitter ' , ' clear_goodreads ' , ' avatar_source '
2011-10-03 16:36:04 +00:00
widgets = {
2012-02-02 18:55:21 +00:00
' tagline ' : forms . Textarea ( attrs = { ' rows ' : 5 , ' onKeyUp ' : " counter(this, 140) " , ' onBlur ' : " counter(this, 140) " } ) ,
2011-10-03 16:36:04 +00:00
}
2013-03-15 01:42:00 +00:00
def __init__ ( self , * args , * * kwargs ) :
profile = kwargs . get ( ' instance ' )
super ( ProfileForm , self ) . __init__ ( * args , * * kwargs )
choices = [ ]
for choice in self . fields [ ' avatar_source ' ] . choices :
if choice [ 0 ] == FACEBOOK and not profile . facebook_id :
pass
elif choice [ 0 ] == TWITTER and not profile . twitter_id :
pass
else :
choices . append ( choice )
self . fields [ ' avatar_source ' ] . choices = choices
def clean ( self ) :
# check that if a social net is cleared, we're not using it a avatar source
if self . cleaned_data . get ( " clear_facebook " , False ) and self . cleaned_data . get ( " avatar_source " , None ) == FACEBOOK :
2014-11-07 19:47:12 +00:00
self . cleaned_data [ " avatar_source " ] == UNGLUEITAR
2013-03-15 01:42:00 +00:00
if self . cleaned_data . get ( " clear_twitter " , False ) and self . cleaned_data . get ( " avatar_source " , None ) == TWITTER :
2014-11-07 19:47:12 +00:00
self . cleaned_data [ " avatar_source " ] == UNGLUEITAR
2013-03-15 01:42:00 +00:00
return self . cleaned_data
2011-10-03 16:36:04 +00:00
class UserData ( forms . Form ) :
username = forms . RegexField (
label = _ ( " New Username " ) ,
max_length = 30 ,
regex = r ' ^[ \ w.@+-]+$ ' ,
2012-01-04 16:34:31 +00:00
help_text = _ ( " 30 characters or fewer. " ) ,
2011-10-03 16:36:04 +00:00
error_messages = {
' invalid ' : _ ( " This value may contain only letters, numbers and @/./+/-/_ characters. " )
}
)
2012-05-26 21:30:12 +00:00
oldusername = None
2014-12-18 16:41:06 +00:00
allow_same = False
2011-10-03 16:36:04 +00:00
def clean_username ( self ) :
username = self . data [ " username " ]
2012-05-26 21:30:12 +00:00
if username != self . oldusername :
users = User . objects . filter ( username__iexact = username )
for user in users :
raise forms . ValidationError ( _ ( " Another user with that username already exists. " ) )
return username
2014-12-18 16:41:06 +00:00
if not self . allow_same :
raise forms . ValidationError ( _ ( " Your username is already " + username ) )
class UserNamePass ( UserData ) :
password1 = forms . CharField ( label = _ ( " Password " ) ,
widget = forms . PasswordInput )
password2 = forms . CharField ( label = _ ( " Password confirmation " ) ,
widget = forms . PasswordInput ,
help_text = _ ( " Enter the same password as above, for verification. " ) )
allow_same = True
def clean_password2 ( self ) :
password1 = self . cleaned_data . get ( " password1 " , " " )
password2 = self . cleaned_data [ " password2 " ]
if password1 != password2 :
raise forms . ValidationError ( _ ( " The two passwords don ' t match. " ) )
return password2
2011-10-11 17:03:40 +00:00
2012-09-21 16:10:13 +00:00
class CloneCampaignForm ( forms . Form ) :
campaign_id = forms . IntegerField ( required = True , widget = forms . HiddenInput )
2011-11-20 02:12:18 +00:00
class OpenCampaignForm ( forms . ModelForm ) :
2011-11-21 03:23:51 +00:00
managers = AutoCompleteSelectMultipleField (
2011-11-20 02:12:18 +00:00
OwnerLookup ,
2011-11-21 03:23:51 +00:00
label = ' Campaign Managers ' ,
2011-11-20 19:02:09 +00:00
widget = AutoCompleteSelectMultipleWidget ( OwnerLookup ) ,
2012-04-03 12:20:46 +00:00
required = True ,
2012-04-09 15:14:31 +00:00
error_messages = { ' required ' : " You must have at least one manager for a campaign. " } ,
2011-11-20 02:12:18 +00:00
)
userid = forms . IntegerField ( required = True , widget = forms . HiddenInput )
class Meta :
model = Campaign
2013-08-08 23:56:26 +00:00
fields = ' name ' , ' work ' , ' managers ' , ' type '
2014-07-04 00:26:36 +00:00
widgets = { ' work ' : forms . HiddenInput , " name " : forms . HiddenInput , }
2011-11-20 02:12:18 +00:00
2012-08-14 01:00:28 +00:00
def getTransferCreditForm ( maximum , data = None , * args , * * kwargs ) :
class TransferCreditForm ( forms . Form ) :
recipient = AutoCompleteSelectField (
OwnerLookup ,
label = ' Recipient ' ,
widget = AutoCompleteSelectWidget ( OwnerLookup ) ,
required = True ,
error_messages = { ' required ' : ' Please ensure the recipient is a valid Unglue.it account. ' } ,
)
amount = forms . IntegerField (
required = True ,
min_value = 1 ,
max_value = maximum ,
label = " Transfer Amount " ,
error_messages = { ' min_value ' : ' Transfer amount must be positive ' , ' max_value ' : ' You only have %(limit_value)s available for transfer ' } ,
)
return TransferCreditForm ( data = data )
2012-12-13 03:35:35 +00:00
class WorkForm ( forms . Form ) :
other_work = forms . ModelChoiceField ( queryset = Work . objects . all ( ) ,
widget = forms . HiddenInput ( ) ,
required = True ,
error_messages = { ' required ' : ' Missing work to merge with. ' } ,
)
work = None
def clean_other_work ( self ) :
if self . cleaned_data [ " other_work " ] . id == self . work . id :
raise forms . ValidationError ( _ ( " You can ' t merge a work into itself " ) )
return self . cleaned_data [ " other_work " ]
def __init__ ( self , work = None , * args , * * kwargs ) :
super ( WorkForm , self ) . __init__ ( * args , * * kwargs )
self . work = work
class OtherWorkForm ( WorkForm ) :
other_work = AutoCompleteSelectField (
WorkLookup ,
2013-04-24 18:30:39 +00:00
label = ' Other Work (title) ' ,
2012-12-13 03:35:35 +00:00
widget = AutoCompleteSelectWidget ( WorkLookup ) ,
required = True ,
error_messages = { ' required ' : ' Missing work to merge with. ' } ,
)
def __init__ ( self , * args , * * kwargs ) :
super ( OtherWorkForm , self ) . __init__ ( * args , * * kwargs )
self . fields [ ' other_work ' ] . widget . update_query_parameters ( { ' language ' : self . work . language } )
2012-08-14 01:00:28 +00:00
2012-04-03 19:50:02 +00:00
class EditManagersForm ( forms . ModelForm ) :
managers = AutoCompleteSelectMultipleField (
OwnerLookup ,
label = ' Campaign Managers ' ,
widget = AutoCompleteSelectMultipleWidget ( OwnerLookup ) ,
required = True ,
2012-04-09 15:14:31 +00:00
error_messages = { ' required ' : " You must have at least one manager for a campaign. " } ,
2012-04-03 19:50:02 +00:00
)
class Meta :
model = Campaign
fields = ( ' id ' , ' managers ' )
widgets = { ' id ' : forms . HiddenInput }
2012-03-26 19:31:41 +00:00
class CustomPremiumForm ( forms . ModelForm ) :
class Meta :
model = Premium
fields = ' campaign ' , ' amount ' , ' description ' , ' type ' , ' limit '
widgets = {
2012-10-04 16:08:40 +00:00
' description ' : forms . Textarea ( attrs = { ' cols ' : 80 , ' rows ' : 4 } ) ,
2012-03-26 19:31:41 +00:00
' campaign ' : forms . HiddenInput ,
' type ' : forms . HiddenInput ( attrs = { ' value ' : ' XX ' } ) ,
' limit ' : forms . TextInput ( attrs = { ' value ' : ' 0 ' } ) ,
}
2013-06-17 22:53:21 +00:00
class OfferForm ( forms . ModelForm ) :
class Meta :
model = Offer
2014-02-25 19:29:54 +00:00
fields = ' work ' , ' price ' , ' license '
2013-06-17 22:53:21 +00:00
widgets = {
' work ' : forms . HiddenInput ,
2013-10-11 16:50:59 +00:00
' license ' : forms . HiddenInput ,
2013-06-17 22:53:21 +00:00
}
2013-08-09 23:00:54 +00:00
date_selector = range ( date . today ( ) . year , settings . MAX_CC_DATE . year + 1 )
2013-10-04 02:54:25 +00:00
class CCDateForm ( object ) :
target = forms . DecimalField ( min_value = D ( settings . UNGLUEIT_MINIMUM_TARGET ) , error_messages = { ' required ' : ' Please specify a Revenue Target. ' } )
minimum_target = settings . UNGLUEIT_MINIMUM_TARGET
maximum_target = settings . UNGLUEIT_MAXIMUM_TARGET
max_cc_date = settings . MAX_CC_DATE
def clean_target ( self ) :
new_target = self . cleaned_data [ ' target ' ]
if new_target < D ( settings . UNGLUEIT_MINIMUM_TARGET ) :
raise forms . ValidationError ( _ ( ' A campaign may not be launched with a target less than $ %s ' % settings . UNGLUEIT_MINIMUM_TARGET ) )
if new_target > D ( settings . UNGLUEIT_MAXIMUM_TARGET ) :
raise forms . ValidationError ( _ ( ' A campaign may not be launched with a target more than $ %s ' % settings . UNGLUEIT_MAXIMUM_TARGET ) )
return new_target
def clean_cc_date_initial ( self ) :
new_cc_date_initial = self . cleaned_data [ ' cc_date_initial ' ]
if new_cc_date_initial . date ( ) > settings . MAX_CC_DATE :
raise forms . ValidationError ( ' The initial Ungluing Date cannot be after %s ' % settings . MAX_CC_DATE )
elif new_cc_date_initial - now ( ) < timedelta ( days = 0 ) :
raise forms . ValidationError ( ' The initial Ungluing date must be in the future! ' )
return new_cc_date_initial
class DateCalculatorForm ( CCDateForm , forms . ModelForm ) :
revenue = forms . DecimalField ( )
cc_date_initial = forms . DateTimeField (
widget = SelectDateWidget ( years = date_selector )
)
class Meta :
model = Campaign
fields = ' target ' , ' cc_date_initial ' , ' revenue ' ,
2014-03-12 22:03:50 +00:00
def getManageCampaignForm ( instance , data = None , initial = None , * args , * * kwargs ) :
2013-08-09 23:00:54 +00:00
2012-05-01 14:56:19 +00:00
def get_queryset ( ) :
work = instance . work
return Edition . objects . filter ( work = work )
2013-10-04 02:54:25 +00:00
class ManageCampaignForm ( CCDateForm , forms . ModelForm ) :
2014-01-10 19:51:59 +00:00
target = forms . DecimalField ( required = ( instance . type in { REWARDS , BUY2UNGLUE } ) )
2014-01-03 19:15:26 +00:00
deadline = forms . DateTimeField (
2014-01-10 19:51:59 +00:00
required = ( instance . type == REWARDS ) ,
2014-01-03 19:15:26 +00:00
widget = SelectDateWidget ( years = date_selector ) if instance . status == ' INITIALIZED ' else forms . HiddenInput
)
2013-10-04 02:54:25 +00:00
cc_date_initial = forms . DateTimeField (
2014-01-10 19:51:59 +00:00
required = ( instance . type == BUY2UNGLUE ) and instance . status == ' INITIALIZED ' ,
2013-10-04 02:54:25 +00:00
widget = SelectDateWidget ( years = date_selector ) if instance . status == ' INITIALIZED ' else forms . HiddenInput
)
2012-05-01 14:56:19 +00:00
paypal_receiver = forms . EmailField (
2012-09-27 16:35:23 +00:00
label = _ ( " contact email address for this campaign " ) ,
2012-05-01 14:56:19 +00:00
max_length = 100 ,
2012-09-27 16:35:23 +00:00
error_messages = { ' required ' : ' You must enter the email we should contact you at for this campaign. ' } ,
2012-05-01 14:56:19 +00:00
)
2012-05-14 05:05:14 +00:00
edition = forms . ModelChoiceField ( get_queryset ( ) , widget = RadioSelect ( ) , empty_label = ' no edition selected ' , required = False , )
2013-03-27 16:22:30 +00:00
publisher = forms . ModelChoiceField ( instance . work . publishers ( ) , empty_label = ' no publisher selected ' , required = False , )
2014-03-12 22:03:50 +00:00
work_description = forms . CharField ( required = False , widget = CKEditorWidget ( ) )
2013-12-18 18:02:57 +00:00
2012-05-01 14:56:19 +00:00
class Meta :
model = Campaign
2014-08-30 16:04:50 +00:00
fields = ' description ' , ' details ' , ' license ' , ' target ' , ' deadline ' , ' paypal_receiver ' , ' edition ' , ' email ' , ' publisher ' , ' cc_date_initial ' , " do_watermark " , " use_add_ask " ,
2014-01-03 19:15:26 +00:00
widgets = { ' deadline ' : SelectDateWidget }
2012-05-01 14:56:19 +00:00
def clean_target ( self ) :
2014-01-10 19:51:59 +00:00
if self . instance . type == THANKS :
2014-01-03 19:15:26 +00:00
return None
2013-10-04 02:54:25 +00:00
new_target = super ( ManageCampaignForm , self ) . clean_target ( )
2012-05-01 14:56:19 +00:00
if self . instance :
if self . instance . status == ' ACTIVE ' and self . instance . target < new_target :
raise forms . ValidationError ( _ ( ' The fundraising target for an ACTIVE campaign cannot be increased. ' ) )
return new_target
2013-08-10 20:29:58 +00:00
def clean_cc_date_initial ( self ) :
2014-01-10 19:51:59 +00:00
if self . instance . type in { REWARDS , THANKS } :
2013-08-10 20:29:58 +00:00
return None
if self . instance :
if self . instance . status != ' INITIALIZED ' :
# can't change this once launched
return self . instance . cc_date_initial
2013-10-04 02:54:25 +00:00
return super ( ManageCampaignForm , self ) . clean_cc_date_initial ( )
2012-07-07 02:10:55 +00:00
2012-05-01 14:56:19 +00:00
def clean_deadline ( self ) :
2014-01-10 19:51:59 +00:00
if self . instance . type in { BUY2UNGLUE , THANKS } :
2014-01-03 19:15:26 +00:00
return None
new_deadline_date = self . cleaned_data [ ' deadline ' ]
new_deadline = new_deadline_date + timedelta ( hours = 23 , minutes = 59 )
if self . instance :
2014-09-25 17:32:01 +00:00
if self . instance . status == ' ACTIVE ' :
2014-10-09 18:33:53 +00:00
return self . instance . deadline
2014-01-03 19:15:26 +00:00
if new_deadline_date - now ( ) > timedelta ( days = int ( settings . UNGLUEIT_LONGEST_DEADLINE ) ) :
raise forms . ValidationError ( _ ( ' The chosen closing date is more than %s days from now ' % settings . UNGLUEIT_LONGEST_DEADLINE ) )
elif new_deadline - now ( ) < timedelta ( days = 0 ) :
raise forms . ValidationError ( _ ( ' The chosen closing date is in the past ' ) )
return new_deadline
2012-05-01 14:56:19 +00:00
def clean_license ( self ) :
new_license = self . cleaned_data [ ' license ' ]
if self . instance :
if self . instance . status == ' ACTIVE ' and self . instance . license != new_license :
2012-05-20 04:13:12 +00:00
# should only allow change to a less restrictive license
2012-05-21 13:25:38 +00:00
if self . instance . license == ' CC BY-ND ' and new_license in [ ' CC BY-NC-ND ' , ' CC BY-NC-SA ' , ' CC BY-NC ' ] :
2012-05-20 04:13:12 +00:00
raise forms . ValidationError ( _ ( ' The proposed license for an ACTIVE campaign may not add restrictions. ' ) )
elif self . instance . license == ' CC BY ' and new_license != ' CC0 ' :
raise forms . ValidationError ( _ ( ' The proposed license for an ACTIVE campaign may not add restrictions. ' ) )
elif self . instance . license == ' CC BY-NC ' and new_license in [ ' CC BY-NC-ND ' , ' CC BY-NC-SA ' , ' CC BY-SA ' , ' CC BY-ND ' ] :
raise forms . ValidationError ( _ ( ' The proposed license for an ACTIVE campaign may not add restrictions. ' ) )
elif self . instance . license == ' CC BY-ND ' and new_license in [ ' CC BY-NC-ND ' , ' CC BY-NC-SA ' , ' CC BY-SA ' , ' CC BY-NC ' ] :
raise forms . ValidationError ( _ ( ' The proposed license for an ACTIVE campaign may not add restrictions. ' ) )
elif self . instance . license == ' CC BY-SA ' and new_license in [ ' CC BY-NC-ND ' , ' CC BY-NC-SA ' , ' CC BY-ND ' , ' CC BY-NC ' ] :
raise forms . ValidationError ( _ ( ' The proposed license for an ACTIVE campaign may not add restrictions. ' ) )
elif self . instance . license == ' CC BY-NC-SA ' and new_license in [ ' CC BY-NC-ND ' , ' CC BY-ND ' ] :
raise forms . ValidationError ( _ ( ' The proposed license for an ACTIVE campaign may not add restrictions. ' ) )
elif self . instance . license == ' CC0 ' :
raise forms . ValidationError ( _ ( ' The proposed license for an ACTIVE campaign may not add restrictions. ' ) )
2014-11-23 21:44:59 +00:00
elif self . instance . license in [ ' GDFL ' , ' LAL ' ] :
raise forms . ValidationError ( _ ( ' Once you start a campaign with GDFL or LAL, you can \' t use any other license. ' ) )
2012-05-01 14:56:19 +00:00
return new_license
2014-03-12 22:03:50 +00:00
return ManageCampaignForm ( instance = instance , data = data , initial = initial )
2011-11-20 02:12:18 +00:00
2013-08-16 19:49:44 +00:00
class CampaignPurchaseForm ( forms . Form ) :
anonymous = forms . BooleanField ( required = False , label = _ ( " Make this purchase anonymous, please " ) )
offer_id = forms . IntegerField ( required = False )
offer = None
2013-10-15 20:18:30 +00:00
library_id = forms . IntegerField ( required = False )
library = None
2013-11-01 20:15:01 +00:00
copies = forms . IntegerField ( required = False , min_value = 1 )
2014-12-16 19:18:51 +00:00
give_to = forms . EmailField ( required = False )
give_message = forms . CharField ( required = False , max_length = 512 , )
2013-10-15 20:18:30 +00:00
2013-08-16 19:49:44 +00:00
def clean_offer_id ( self ) :
offer_id = self . cleaned_data [ ' offer_id ' ]
try :
self . offer = Offer . objects . get ( id = offer_id )
except Offer . DoesNotExist :
raise forms . ValidationError ( _ ( " Sorry, that offer is not valid. " ) )
2013-10-15 20:18:30 +00:00
def clean_library_id ( self ) :
library_id = self . cleaned_data [ ' library_id ' ]
if library_id :
try :
self . library = Library . objects . get ( id = library_id )
except Library . DoesNotExist :
raise forms . ValidationError ( _ ( " Sorry, that Library is not valid. " ) )
2014-12-16 19:18:51 +00:00
def clean_copies ( self ) :
copies = self . cleaned_data . get ( ' copies ' , 1 )
return copies if copies else 1
2014-12-18 06:07:59 +00:00
def clean_anonymous ( self ) :
if self . data . get ( ' give ' , False ) :
return True
else :
return self . cleaned_data [ ' anonymous ' ]
2014-12-16 19:18:51 +00:00
2013-10-15 20:18:30 +00:00
def clean ( self ) :
if self . offer . license == LIBRARY :
if not self . library :
raise forms . ValidationError ( _ ( " No library specified. " ) )
2014-12-16 19:18:51 +00:00
if self . data . get ( ' give ' , False ) :
if not self . cleaned_data . get ( ' give_to ' , None ) :
raise forms . ValidationError ( _ ( " Gift recipient email is needed. " ) )
else :
if ' give_to ' in self . _errors :
del self . _errors [ ' give_to ' ]
2013-10-15 20:18:30 +00:00
return self . cleaned_data
2013-08-16 19:49:44 +00:00
def amount ( self ) :
2013-11-01 20:15:01 +00:00
return self . offer . price * self . cleaned_data . get ( ' copies ' , 1 ) if self . offer else None
2013-08-16 19:49:44 +00:00
@property
def trans_extra ( self ) :
2013-10-15 20:18:30 +00:00
pe = PledgeExtra ( anonymous = self . cleaned_data [ ' anonymous ' ] ,
2013-08-20 02:54:43 +00:00
offer = self . offer )
2013-10-15 20:18:30 +00:00
if self . library :
pe . extra [ ' library_id ' ] = self . library . id
2013-11-01 20:15:01 +00:00
pe . extra [ ' copies ' ] = self . cleaned_data . get ( ' copies ' , 1 )
2014-12-16 19:18:51 +00:00
if self . data . get ( ' give ' , False ) :
pe . extra [ ' give_to ' ] = self . cleaned_data [ ' give_to ' ]
pe . extra [ ' give_message ' ] = self . cleaned_data [ ' give_message ' ]
2013-10-15 20:18:30 +00:00
return pe
2013-08-16 19:49:44 +00:00
2014-02-20 03:18:23 +00:00
class CampaignThanksForm ( forms . Form ) :
anonymous = forms . BooleanField ( required = False , label = _ ( " Make this contribution anonymous, please " ) )
preapproval_amount = forms . DecimalField (
2014-05-19 16:09:47 +00:00
required = True ,
2014-02-20 03:18:23 +00:00
min_value = D ( ' 1.00 ' ) ,
max_value = D ( ' 2000.00 ' ) ,
decimal_places = 2 ,
label = " Pledge Amount " ,
)
@property
def trans_extra ( self ) :
pe = PledgeExtra ( anonymous = self . cleaned_data [ ' anonymous ' ] )
2011-10-11 17:03:40 +00:00
class CampaignPledgeForm ( forms . Form ) :
2011-11-20 02:12:18 +00:00
preapproval_amount = forms . DecimalField (
2012-03-15 21:54:38 +00:00
required = False ,
min_value = D ( ' 1.00 ' ) ,
2012-10-13 14:13:46 +00:00
max_value = D ( ' 2000.00 ' ) ,
2011-11-20 02:12:18 +00:00
decimal_places = 2 ,
label = " Pledge Amount " ,
)
2013-08-16 19:49:44 +00:00
def amount ( self ) :
return self . cleaned_data [ " preapproval_amount " ] if self . cleaned_data else None
2012-10-03 20:02:51 +00:00
anonymous = forms . BooleanField ( required = False , label = _ ( " Make this pledge anonymous, please " ) )
2012-08-01 12:52:39 +00:00
ack_name = forms . CharField ( required = False , max_length = 64 , label = _ ( " What name should we display? " ) )
ack_dedication = forms . CharField ( required = False , max_length = 140 , label = _ ( " Your dedication: " ) )
2011-11-30 02:02:51 +00:00
premium_id = forms . IntegerField ( required = False )
2012-09-07 13:46:38 +00:00
premium = None
2012-03-15 21:54:38 +00:00
2012-09-07 13:46:38 +00:00
@property
2013-08-16 19:49:44 +00:00
def trans_extra ( self ) :
2012-09-07 13:46:38 +00:00
return PledgeExtra ( anonymous = self . cleaned_data [ ' anonymous ' ] ,
ack_name = self . cleaned_data [ ' ack_name ' ] ,
ack_dedication = self . cleaned_data [ ' ack_dedication ' ] ,
premium = self . premium )
2012-03-15 21:54:38 +00:00
def clean_preapproval_amount ( self ) :
2012-08-31 07:16:04 +00:00
preapproval_amount = self . cleaned_data [ ' preapproval_amount ' ]
if preapproval_amount is None :
2012-03-15 21:54:38 +00:00
raise forms . ValidationError ( _ ( " Please enter a pledge amount. " ) )
2012-08-31 07:16:04 +00:00
return preapproval_amount
2012-03-21 21:53:33 +00:00
2012-09-07 13:46:38 +00:00
def clean_premium_id ( self ) :
premium_id = self . cleaned_data [ ' premium_id ' ]
try :
self . premium = Premium . objects . get ( id = premium_id )
if self . premium . limit > 0 :
if self . premium . limit < = self . premium . premium_count :
raise forms . ValidationError ( _ ( " Sorry, that premium is fully subscribed. " ) )
except Premium . DoesNotExist :
raise forms . ValidationError ( _ ( " Sorry, that premium is not valid. " ) )
2011-11-30 02:02:51 +00:00
def clean ( self ) :
# check on whether the preapproval amount is < amount for premium tier. If so, put an error message
2012-10-13 14:13:46 +00:00
preapproval_amount = self . cleaned_data . get ( " preapproval_amount " )
if preapproval_amount is None :
# preapproval_amount failed validation, that error is the relevant one
return self . cleaned_data
2012-10-14 15:33:13 +00:00
elif self . premium is None :
raise forms . ValidationError ( _ ( " Please select a premium. " ) )
2012-10-13 14:13:46 +00:00
elif preapproval_amount < self . premium . amount :
logger . info ( " raising form validating error " )
raise forms . ValidationError ( _ ( " Sorry, you must pledge at least $ %s to select that premium. " % ( self . premium . amount ) ) )
2012-09-07 13:46:38 +00:00
return self . cleaned_data
2011-11-30 02:02:51 +00:00
2014-09-13 20:19:47 +00:00
class TokenCCMixin ( forms . Form ) :
2014-08-30 17:28:13 +00:00
stripe_token = forms . CharField ( required = True , widget = forms . HiddenInput ( ) )
2012-10-15 03:41:17 +00:00
2014-09-13 20:19:47 +00:00
class BaseCCMixin ( forms . Form ) :
2012-10-02 13:32:33 +00:00
work_id = forms . IntegerField ( required = False , widget = forms . HiddenInput ( ) )
2012-09-10 23:47:36 +00:00
preapproval_amount = forms . DecimalField (
2011-12-03 00:37:27 +00:00
required = False ,
min_value = D ( ' 1.00 ' ) ,
max_value = D ( ' 100000.00 ' ) ,
decimal_places = 2 ,
2013-09-06 17:58:23 +00:00
label = " Amount " ,
2011-12-03 00:37:27 +00:00
)
2014-09-13 20:19:47 +00:00
class UserCCMixin ( forms . Form ) :
username = forms . CharField ( max_length = 30 , required = True , widget = forms . HiddenInput ( ) )
class PlainCCForm ( TokenCCMixin , forms . Form ) :
pass
class BaseCCForm ( BaseCCMixin , TokenCCMixin , forms . Form ) :
pass
2011-12-03 00:37:27 +00:00
2014-02-20 03:18:23 +00:00
class AnonCCForm ( BaseCCForm ) :
email = forms . CharField ( max_length = 30 , required = False , widget = forms . TextInput ( ) )
2014-09-13 20:19:47 +00:00
class CCForm ( UserCCMixin , BaseCCForm ) :
pass
class AccountCCForm ( BaseCCMixin , UserCCMixin , forms . Form ) :
pass
2014-02-20 03:18:23 +00:00
2012-09-06 05:01:17 +00:00
class DonateForm ( forms . Form ) :
preapproval_amount = forms . DecimalField ( widget = forms . HiddenInput ( ) )
username = forms . CharField ( max_length = 30 , required = True , widget = forms . HiddenInput ( ) )
work_id = forms . IntegerField ( required = False , widget = forms . HiddenInput ( ) )
2012-10-05 16:08:06 +00:00
title = forms . CharField ( max_length = 200 , required = False , widget = forms . HiddenInput ( ) )
2012-09-06 05:01:17 +00:00
2011-11-01 00:26:05 +00:00
class GoodreadsShelfLoadingForm ( forms . Form ) :
2011-11-15 20:51:38 +00:00
goodreads_shelf_name_number = forms . CharField ( widget = forms . Select ( choices = (
2011-11-01 00:26:05 +00:00
( ' all ' , ' all ' ) ,
) ) )
2011-11-16 18:20:10 +00:00
class LibraryThingForm ( forms . Form ) :
2011-11-16 19:58:39 +00:00
lt_username = forms . CharField ( max_length = 30 , required = True )
2012-05-21 14:56:18 +00:00
class PledgeCancelForm ( forms . Form ) :
2012-05-23 14:22:48 +00:00
# which campaign whose active transaction to cancel?
2012-05-21 14:56:18 +00:00
campaign_id = forms . IntegerField ( required = True , widget = forms . HiddenInput ( ) )
2012-05-23 14:22:48 +00:00
2011-12-20 22:42:06 +00:00
class CampaignAdminForm ( forms . Form ) :
2012-05-21 14:56:18 +00:00
campaign_id = forms . IntegerField ( )
2011-12-29 01:43:52 +00:00
class EmailShareForm ( forms . Form ) :
2012-04-16 19:28:06 +00:00
recipient = forms . EmailField ( error_messages = { ' required ' : ' Please specify a recipient. ' } )
subject = forms . CharField ( max_length = 100 , error_messages = { ' required ' : ' Please specify a subject. ' } )
message = forms . CharField ( widget = forms . Textarea ( ) , error_messages = { ' required ' : ' Please include a message. ' } )
2012-02-28 22:28:33 +00:00
# allows us to return user to original page by passing it as hidden form input
# we can't rely on POST or GET since the emailshare view handles both
# and may iterate several times as it catches user errors, losing URL info
next = forms . CharField ( widget = forms . HiddenInput ( ) )
2012-01-09 20:53:09 +00:00
class FeedbackForm ( forms . Form ) :
2012-04-16 19:28:06 +00:00
sender = forms . EmailField ( widget = forms . TextInput ( attrs = { ' size ' : 50 } ) , label = " Your email " , error_messages = { ' required ' : ' Please specify your email address. ' } )
subject = forms . CharField ( max_length = 500 , widget = forms . TextInput ( attrs = { ' size ' : 50 } ) , error_messages = { ' required ' : ' Please specify a subject. ' } )
message = forms . CharField ( widget = forms . Textarea ( ) , error_messages = { ' required ' : ' Please specify a message. ' } )
2012-02-28 22:28:33 +00:00
page = forms . CharField ( widget = forms . HiddenInput ( ) )
2012-04-16 19:28:06 +00:00
notarobot = forms . IntegerField ( label = " Please prove you ' re not a robot " , error_messages = { ' required ' : " You must do the sum to prove you ' re not a robot. " } )
2012-02-28 22:28:33 +00:00
answer = forms . IntegerField ( widget = forms . HiddenInput ( ) )
num1 = forms . IntegerField ( widget = forms . HiddenInput ( ) )
num2 = forms . IntegerField ( widget = forms . HiddenInput ( ) )
def clean ( self ) :
cleaned_data = self . cleaned_data
notarobot = str ( cleaned_data . get ( " notarobot " ) )
answer = str ( cleaned_data . get ( " answer " ) )
2012-04-16 19:28:06 +00:00
if notarobot != answer :
2012-02-28 22:28:33 +00:00
raise forms . ValidationError ( _ ( " Whoops, try that sum again. " ) )
return cleaned_data
2013-02-26 17:43:54 +00:00
class MsgForm ( forms . Form ) :
msg = forms . CharField ( widget = forms . Textarea ( ) , error_messages = { ' required ' : ' Please specify a message. ' } )
def full_clean ( self ) :
super ( MsgForm , self ) . full_clean ( )
if self . data . has_key ( " supporter " ) :
try :
self . cleaned_data [ ' supporter ' ] = User . objects . get ( id = self . data [ " supporter " ] )
except User . DoesNotExist :
raise ValidationError ( " Supporter does not exist " )
else :
raise ValidationError ( " Supporter is not specified " )
if self . data . has_key ( " work " ) :
try :
self . cleaned_data [ ' work ' ] = Work . objects . get ( id = self . data [ " work " ] )
except Work . DoesNotExist :
raise ValidationError ( " Work does not exist " )
else :
raise ValidationError ( " Work is not specified " )
2013-04-04 15:09:18 +00:00
class PressForm ( forms . ModelForm ) :
class Meta :
model = Press
widgets = {
2013-04-04 15:40:03 +00:00
' date ' : SelectDateWidget ( years = range ( 2010 , 2025 ) ) ,
2013-04-04 15:09:18 +00:00
}
2013-05-31 19:19:58 +00:00
class KindleEmailForm ( forms . Form ) :
kindle_email = forms . EmailField ( )
2013-07-15 20:35:41 +00:00
2013-07-23 16:23:04 +00:00
2014-10-27 23:11:44 +00:00
class LibModeForm ( forms . ModelForm ) :
2013-07-23 16:23:04 +00:00
class Meta :
2013-07-26 22:30:45 +00:00
model = Libpref
2014-10-27 15:55:46 +00:00
fields = ( )
2014-12-18 18:37:28 +00:00
class RegiftForm ( forms . Form ) :
give_to = forms . EmailField ( label = " email address of recipient " )
give_message = forms . CharField ( max_length = 512 , label = " your gift message " , initial = " Here ' s an ebook from unglue.it, I hope you like it! - me " )
2015-01-14 20:07:54 +00:00
class SubjectSelectForm ( forms . Form ) :
add_kw = AutoCompleteSelectField (
SubjectLookup ,
widget = AutoCompleteSelectWidget ( SubjectLookup , allow_new = False ) ,
label = ' Keyword ' ,
)