def clean_entity_ids(self): return utils.split_ints(self.cleaned_data["entity_ids"])
def ajax_notifications(request, slug): """Notifications tab.""" project = get_object_or_404(Project.objects.visible_for( request.user).available(), slug=slug) available_locales = project.locales.order_by("name") # Send notifications if request.method == "POST": form = forms.NotificationsForm(request.POST) if not form.is_valid(): return JsonResponse(dict(form.errors.items())) contributors = User.objects.filter( translation__entity__resource__project=project, ) # For performance reasons, only filter contributors for selected # locales if different from all project locales available_ids = sorted( list(available_locales.values_list("id", flat=True))) selected_ids = sorted( split_ints(form.cleaned_data.get("selected_locales"))) if available_ids != selected_ids: contributors = User.objects.filter( translation__entity__resource__project=project, translation__locale__in=available_locales.filter( id__in=selected_ids), ) identifier = uuid.uuid4().hex for contributor in contributors.distinct(): notify.send( request.user, recipient=contributor, verb="has sent a message in", target=project, description=form.cleaned_data.get("message"), identifier=identifier, ) # Detect previously sent notifications using a unique identifier # TODO: We should simplify this with a custom Notifications model notifications = [] identifiers = { data["identifier"] for data in list( Notification.objects.filter( description__isnull=False, target_content_type=ContentType.objects.get_for_model(project), target_object_id=project.id, ).values_list("data", flat=True)) } for identifier in identifiers: notifications.append( Notification.objects.filter(data__contains=identifier)[0]) notifications.sort(key=lambda x: x.timestamp, reverse=True) # Recipient shortcuts incomplete = [] complete = [] for available_locale in available_locales: completion_percent = available_locale.get_chart( project)["completion_percent"] if completion_percent == 100: complete.append(available_locale.pk) else: incomplete.append(available_locale.pk) return render( request, "projects/includes/manual_notifications.html", { "form": forms.NotificationsForm(), "project": project, "available_locales": available_locales, "notifications": notifications, "incomplete": incomplete, "complete": complete, }, )
def clean_exclude_entities(self): return utils.split_ints(self.cleaned_data["exclude_entities"])
def entities(request): """Get entities for the specified project, locale and paths.""" try: project = request.POST['project'] locale = request.POST['locale'] paths = request.POST.getlist('paths[]') limit = int(request.POST.get('limit', 50)) except (MultiValueDictKeyError, ValueError) as err: return HttpResponseBadRequest('Bad Request: {error}'.format(error=err)) project = get_object_or_404(Project, slug=project) locale = get_object_or_404(Locale, code=locale) status = request.POST.get('status', '') extra = request.POST.get('extra', '') time = request.POST.get('time', '') author = request.POST.get('author', '') search = request.POST.get('search', '') exclude_entities = split_ints(request.POST.get('excludeEntities', '')) # Only return entities with provided IDs (batch editing) entity_ids = split_ints(request.POST.get('entityIds', '')) if entity_ids: entities = (Entity.objects.filter( pk__in=entity_ids).prefetch_resources_translations( locale).distinct().order_by('order')) return JsonResponse( { 'entities': Entity.map_entities(locale, entities), 'stats': TranslatedResource.objects.stats( project, paths, locale), }, safe=False) entities = Entity.for_project_locale(project, locale, paths, status, search, exclude_entities, extra, time, author) # Only return a list of entity PKs (batch editing: select all) if request.POST.get('pkOnly', None): return JsonResponse({ 'entity_pks': list(entities.values_list('pk', flat=True)), }) visible_entities = [] # In-place view: load all entities if request.POST.get('inplaceEditor', None): has_next = False entities_to_map = Entity.for_project_locale(project, locale, paths, None, None, exclude_entities) visible_entities = entities.values_list('pk', flat=True) # Out-of-context view: paginate entities else: paginator = Paginator(entities, limit) try: entities_page = paginator.page(1) except EmptyPage: return JsonResponse({ 'has_next': False, 'stats': {}, 'authors': [], 'counts_per_minute': [], }) has_next = entities_page.has_next() entities_to_map = entities_page.object_list # If requested entity not on the first page entity = request.POST.get('entity', None) if entity: try: entity_pk = int(entity) except ValueError as err: return HttpResponseBadRequest( 'Bad Request: {error}'.format(error=err)) # TODO: entities_to_map.values_list() doesn't return entities from selected page if entity_pk not in [e.pk for e in entities_to_map]: if entity_pk in entities.values_list('pk', flat=True): entities_to_map = list(entities_to_map) + list( entities.filter(pk=entity_pk)) return JsonResponse( { 'entities': Entity.map_entities(locale, entities_to_map, visible_entities), 'has_next': has_next, 'stats': TranslatedResource.objects.stats(project, paths, locale), }, safe=False)
def batch_edit_translations(request): try: l = request.POST['locale'] action = request.POST['action'] entity_pks = split_ints(request.POST.get('entities', '')) except MultiValueDictKeyError as e: return HttpResponseBadRequest('Bad Request: {error}'.format(error=e)) locale = get_object_or_404(Locale, code=l) entities = (Entity.objects.filter( pk__in=entity_pks).prefetch_resources_translations(locale)) if not entities.exists(): return JsonResponse({'count': 0}) # Batch editing is only available to translators. # Check if user has translate permissions for all of the projects in passed entities. projects = Project.objects.filter(pk__in=entities.values_list( 'resource__project__pk', flat=True).distinct()) for project in projects: if not request.user.can_translate(project=project, locale=locale): return HttpResponseForbidden( "Forbidden: You don't have permission for batch editing") translation_pks = set() for entity in entities: if entity.string_plural == "": translation_pks.add(entity.get_translation()['pk']) else: for plural_form in range(0, locale.nplurals or 1): translation_pks.add(entity.get_translation(plural_form)['pk']) translation_pks.discard(None) translations = Translation.objects.filter(pk__in=translation_pks) latest_translation_pk = None changed_translation_pks = [] # Must be executed before translations set changes, which is why # we need to force evaluate QuerySets by wrapping them inside list() def get_translations_info(translations): count = translations.count() translated_resources = list(translations.translated_resources(locale)) changed_entities = list( Entity.objects.filter(translation__in=translations).distinct()) return count, translated_resources, changed_entities if action == 'approve': translations = translations.filter(approved=False) changed_translation_pks = list( translations.values_list('pk', flat=True)) if changed_translation_pks: latest_translation_pk = translations.last().pk count, translated_resources, changed_entities = get_translations_info( translations) translations.update(approved=True, approved_user=request.user, approved_date=timezone.now()) elif action == 'delete': count, translated_resources, changed_entities = get_translations_info( translations) translations.delete() elif action == 'replace': find = request.POST.get('find') replace = request.POST.get('replace') try: translations, changed_translations = translations.find_and_replace( find, replace, request.user) changed_translation_pks = [c.pk for c in changed_translations] if changed_translation_pks: latest_translation_pk = max(changed_translation_pks) except Translation.NotAllowed: return JsonResponse({ 'error': 'Empty translations not allowed', }) count, translated_resources, changed_entities = get_translations_info( translations) if count == 0: return JsonResponse({'count': 0}) # Update stats for translated_resource in translated_resources: translated_resource.calculate_stats(save=False) bulk_update(translated_resources, update_fields=[ 'total_strings', 'approved_strings', 'fuzzy_strings', 'translated_strings', ]) project = entity.resource.project project.aggregate_stats() locale.aggregate_stats() ProjectLocale.objects.get(locale=locale, project=project).aggregate_stats() # Mark translations as changed changed_entities_array = [] existing = ChangedEntityLocale.objects.values_list('entity', 'locale').distinct() for changed_entity in changed_entities: key = (changed_entity.pk, locale.pk) # Remove duplicate changes to prevent unique constraint violation if not key in existing: changed_entities_array.append( ChangedEntityLocale(entity=changed_entity, locale=locale)) ChangedEntityLocale.objects.bulk_create(changed_entities_array) # Update latest translation if latest_translation_pk: Translation.objects.get( pk=latest_translation_pk).update_latest_translation() # Update translation memory memory_entries = [ TranslationMemoryEntry( source=t.entity.string, target=t.string, locale=locale, entity=t.entity, translation=t, project=project, ) for t in Translation.objects.filter(pk__in=changed_translation_pks). prefetch_related('entity__resource') ] TranslationMemoryEntry.objects.bulk_create(memory_entries) return JsonResponse({'count': count})
def ajax_notifications(request, slug): """Notifications tab.""" project = get_object_or_404(Project.objects.available(), slug=slug) available_locales = project.locales.order_by('name') # Send notifications if request.method == 'POST': form = forms.NotificationsForm(request.POST) if not form.is_valid(): return JsonResponse(dict(form.errors.items())) contributors = User.objects.filter( translation__entity__resource__project=project, ) # For performance reasons, only filter contributors for selected # locales if different from all project locales available_ids = sorted(list(available_locales.values_list('id', flat=True))) selected_ids = sorted(split_ints(form.cleaned_data.get('selected_locales'))) if available_ids != selected_ids: contributors = User.objects.filter( translation__entity__resource__project=project, translation__locale__in=available_locales.filter(id__in=selected_ids) ) identifier = uuid.uuid4().hex for contributor in contributors.distinct(): notify.send( request.user, recipient=contributor, verb='has sent a message in', target=project, description=form.cleaned_data.get('message'), identifier=identifier ) # Detect previously sent notifications using a unique identifier # TODO: We should simplify this with a custom Notifications model notifications = [] identifiers = set(list(Notification.objects.filter( description__isnull=False, target_content_type=ContentType.objects.get_for_model(project), target_object_id=project.id ).values_list("data", flat=True))) for identifier in identifiers: notifications.append(Notification.objects.filter(data__contains=identifier)[0]) notifications.sort(key=lambda x: x.timestamp, reverse=True) # Recipient shortcuts incomplete = [] complete = [] for available_locale in available_locales: completion_percent = available_locale.get_chart(project)['completion_percent'] if completion_percent == 100: complete.append(available_locale.pk) else: incomplete.append(available_locale.pk) return render(request, 'projects/includes/manual_notifications.html', { 'form': forms.NotificationsForm(), 'project': project, 'available_locales': available_locales, 'notifications': notifications, 'incomplete': incomplete, 'complete': complete, })
def clean_entities(self): return utils.split_ints(self.cleaned_data['entities'])
def batch_edit_translations(request): try: l = request.POST['locale'] action = request.POST['action'] entity_pks = split_ints(request.POST.get('entities', '')) except MultiValueDictKeyError as e: return HttpResponseBadRequest('Bad Request: {error}'.format(error=e)) locale = get_object_or_404(Locale, code=l) entities = ( Entity.objects.filter(pk__in=entity_pks) .prefetch_resources_translations(locale) ) if not entities.exists(): return JsonResponse({'count': 0}) # Batch editing is only available to translators. # Check if user has translate permissions for all of the projects in passed entities. projects = Project.objects.filter(pk__in=entities.values_list('resource__project__pk', flat=True).distinct()) for project in projects: if not request.user.can_translate(project=project, locale=locale): return HttpResponseForbidden( "Forbidden: You don't have permission for batch editing" ) translation_pks = set() for entity in entities: if entity.string_plural == "": translation_pks.add(entity.get_translation()['pk']) else: for plural_form in range(0, locale.nplurals or 1): translation_pks.add(entity.get_translation(plural_form)['pk']) translation_pks.discard(None) translations = Translation.objects.filter(pk__in=translation_pks) latest_translation_pk = None changed_translation_pks = [] # Must be executed before translations set changes, which is why # we need to force evaluate QuerySets by wrapping them inside list() def get_translations_info(translations): count = translations.count() translated_resources = list(translations.translated_resources(locale)) changed_entities = list(Entity.objects.filter(translation__in=translations).distinct()) return count, translated_resources, changed_entities if action == 'approve': translations = translations.filter(approved=False) changed_translation_pks = list(translations.values_list('pk', flat=True)) if changed_translation_pks: latest_translation_pk = translations.last().pk count, translated_resources, changed_entities = get_translations_info(translations) translations.update( approved=True, approved_user=request.user, approved_date=timezone.now() ) elif action == 'delete': count, translated_resources, changed_entities = get_translations_info(translations) TranslationMemoryEntry.objects.filter(translation__in=translations).delete() translations.delete() elif action == 'replace': find = request.POST.get('find') replace = request.POST.get('replace') try: translations, changed_translations = translations.find_and_replace(find, replace, request.user) changed_translation_pks = [c.pk for c in changed_translations] if changed_translation_pks: latest_translation_pk = max(changed_translation_pks) except Translation.NotAllowed: return JsonResponse({ 'error': 'Empty translations not allowed', }) count, translated_resources, changed_entities = get_translations_info(translations) if count == 0: return JsonResponse({'count': 0}) # Update stats for translated_resource in translated_resources: translated_resource.calculate_stats(save=False) bulk_update(translated_resources, update_fields=[ 'total_strings', 'approved_strings', 'fuzzy_strings', 'translated_strings', ]) project = entity.resource.project project.aggregate_stats() locale.aggregate_stats() ProjectLocale.objects.get(locale=locale, project=project).aggregate_stats() # Mark translations as changed changed_entities_array = [] existing = ChangedEntityLocale.objects.values_list('entity', 'locale').distinct() for changed_entity in changed_entities: key = (changed_entity.pk, locale.pk) # Remove duplicate changes to prevent unique constraint violation if key not in existing: changed_entities_array.append( ChangedEntityLocale(entity=changed_entity, locale=locale) ) ChangedEntityLocale.objects.bulk_create(changed_entities_array) # Update latest translation if latest_translation_pk: Translation.objects.get(pk=latest_translation_pk).update_latest_translation() # Update translation memory memory_entries = [TranslationMemoryEntry( source=t.entity.string, target=t.string, locale=locale, entity=t.entity, translation=t, project=project, ) for t in Translation.objects.filter(pk__in=changed_translation_pks).prefetch_related('entity__resource')] TranslationMemoryEntry.objects.bulk_create(memory_entries) return JsonResponse({ 'count': count })
def entities(request): """Get entities for the specified project, locale and paths.""" try: project = request.POST['project'] locale = request.POST['locale'] paths = request.POST.getlist('paths[]') limit = int(request.POST.get('limit', 50)) except (MultiValueDictKeyError, ValueError) as err: return HttpResponseBadRequest('Bad Request: {error}'.format(error=err)) project = get_object_or_404(Project, slug=project) locale = get_object_or_404(Locale, code=locale) status = request.POST.get('status', '') extra = request.POST.get('extra', '') time = request.POST.get('time', '') author = request.POST.get('author', '') search = request.POST.get('search', '') exclude_entities = split_ints(request.POST.get('excludeEntities', '')) # Only return entities with provided IDs (batch editing) entity_ids = split_ints(request.POST.get('entityIds', '')) if entity_ids: entities = ( Entity.objects.filter(pk__in=entity_ids) .prefetch_resources_translations(locale) .distinct() .order_by('order') ) return JsonResponse({ 'entities': Entity.map_entities(locale, entities), 'stats': TranslatedResource.objects.stats(project, paths, locale), }, safe=False) entities = Entity.for_project_locale( project, locale, paths, status, search, exclude_entities, extra, time, author ) # Only return a list of entity PKs (batch editing: select all) if request.POST.get('pkOnly', None): return JsonResponse({ 'entity_pks': list(entities.values_list('pk', flat=True)), }) visible_entities = [] # In-place view: load all entities if request.POST.get('inplaceEditor', None): has_next = False entities_to_map = Entity.for_project_locale( project, locale, paths, None, None, exclude_entities ) visible_entities = entities.values_list('pk', flat=True) # Out-of-context view: paginate entities else: paginator = Paginator(entities, limit) try: entities_page = paginator.page(1) except EmptyPage: return JsonResponse({ 'has_next': False, 'stats': {}, 'authors': [], 'counts_per_minute': [], }) has_next = entities_page.has_next() entities_to_map = entities_page.object_list # If requested entity not on the first page entity = request.POST.get('entity', None) if entity: try: entity_pk = int(entity) except ValueError as err: return HttpResponseBadRequest('Bad Request: {error}'.format(error=err)) # TODO: entities_to_map.values_list() doesn't return entities from selected page if entity_pk not in [e.pk for e in entities_to_map]: if entity_pk in entities.values_list('pk', flat=True): entities_to_map = list(entities_to_map) + list(entities.filter(pk=entity_pk)) return JsonResponse({ 'entities': Entity.map_entities(locale, entities_to_map, visible_entities), 'has_next': has_next, 'stats': TranslatedResource.objects.stats(project, paths, locale), }, safe=False)