-
Notifications
You must be signed in to change notification settings - Fork 288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion feedback and accepting altered suggestions #4659
Changes from all commits
335baa2
4060a85
350ac7f
a99475c
9250767
e4a49a1
96577ee
64fd646
c1b2c7d
d7bf7a9
6726e3c
c053133
526fc49
4c3c615
3e8ab9a
c001c11
668d228
ad3879e
dfae55d
2bd73b6
22746b6
ef12779
727c254
cedec4b
dd4e1c4
c099ab4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,7 +37,7 @@ | |
from .form_fields import ( | ||
CategoryChoiceField, ISODateTimeField, MultipleArgsField, | ||
CommaSeparatedCheckboxSelectMultiple) | ||
from .models import Unit | ||
from .models import Unit, Suggestion | ||
from .util import FUZZY, OBSOLETE, TRANSLATED, UNTRANSLATED | ||
|
||
|
||
|
@@ -259,6 +259,10 @@ class Meta(object): | |
) | ||
similarity = forms.FloatField(required=False) | ||
mt_similarity = forms.FloatField(required=False) | ||
suggestion = forms.ModelChoiceField( | ||
queryset=Suggestion.objects.all(), | ||
required=False) | ||
comment = forms.CharField(required=False) | ||
|
||
def __init__(self, *args, **kwargs): | ||
self.request = kwargs.pop('request', None) | ||
|
@@ -279,16 +283,42 @@ def clean_target_f(self): | |
|
||
return value | ||
|
||
def clean_state(self): | ||
def clean_similarity(self): | ||
value = self.cleaned_data['similarity'] | ||
|
||
if 0 <= value <= 1 or value is None: | ||
return value | ||
|
||
raise forms.ValidationError( | ||
_('Value of `similarity` should be in in the [0..1] range') | ||
) | ||
|
||
def clean_mt_similarity(self): | ||
value = self.cleaned_data['mt_similarity'] | ||
|
||
if 0 <= value <= 1 or value is None: | ||
return value | ||
|
||
raise forms.ValidationError( | ||
_('Value of `mt_similarity` should be in in the [0..1] range') | ||
) | ||
|
||
def clean(self): | ||
old_state = self.instance.state # Integer | ||
is_fuzzy = self.cleaned_data['state'] # Boolean | ||
new_target = self.cleaned_data['target_f'] | ||
|
||
# If suggestion is provided set `old_state` should be `TRANSLATED`. | ||
if self.cleaned_data['suggestion']: | ||
old_state = TRANSLATED | ||
|
||
if (self.request is not None and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for me putting I know its not the pep8 spec, but these blocks of logic really do need to be understood when read, and i find the spec unreadable. ignore if your really really want to 8/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't changed that line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. apologies, for sure |
||
not check_permission('administrate', self.request) and | ||
is_fuzzy): | ||
raise forms.ValidationError(_('Needs work flag must be ' | ||
'cleared')) | ||
self.add_error('state', | ||
forms.ValidationError( | ||
_('Needs work flag must be ' | ||
'cleared'))) | ||
|
||
if new_target: | ||
if old_state == UNTRANSLATED: | ||
|
@@ -320,31 +350,12 @@ def clean_state(self): | |
self.updated_fields.append((SubmissionFields.STATE, | ||
old_state, new_state)) | ||
|
||
return new_state | ||
|
||
self.instance._state_updated = False | ||
|
||
return old_state | ||
|
||
def clean_similarity(self): | ||
value = self.cleaned_data['similarity'] | ||
|
||
if 0 <= value <= 1 or value is None: | ||
return value | ||
|
||
raise forms.ValidationError( | ||
_('Value of `similarity` should be in in the [0..1] range') | ||
) | ||
|
||
def clean_mt_similarity(self): | ||
value = self.cleaned_data['mt_similarity'] | ||
|
||
if 0 <= value <= 1 or value is None: | ||
return value | ||
self.cleaned_data['state'] = new_state | ||
else: | ||
self.instance._state_updated = False | ||
self.cleaned_data['state'] = old_state | ||
|
||
raise forms.ValidationError( | ||
_('Value of `mt_similarity` should be in in the [0..1] range') | ||
) | ||
return super(UnitForm, self).clean() | ||
|
||
return UnitForm | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# -*- coding: utf-8 -*- | ||
from __future__ import unicode_literals | ||
|
||
from django.core.management import call_command | ||
from django.db import migrations | ||
|
||
|
||
def flush_django_cache(apps, schema_editor): | ||
call_command('flush_cache', '--django-cache') | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('pootle_store', '0007_case_sensitive_schema'), | ||
] | ||
|
||
operations = [ | ||
migrations.RunPython(flush_django_cache), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,11 +17,13 @@ | |
from django.utils.safestring import mark_safe | ||
from django.utils.translation import ugettext as _ | ||
|
||
from pootle_comment import get_model as get_comment_model | ||
from pootle_misc.checks import check_names | ||
from pootle_statistics.models import ( | ||
Submission, SubmissionFields, SubmissionTypes) | ||
|
||
from pootle_store.fields import to_python | ||
from pootle_store.models import Suggestion | ||
from pootle_store.util import STATES_MAP | ||
|
||
|
||
|
@@ -60,24 +62,46 @@ def gravatar_url(self, size=80): | |
|
||
class SuggestionEvent(object): | ||
|
||
def __init__(self, submission_type, username, full_name): | ||
def __init__(self, submission_type, username, full_name, comment): | ||
self.submission_type = submission_type | ||
self.username = username | ||
self.full_name = full_name | ||
self.comment = comment | ||
|
||
@cached_property | ||
def user(self): | ||
return DisplayUser(self.username, self.full_name) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. im still finding this kinda hard to follow. Can we put all of the statements as class attrs ie Class SuggestionEvent(object):
msg_accepted_suggestion = _(u'Accepted suggestion from %(author)s', params)
msg_accepted_suggestion_comment = _(u'Accepted suggestion from %(author)s with comment: %(comment)s' etc if self.submission_type == SubmissionTypes.SUGG_ADD:
message = self.msg_add_suggestion There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually im not sure if this will work properly in terms of gettext. just trying to think how to make this a bit tidier |
||
@property | ||
def description(self): | ||
author_link = self.user.author_link | ||
params = { | ||
'author': self.user.author_link | ||
} | ||
sugg_accepted_desc = _(u'Accepted suggestion from %(author)s', params) | ||
sugg_rejected_desc = _(u'Rejected suggestion from %(author)s', params) | ||
|
||
if self.comment: | ||
params.update({ | ||
'comment': format_html(u'<span class="comment">{}</span>', | ||
self.comment), | ||
}) | ||
sugg_accepted_desc = _( | ||
u'Accepted suggestion from %(author)s ' | ||
u'with comment: %(comment)s', | ||
params | ||
) | ||
sugg_rejected_desc = _( | ||
u'Rejected suggestion from %(author)s ' | ||
u'with comment: %(comment)s', | ||
params | ||
) | ||
|
||
description_dict = { | ||
SubmissionTypes.SUGG_ADD: _(u'Added suggestion'), | ||
SubmissionTypes.SUGG_ACCEPT: _(u'Accepted suggestion from %s', | ||
author_link), | ||
SubmissionTypes.SUGG_REJECT: _(u'Rejected suggestion from %s', | ||
author_link)} | ||
SubmissionTypes.SUGG_ACCEPT: sugg_accepted_desc, | ||
SubmissionTypes.SUGG_REJECT: sugg_rejected_desc, | ||
} | ||
|
||
return description_dict.get(self.submission_type, None) | ||
|
||
|
||
|
@@ -126,6 +150,10 @@ def suggestion_username(self): | |
def suggestion_target(self): | ||
return self.values['suggestion__target_f'] | ||
|
||
@property | ||
def suggestion_comment(self): | ||
return self.values['comment'] | ||
|
||
|
||
class TimelineEntry(object): | ||
|
||
|
@@ -165,11 +193,15 @@ def state_change_entry(self): | |
|
||
def suggestion_entry(self): | ||
entry = self.entry_dict | ||
|
||
suggestion_description = mark_safe( | ||
SuggestionEvent( | ||
self.submission.type, | ||
self.submission.suggestion_username, | ||
self.submission.suggestion_full_name).description) | ||
self.submission.suggestion_full_name, | ||
self.submission.suggestion_comment, | ||
).description | ||
) | ||
entry.update( | ||
{'suggestion_text': self.submission.suggestion_target, | ||
'suggestion_description': suggestion_description}) | ||
|
@@ -219,6 +251,29 @@ def submissions(self): | |
creation_time=self.object.commented_on) | ||
.order_by("id")) | ||
|
||
@cached_property | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. im not sure there is much point using a cached_property if we dont do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hi @phlax, it should be cached because we call it twice otherwise. but probs I misunderstood. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thinking about it you are right, i suppose i just wonder if we should turn it into a list |
||
def submissions_values(self): | ||
return list(self.submissions.values(*self.fields)) | ||
|
||
@property | ||
def suggestion_ids(self): | ||
return list(set([ | ||
x["suggestion_id"] | ||
for x in self.submissions_values | ||
if x["suggestion_id"] | ||
])) | ||
|
||
@cached_property | ||
def comment_dict(self): | ||
Comment = get_comment_model() | ||
return dict([ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we rename this comment_dict or similar |
||
# we need convert `object_pk` because it is TextField | ||
(int(x[0]), x[1]) | ||
for x in Comment.objects.for_model(Suggestion) | ||
.filter(object_pk__in=self.suggestion_ids) | ||
.values_list("object_pk", "comment") | ||
]) | ||
|
||
def add_creation_entry(self, grouped_entries): | ||
User = get_user_model() | ||
has_creation_entry = ( | ||
|
@@ -238,8 +293,16 @@ def add_creation_entry(self, grouped_entries): | |
def get_grouped_entries(self): | ||
grouped_entries = [] | ||
grouped_timeline = groupby( | ||
self.submissions.values(*self.fields), | ||
key=lambda x: ("%d\001%s" % (x['submitter_id'], x['creation_time']))) | ||
self.submissions_values, | ||
key=lambda item: "\001".join([ | ||
str(x) for x in | ||
[ | ||
item['submitter_id'], | ||
item['creation_time'], | ||
item['suggestion_id'], | ||
] | ||
]) | ||
) | ||
# Group by submitter id and creation_time because | ||
# different submissions can have same creation time | ||
for key, values in grouped_timeline: | ||
|
@@ -257,4 +320,5 @@ def get_grouped_entries(self): | |
return grouped_entries | ||
|
||
def get_entry(self, item): | ||
item["comment"] = self.comment_dict.get(item["suggestion_id"]) | ||
return self.entry_class(ProxySubmission(item)).entry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I see the
clean_similarity()
andclean_mt_similarity()
methods have been moved with no change/intent. If there was a cleanup intention please consider doing so separately.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They were not just moved.
clean_state()
functionality was moved toclean()
becauseclean_A()
shouldn't depend onclean_B()
.