PageRenderTime 62ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/reviewboard/reviews/forms.py

https://github.com/clach04/reviewboard
Python | 334 lines | 244 code | 52 blank | 38 comment | 33 complexity | 6c671bbff32028abda7240a2a44fd72d MD5 | raw file
  1. import logging
  2. import re
  3. from django import forms
  4. from django.contrib.admin.widgets import FilteredSelectMultiple
  5. from django.utils.translation import ugettext as _
  6. from reviewboard.diffviewer import forms as diffviewer_forms
  7. from reviewboard.diffviewer.models import DiffSet
  8. from reviewboard.reviews.errors import OwnershipError
  9. from reviewboard.reviews.models import DefaultReviewer, Group, ReviewRequest, \
  10. ReviewRequestDraft, Screenshot
  11. from reviewboard.scmtools.errors import SCMError, ChangeNumberInUseError, \
  12. InvalidChangeNumberError, \
  13. ChangeSetError
  14. from reviewboard.scmtools.models import Repository
  15. from reviewboard.site.validation import validate_review_groups, validate_users
  16. class DefaultReviewerForm(forms.ModelForm):
  17. name = forms.CharField(
  18. label=_("Name"),
  19. max_length=64,
  20. widget=forms.TextInput(attrs={'size': '30'}))
  21. file_regex = forms.CharField(
  22. label=_("File regular expression"),
  23. max_length=256,
  24. widget=forms.TextInput(attrs={'size': '60'}),
  25. help_text=_('File paths are matched against this regular expression '
  26. 'to determine if these reviewers should be added.'))
  27. repository = forms.ModelMultipleChoiceField(
  28. label=_('Repositories'),
  29. required=False,
  30. queryset=Repository.objects.filter(visible=True).order_by('name'),
  31. help_text=_('The list of repositories to specifically match this '
  32. 'default reviewer for. If left empty, this will match '
  33. 'all repositories.'),
  34. widget=FilteredSelectMultiple(_("Repositories"), False))
  35. def clean_file_regex(self):
  36. """Validates that the specified regular expression is valid."""
  37. file_regex = self.cleaned_data['file_regex']
  38. try:
  39. re.compile(file_regex)
  40. except Exception, e:
  41. raise forms.ValidationError(e)
  42. return file_regex
  43. def clean(self):
  44. validate_users(self, 'people')
  45. validate_review_groups(self, 'groups')
  46. # Now make sure the repositories are valid.
  47. local_site = self.cleaned_data['local_site']
  48. repositories = self.cleaned_data['repository']
  49. for repository in repositories:
  50. if repository.local_site != local_site:
  51. raise forms.ValidationError([
  52. _("The repository '%s' doesn't exist on the local site.")
  53. % repository.name,
  54. ])
  55. return self.cleaned_data
  56. class Meta:
  57. model = DefaultReviewer
  58. class GroupForm(forms.ModelForm):
  59. def clean(self):
  60. validate_users(self)
  61. return self.cleaned_data
  62. class Meta:
  63. model = Group
  64. class NewReviewRequestForm(forms.Form):
  65. """
  66. A form that handles creation of new review requests. These take
  67. information on the diffs, the repository the diffs are against, and
  68. optionally a changelist number (for use in certain repository types
  69. such as Perforce).
  70. """
  71. NO_REPOSITORY_ENTRY = _('(None - Graphics only)')
  72. basedir = forms.CharField(
  73. label=_("Base Directory"),
  74. required=False,
  75. help_text=_("The absolute path in the repository the diff was "
  76. "generated in."),
  77. widget=forms.TextInput(attrs={'size': '62'}))
  78. diff_path = forms.FileField(
  79. label=_("Diff"),
  80. required=False,
  81. help_text=_("The new diff to upload."),
  82. widget=forms.FileInput(attrs={'size': '62'}))
  83. parent_diff_path = forms.FileField(
  84. label=_("Parent Diff"),
  85. required=False,
  86. help_text=_("An optional diff that the main diff is based on. "
  87. "This is usually used for distributed revision control "
  88. "systems (Git, Mercurial, etc.)."),
  89. widget=forms.FileInput(attrs={'size': '62'}))
  90. repository = forms.ModelChoiceField(
  91. label=_("Repository"),
  92. queryset=Repository.objects.none(),
  93. empty_label=NO_REPOSITORY_ENTRY,
  94. required=False)
  95. changenum = forms.IntegerField(label=_("Change Number"), required=False)
  96. field_mapping = {}
  97. def __init__(self, user, local_site, *args, **kwargs):
  98. super(NewReviewRequestForm, self).__init__(*args, **kwargs)
  99. # Repository ID : visible fields mapping. This is so we can
  100. # dynamically show/hide the relevant fields with javascript.
  101. valid_repos = []
  102. self.field_mapping = {}
  103. repos = Repository.objects.accessible(user, local_site=local_site)
  104. for repo in repos.order_by('name'):
  105. try:
  106. self.field_mapping[repo.id] = repo.get_scmtool().get_fields()
  107. valid_repos.append((repo.id, repo.name))
  108. except Exception, e:
  109. logging.error('Error loading SCMTool for repository '
  110. '%s (ID %d): %s' % (repo.name, repo.id, e),
  111. exc_info=1)
  112. queryset = Repository.objects.filter(pk__in=self.field_mapping.keys())
  113. queryset = queryset.only('name')
  114. self.fields['repository'].queryset = queryset
  115. # If we have any repository entries we can show, then we should
  116. # show the first one.
  117. #
  118. # TODO: Make this available as a per-user default.
  119. if valid_repos:
  120. self.fields['repository'].initial = valid_repos[0][0]
  121. # Now add the dummy "None" repository to the choices and the
  122. # associated description.
  123. valid_repos.insert(0, ('', self.NO_REPOSITORY_ENTRY))
  124. self.field_mapping[''] = ['no_repository_explanation']
  125. self.fields['repository'].choices = valid_repos
  126. @staticmethod
  127. def create_from_list(data, constructor, error):
  128. """Helper function to combine the common bits of clean_target_people
  129. and clean_target_groups"""
  130. names = [x for x in map(str.strip, re.split(',\s*', data)) if x]
  131. return set([constructor(name) for name in names])
  132. def create(self, user, diff_file, parent_diff_file, local_site=None):
  133. # Django forms now use defer() to give us the object we need. This
  134. # actually fails with the JSONField in Repository, as we end up
  135. # getting the serialized data and not the dict we place there. So,
  136. # fetch this again.
  137. if self.cleaned_data['repository']:
  138. repository = Repository.objects.get(
  139. pk=self.cleaned_data['repository'].pk)
  140. else:
  141. repository = None
  142. changenum = self.cleaned_data['changenum'] or None
  143. # It's a little odd to validate this here, but we want to have access to
  144. # the user.
  145. if changenum:
  146. try:
  147. changeset = repository.get_scmtool().get_changeset(changenum)
  148. except ChangeSetError, e:
  149. self.errors['changenum'] = forms.util.ErrorList([str(e)])
  150. raise e
  151. except NotImplementedError:
  152. # This scmtool doesn't have changesets
  153. self.errors['changenum'] = forms.util.ErrorList(['Changesets are not supported.'])
  154. raise ChangeSetError(None)
  155. except SCMError, e:
  156. self.errors['changenum'] = forms.util.ErrorList([str(e)])
  157. raise ChangeSetError(None)
  158. if not changeset:
  159. self.errors['changenum'] = forms.util.ErrorList([
  160. 'This change number does not represent a valid '
  161. 'changeset.'])
  162. raise InvalidChangeNumberError()
  163. if user.username != changeset.username:
  164. self.errors['changenum'] = forms.util.ErrorList([
  165. 'This change number is owned by another user.'])
  166. raise OwnershipError()
  167. try:
  168. review_request = ReviewRequest.objects.create(user, repository,
  169. changenum, local_site)
  170. except ChangeNumberInUseError:
  171. # The user is updating an existing review request, rather than
  172. # creating a new one.
  173. review_request = ReviewRequest.objects.get(changenum=changenum,
  174. repository=repository)
  175. review_request.update_from_changenum(changenum)
  176. if review_request.status == 'D':
  177. # Act like we're creating a brand new review request if the
  178. # old one is discarded.
  179. review_request.status = 'P'
  180. review_request.public = False
  181. review_request.save()
  182. if diff_file:
  183. diff_form = UploadDiffForm(
  184. review_request,
  185. data={
  186. 'basedir': self.cleaned_data['basedir'],
  187. },
  188. files={
  189. 'path': diff_file,
  190. 'parent_diff_path': parent_diff_file,
  191. })
  192. diff_form.full_clean()
  193. class SavedError(Exception):
  194. """Empty exception class for when we already saved the
  195. error info.
  196. """
  197. pass
  198. try:
  199. diff_form.create(diff_file, parent_diff_file,
  200. attach_to_history=True)
  201. if 'path' in diff_form.errors:
  202. self.errors['diff_path'] = diff_form.errors['path']
  203. raise SavedError
  204. elif 'base_diff_path' in diff_form.errors:
  205. self.errors['base_diff_path'] = diff_form.errors['base_diff_path']
  206. raise SavedError
  207. except SavedError:
  208. review_request.delete()
  209. raise
  210. except diffviewer_forms.EmptyDiffError:
  211. review_request.delete()
  212. self.errors['diff_path'] = forms.util.ErrorList([
  213. 'The selected file does not appear to be a diff.'])
  214. raise
  215. except Exception, e:
  216. review_request.delete()
  217. self.errors['diff_path'] = forms.util.ErrorList([e])
  218. raise
  219. review_request.add_default_reviewers()
  220. review_request.save()
  221. return review_request
  222. class UploadDiffForm(diffviewer_forms.UploadDiffForm):
  223. """
  224. A specialized UploadDiffForm that knows how to interact with review
  225. requests.
  226. """
  227. def __init__(self, review_request, data=None, *args, **kwargs):
  228. super(UploadDiffForm, self).__init__(review_request.repository,
  229. data, *args, **kwargs)
  230. self.review_request = review_request
  231. if ('basedir' in self.fields and
  232. (not data or 'basedir' not in data)):
  233. try:
  234. diffset = DiffSet.objects.filter(
  235. history=review_request.diffset_history_id).latest()
  236. self.fields['basedir'].initial = diffset.basedir
  237. except DiffSet.DoesNotExist:
  238. pass
  239. def create(self, diff_file, parent_diff_file=None,
  240. attach_to_history=False):
  241. history = None
  242. if attach_to_history:
  243. history = self.review_request.diffset_history
  244. diffset = super(UploadDiffForm, self).create(diff_file,
  245. parent_diff_file,
  246. history)
  247. if not attach_to_history:
  248. # Set the initial revision to be one newer than the most recent
  249. # public revision, so we can reference it in the diff viewer.
  250. #
  251. # TODO: It would be nice to later consolidate this with the logic
  252. # in DiffSet.save.
  253. public_diffsets = self.review_request.diffset_history.diffsets
  254. try:
  255. latest_diffset = public_diffsets.latest()
  256. diffset.revision = latest_diffset.revision + 1
  257. except DiffSet.DoesNotExist:
  258. diffset.revision = 1
  259. diffset.save()
  260. return diffset
  261. class UploadScreenshotForm(forms.Form):
  262. """
  263. A form that handles uploading of new screenshots.
  264. A screenshot takes a path argument and optionally a caption.
  265. """
  266. caption = forms.CharField(required=False)
  267. path = forms.ImageField(required=True)
  268. def create(self, file, review_request):
  269. screenshot = Screenshot(caption='',
  270. draft_caption=self.cleaned_data['caption'])
  271. screenshot.image.save(file.name, file, save=True)
  272. draft = ReviewRequestDraft.create(review_request)
  273. draft.screenshots.add(screenshot)
  274. draft.save()
  275. return screenshot