def add_comment(request): """Add a comment.""" form = forms.AddCommentsForm(request.POST) if not form.is_valid(): return JsonResponse( { "status": False, "message": "{error}".format(error=form.errors.as_json(escape_html=True)), }, status=400, ) user = request.user comment = form.cleaned_data["comment"] translationId = form.cleaned_data["translation"] entity = get_object_or_404(Entity, pk=form.cleaned_data["entity"]) locale = get_object_or_404(Locale, code=form.cleaned_data["locale"]) if translationId: translation = get_object_or_404(Translation, pk=translationId) if translationId: c = Comment(author=user, translation=translation, content=comment) log_action("comment:added", user, translation=translation) else: c = Comment(author=user, entity=entity, locale=locale, content=comment) log_action("comment:added", user, entity=entity, locale=locale) c.save() return JsonResponse({"status": True})
def test_log_action_unknown_action_type(user_a, translation_a): # We send an unsupported action type. with pytest.raises(ValidationError): utils.log_action( "test:unknown", user_a, translation=translation_a, )
def test_log_action_deleted_wrong_keys(user_a, translation_a): # We send the wrong parameters for the "deleted" action. with pytest.raises(ValidationError): utils.log_action( "translation:deleted", user_a, translation=translation_a, )
def test_log_action_deleted_wrong_keys(user_a, translation_a): # We send the wrong parameters for the "deleted" action. with pytest.raises(ValidationError): utils.log_action( ActionLog.ActionType.TRANSLATION_DELETED, user_a, translation=translation_a, )
def unreject_translation(request): """Unreject given translation.""" try: t = request.POST["translation"] paths = request.POST.getlist("paths[]") except MultiValueDictKeyError as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) translation = get_object_or_404(Translation, pk=t) project = translation.entity.resource.project locale = translation.locale # Read-only translations cannot be un-rejected if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) # Only privileged users or authors can un-reject translations if not (request.user.can_translate(locale, project) or request.user == translation.user or translation.approved): return JsonResponse( { "status": False, "message": "Forbidden: You can't unreject this translation.", }, status=403, ) translation.unreject(request.user) log_action( ActionLog.ActionType.TRANSLATION_UNREJECTED, request.user, translation=translation, ) active_translation = translation.entity.reset_active_translation( locale=locale, plural_form=translation.plural_form, ) return JsonResponse({ "translation": active_translation.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), })
def test_log_action_non_deleted_wrong_keys(user_a, entity_a, locale_a): # We send the wrong parameters for a non-"deleted" action. with pytest.raises(ValidationError): utils.log_action( "translation:approved", user_a, entity=entity_a, locale=locale_a, )
def test_log_action_non_deleted_wrong_keys(user_a, entity_a, locale_a): # We send the wrong parameters for a non-"deleted" action. with pytest.raises(ValidationError): utils.log_action( ActionLog.ActionType.TRANSLATION_APPROVED, user_a, entity=entity_a, locale=locale_a, )
def test_log_action_valid_with_translation(user_a, translation_a): utils.log_action( ActionLog.ActionType.TRANSLATION_CREATED, user_a, translation=translation_a, ) log = ActionLog.objects.filter(performed_by=user_a, translation=translation_a) assert len(log) == 1 assert log[0].action_type == ActionLog.ActionType.TRANSLATION_CREATED
def test_log_action_valid_with_translation(user_a, translation_a): utils.log_action( "translation:created", user_a, translation=translation_a, ) log = ActionLog.objects.filter(performed_by=user_a, translation=translation_a) assert len(log) == 1 assert log[0].action_type == "translation:created"
def delete_translation(request): """Delete given translation.""" try: translation_id = request.POST["translation"] except MultiValueDictKeyError as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) translation = get_object_or_404(Translation, pk=translation_id) entity = translation.entity project = entity.resource.project locale = translation.locale # Read-only translations cannot be deleted if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) # Only privileged users or authors can delete translations if not translation.rejected or not (request.user.can_translate( locale, project) or request.user == translation.user or translation.approved): return JsonResponse( { "status": False, "message": "Forbidden: You can't delete this translation.", }, status=403, ) translation.delete() log_action( ActionLog.ActionType.TRANSLATION_DELETED, request.user, entity=entity, locale=locale, ) return JsonResponse({"status": True})
def test_log_action_valid_with_entity_locale(user_a, entity_a, locale_a): utils.log_action( ActionLog.ActionType.TRANSLATION_DELETED, user_a, entity=entity_a, locale=locale_a, ) log = ActionLog.objects.filter( performed_by=user_a, entity=entity_a, locale=locale_a, ) assert len(log) == 1 assert log[0].action_type == ActionLog.ActionType.TRANSLATION_DELETED
def test_log_action_valid_with_entity_locale(user_a, entity_a, locale_a): utils.log_action( "translation:deleted", user_a, entity=entity_a, locale=locale_a, ) log = ActionLog.objects.filter( performed_by=user_a, entity=entity_a, locale=locale_a, ) assert len(log) == 1 assert log[0].action_type == "translation:deleted"
def add_comment(request): """Add a comment.""" form = forms.AddCommentForm(request.POST) if not form.is_valid(): return JsonResponse( { "status": False, "message": "{error}".format(error=form.errors.as_json(escape_html=True)), }, status=400, ) user = request.user comment = form.cleaned_data["comment"] translationId = form.cleaned_data["translation"] entity = get_object_or_404(Entity, pk=form.cleaned_data["entity"]) locale = get_object_or_404(Locale, code=form.cleaned_data["locale"]) if translationId: translation = get_object_or_404(Translation, pk=translationId) else: translation = None # Translation comment if translation: c = Comment(author=user, translation=translation, content=comment) log_action(ActionLog.ActionType.COMMENT_ADDED, user, translation=translation) # Team comment else: c = Comment(author=user, entity=entity, locale=locale, content=comment) log_action(ActionLog.ActionType.COMMENT_ADDED, user, entity=entity, locale=locale) c.save() _send_add_comment_notifications(user, comment, entity, locale, translation) return JsonResponse({"status": True})
def add_comment(request): # TODO: Remove as part of bug 1361318 return JsonResponse( { "status": False, "message": "Not Implemented" }, status=501, ) """Add a comment.""" try: comment = request.POST["comment"] comment = bleach.clean( comment, strip=True, tags=settings.ALLOWED_TAGS, attributes=settings.ALLOWED_ATTRIBUTES, ) translationId = request.POST["translationId"] except MultiValueDictKeyError as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) user = request.user translation = get_object_or_404(Translation, pk=translationId) c = Comment( author=user, translation=translation, content=comment, ) c.save() log_action("comment:added", user, translation=translation) return JsonResponse({"status": True})
def test_log_action_missing_arg(user_a, entity_a, locale_a): # No translation nor (entity and locale) pair. with pytest.raises(ValidationError): utils.log_action("translation:created", user_a) # Missing locale. with pytest.raises(ValidationError): utils.log_action("translation:deleted", user_a, entity=entity_a) # Missing entity. with pytest.raises(ValidationError): utils.log_action("translation:deleted", user_a, locale=locale_a)
def test_log_action_missing_arg(user_a, entity_a, locale_a): # No translation nor (entity and locale) pair. with pytest.raises(ValidationError): utils.log_action(ActionLog.ActionType.TRANSLATION_CREATED, user_a) # Missing locale. with pytest.raises(ValidationError): utils.log_action(ActionLog.ActionType.TRANSLATION_DELETED, user_a, entity=entity_a) # Missing entity. with pytest.raises(ValidationError): utils.log_action(ActionLog.ActionType.TRANSLATION_DELETED, user_a, locale=locale_a)
def update_translation(request): """Update entity translation for the specified locale and user. Note that this view is also used to approve a translation by the old Translate app. Once we migrate to Translate.Next, we'll want to rework this view to remove the bits about approving a translation, because that has been delegated to the `approve_translation` view. """ try: entity = request.POST["entity"] string = request.POST["translation"] locale = request.POST["locale"] plural_form = request.POST["plural_form"] original = request.POST["original"] ignore_warnings = request.POST.get("ignore_warnings", "false") == "true" approve = request.POST.get("approve", "false") == "true" force_suggestions = request.POST.get("force_suggestions", "false") == "true" paths = request.POST.getlist("paths[]") except MultiValueDictKeyError as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) try: e = Entity.objects.get(pk=entity) except Entity.DoesNotExist as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) try: locale = Locale.objects.get(code=locale) except Locale.DoesNotExist as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) if plural_form == "-1": plural_form = None user = request.user project = e.resource.project # Read-only translations cannot saved if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) now = timezone.now() can_translate = request.user.can_translate( project=project, locale=locale) and (not force_suggestions or approve) translations = Translation.objects.filter(entity=e, locale=locale, plural_form=plural_form) same_translations = translations.filter(string=string).order_by( "-active", "rejected", "-date") # If same translation exists in the DB, don't save it again. if utils.is_same(same_translations, can_translate): return JsonResponse({"same": True}) # Check for errors. # Checks are disabled for the tutorial. use_checks = project.slug != "tutorial" failed_checks = None if use_checks: failed_checks = run_checks( e, locale.code, original, string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({"failedChecks": failed_checks}) # Translations exist if len(translations) > 0: # Same translation exists if len(same_translations) > 0: t = same_translations[0] # If added by privileged user, approve and unfuzzy it if can_translate and (t.fuzzy or not t.approved): if not t.active: translations.filter(active=True).update(active=False) t.active = True t.approved = True t.fuzzy = False t.rejected = False t.rejected_user = None t.rejected_date = None if t.approved_user is None: t.approved_user = user t.approved_date = now t.save(failed_checks=failed_checks) log_action("translation:approved", user, translation=t) return JsonResponse({ "type": "updated", "translation": t.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), }) # Different translation added else: t = Translation( entity=e, locale=locale, plural_form=plural_form, string=string, user=user, date=now, approved=can_translate, ) if can_translate: t.approved_user = user t.approved_date = now t.save(failed_checks=failed_checks) log_action("translation:created", user, translation=t) active_translation = e.reset_active_translation( locale=locale, plural_form=plural_form, ) return JsonResponse({ "type": "added", "translation": active_translation.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), }) # No translations saved yet else: t = Translation( entity=e, locale=locale, plural_form=plural_form, string=string, user=user, date=now, active=True, approved=can_translate, ) if can_translate: t.approved_user = user t.approved_date = now t.save(failed_checks=failed_checks) log_action("translation:created", user, translation=t) return JsonResponse({ "type": "saved", "translation": t.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), })
def create_translation(request): """ Create a new translation. """ form = forms.CreateTranslationForm(request.POST) if not form.is_valid(): problems = [] for field, errors in form.errors.items(): problems.append('Error validating field `{0}`: "{1}"'.format( field, " ".join(errors))) return JsonResponse({ "status": False, "message": "\n".join(problems) }, status=400) entity = form.cleaned_data["entity"] string = form.cleaned_data["translation"] locale = form.cleaned_data["locale"] plural_form = form.cleaned_data["plural_form"] original = form.cleaned_data["original"] ignore_warnings = form.cleaned_data["ignore_warnings"] approve = form.cleaned_data["approve"] force_suggestions = form.cleaned_data["force_suggestions"] resources = form.cleaned_data["paths"] project = entity.resource.project # Read-only translations cannot saved if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) translations = Translation.objects.filter(entity=entity, locale=locale, plural_form=plural_form) same_translations = translations.filter(string=string) # If same translation exists in the DB, don't save it again. if same_translations: return JsonResponse({"status": False, "same": True}) # Look for failed checks. # Checks are disabled for the tutorial. use_checks = project.slug != "tutorial" user = request.user failed_checks = None if use_checks: failed_checks = run_checks( entity, locale.code, original, string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ "status": False, "failedChecks": failed_checks }) now = timezone.now() can_translate = user.can_translate( project=project, locale=locale) and (not force_suggestions or approve) translation = Translation( entity=entity, locale=locale, plural_form=plural_form, string=string, user=user, date=now, approved=can_translate, ) if can_translate: translation.approved_user = user translation.approved_date = now translation.save(failed_checks=failed_checks) log_action("translation:created", user, translation=translation) if translations: translation = entity.reset_active_translation( locale=locale, plural_form=plural_form, ) return JsonResponse({ "status": True, "translation": translation.serialize(), "stats": TranslatedResource.objects.stats(project, resources, locale), })
def reject_translation(request): """Reject given translation.""" try: t = request.POST["translation"] paths = request.POST.getlist("paths[]") except MultiValueDictKeyError as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) translation = get_object_or_404(Translation, pk=t) project = translation.entity.resource.project locale = translation.locale # Read-only translations cannot be rejected if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) # Non-privileged users can only reject own unapproved translations if not request.user.can_translate(locale, project): if translation.user == request.user: if translation.approved is True: return JsonResponse( { "status": False, "message": "Forbidden: You can't reject approved translations.", }, status=403, ) else: return JsonResponse( { "status": False, "message": "Forbidden: You can't reject translations from other users.", }, status=403, ) translation.reject(request.user) log_action("translation:rejected", request.user, translation=translation) active_translation = translation.entity.reset_active_translation( locale=locale, plural_form=translation.plural_form, ) return JsonResponse({ "translation": active_translation.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), })
def approve_translation(request): """Approve given translation.""" try: t = request.POST["translation"] ignore_warnings = request.POST.get("ignore_warnings", "false") == "true" paths = request.POST.getlist("paths[]") except MultiValueDictKeyError as e: return JsonResponse( { "status": False, "message": "Bad Request: {error}".format(error=e) }, status=400, ) translation = get_object_or_404(Translation, pk=t) entity = translation.entity project = entity.resource.project locale = translation.locale user = request.user # Read-only translations cannot be approved if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) if translation.approved: return JsonResponse( { "status": False, "message": "Forbidden: This translation is already approved.", }, status=403, ) # Only privileged users can approve translations if not user.can_translate(locale, project): return JsonResponse( { "status": False, "message": "Forbidden: You don't have permission to approve this translation.", }, status=403, ) # Check for errors. # Checks are disabled for the tutorial. use_checks = project.slug != "tutorial" if use_checks: failed_checks = run_checks( entity, locale.code, entity.string, translation.string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({ "string": translation.string, "failedChecks": failed_checks }) translation.approve(user) log_action("translation:approved", user, translation=translation) active_translation = translation.entity.reset_active_translation( locale=locale, plural_form=translation.plural_form, ) return JsonResponse({ "translation": active_translation.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), })
def create_translation(request): """ Create a new translation. """ form = forms.CreateTranslationForm(request.POST) if not form.is_valid(): problems = [] for field, errors in form.errors.items(): problems.append( 'Error validating field `{}`: "{}"'.format(field, " ".join(errors)) ) return JsonResponse( {"status": False, "message": "\n".join(problems)}, status=400 ) entity = form.cleaned_data["entity"] string = form.cleaned_data["translation"] locale = form.cleaned_data["locale"] plural_form = form.cleaned_data["plural_form"] original = form.cleaned_data["original"] ignore_warnings = form.cleaned_data["ignore_warnings"] approve = form.cleaned_data["approve"] force_suggestions = form.cleaned_data["force_suggestions"] paths = form.cleaned_data["paths"] machinery_sources = form.cleaned_data["machinery_sources"] project = entity.resource.project # Read-only translations cannot saved if utils.readonly_exists(project, locale): return JsonResponse( { "status": False, "message": "Forbidden: This string is in read-only mode.", }, status=403, ) translations = Translation.objects.filter( entity=entity, locale=locale, plural_form=plural_form, ) same_translations = translations.filter(string=string) # If same translation exists in the DB, don't save it again. if same_translations: return JsonResponse({"status": False, "same": True}) # Look for failed checks. # Checks are disabled for the tutorial. use_checks = project.slug != "tutorial" user = request.user first_contribution = user.is_new_contributor(locale) failed_checks = None if use_checks: failed_checks = run_checks( entity, locale.code, original, string, user.profile.quality_checks, ) if are_blocking_checks(failed_checks, ignore_warnings): return JsonResponse({"status": False, "failedChecks": failed_checks}) now = timezone.now() can_translate = user.can_translate(project=project, locale=locale) and ( not force_suggestions or approve ) translation = Translation( entity=entity, locale=locale, plural_form=plural_form, string=string, user=user, date=now, approved=can_translate, machinery_sources=machinery_sources, ) if can_translate: translation.approved_user = user translation.approved_date = now translation.save(failed_checks=failed_checks) log_action(ActionLog.ActionType.TRANSLATION_CREATED, user, translation=translation) if translations: translation = entity.reset_active_translation( locale=locale, plural_form=plural_form, ) # When user makes their first contribution to the team, notify team managers if first_contribution: desc = """ <a href="{user_href}">{user}</a> has made their first contribution to <a href="{locale_href}">{locale} ({locale_code})</a>. Please welcome them to the team, and make sure to <a href="{review_href}">review their suggestions</a>. """.format( user=user.name_or_email, user_href=reverse( "pontoon.contributors.contributor.username", kwargs={ "username": user.username, }, ), locale=locale.name, locale_code=locale.code, locale_href=reverse( "pontoon.teams.team", kwargs={ "locale": locale.code, }, ), review_href=reverse( "pontoon.translate", kwargs={ "locale": locale.code, "project": project.slug, "resource": entity.resource.path, }, ) + f"?string={entity.pk}", ) for manager in locale.managers_group.user_set.filter( profile__new_contributor_notifications=True ): notify.send( sender=manager, recipient=manager, verb="has reviewed suggestions", # Triggers render of description only description=desc, ) return JsonResponse( { "status": True, "translation": translation.serialize(), "stats": TranslatedResource.objects.stats(project, paths, locale), } )