Example #1
0
    def context(self):
        if self.submission.new_value:
            return dict(value=self.submission.new_value,
                        sidetitle=_(u"Comment:"),
                        comment=True)

        return dict(description=_(u"Removed comment"))
Example #2
0
class AbstractFormat(models.Model):
    class Meta(object):
        abstract = True
        unique_together = ["title", "extension"]

    name = models.CharField(_('Format name'),
                            max_length=30,
                            unique=True,
                            db_index=True)
    title = models.CharField(_('Format title'), max_length=255, db_index=False)
    enabled = models.BooleanField(verbose_name=_('Enabled'), default=True)
    monolingual = models.BooleanField(verbose_name=_('Monolingual format'),
                                      default=False)
Example #3
0
    def clean(self):
        """Fail validation if:

        - URL and body are blank
        - Current virtual path exists in other page models
        """
        if not self.url and not self.body:
            # Translators: 'URL' and 'content' refer to form fields.
            raise ValidationError(_('URL or content must be provided.'))

        pages = [p.objects.filter(Q(virtual_path=self.virtual_path),
                                  ~Q(pk=self.pk),).exists() for p in
                 AbstractPage.__subclasses__()]
        if True in pages:
            raise ValidationError(_(u'Virtual path already in use.'))
Example #4
0
    def context(self):
        params = {'author': self.user.author_link}
        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_rejected_desc = _(
                u'Rejected suggestion from %(author)s '
                u'with comment: %(comment)s', params)

        return dict(value=self.suggestion.target,
                    description=format_html(sugg_rejected_desc))
Example #5
0
 def clean_target_f(self):
     target = multistring(self.cleaned_data["target_f"] or [u''])
     if self.unit.get_suggestions().filter(target_f=target).exists():
         self.add_error(
             "target_f",
             forms.ValidationError(
                 _("Suggestion '%s' already exists.", target)))
     elif target == self.unit.target:
         self.add_error(
             "target_f",
             forms.ValidationError(
                 _("Suggestion '%s' equals to current unit target value.",
                   target)))
     else:
         return self.cleaned_data["target_f"]
Example #6
0
    class UnitCommentForm(forms.ModelForm):
        class Meta(object):
            fields = ('translator_comment', )
            model = Unit

        translator_comment = forms.CharField(
            required=True,
            label=_("Translator comment"),
            widget=forms.Textarea(attrs=comment_attrs),
        )

        def __init__(self, *args, **kwargs):
            self.request = kwargs.pop('request', None)
            self.previous_value = ''

            super(UnitCommentForm, self).__init__(*args, **kwargs)

            if self.request.method == 'DELETE':
                self.fields['translator_comment'].required = False

        def clean_translator_comment(self):
            # HACKISH: Setting empty string when `DELETE` is being used
            if self.request.method == 'DELETE':
                self.previous_value = self.instance.translator_comment
                return ''

            return self.cleaned_data['translator_comment']

        def save(self, *args, **kwargs):
            self.instance.save(user=self.request.user)
Example #7
0
def validate_project_checker(value):
    if value not in PROJECT_CHECKERS.keys():
        raise ValidationError(
            # Translators: this refers to the project quality checker
            _('"%(code)s" cannot be used as a project checker'),
            params={'code': value},
        )
Example #8
0
def get_translation_states(path_obj):
    states = []

    def make_dict(state, title, filter_url=True):
        filter_name = filter_url and state or None
        return {
            'state': state,
            'title': title,
            'url': path_obj.get_translate_url(state=filter_name)
        }

    states.append(make_dict('total', _("Total"), False))
    states.append(make_dict('translated', _("Translated")))
    states.append(make_dict('fuzzy', _("Fuzzy")))
    states.append(make_dict('untranslated', _("Untranslated")))
    return states
Example #9
0
 def clean_action(self):
     if not self.target_object.is_pending:
         self.add_error(
             "action",
             forms.ValidationError(
                 _("Suggestion '%s' has already been accepted or rejected.",
                   self.target_object)))
     return self.data["action"]
