Skip to content

Commit

Permalink
Merge pull request #369 from mathjazz/bug-983096-batch
Browse files Browse the repository at this point in the history
Bug 983096: Batch operations
  • Loading branch information
mathjazz committed Apr 4, 2016
2 parents 48e125e + 6040e89 commit 0c78812
Show file tree
Hide file tree
Showing 11 changed files with 965 additions and 284 deletions.
16 changes: 12 additions & 4 deletions pontoon/base/forms.py
Expand Up @@ -6,14 +6,22 @@
from pontoon.sync.formats import SUPPORTED_FORMAT_PARSERS


class NoTabStopCharField(forms.CharField):
widget = forms.TextInput(attrs={'tabindex': '-1'})


class NoTabStopFileField(forms.FileField):
widget = forms.FileInput(attrs={'tabindex': '-1'})


class DownloadFileForm(forms.Form):
slug = forms.CharField()
code = forms.CharField()
part = forms.CharField()
slug = NoTabStopCharField()
code = NoTabStopCharField()
part = NoTabStopCharField()


class UploadFileForm(DownloadFileForm):
uploadfile = forms.FileField()
uploadfile = NoTabStopFileField()

def clean(self):
cleaned_data = super(UploadFileForm, self).clean()
Expand Down
97 changes: 78 additions & 19 deletions pontoon/base/models.py
Expand Up @@ -960,6 +960,19 @@ def has_suggestions(self, locale):
def unchanged(self, locale):
return self.with_status_counts(locale).filter(unchanged_count=F('expected_count'))

def prefetch_resources_translations(self, locale):
"""
Prefetch resources and translations for given locale.
"""
return self.prefetch_related(
'resource',
Prefetch(
'translation_set',
queryset=Translation.objects.filter(locale=locale),
to_attr='fetched_translations'
)
)


class Entity(DirtyFieldsMixin, models.Model):
resource = models.ForeignKey(Resource, related_name='entities')
Expand Down Expand Up @@ -1041,8 +1054,8 @@ def get_translation(self, plural_form=None):
}

