def serialize_object(self, obj, request=None, *args, **kwargs): """Serialize a diff comment. Args: obj (reviewboard.reviews.models.diff_comment.Comment): The diff comment to serialize. request (django.http.HttpRequest, optional): The HTTP request from the client. *args (tuple): Additional positional arguments. **kwargs (dict): Additional keyword arguments. Returns: dict: The serialized diff comment. """ result = super(ReviewDiffCommentResource, self).serialize_object( obj, request=request, *args, **kwargs) if not dvcs_feature.is_enabled(request=request): result.pop('base_filediff_id', None) return result
def serialize_object(self, obj, request=None, *args, **kwargs): """Serialize a diff comment. Args: obj (reviewboard.reviews.models.diff_comment.Comment): The diff comment to serialize. request (django.http.HttpRequest, optional): The HTTP request from the client. *args (tuple): Additional positional arguments. **kwargs (dict): Additional keyword arguments. Returns: dict: The serialized diff comment. """ result = super(ReviewDiffCommentResource, self).serialize_object( obj, request=request, *args, **kwargs) if not dvcs_feature.is_enabled(request=request): result.pop('base_filediff_id', None) return result
def serialize_object(self, obj, request=None, *args, **kwargs): """Serialize a DiffSet. This method excludes fields from features that are not enabled. Args: obj (reviewboard.diffviewer.models.diffset.DiffSet): The DiffSet to serialize. request (django.http.HttpRequest, optional): The HTTP request from the client. *args (tuple): Additional positional arguments. **kwargs (dict): Additional keyword arguments. Returns: dict: The serialized DiffSet. """ result = super(DiffResource, self).serialize_object(obj, request=request, *args, **kwargs) if not dvcs_feature.is_enabled(request=request): # The field may not have been serialized (e.g., if `only-fields` # was set to a subset of fields that excludes # `created_with_history`). result.pop('commit_count', None) return result
def serialize_object(self, obj, request=None, *args, **kwargs): """Serialize a DiffSet. This method excludes fields from features that are not enabled. Args: obj (reviewboard.diffviewer.models.diffset.DiffSet): The DiffSet to serialize. request (django.http.HttpRequest, optional): The HTTP request from the client. *args (tuple): Additional positional arguments. **kwargs (dict): Additional keyword arguments. Returns: dict: The serialized DiffSet. """ result = super(DiffResource, self).serialize_object( obj, request=request, *args, **kwargs) if not dvcs_feature.is_enabled(request=request): # The field may not have been serialized (e.g., if `only-fields` # was set to a subset of fields that excludes # `created_with_history`). result.pop('commit_count', None) return result
def test_get_capability_dvcs_disabled(self): """Testing the GET / API for capabilities with the DVCS feature disabled """ with override_feature_check(dvcs_feature.feature_id, False): self.assertFalse(dvcs_feature.is_enabled()) rsp = self.api_get(get_root_url(), expected_mimetype=root_item_mimetype) self.assertEqual(rsp['stat'], 'ok') self.assertIn('capabilities', rsp) caps = rsp['capabilities'] self.assertIn('review_requests', caps) self.assertNotIn('supports_history', caps['review_requests'])
def create_comment(self, request, comments_m2m, base_filediff_id=None, **kwargs): """Create a review comment. Args: request (django.http.HttpRequest): The HTTP request from the client. comments_m2m (django.db.models.ManyToManyField): The review's comments relation, where the new comment will be added. base_filediff_id (int, optional): The ID of the base filediff for the :term:`cumulative diff` the comment is on. **kwargs (dict): Additional keyword arguments to pass on to the base class method. Returns: tuple or djblets.webapi.errors.WebAPIError: Either a successful payload containing the comment, or an error payload. """ rsp = super(ReviewDiffCommentResource, self).create_comment(comments_m2m=comments_m2m, save=False, **kwargs) if (isinstance(rsp, tuple) and isinstance(rsp[1], dict) and self.item_result_key in rsp[1]): comment = rsp[1][self.item_result_key] if (base_filediff_id is not None and dvcs_feature.is_enabled(request=request)): comment.base_filediff_id = base_filediff_id comment.save() comments_m2m.add(comment) return rsp
def get_links(self, child_resources=[], obj=None, request=None, *args, **kwargs): """Return the links for the resource. This method will filter out the draft diffcommit resource when the DVCS feature is disabled. Args: child_resources (list of djblets.webapi.resources.base. WebAPIResource): The child resources for which links will be serialized. review_request_id (unicode): A string represenation of the ID of the review request for which links are being returned. request (django.http.HttpRequest): The HTTP request from the client. *args (tuple): Additional positional arguments. **kwargs (dict): Additional keyword arguments. Returns: dict: A dictionary of the links for the resource. """ if (obj is not None and not dvcs_feature.is_enabled() and resources.draft_diffcommit in child_resources): child_resources = list(child_resources) child_resources.remove(resources.draft_diffcommit) return super(ReviewRequestDraftResource, self).get_links(child_resources, obj=obj, request=request, *args, **kwargs)
def create_comment(self, request, comments_m2m, base_filediff_id=None, **kwargs): """Create a review comment. Args: request (django.http.HttpRequest): The HTTP request from the client. comments_m2m (django.db.models.ManyToManyField): The review's comments relation, where the new comment will be added. base_filediff_id (int, optional): The ID of the base filediff for the :term:`cumulative diff` the comment is on. **kwargs (dict): Additional keyword arguments to pass on to the base class method. Returns: tuple or djblets.webapi.errors.WebAPIError: Either a successful payload containing the comment, or an error payload. """ rsp = super(ReviewDiffCommentResource, self).create_comment( comments_m2m=comments_m2m, save=False, **kwargs) if (isinstance(rsp, tuple) and isinstance(rsp[1], dict) and self.item_result_key in rsp[1]): comment = rsp[1][self.item_result_key] if (base_filediff_id is not None and dvcs_feature.is_enabled(request=request)): comment.base_filediff_id = base_filediff_id comment.save() comments_m2m.add(comment) return rsp
def get_links(self, child_resources=[], obj=None, request=None, *args, **kwargs): """Return the links for the resource. If the DVCS feature is disabled, links to resources that require the feature will not be included. Args: child_resource (list of reviewboard.webapi.base.WebAPIResource): The list of child resources for which links are to be serialized. obj (reviewboard.diffviewer.models.diffset.DiffSet, optional): The object whose links are being serialized. request (django.http.HttpRequest, optional): The HTTP request from the client. *args (tuple): Additional positional arguments. **kwargs (dict): Additional keyword arguments. Returns: dict: A dictionary of serialized links for the resource. """ if (obj is not None and not dvcs_feature.is_enabled(request=request) and resources.diffcommit in child_resources): child_resources = list(child_resources) child_resources.remove(resources.diffcommit) return super(DiffResource, self).get_links(child_resources, obj=obj, request=request, *args, **kwargs)
def get_links(self, child_resources=[], obj=None, request=None, *args, **kwargs): """Return the links for the resource. This method will filter out the draft diffcommit resource when the DVCS feature is disabled. Args: child_resources (list of djblets.webapi.resources.base. WebAPIResource): The child resources for which links will be serialized. review_request_id (unicode): A string represenation of the ID of the review request for which links are being returned. request (django.http.HttpRequest): The HTTP request from the client. *args (tuple): Additional positional arguments. **kwargs (dict): Additional keyword arguments. Returns: dict: A dictionary of the links for the resource. """ if (obj is not None and not dvcs_feature.is_enabled() and resources.draft_diffcommit in child_resources): child_resources = list(child_resources) child_resources.remove(resources.draft_diffcommit) return super(ReviewRequestDraftResource, self).get_links( child_resources, obj=obj, request=request, *args, **kwargs)
def get_links(self, child_resources=[], obj=None, request=None, *args, **kwargs): """Return the links for the resource. If the DVCS feature is disabled, links to resources that require the feature will not be included. Args: child_resource (list of reviewboard.webapi.base.WebAPIResource): The list of child resources for which links are to be serialized. obj (reviewboard.diffviewer.models.diffset.DiffSet, optional): The object whose links are being serialized. request (django.http.HttpRequest, optional): The HTTP request from the client. *args (tuple): Additional positional arguments. **kwargs (dict): Additional keyword arguments. Returns: dict: A dictionary of serialized links for the resource. """ if (obj is not None and not dvcs_feature.is_enabled(request=request) and resources.diffcommit in child_resources): child_resources = list(child_resources) child_resources.remove(resources.diffcommit) return super(DiffResource, self).get_links( child_resources, obj=obj, request=request, *args, **kwargs)
def update(self, request, finalize_commit_series=False, validation_info=None, extra_fields={}, *args, **kwargs): """Update a diff. This is used for two purposes: 1. For updating extra data on a draft diff. Extra data can be stored later lookup. See :ref:`webapi2.0-extra-data` for more information. 2. For finalization of a draft diff on a review request created with commit history. """ try: review_request = resources.review_request.get_object( request, *args, **kwargs) diffset = self.get_object(request, *args, **kwargs) except ObjectDoesNotExist: return DOES_NOT_EXIST if not review_request.is_mutable_by(request.user): return self.get_no_access_error(request) if extra_fields: try: self.import_extra_data(diffset, diffset.extra_data, extra_fields) except ImportExtraDataError as e: return e.error_payload if finalize_commit_series: if review_request.created_with_history: cumulative_diff = request.FILES.get('cumulative_diff') parent_diff = request.FILES.get('parent_diff') error_rsp = self._finalize_commit_series( request, diffset, cumulative_diff, parent_diff, validation_info) if error_rsp is not None: return error_rsp # Only add default reviewers if this is the first time we've # published any diffsets. if review_request.can_add_default_reviewers(): diffset.review_request_draft.get().add_default_reviewers() elif dvcs_feature.is_enabled(request=request): return INVALID_ATTRIBUTE, { 'reason': 'This review request was not created with ' 'commit history support.', } else: # Otherwise we silently ignore this option. finalize_commit_series = False if extra_fields or finalize_commit_series: diffset.save(update_fields=('extra_data', )) return 200, { self.item_result_key: diffset, }
def create(self, request, extra_fields={}, local_site=None, *args, **kwargs): """Creates a new diff by parsing an uploaded diff file. This will implicitly create the new Review Request draft, which can be updated separately and then published. This accepts a unified diff file, validates it, and stores it along with the draft of a review request. The new diff will have a revision of 0. A parent diff can be uploaded along with the main diff. A parent diff is a diff based on an existing commit in the repository, which will be applied before the main diff. The parent diff will not be included in the diff viewer. It's useful when developing a change based on a branch that is not yet committed. In this case, a parent diff of the parent branch would be provided along with the diff of the new commit, and only the new commit will be shown. It is expected that the client will send the data as part of a :mimetype:`multipart/form-data` mimetype. The main diff's name and content would be stored in the ``path`` field. If a parent diff is provided, its name and content would be stored in the ``parent_diff_path`` field. An example of this would be:: -- SoMe BoUnDaRy Content-Disposition: form-data; name=path; filename="foo.diff" <Unified Diff Content Here> -- SoMe BoUnDaRy -- Extra data can be stored later lookup. See :ref:`webapi2.0-extra-data` for more information. """ # Prevent a circular dependency, as ReviewRequestDraftResource # needs DraftDiffResource, which needs DiffResource. from reviewboard.webapi.resources.review_request_draft import \ ReviewRequestDraftResource try: review_request = \ resources.review_request.get_object(request, *args, **kwargs) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if not review_request.is_mutable_by(request.user): return self.get_no_access_error(request) if review_request.repository is None: return INVALID_ATTRIBUTE, { 'reason': 'This review request was created as attachments-' 'only, with no repository.', } elif review_request.created_with_history: assert dvcs_feature.is_enabled(request=request) if 'path' in request.FILES: return INVALID_FORM_DATA, { 'reason': ( 'This review request was created with support for ' 'multiple commits.\n' '\n' 'Create an empty diff revision and upload commits to ' 'that instead.' ), } diffset = DiffSet.objects.create_empty( repository=review_request.repository, base_commit_id=request.POST.get('base_commit_id')) diffset.update_revision_from_history( review_request.diffset_history) diffset.save(update_fields=('revision',)) else: form_data = request.POST.copy() form = UploadDiffForm(review_request, data=form_data, files=request.FILES, request=request) if not form.is_valid(): return INVALID_FORM_DATA, { 'fields': self._get_form_errors(form), } try: diffset = form.create() except FileNotFoundError as e: return REPO_FILE_NOT_FOUND, { 'file': e.path, 'revision': six.text_type(e.revision) } except EmptyDiffError as e: return DIFF_EMPTY except DiffTooBigError as e: return DIFF_TOO_BIG, { 'reason': six.text_type(e), 'max_size': e.max_diff_size, } except Exception as e: # This could be very wrong, but at least they'll see the error. # We probably want a new error type for this. logging.error("Error uploading new diff: %s", e, exc_info=1, request=request) return INVALID_FORM_DATA, { 'fields': { 'path': [six.text_type(e)] } } discarded_diffset = None try: draft = review_request.draft.get() if draft.diffset and draft.diffset != diffset: discarded_diffset = draft.diffset except ReviewRequestDraft.DoesNotExist: try: draft = ReviewRequestDraftResource.prepare_draft( request, review_request) except PermissionDenied: return self.get_no_access_error(request) draft.diffset = diffset # We only want to add default reviewers the first time. Was bug 318. if review_request.can_add_default_reviewers(): draft.add_default_reviewers() draft.save() if extra_fields: try: self.import_extra_data(diffset, diffset.extra_data, extra_fields) except ImportExtraDataError as e: return e.error_payload diffset.save(update_fields=['extra_data']) if discarded_diffset: discarded_diffset.delete() # E-mail gets sent when the draft is saved. return 201, { self.item_result_key: diffset, }
def create(self, request, filediff_id, interfilediff_id=None, base_filediff_id=None, *args, **kwargs): """Creates a new diff comment. This will create a new diff comment on this review. The review must be a draft review. Extra data can be stored later lookup. See :ref:`webapi2.0-extra-data` for more information. """ try: review_request = \ resources.review_request.get_object(request, *args, **kwargs) review = resources.review.get_object(request, *args, **kwargs) except ObjectDoesNotExist: return DOES_NOT_EXIST if not resources.review.has_modify_permissions(request, review): return self.get_no_access_error(request) filediff = None interfilediff = None invalid_fields = {} try: filediff = FileDiff.objects.get( pk=filediff_id, diffset__history__review_request=review_request) except ObjectDoesNotExist: invalid_fields['filediff_id'] = [ 'This is not a valid filediff ID.', ] if filediff is None or not dvcs_feature.is_enabled(request=request): base_filediff_id = None if base_filediff_id is not None: if not review_request.created_with_history: invalid_fields['base_filediff_id'] = [ 'This field cannot be specified on review requests ' 'created without history support.' ] elif interfilediff_id is not None: invalid_fields.update({ 'base_filediff_id': [ 'This field cannot be specified with ' 'interfilediff_id.', ], 'interfilediff_id': [ 'This field cannot be specified with ' 'base_filediff_id.', ], }) elif base_filediff_id == filediff_id: invalid_fields['base_filediff_id'] = [ 'This cannot be the same as filediff_id.', ] elif base_filediff_id > filediff_id: invalid_fields['base_filediff_id'] = [ 'This is not a valid base filediff ID.', ] else: base_filediff_exists = ( FileDiff.objects .filter(diffset_id=filediff.diffset_id, pk=base_filediff_id) .exclude(commit_id=filediff.commit_id) .exists() ) if not base_filediff_exists: invalid_fields['base_filediff_id'] = [ 'This is not a valid base filediff ID.', ] else: ancestor_ids = ( ancestor.pk for ancestor in filediff.get_ancestors( minimal=False) ) if base_filediff_id not in ancestor_ids: invalid_fields['base_filediff_id'] = [ 'This is not a valid base filediff ID.', ] if filediff and interfilediff_id: if interfilediff_id == filediff.id: invalid_fields.setdefault('interfilediff_id', []).append( 'This cannot be the same as filediff_id.') else: try: interfilediff = FileDiff.objects.get( pk=interfilediff_id, diffset__history=filediff.diffset.history) except ObjectDoesNotExist: invalid_fields.setdefault('interfilediff_id', []).append( 'This is not a valid interfilediff ID.') if invalid_fields: return INVALID_FORM_DATA, { 'fields': invalid_fields, } return self.create_comment( request=request, review=review, comments_m2m=review.comments, filediff=filediff, interfilediff=interfilediff, fields=('filediff', 'interfilediff', 'first_line', 'num_lines'), base_filediff_id=base_filediff_id, **kwargs)
def create(self, request, extra_fields={}, local_site=None, *args, **kwargs): """Creates a new diff by parsing an uploaded diff file. This will implicitly create the new Review Request draft, which can be updated separately and then published. This accepts a unified diff file, validates it, and stores it along with the draft of a review request. The new diff will have a revision of 0. A parent diff can be uploaded along with the main diff. A parent diff is a diff based on an existing commit in the repository, which will be applied before the main diff. The parent diff will not be included in the diff viewer. It's useful when developing a change based on a branch that is not yet committed. In this case, a parent diff of the parent branch would be provided along with the diff of the new commit, and only the new commit will be shown. It is expected that the client will send the data as part of a :mimetype:`multipart/form-data` mimetype. The main diff's name and content would be stored in the ``path`` field. If a parent diff is provided, its name and content would be stored in the ``parent_diff_path`` field. An example of this would be:: -- SoMe BoUnDaRy Content-Disposition: form-data; name=path; filename="foo.diff" <Unified Diff Content Here> -- SoMe BoUnDaRy -- Extra data can be stored later lookup. See :ref:`webapi2.0-extra-data` for more information. """ # Prevent a circular dependency, as ReviewRequestDraftResource # needs DraftDiffResource, which needs DiffResource. from reviewboard.webapi.resources.review_request_draft import \ ReviewRequestDraftResource try: review_request = \ resources.review_request.get_object(request, *args, **kwargs) except ReviewRequest.DoesNotExist: return DOES_NOT_EXIST if not review_request.is_mutable_by(request.user): return self.get_no_access_error(request) if review_request.repository is None: return INVALID_ATTRIBUTE, { 'reason': 'This review request was created as attachments-' 'only, with no repository.', } elif review_request.created_with_history: assert dvcs_feature.is_enabled(request=request) if 'path' in request.FILES: return INVALID_FORM_DATA, { 'reason': ('This review request was created with support for ' 'multiple commits.\n' '\n' 'Create an empty diff revision and upload commits to ' 'that instead.'), } diffset = DiffSet.objects.create_empty( repository=review_request.repository, base_commit_id=request.POST.get('base_commit_id')) diffset.update_revision_from_history( review_request.diffset_history) diffset.save(update_fields=('revision', )) else: form_data = request.POST.copy() form = UploadDiffForm(review_request, data=form_data, files=request.FILES, request=request) if not form.is_valid(): return INVALID_FORM_DATA, { 'fields': self._get_form_errors(form), } try: diffset = form.create() except FileNotFoundError as e: return REPO_FILE_NOT_FOUND, { 'file': e.path, 'revision': six.text_type(e.revision) } except EmptyDiffError as e: return DIFF_EMPTY except DiffTooBigError as e: return DIFF_TOO_BIG, { 'reason': six.text_type(e), 'max_size': e.max_diff_size, } except Exception as e: # This could be very wrong, but at least they'll see the error. # We probably want a new error type for this. logger.error("Error uploading new diff: %s", e, exc_info=1, request=request) return INVALID_FORM_DATA, { 'fields': { 'path': [six.text_type(e)] } } discarded_diffset = None try: draft = review_request.draft.get() if draft.diffset and draft.diffset != diffset: discarded_diffset = draft.diffset except ReviewRequestDraft.DoesNotExist: try: draft = ReviewRequestDraftResource.prepare_draft( request, review_request) except PermissionDenied: return self.get_no_access_error(request) draft.diffset = diffset # We only want to add default reviewers the first time. Was bug 318. if review_request.can_add_default_reviewers(): draft.add_default_reviewers() draft.save() if extra_fields: try: self.import_extra_data(diffset, diffset.extra_data, extra_fields) except ImportExtraDataError as e: return e.error_payload diffset.save(update_fields=['extra_data']) if discarded_diffset: discarded_diffset.delete() # E-mail gets sent when the draft is saved. return 201, { self.item_result_key: diffset, }
def update(self, request, finalize_commit_series=False, validation_info=None, extra_fields={}, *args, **kwargs): """Update a diff. This is used for two purposes: 1. For updating extra data on a draft diff. Extra data can be stored later lookup. See :ref:`webapi2.0-extra-data` for more information. 2. For finalization of a draft diff on a review request created with commit history. """ try: review_request = resources.review_request.get_object( request, *args, **kwargs) diffset = self.get_object(request, *args, **kwargs) except ObjectDoesNotExist: return DOES_NOT_EXIST if not review_request.is_mutable_by(request.user): return self.get_no_access_error(request) if extra_fields: try: self.import_extra_data(diffset, diffset.extra_data, extra_fields) except ImportExtraDataError as e: return e.error_payload if finalize_commit_series: if review_request.created_with_history: cumulative_diff = request.FILES.get('cumulative_diff') parent_diff = request.FILES.get('parent_diff') error_rsp = self._finalize_commit_series(request, diffset, cumulative_diff, parent_diff, validation_info) if error_rsp is not None: return error_rsp # Only add default reviewers if this is the first time we've # published any diffsets. if review_request.can_add_default_reviewers(): diffset.review_request_draft.get().add_default_reviewers() elif dvcs_feature.is_enabled(request=request): return INVALID_ATTRIBUTE, { 'reason': 'This review request was not created with ' 'commit history support.', } else: # Otherwise we silently ignore this option. finalize_commit_series = False if extra_fields or finalize_commit_series: diffset.save(update_fields=('extra_data',)) return 200, { self.item_result_key: diffset, }
def create(self, request, filediff_id, interfilediff_id=None, base_filediff_id=None, *args, **kwargs): """Creates a new diff comment. This will create a new diff comment on this review. The review must be a draft review. Extra data can be stored later lookup. See :ref:`webapi2.0-extra-data` for more information. """ try: review_request = \ resources.review_request.get_object(request, *args, **kwargs) review = resources.review.get_object(request, *args, **kwargs) except ObjectDoesNotExist: return DOES_NOT_EXIST if not resources.review.has_modify_permissions(request, review): return self.get_no_access_error(request) filediff = None interfilediff = None invalid_fields = {} try: filediff = FileDiff.objects.get( pk=filediff_id, diffset__history__review_request=review_request) except ObjectDoesNotExist: invalid_fields['filediff_id'] = [ 'This is not a valid filediff ID.', ] if filediff is None or not dvcs_feature.is_enabled(request=request): base_filediff_id = None if base_filediff_id is not None: if not review_request.created_with_history: invalid_fields['base_filediff_id'] = [ 'This field cannot be specified on review requests ' 'created without history support.' ] elif interfilediff_id is not None: invalid_fields.update({ 'base_filediff_id': [ 'This field cannot be specified with ' 'interfilediff_id.', ], 'interfilediff_id': [ 'This field cannot be specified with ' 'base_filediff_id.', ], }) elif base_filediff_id == filediff_id: invalid_fields['base_filediff_id'] = [ 'This cannot be the same as filediff_id.', ] elif base_filediff_id > filediff_id: invalid_fields['base_filediff_id'] = [ 'This is not a valid base filediff ID.', ] else: base_filediff_exists = ( FileDiff.objects .filter(diffset_id=filediff.diffset_id, pk=base_filediff_id) .exclude(commit_id=filediff.commit_id) .exists() ) if not base_filediff_exists: invalid_fields['base_filediff_id'] = [ 'This is not a valid base filediff ID.', ] else: ancestor_ids = ( ancestor.pk for ancestor in filediff.get_ancestors( minimal=False) ) if base_filediff_id not in ancestor_ids: invalid_fields['base_filediff_id'] = [ 'This is not a valid base filediff ID.', ] if filediff and interfilediff_id: if interfilediff_id == filediff.id: invalid_fields.setdefault('interfilediff_id', []).append( 'This cannot be the same as filediff_id.') else: try: interfilediff = FileDiff.objects.get( pk=interfilediff_id, diffset__history=filediff.diffset.history) except ObjectDoesNotExist: invalid_fields.setdefault('interfilediff_id', []).append( 'This is not a valid interfilediff ID.') if invalid_fields: return INVALID_FORM_DATA, { 'fields': invalid_fields, } return self.create_comment( request=request, review=review, comments_m2m=review.comments, filediff=filediff, interfilediff=interfilediff, fields=('filediff', 'interfilediff', 'first_line', 'num_lines'), base_filediff_id=base_filediff_id, **kwargs)