forms.py 9.0 KB

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