@classmethod
def for_project_locale(self, project, locale, paths=None, exclude=None,
filter_type=None, filter_search=None):
def for_project_locale(self, project, locale, paths=None, filter_type=None,
filter_search=None, exclude=None):
"""Get project entities with locale translations."""
if filter_type and filter_type != 'all':
if filter_type == 'untranslated':
Expand Down Expand Up @@ -1098,14 +1111,7 @@ def for_project_locale(self, project, locale, paths=None, exclude=None,
# https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships
entities = Entity.objects.filter(search_query, pk__in=entities).distinct()

entities = entities.prefetch_related(
'resource',
Prefetch(
'translation_set',
queryset=Translation.objects.filter(locale=locale),
to_attr='fetched_translations'
)
)
entities = entities.prefetch_resources_translations(locale)

if exclude:
entities = entities.exclude(pk__in=exclude)
Expand Down Expand Up @@ -1163,6 +1169,52 @@ def extra_default():
return {}


class TranslationNotAllowed(Exception):
"""Raised when submitted Translation cannot be saved."""


class TranslationQuerySet(models.QuerySet):
def translated_resources(self, locale):
return TranslatedResource.objects.filter(
resource__entities__translation__in=self,
locale=locale
).distinct()

def find_and_replace(self, find, replace, user):
# Find translations
translations = self.filter(string__contains=find)

if translations.count() == 0:
return translations

# Empty translations produced by replace might not be always allowed
forbidden = (
translations.filter(string=find)
.exclude(entity__resource__format__in=Resource.ASYMMETRIC_FORMATS)
)
if not replace and forbidden.exists():
raise Translation.NotAllowed

# Create translations' clones and replace strings
now = timezone.now()
translations_to_create = []
for translation in translations:
translation.pk = None # Create new translation
translation.string = translation.string.replace(find, replace)
translation.user = translation.approved_user = user
translation.date = translation.approved_date = now
translation.approved = True
translation.fuzzy = False
translations_to_create.append(translation)

# Unapprove old translations
translations.update(approved=False, approved_user=None, approved_date=None)

# Create new translations
Translation.objects.bulk_create(translations_to_create)
return translations


class Translation(DirtyFieldsMixin, models.Model):
entity = models.ForeignKey(Entity)
locale = models.ForeignKey(Locale)
Expand All @@ -1177,6 +1229,9 @@ class Translation(DirtyFieldsMixin, models.Model):
approved_date = models.DateTimeField(null=True, blank=True)
fuzzy = models.BooleanField(default=False)

objects = TranslationQuerySet.as_manager()
NotAllowed = TranslationNotAllowed

# extra stores data that we want to save for the specific format
# this translation is stored in, but that we otherwise don't care
# about.
Expand Down Expand Up @@ -1324,7 +1379,10 @@ def aggregate_stats(self, instance):
instance.translated_strings = aggregated_stats['translated'] or 0
instance.fuzzy_strings = aggregated_stats['fuzzy'] or 0

instance.save()
instance.save(update_fields=[
'total_strings', 'approved_strings',
'fuzzy_strings', 'translated_strings'
])

def stats(self, project, paths, locale):
"""
Expand Down Expand Up @@ -1355,14 +1413,7 @@ class TranslatedResource(AggregatedStats):

objects = TranslatedResourceQuerySet.as_manager()

def __unicode__(self):
translated = float(self.translated_strings + self.approved_strings)
percent = 0
if self.resource.total_strings > 0:
percent = translated * 100 / self.resource.total_strings
return str(int(round(percent)))

def calculate_stats(self):
def calculate_stats(self, save=True):
"""Update stats, including denormalized ones."""
resource = self.resource
locale = self.locale
Expand All @@ -1388,6 +1439,14 @@ def calculate_stats(self):

translated = max(translated_entities.count() - approved - fuzzy, 0)

if not save:
self.total_strings = resource.total_strings
self.approved_strings = approved
self.fuzzy_strings = fuzzy
self.translated_strings = translated

return False

# Calculate diffs to reduce DB queries
total_strings_diff = resource.total_strings - self.total_strings
approved_strings_diff = approved - self.approved_strings
Expand Down
78 changes: 7 additions & 71 deletions pontoon/base/static/css/style.css
Expand Up @@ -410,7 +410,7 @@ label {
top: 10px;
}

.select > .button.breadcrumbs::after {
.select > .button.breadcrumbs:after {
content: "";
border-top: 20px solid transparent;
border-bottom: 20px solid transparent;
Expand Down Expand Up @@ -496,25 +496,27 @@ label {
}

.project.select .menu ul li .check {
color: #AAAAAA;
color: #3F4752;
font-size: 16px;
position: absolute;
right: 0;
top: 3px;
}

.project.select .menu ul li .check:before {
content: "";
content: "";
}

.project.select .menu ul li:hover .check:before {
content: "";
.project.select .menu ul li.hover .check:before {
content: "";
color: #7BC876;
margin-left: -2px;
}

.project.select .menu ul li .check.enabled:before {
content: "";
color: #7BC876;
margin-left: 0;
}

.project.select .menu ul li .name,
Expand All @@ -537,54 +539,6 @@ label {
width: 100%;
}

.part .menu nav ul li {
border-color: transparent;
border-style: solid solid none;
border-width: 1px 1px 0;
}

.part .menu nav ul li.hover {
background: transparent;
}

.part .menu nav ul li.active {
background: #272A2F;
border-color: #5E6475;
}

.part .menu nav ul li.hover a {
color: #AAAAAA;
}

.part .menu nav ul li.active a,
.part .menu nav ul li:hover a {
color: #FFFFFF;
}

.part .menu section {
padding-top: 20px;
}

.part .menu section .check-box {
cursor: pointer;
display: table;
padding-left: 1px;
text-align: left;
}

.part .menu section .check-box.hover {
background: transparent;
color: inherit;
}

.part .menu section .check-box:hover {
color: #FFFFFF;
}

.part .menu section .check-box input[type="checkbox"] {
display: none;
}

.locale.select .new ~ input[type="search"] {
padding-right: 50px;
}
Expand Down Expand Up @@ -1232,13 +1186,6 @@ body > .container {
text-transform: uppercase;
}

.part .tabs nav ul li {
width: 50%;

-moz-box-sizing: border-box;
box-sizing: border-box;
}

.tabs nav ul li a {
display: inline-block;
outline: none;
Expand All @@ -1249,17 +1196,6 @@ body > .container {
box-sizing: border-box;
}

.part .tabs nav ul li a {
line-height: 14px;
}

.part .tabs nav ul li a span.fa {
color: #7BC876;
float: none;
line-height: 12px;
padding-right: 5px;
}

.tabs > section {
display: none;
}
Expand Down

0 comments on commit 0c78812

Please sign in to comment.