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"))
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)
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.'))
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))
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"]
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)
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}, )
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
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"]
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
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")), }
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])
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']), })
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))
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
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()
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
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
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)
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
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'
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])
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 _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)
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()
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()
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)
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)
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)
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)