forms.py 6.1 KB

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