forms.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. from functools import partial
  2. import itertools
  3. import urlparse
  4. from flask.ext.wtf import Form
  5. from wtforms import Form as InsecureForm
  6. from wtforms import (TextField, DateField, DecimalField, IntegerField, SelectField,
  7. SelectMultipleField, FieldList, FormField)
  8. from wtforms.widgets import TextInput, ListWidget, html_params, HTMLString, CheckboxInput, Select
  9. from wtforms.validators import DataRequired, Optional, URL, Email, Length, NumberRange, ValidationError
  10. from flask.ext.babel import lazy_gettext as _
  11. from babel.support import LazyProxy
  12. from .constants import STEPS
  13. from .models import ISP
  14. class InputListWidget(ListWidget):
  15. def __call__(self, field, **kwargs):
  16. kwargs.setdefault('id', field.id)
  17. html = ['<%s %s>' % (self.html_tag, html_params(**kwargs))]
  18. for subfield in field:
  19. html.append('<li>%s</li>' % (subfield()))
  20. html.append('</%s>' % self.html_tag)
  21. return HTMLString(''.join(html))
  22. class MultiCheckboxField(SelectMultipleField):
  23. """
  24. A multiple-select, except displays a list of checkboxes.
  25. Iterating the field will produce subfields, allowing custom rendering of
  26. the enclosed checkbox fields.
  27. """
  28. widget = ListWidget(prefix_label=False)
  29. option_widget = CheckboxInput()
  30. class MyFormField(FormField):
  31. @property
  32. def flattened_errors(self):
  33. return list(itertools.chain.from_iterable(self.errors.values()))
  34. class Unique(object):
  35. """ validator that checks field uniqueness """
  36. def __init__(self, model, field, message=None, allow_edit=False):
  37. self.model = model
  38. self.field = field
  39. if not message:
  40. message = _(u'this element already exists')
  41. self.message = message
  42. def __call__(self, form, field):
  43. default=field.default() if callable(field.default) else field.default
  44. if field.object_data != default and field.object_data == field.data:
  45. return
  46. check = self.model.query.filter(self.field == field.data).first()
  47. if check:
  48. raise ValidationError(self.message)
  49. TECHNOLOGIES_CHOICES=(
  50. ('ftth', _('FTTH')),
  51. ('dsl', _('DSL')),
  52. ('wifi', _('Wi-Fi')),
  53. )
  54. class CoveredArea(InsecureForm):
  55. name = TextField(_(u'name'), widget=partial(TextInput(), class_='input-medium', placeholder=_(u'Area')))
  56. technologies = SelectMultipleField(_(u'technologies'), choices=TECHNOLOGIES_CHOICES,
  57. widget=partial(Select(True), **{'class': 'selectpicker', 'data-title': _(u'Technologies deployed')}))
  58. # area =
  59. def validate(self, *args, **kwargs):
  60. r=super(CoveredArea, self).validate(*args, **kwargs)
  61. if bool(self.name.data) != bool(self.technologies.data):
  62. self._fields['name'].errors += [_(u'You must fill both fields')]
  63. r=False
  64. return r
  65. class OtherWebsites(InsecureForm):
  66. name = TextField(_(u'name'), widget=partial(TextInput(), class_='input-small', placeholder=_(u'Name')))
  67. url = TextField(_(u'url'), widget=partial(TextInput(), class_='input-medium', placeholder=_(u'URL')),
  68. validators=[Optional(), URL(require_tld=True)])
  69. STEP_CHOICES = [(k, LazyProxy(lambda k, s: u'%u - %s' % (k, s), k, STEPS[k], enable_cache=False)) for k in STEPS]
  70. class ProjectForm(Form):
  71. name = TextField(_(u'full name'), description=[_(u'E.g. French Data Network')],
  72. validators=[DataRequired(), Length(min=2), Unique(ISP, ISP.name)])
  73. shortname = TextField(_(u'short name'), description=[_(u'E.g. FDN')],
  74. validators=[Optional(), Length(min=2, max=15), Unique(ISP, ISP.shortname)])
  75. description = TextField(_(u'description'), description=[None, _(u'Short text describing the project')])
  76. logo_url = TextField(_(u'logo url'), validators=[Optional(), URL(require_tld=True)])
  77. website = TextField(_(u'website'), validators=[Optional(), URL(require_tld=True)])
  78. other_websites= FieldList(MyFormField(OtherWebsites, widget=partial(InputListWidget(), class_='formfield')),
  79. min_entries=1, widget=InputListWidget(),
  80. description=[None, _(u'Additional websites that you host (e.g. wiki, etherpad...)')])
  81. contact_email = TextField(_(u'contact email'), validators=[Optional(), Email()],
  82. description=[None, _(u'General contact email address')])
  83. main_ml = TextField(_(u'main mailing list'), validators=[Optional(), Email()],
  84. description=[None, _(u'Address of your main mailing list')])
  85. creation_date = DateField(_(u'creation date'), validators=[Optional()], widget=partial(TextInput(), placeholder=_(u'YYYY-mm-dd')),
  86. description=[None, _(u'Date at which the legal structure for your project was created')])
  87. chatrooms = FieldList(TextField(_(u'chatrooms')), min_entries=1, widget=InputListWidget(),
  88. description=[None, _(u'In URI form, e.g. <code>irc://irc.isp.net/#isp</code> or '+
  89. '<code>xmpp:isp@chat.isp.net?join</code>')])
  90. covered_areas = FieldList(MyFormField(CoveredArea, widget=partial(InputListWidget(), class_='formfield')),
  91. min_entries=1, widget=InputListWidget(),
  92. description=[None, _(u'Descriptive name of the covered areas and technologies deployed')])
  93. latitude = DecimalField(_(u'latitude'), validators=[Optional(), NumberRange(min=-90, max=90)],
  94. description=[None, _(u'Geographical coordinates of your registered office or usual meeting location.')])
  95. longitude = DecimalField(_(u'longitude'), validators=[Optional(), NumberRange(min=-180, max=180)])
  96. step = SelectField(_(u'progress step'), choices=STEP_CHOICES, coerce=int)
  97. member_count = IntegerField(_(u'members'), validators=[Optional(), NumberRange(min=0)],
  98. description=[None, _('Number of members')])
  99. subscriber_count = IntegerField(_(u'subscribers'), validators=[Optional(), NumberRange(min=0)],
  100. description=[None, _('Number of subscribers to an internet access')])
  101. tech_email = TextField(_('Email'), validators=[Email(), DataRequired()], description=[None,
  102. _('Technical contact, in case of problems with your submission')])
  103. def validate(self, *args, **kwargs):
  104. r=super(ProjectForm, self).validate(*args, **kwargs)
  105. if (self.latitude.data is None) != (self.longitude.data is None):
  106. self._fields['longitude'].errors += [_(u'You must fill both fields')]
  107. r=False
  108. return r
  109. def validate_covered_areas(self, field):
  110. if len(filter(lambda e: e['name'], field.data)) == 0:
  111. # not printed, whatever..
  112. raise ValidationError(_(u'You must specify at least one area'))
  113. def to_json(self, json=None):
  114. if json is None:
  115. json={}
  116. json['name'] = self.name.data
  117. def optstr(k, v):
  118. if k in json or v:
  119. json[k]=v
  120. def optlist(k, v):
  121. if k in json or len(v):
  122. json[k]=v
  123. optstr('shortname', self.shortname.data)
  124. optstr('description', self.description.data)
  125. optstr('logoURL', self.logo_url.data)
  126. optstr('website', self.website.data)
  127. optstr('otherWebsites', dict(((w['name'], w['url']) for w in self.other_websites.data if w['name'])))
  128. optstr('email', self.contact_email.data)
  129. optstr('mainMailingList', self.main_ml.data)
  130. optstr('creationDate', self.creation_date.data.isoformat() if self.creation_date.data else None)
  131. optstr('progressStatus', self.step.data)
  132. optstr('memberCount', self.member_count.data)
  133. optstr('subscriberCount', self.subscriber_count.data)
  134. optlist('chatrooms', filter(bool, self.chatrooms.data)) # remove empty strings
  135. optstr('coordinates', {'latitude': self.latitude.data, 'longitude': self.longitude.data}
  136. if self.latitude.data else {})
  137. optlist('coveredAreas', filter(lambda e: e['name'], self.covered_areas.data))
  138. return json
  139. @classmethod
  140. def edit_json(cls, isp):
  141. json=isp.json
  142. obj=type('abject', (object,), {})()
  143. def set_attr(attr, itemk=None, d=json):
  144. if itemk is None:
  145. itemk=attr
  146. if itemk in d:
  147. setattr(obj, attr, d[itemk])
  148. set_attr('name')
  149. set_attr('shortname')
  150. set_attr('description')
  151. set_attr('logo_url', 'logoURL')
  152. set_attr('website')
  153. set_attr('contact_email', 'email')
  154. set_attr('main_ml', 'mainMailingList')
  155. set_attr('creation_date', 'creationDate')
  156. if hasattr(obj, 'creation_date'):
  157. obj.creation_date=ISP.str2date(obj.creation_date)
  158. set_attr('step', 'progressStatus')
  159. set_attr('member_count', 'memberCount')
  160. set_attr('subscriber_count', 'subscriberCount')
  161. set_attr('chatrooms', 'chatrooms')
  162. if 'coordinates' in json:
  163. set_attr('latitude', d=json['coordinates'])
  164. set_attr('longitude', d=json['coordinates'])
  165. if 'otherWebsites' in json:
  166. setattr(obj, 'other_websites', [{'name': n, 'url': w} for n, w in json['otherWebsites'].iteritems()])
  167. set_attr('covered_areas', 'coveredAreas')
  168. obj.tech_email=isp.tech_email
  169. return cls(obj=obj)
  170. class URLField(TextField):
  171. def _value(self):
  172. if isinstance(self.data, basestring):
  173. return self.data
  174. elif self.data is None:
  175. return ''
  176. else:
  177. return urlparse.urlunsplit(self.data)
  178. def process_formdata(self, valuelist):
  179. if valuelist:
  180. try:
  181. self.data = urlparse.urlsplit(valuelist[0])
  182. except:
  183. self.data = None
  184. raise ValidationError(_(u'Invalid URL'))
  185. def is_url_unique(url):
  186. if isinstance(url, basestring):
  187. url=urlparse.urlsplit(url)
  188. t=list(url)
  189. t[2]=''
  190. u1=urlparse.urlunsplit(t)
  191. t[0]='http' if t[0] == 'https' else 'https'
  192. u2=urlparse.urlunsplit(t)
  193. if ISP.query.filter(ISP.json_url.startswith(u1) | ISP.json_url.startswith(u2)).count() > 0:
  194. return False
  195. return True
  196. class ProjectJSONForm(Form):
  197. json_url = URLField(_(u'base url'), description=[_(u'E.g. https://isp.com/'),
  198. _(u'A ressource implementing our JSON-Schema specification '+
  199. 'must exist at path /isp.json')])
  200. tech_email = TextField(_(u'Email'), validators=[Email()], description=[None,
  201. _(u'Technical contact, in case of problems')])
  202. def validate_json_url(self, field):
  203. if not field.data.netloc:
  204. raise ValidationError(_(u'Invalid URL'))
  205. if field.data.scheme not in ('http', 'https'):
  206. raise ValidationError(_(u'Invalid URL (must be HTTP(S))'))
  207. if not field.object_data and not is_url_unique(field.data):
  208. raise ValidationError(_(u'This URL is already in our database'))
  209. class RequestEditToken(Form):
  210. tech_email = TextField(_(u'Tech Email'), validators=[Email()], description=[None,
  211. _(u'The Technical contact you provided while registering')])