forms.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. from recaptcha.client.captcha import submit as recaptcha_submit
  2. import floppyforms as forms
  3. from django.utils.translation import ugettext_lazy as _
  4. from misago.conf import settings
  5. class Form(forms.Form):
  6. """
  7. Misago-native form abstract extending Django's one with automatic trimming
  8. of user input, captacha support and more accessible validation errors
  9. """
  10. validate_repeats = []
  11. repeats_errors = []
  12. dont_strip = []
  13. error_source = None
  14. def __init__(self, data=None, file=None, request=None, *args, **kwargs):
  15. self.form_finalized = False
  16. self.request = request
  17. # Extract request from first arguments
  18. if data != None:
  19. super(Form, self).__init__(data, file, *args, **kwargs)
  20. else:
  21. super(Form, self).__init__(*args, **kwargs)
  22. # Let forms do mumbo-jumbo with fields removing
  23. self.ensure_finalization()
  24. # Kill captcha fields
  25. try:
  26. if settings.bots_registration != 'recaptcha' or self.request.session.get('captcha_passed'):
  27. del self.fields['recaptcha']
  28. except KeyError:
  29. pass
  30. try:
  31. if settings.bots_registration != 'qa' or self.request.session.get('captcha_passed'):
  32. del self.fields['captcha_qa']
  33. else:
  34. # Make sure we have any questions loaded
  35. self.fields['captcha_qa'].label = settings.qa_test
  36. self.fields['captcha_qa'].help_text = settings.qa_test_help
  37. except KeyError:
  38. pass
  39. @property
  40. def has_captcha(self):
  41. return 'recaptcha' in self.fields or 'captcha_qa' in self.fields
  42. def ensure_finalization(self):
  43. if not self.form_finalized:
  44. self.form_finalized = True
  45. self.finalize_form()
  46. def finalize_form(self):
  47. pass
  48. def full_clean(self):
  49. """
  50. Trim inputs and strip newlines
  51. """
  52. self.ensure_finalization()
  53. self.data = self.data.copy()
  54. for key, field in self.fields.iteritems():
  55. try:
  56. if field.__class__.__name__ in ['ModelChoiceField', 'TreeForeignKey'] and self.data[key]:
  57. self.data[key] = int(self.data[key])
  58. elif field.__class__.__name__ == 'ModelMultipleChoiceField':
  59. self.data.setlist(key, [int(x) for x in self.data.getlist(key, [])])
  60. elif field.__class__.__name__ not in ['DateField', 'DateTimeField']:
  61. if not key in self.dont_strip:
  62. if field.__class__.__name__ in ['MultipleChoiceField', 'TypedMultipleChoiceField']:
  63. self.data.setlist(key, [x.strip() for x in self.data.getlist(key, [])])
  64. else:
  65. self.data[key] = self.data[key].strip()
  66. if field.__class__.__name__ in ['MultipleChoiceField', 'TypedMultipleChoiceField']:
  67. self.data.setlist(key, [x.replace("\r\n", '') for x in self.data.getlist(key, [])])
  68. elif not field.widget.__class__.__name__ in ['Textarea']:
  69. self.data[key] = self.data[key].replace("\r\n", '')
  70. except (KeyError, AttributeError):
  71. pass
  72. super(Form, self).full_clean()
  73. def clean(self):
  74. """
  75. Clean data, do magic checks and stuff
  76. """
  77. cleaned_data = super(Form, self).clean()
  78. self._check_all()
  79. return cleaned_data
  80. def clean_recaptcha(self):
  81. """
  82. Test reCaptcha, scream if it went wrong
  83. """
  84. response = recaptcha_submit(
  85. self.request.POST.get('recaptcha_challenge_field'),
  86. self.request.POST.get('recaptcha_response_field'),
  87. settings.recaptcha_private,
  88. self.request.session.get_ip(self.request)
  89. ).is_valid
  90. if not response:
  91. raise forms.ValidationError(_("Entered words are incorrect. Please try again."))
  92. self.request.session['captcha_passed'] = True
  93. return ''
  94. def clean_captcha_qa(self):
  95. """
  96. Test QA Captcha, scream if it went wrong
  97. """
  98. if not unicode(self.cleaned_data['captcha_qa']).lower() in (name.lower() for name in unicode(settings.qa_test_answers).splitlines()):
  99. raise forms.ValidationError(_("The answer you entered is incorrect."))
  100. self.request.session['captcha_passed'] = True
  101. return self.cleaned_data['captcha_qa']
  102. def _check_all(self):
  103. # Check repeated fields
  104. self._check_repeats()
  105. # Check CSRF, we dont allow un-csrf'd forms in Misago
  106. self._check_csrf()
  107. # Check if we have any errors from fields, if we do, we will set fancy form-wide error message
  108. self._check_fields_errors()
  109. def _check_repeats(self):
  110. for index, repeat in enumerate(self.validate_repeats):
  111. # Check empty fields
  112. for field in repeat:
  113. if not field in self.data:
  114. try:
  115. if len(repeat) == 2:
  116. self.errors['_'.join(repeat)] = [self.repeats_errors[index]['fill_both']]
  117. else:
  118. self.errors['_'.join(repeat)] = [self.repeats_errors[index]['fill_all']]
  119. except (IndexError, KeyError):
  120. if len(repeat) == 2:
  121. self.errors['_'.join(repeat)] = [_("You have to fill in both fields.")]
  122. else:
  123. self.errors['_'.join(repeat)] = [_("You have to fill in all fields.")]
  124. break
  125. else:
  126. # Check different fields
  127. past_field = self.data[repeat[0]]
  128. for field in repeat:
  129. if self.data[field] != past_field:
  130. try:
  131. self.errors['_'.join(repeat)] = [self.repeats_errors[index]['different']]
  132. except (IndexError, KeyError):
  133. self.errors['_'.join(repeat)] = [_("Entered values differ from each other.")]
  134. break
  135. past_field = self.data[field]
  136. def _check_csrf(self):
  137. if not self.request.csrf.request_secure(self.request):
  138. raise forms.ValidationError(_("Request authorization is invalid. Please resubmit your form."))
  139. def _check_fields_errors(self):
  140. if self.errors:
  141. if self.error_source and self.error_source in self.errors:
  142. field_error, self.errors[self.error_source] = self.errors[self.error_source][0], []
  143. raise forms.ValidationError(field_error)
  144. raise forms.ValidationError(_("Form contains errors."))
  145. def empty_errors(self):
  146. for i in self.errors:
  147. self.errors[i] = []