Example #10
0
def pluralize_source(unit):
    if not unit.hasplural():
        return [(0, unit.source, None)]

    count = len(unit.source.strings)
    if count == 1:
        return [(0, unit.source.strings[0],
                 "%s+%s" % (_('Singular'), _('Plural')))]

    if count == 2:
        return [(0, unit.source.strings[0], _('Singular')),
                (1, unit.source.strings[1], _('Plural'))]

    forms = []
    for i, source in enumerate(unit.source.strings):
        forms.append((i, source, _('Plural Form %d', i)))
    return forms
Example #11
0
class SubmissionFields(object):
    NONE = 0  # non-field submission
    SOURCE = 1  # xtle_store.models.Unit.source
    TARGET = 2  # xtle_store.models.Unit.target
    STATE = 3  # xtle_store.models.Unit.state
    COMMENT = 4  # xtle_store.models.Unit.translator_comment
    CHECK = 5

    TRANSLATION_FIELDS = [TARGET]

    NAMES_MAP = {
        NONE: "",
        SOURCE: _("Source"),
        TARGET: _("Target"),
        STATE: _("State"),
        COMMENT: _("Comment"),
        CHECK: (_("Check")),
    }
Example #12
0
class LegalPage(AbstractPage):

    display_name = _('Legal Page')

    def localized_title(self):
        return _(self.title)

    def get_edit_url(self):
        return reverse('xtle-staticpages-edit', args=['legal', self.pk])
Example #13
0
def preview_content(request):
    """Returns content rendered based on the configured markup settings."""
    if 'text' not in request.POST:
        return JsonResponseBadRequest({
            'msg': _('Text is missing'),
        })

    return JsonResponse({
        'rendered': apply_markup_filter(request.POST['text']),
    })
Example #14
0
    def context(self):
        params = {'author': self.user.author_link}
        sugg_accepted_desc = _(u'Accepted 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)

        target = self.suggestion.target
        submission = self.suggestion.submission_set.filter(
            field=SubmissionFields.TARGET).first()
        if submission:
            target = submission.new_value
        return dict(value=target,
                    translation=True,
                    description=format_html(sugg_accepted_desc))
Example #15
0
    def get_form(self, form_class=None):
        form = super(PageCreateView, self).get_form(form_class)

        if self.page_type == ANN_TYPE:
            del form.fields['url']
            # Translators: 'projects' must not be translated.
            msg = _(u'projects/<project_code> or <language_code> or '
                    u'<language_code>/<project_code>')
            form.fields['virtual_path'].widget.attrs['placeholder'] = msg
            form.fields['virtual_path'].widget.attrs['size'] = 100

        return form
Example #16
0
 def clean(self):
     self_review = (self.request_user == self.target_object.user
                    and self.cleaned_data.get("action") == "reject")
     permission = ("view" if self_review else "review")
     has_permission = check_user_permission(
         self.request_user, permission,
         self.target_object.unit.store.parent)
     if not has_permission:
         raise forms.ValidationError(
             _("Insufficient rights to access this page."))
     if not self.errors:
         super(SuggestionReviewForm, self).clean()
Example #17
0
    def context(self):
        ctx = dict(description=_(u"Unit created"))
        if self.target_event is not None:
            if self.target_event.value.old_value != '':
                ctx['value'] = self.target_event.value.old_value
                ctx['translation'] = True
        else:
            if self.unit_source.unit.istranslated():
                ctx['value'] = self.unit_source.unit.target
                ctx['translation'] = True

        return ctx
Example #18
0
def pluralize_target(unit, nplurals=None):
    if not unit.hasplural():
        return [(0, unit.target, None)]

    if nplurals is None:
        try:
            nplurals = unit.store.translation_project.language.nplurals
        except ObjectDoesNotExist:
            pass
    forms = []
    if nplurals is None:
        for i, target in enumerate(unit.target.strings):
            forms.append((i, target, _('Plural Form %d', i)))
    else:
        for i in range(nplurals):
            try:
                target = unit.target.strings[i]
            except IndexError:
                target = ''
            forms.append((i, target, _('Plural Form %d', i)))

    return forms
Example #19
0
    def format_output(self, rendered_widgets):
        from django.utils.safestring import mark_safe
        if len(rendered_widgets) == 1:
            return mark_safe(rendered_widgets[0])

        output = ''
        for i, widget in enumerate(rendered_widgets):
            output += '<div lang="%s" title="%s">' % \
                (get_language(), _('Plural Form %d', i))
            output += widget
            output += '</div>'

        return mark_safe(output)
Example #20
0
    def get_initial(self):
        initial = super(PageModelMixin, self).get_initial()

        initial_args = {
            'title': _('Page Title'),
        }

        if self.page_type != ANN_TYPE:
            next_page_number = AbstractPage.max_pk() + 1
            initial_args['virtual_path'] = 'page-%d' % next_page_number

        initial.update(initial_args)

        return initial
Example #21
0
        def add_page_field(self, page):
            """Adds `page` as a required field to this form."""
            url = page.url and page.url or \
                reverse('xtle-staticpages-display', args=[page.virtual_path])
            label_params = {
                'url': url,
                'classes': 'js-agreement-popup',
                'title': page.title,
            }
            label = mark_safe(
                _(
                    'I have read and accept: <a href="%(url)s" '
                    'class="%(classes)s">%(title)s</a>', label_params))

            field_name = 'legal_%d' % page.pk
            self.fields[field_name] = forms.BooleanField(label=label,
                                                         required=True)
            self.fields[field_name].widget.attrs['class'] = 'js-legalfield'
Example #22
0
class StaticPage(AbstractPage):

    display_name = _('Regular Page')

    @classmethod
    def get_announcement_for(cls, xtle_path, user=None):
        """Return the announcement for the specified xtle path and user."""
        virtual_path = ANN_VPATH + xtle_path.strip('/')
        try:
            return cls.objects.live(user).get(virtual_path=virtual_path)
        except StaticPage.DoesNotExist:
            return None

    def get_edit_url(self):
        page_type = 'static'
        if self.virtual_path.startswith(ANN_VPATH):
            page_type = ANN_TYPE
        return reverse('xtle-staticpages-edit', args=[page_type, self.pk])
Example #23
0
    def validate_email(self):
        """Ensure emails are unique across the models tracking emails.

        Since it's essential to keep email addresses unique to support our
        workflows, a `ValidationError` will be raised if the email trying
        to be saved is already assigned to some other user.
        """
        lookup = Q(email__iexact=self.email)
        if self.pk is not None:
            # When there's an update, ensure no one else has this address
            lookup &= ~Q(user=self)

        try:
            EmailAddress.objects.get(lookup)
        except EmailAddress.DoesNotExist:
            pass
        else:
            raise ValidationError({
                'email': [_('This email address already exists.')]
            })
Example #24
0
        def _wrapped(request, *args, **kwargs):
            path_obj = args[0]
            directory = getattr(path_obj, 'directory', path_obj)

            # HACKISH: some old code relies on
            # `request.translation_project`, `request.language` etc.
            # being set, so we need to set that too.
            attr_name = CLS2ATTR.get(path_obj.__class__.__name__, 'path_obj')
            setattr(request, attr_name, path_obj)

            request.permissions = get_matching_permissions(
                request.user, directory)

            if not permission_code:
                return func(request, *args, **kwargs)

            if not check_permission(permission_code, request):
                raise PermissionDenied(
                    _("Insufficient rights to access this page."), )

            return func(request, *args, **kwargs)
Example #25
0
        def clean(self):
            old_state = self.instance.state  # Integer
            is_fuzzy = self.cleaned_data['is_fuzzy']  # 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

                # Skip `TARGET` field submission if suggestion value is equal
                # to submitted translation
                if new_target == self.cleaned_data['suggestion'].target_f:
                    self._updated_fields = []
            not_cleared = (self.request is not None and
                           not check_permission('administrate', self.request)
                           and is_fuzzy)
            if not_cleared:
                self.add_error(
                    'is_fuzzy',
                    forms.ValidationError(
                        _('Needs work flag must be cleared')))

            if new_target:
                if is_fuzzy:
                    new_state = FUZZY
                else:
                    new_state = TRANSLATED
            else:
                new_state = UNTRANSLATED
            if old_state not in [new_state, OBSOLETE]:
                self._updated_fields.append(
                    (SubmissionFields.STATE, old_state, new_state))

                self.cleaned_data['state'] = new_state
            else:
                self.cleaned_data['state'] = old_state

            return super(UnitForm, self).clean()
Example #26
0
class SubmitForm(SubmitFormMixin, forms.Form):
    is_fuzzy = forms.BooleanField(initial=False, label=_("Needs work"))
    target_f = MultiStringFormField(required=False, require_all_fields=False)

    def clean_is_fuzzy(self):
        return self.data["is_fuzzy"] != "0"

    def save_unit(self):
        user = self.request_user
        target = multistring(self.cleaned_data["target_f"] or [u''])
        if target != self.unit.target:
            self.unit.target = self.cleaned_data["target_f"]
        if self.unit.target:
            if self.cleaned_data["is_fuzzy"]:
                self.unit.state = FUZZY
            else:
                self.unit.state = TRANSLATED
        else:
            self.unit.state = UNTRANSLATED
        self.unit.save(user=user, changed_with=SubmissionTypes.WEB)

    def save(self):
        with update_store_after(self.unit.store):
            self.save_unit()
Example #27
0
class User(AbstractBaseUser):
    """The Xtle User.

    ``username``, ``password`` and ``email`` are required. Other fields
    are optional.

    Note that the ``password`` and ``last_login`` fields are inherited
    from ``AbstractBaseUser``.
    """

    username = models.CharField(
        _('Username'), max_length=30, unique=True,
        help_text=_('Required. 30 characters or fewer. Letters, numbers and '
                    '@/./+/-/_ characters'),
        validators=[
            RegexValidator(
                re.compile(r'^[\w.@+-]+$', re.UNICODE),
                _('Enter a valid username.'),
                'invalid')
        ],
        error_messages={
            'unique': _('A user with that username already exists.'),
        },
    )
    email = models.EmailField(_('Email Address'), max_length=255)
    full_name = models.CharField(_('Full Name'), max_length=255, blank=True)

    is_active = models.BooleanField(
        _('Active'),
        default=True,
        db_index=True,
        help_text=_('Designates whether this user should be treated as '
                    'active. Unselect this instead of deleting accounts.'))
    is_superuser = models.BooleanField(
        _('Superuser Status'),
        default=False,
        db_index=True,
        help_text=_('Designates that this user has all permissions without '
                    'explicitly assigning them.'))

    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    # Translation setting fields
    unit_rows = models.SmallIntegerField(default=9,
                                         verbose_name=_("Number of Rows"))
    alt_src_langs = models.ManyToManyField(
        'xtle_language.Language', blank=True, db_index=True,
        limit_choices_to=~Q(code='templates'),
        verbose_name=_("Alternative Source Languages"))

    # Score-related fields
    score = models.FloatField(_('Score'), null=False, default=0)
    is_employee = models.BooleanField(_('Is employee?'), default=False)
    twitter = models.CharField(_('Twitter'), max_length=15, null=True,
                               blank=True)
    website = models.URLField(_('Website'), null=True, blank=True)
    linkedin = models.URLField(_('LinkedIn'), null=True, blank=True)
    bio = models.TextField(_('Short Bio'), null=True, blank=True)

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    objects = UserManager()

    @property
    def display_name(self):
        """Human-readable display name."""
        return (self.get_full_name()
                if self.get_full_name()
                else self.get_short_name())

    @property
    def public_score(self):
        return formatter.number(round(self.score))

    @property
    def has_contact_details(self):
        """Returns ``True`` if any contact details have been set."""
        return bool(self.website or self.twitter or self.linkedin)

    @property
    def twitter_url(self):
        return 'https://twitter.com/{0}'.format(self.twitter)

    @cached_property
    def is_meta(self):
        """Returns `True` if this is a special fake user."""
        return self.username in \
            UserManager.META_USERS + settings.XTLE_META_USERS

    @cached_property
    def email_hash(self):
        try:
            return md5(force_bytes(self.email)).hexdigest()
        except UnicodeEncodeError:
            return None

    def __unicode__(self):
        return self.username

    def save(self, *args, **kwargs):
        old_email = (None
                     if self.pk is None
                     else User.objects.get(pk=self.pk).email)

        super(User, self).save(*args, **kwargs)

        self.sync_email(old_email)

    def delete(self, *args, **kwargs):
        """Deletes a user instance.

        Trying to delete a meta user raises the `ProtectedError` exception.
        """
        if self.is_meta:
            raise ProtectedError('Cannot remove meta user instances', None)

        purge = kwargs.pop("purge", False)

        if purge:
            UserPurger(self).purge()
        else:
            UserMerger(self, User.objects.get_nobody_user()).merge()

        super(User, self).delete(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('xtle-user-profile', args=[self.username])

    def field_values(self):
        """Returns the user's field-values (can be encoded as e.g. JSON)."""
        values = model_to_dict(self, exclude=['password'])
        values["alt_src_langs"] = list(
            values["alt_src_langs"].values_list("pk", flat=True))
        return values

    @property
    def is_anonymous(self):
        """Returns `True` if this is an anonymous user."""
        return self.username == 'nobody'

    @property
    def is_authenticated(self):
        """Returns `True` if this is an authenticated user."""
        return self.username != 'nobody'

    def is_system(self):
        """Returns `True` if this is the special `system` user."""
        return self.username == 'system'

    def has_manager_permissions(self):
        """Tells if the user is a manager for any language, project or TP."""
        if self.is_anonymous:
            return False
        if self.is_superuser:
            return True
        criteria = {
            'positive_permissions__codename': 'administrate',
            'directory__xtle_path__regex': r'^/[^/]*/([^/]*/)?$',
        }
        return self.permissionset_set.filter(**criteria).exists()

    def get_full_name(self):
        """Returns the user's full name."""
        return self.full_name.strip()

    def get_short_name(self):
        """Returns the short name for the user."""
        return self.username

    def email_user(self, subject, message, from_email=None):
        """Sends an email to this user."""
        send_mail(subject, message, from_email, [self.email])

    def clean_fields(self, exclude=None):
        super(User, self).clean_fields(exclude=exclude)

        self.validate_email()

    def validate_email(self):
        """Ensure emails are unique across the models tracking emails.

        Since it's essential to keep email addresses unique to support our
        workflows, a `ValidationError` will be raised if the email trying
        to be saved is already assigned to some other user.
        """
        lookup = Q(email__iexact=self.email)
        if self.pk is not None:
            # When there's an update, ensure no one else has this address
            lookup &= ~Q(user=self)

        try:
            EmailAddress.objects.get(lookup)
        except EmailAddress.DoesNotExist:
            pass
        else:
            raise ValidationError({
                'email': [_('This email address already exists.')]
            })

    def sync_email(self, old_email):
        """Syncs up `self.email` with allauth's own `EmailAddress` model.

        :param old_email: Address this user previously had
        """
        if old_email != self.email:  # Update
            EmailAddress.objects.filter(
                user=self,
                email__iexact=old_email,
            ).update(email=self.email)
        else:
            sync_user_email_addresses(self)

    def gravatar_url(self, size=80):
        if not self.email_hash:
            return ''

        return 'https://secure.gravatar.com/avatar/%s?s=%d&d=mm' % \
            (self.email_hash, size)

    def get_suggestion_reviews(self):
        return self.submission_set.get_unit_suggestion_reviews()

    def get_unit_rows(self):
        return min(max(self.unit_rows, 5), 49)

    def get_unit_states_changed(self):
        return self.submission_set.get_unit_state_changes()

    def get_units_created(self):
        """Units that were created by this user.

        :return: Queryset of `Unit`s that were created by this user.
        """
        return Unit.objects.filter(unit_source__created_by=self)

    def last_event(self, locale=None):
        """Returns the latest submission linked with this user. If there's
        no activity, `None` is returned instead.
        """
        last_event = Submission.objects.select_related(
            "unit",
            "unit__store",
            "unit__store__parent").filter(submitter=self).last()
        if last_event:
            return ActionDisplay(
                last_event.get_submission_info(),
                locale=locale)
Example #28
0
    def wrapped(request, *args, **kwargs):
        if request.is_ajax():
            xtle_path = request.GET.get('path', None)
            if xtle_path is None:
                raise Http400(_('Arguments missing.'))

            language_code, project_code, dir_path, filename = \
                split_xtle_path(xtle_path)
            kwargs['dir_path'] = dir_path
            kwargs['filename'] = filename

            # Remove potentially present but unwanted args
            try:
                del kwargs['language_code']
                del kwargs['project_code']
            except KeyError:
                pass
        else:
            language_code = kwargs.pop('language_code', None)
            project_code = kwargs.pop('project_code', None)

        if language_code and project_code:
            try:
                path_obj = TranslationProject.objects.get_for_user(
                    user=request.user,
                    language_code=language_code,
                    project_code=project_code,
                )
            except TranslationProject.DoesNotExist:
                path_obj = None

            if path_obj is None:
                if not request.is_ajax():
                    # Explicit selection via the UI: redirect either to
                    # ``/language_code/`` or ``/projects/project_code/``
                    user_choice = request.COOKIES.get('user-choice', None)
                    if user_choice and user_choice in (
                            'language',
                            'project',
                    ):
                        url = {
                            'language':
                            reverse('xtle-language-browse',
                                    args=[language_code]),
                            'project':
                            reverse('xtle-project-browse',
                                    args=[project_code, '', '']),
                        }
                        response = redirect(url[user_choice])
                        response.delete_cookie('user-choice')

                        return response

                raise Http404
        elif language_code:
            user_projects = Project.accessible_by_user(request.user)
            language = get_object_or_404(Language, code=language_code)
            children = language.children \
                               .filter(project__code__in=user_projects)
            language.set_children(children)
            path_obj = language
        elif project_code:
            try:
                path_obj = Project.objects.get_for_user(
                    project_code, request.user)
            except Project.DoesNotExist:
                raise Http404
        else:  # No arguments: all user-accessible projects
            user_projects = Project.objects.for_user(request.user)
            path_obj = ProjectSet(user_projects)

        request.ctx_obj = path_obj
        request.ctx_path = path_obj.xtle_path
        request.resource_obj = path_obj
        request.xtle_path = path_obj.xtle_path

        return func(request, path_obj, *args, **kwargs)
Example #29
0
 def wrapped(request, *args, **kwargs):
     if not request.user.is_superuser:
         raise PermissionDenied(
             _("You do not have rights to administer Xtle."))
     return func(request, *args, **kwargs)
Example #30
0
class SuggestionsReview(object):
    accept_email_template = (
        'editor/email/suggestions_accepted_with_comment.txt')
    accept_email_subject = _(u"Suggestion accepted with comment")
    reject_email_template = (
        'editor/email/suggestions_rejected_with_comment.txt')
    reject_email_subject = _(u"Suggestion rejected with comment")

    def __init__(self, suggestions=None, reviewer=None, review_type=None):
        self.suggestions = suggestions
        self.reviewer = reviewer or User.objects.get_system_user()
        self._review_type = review_type

    @property
    def review_type(self):
        return (
            SubmissionTypes.SYSTEM
            if self._review_type is None
            else self._review_type)

    @property
    def users_and_suggestions(self):
        users = {}
        for suggestion in self.suggestions:
            users[suggestion.user] = users.get(suggestion.user, [])
            users[suggestion.user].append(suggestion)
        return users

    @cached_property
    def states(self):
        return states.get(Suggestion)

    def add(self, unit, translation, user=None):
        """Adds a new suggestion to the unit.

        :param translation: suggested translation text
        :param user: user who is making the suggestion. If it's ``None``,
            the ``system`` user will be used.

        :return: a tuple ``(suggestion, created)`` where ``created`` is a
            boolean indicating if the suggestion was successfully added.
            If the suggestion already exists it's returned as well.
        """
        dont_add = (
            not filter(None, translation)
            or translation == unit.target
            or unit.get_suggestions().filter(target_f=translation).exists())
        if dont_add:
            return (None, False)
        if isinstance(user, User):
            user = user.id
        user = user or User.objects.get_system_user().id
        try:
            suggestion = Suggestion.objects.pending().get(
                unit=unit,
                user_id=user,
                target_f=translation)
            return (suggestion, False)
        except Suggestion.DoesNotExist:
            suggestion = Suggestion.objects.create(
                unit=unit,
                user_id=user,
                state_id=self.states["pending"],
                target=translation,
                creation_time=make_aware(timezone.now()))
        return (suggestion, True)

    def update_unit_on_accept(self, suggestion, target=None):
        unit = suggestion.unit
        unit.target = target or suggestion.target
        if unit.isfuzzy():
            unit.state = TRANSLATED
        unit.save(
            user=suggestion.user,
            changed_with=self.review_type,
            reviewed_by=self.reviewer)

    def accept_suggestion(self, suggestion, target=None):
        suggestion.state_id = self.states["accepted"]
        suggestion.reviewer = self.reviewer
        old_revision = suggestion.unit.revision
        self.update_unit_on_accept(suggestion, target=target)
        if suggestion.unit.revision > old_revision:
            suggestion.submission_set.add(
                *suggestion.unit.submission_set.filter(
                    revision=suggestion.unit.revision))
            suggestion.review_time = suggestion.unit.mtime
        else:
            suggestion.review_time = timezone.now()
        suggestion.save()

    def reject_suggestion(self, suggestion):
        store = suggestion.unit.store
        suggestion.state_id = self.states["rejected"]
        suggestion.review_time = make_aware(timezone.now())
        suggestion.reviewer = self.reviewer
        suggestion.save()
        unit = suggestion.unit
        if unit.changed:
            # if the unit is translated and suggestion was rejected
            # set the reviewer info
            unit.change.reviewed_by = self.reviewer
            unit.change.reviewed_on = suggestion.review_time
            unit.change.save()
        update_data.send(store.__class__, instance=store)

    def accept_suggestions(self, target=None):
        for suggestion in self.suggestions:
            self.accept_suggestion(suggestion, target=target)

    def accept(self, comment="", target=None):
        self.accept_suggestions(target=target)
        if self.should_notify(comment):
            self.notify_suggesters(rejected=False, comment=comment)

    def build_absolute_uri(self, url):
        return site.get().build_absolute_uri(url)

    def get_email_message(self, suggestions, comment, template):
        for suggestion in suggestions:
            suggestion.unit_url = (
                self.build_absolute_uri(
                    suggestion.unit.get_translate_url()))
        return loader.render_to_string(
            template,
            context=dict(suggestions=suggestions,
                         comment=comment))

    def notify_suggesters(self, rejected=True, comment=""):
        for suggester, suggestions in self.users_and_suggestions.items():
            if rejected:
                template = self.reject_email_template
                subject = self.reject_email_subject
            else:
                template = self.accept_email_template
                subject = self.accept_email_subject
            self.send_mail(template, subject, suggester, suggestions, comment)

    def reject_suggestions(self):
        for suggestion in self.suggestions:
            self.reject_suggestion(suggestion)

    def reject(self, comment=""):
        self.reject_suggestions()
        if self.should_notify(comment):
            self.notify_suggesters(rejected=True, comment=comment)

    def send_mail(self, template, subject, suggester, suggestions, comment):
        send_mail(
            subject,
            self.get_email_message(
                suggestions,
                comment,
                template),
            from_email=None,
            recipient_list=[suggester.email],
            fail_silently=True)

    def should_notify(self, comment):
        return (
            comment
            and settings.XTLE_EMAIL_FEEDBACK_ENABLED)