<commit_before>"""EntryAdmin for Zinnia"""
from django.forms import Media
from django.contrib import admin
from django.conf.urls import url
from django.conf.urls import patterns
from django.utils import timezone
from django.utils.text import Truncator
from django.utils.html import strip_tags
from django.core.urlresolvers import reverse
from django.core.urlresolvers import NoReverseMatch
from django.conf import settings as project_settings
from django.views.decorators.csrf import csrf_exempt
from django.utils.translation import get_language
from django.template.response import TemplateResponse
from django.utils.translation import ungettext_lazy
from django.utils.translation import ugettext_lazy as _

from zinnia import settings
from zinnia.managers import HIDDEN
from zinnia.managers import PUBLISHED
from zinnia.models.author import Author
from zinnia.ping import DirectoryPinger
from zinnia.admin.forms import EntryAdminForm
from zinnia.admin.filters import AuthorListFilter

class EntryAdmin(admin.ModelAdmin):
    """Admin for Entry model"""
    form = EntryAdminForm
    date_hierarchy = 'creation_date'
    fieldsets = ((_('Content'), {'fields': ('title', 'content',
                                            'image', 'status')}),
                 (_('Options'), {'fields': ('featured', 'excerpt', 'template',
                                            'related', 'authors',
                                            'creation_date',
                                            'start_publication',
                                            'end_publication'),
                                 'classes': ('collapse', 'collapse-closed')}),
                 (_('Privacy'), {'fields': ('password', 'login_required',),
                                 'classes': ('collapse', 'collapse-closed')}),
                 (_('Discussion'), {'fields': ('comment_enabled',
                                               'pingback_enabled')}),
                 (_('Publication'), {'fields': ('categories', 'tags',
                                                'sites', 'slug')}))
    list_filter = ('categories', AuthorListFilter, 'status', 'featured',
                   'login_required', 'comment_enabled', 'pingback_enabled',
                   'creation_date', 'start_publication',
                   'end_publication', 'sites')
    list_display = ('get_title', 'get_authors', 'get_categories',
                    'get_tags', 'get_sites',
                    'get_comments_are_open', 'pingback_enabled',
                    'get_is_actual', 'get_is_visible', 'get_link',
                    'get_short_url', 'creation_date')
    radio_fields = {'template': admin.VERTICAL}
    filter_horizontal = ('categories', 'authors', 'related')
    prepopulated_fields = {'slug': ('title', )}
    search_fields = ('title', 'excerpt', 'content', 'tags')
    actions = ['make_mine', 'make_published', 'make_hidden',
               'close_comments', 'close_pingbacks',
               'ping_directories', 'make_tweet', 'put_on_top']
    actions_on_top = True
    actions_on_bottom = True

    def __init__(self, model, admin_site):
        self.form.admin_site = admin_site
        super(EntryAdmin, self).__init__(model, admin_site)

    # Custom Display
    def get_title(self, entry):
        """Return the title with word count and number of comments"""
        title = _('%(title)s (%(word_count)i words)') % \
                {'title': entry.title, 'word_count': entry.word_count}
        comments = entry.comments.count()
        if comments:
            return ungettext_lazy('%(title)s (%(comments)i comment)',
                                  '%(title)s (%(comments)i comments)',
                                  comments) % \
                                  {'title': title, 'comments': comments}
        return title
    get_title.short_description = _('title')

    def get_authors(self, entry):
        """Return the authors in HTML"""
        try:
            authors = ['%s' %
                       (reverse('zinnia_author_detail',
                                args=[author.username]),
                        author.username) for author in entry.authors.all()]
        except NoReverseMatch:
            authors = [author.username for author in entry.authors.all()]
        return ', '.join(authors)
    get_authors.allow_tags = True
    get_authors.short_description = _('author(s)')

    def get_categories(self, entry):
        """Return the categories linked in HTML"""
        try:
            categories = ['%s' %
                          (category.get_absolute_url(), category.title)
                          for category in entry.categories.all()]
        except NoReverseMatch:
            categories = [category.title for category in
                          entry.categories.all()]
        return ', '.join(categories)
    get_categories.allow_tags = True
    get_categories.short_description = _('category(s)')

    def get_tags(self, entry):
        """Return the tags linked in HTML"""
        try:
            return ', '.join(['%s' %
                              (reverse('zinnia_tag_detail', args=[tag]), tag)
                              for tag in entry.tag_list])
        except NoReverseMatch:
            return entry.tags
    get_tags.allow_tags = True
    get_tags.short_description = _('tag(s)')

    def get_sites(self, entry):
        """Return the sites linked in HTML"""
        return ', '.join(
            ['%(name)s' %
             site.__dict__ for site in entry.sites.all()])
    get_sites.allow_tags = True
    get_sites.short_description = _('site(s)')

    def get_comments_are_open(self, entry):
        """Admin wrapper for entry.comments_are_open"""
        return entry.comments_are_open
    get_comments_are_open.boolean = True
    get_comments_are_open.short_description = _('comment enabled')

    def get_is_actual(self, entry):
        """Admin wrapper for entry.is_actual"""
        return entry.is_actual
    get_is_actual.boolean = True
    get_is_actual.short_description = _('is actual')

    def get_is_visible(self, entry):
        """Admin wrapper for entry.is_visible"""
        return entry.is_visible
    get_is_visible.boolean = True
    get_is_visible.short_description = _('is visible')

    def get_link(self, entry):
        """Return a formated link to the entry"""
        return u'%s' % (
            entry.get_absolute_url(), _('View'))
    get_link.allow_tags = True
    get_link.short_description = _('View on site')

    def get_short_url(self, entry):
        """Return the short url in HTML"""
        short_url = entry.short_url
        if not short_url:
            return _('Unavailable')
        return '%(url)s' % \
               {'url': short_url}
    get_short_url.allow_tags = True
    get_short_url.short_description = _('short url')

    # Custom Methods
    def save_model(self, request, entry, form, change):
        """Save the authors, update time, make an excerpt"""
        if not form.cleaned_data.get('excerpt') and entry.status == PUBLISHED:
            entry.excerpt = Truncator(strip_tags(entry.content)).words(50)

        if entry.pk and not request.user.has_perm('zinnia.can_change_author'):
            form.cleaned_data['authors'] = entry.authors.all()

        if not form.cleaned_data.get('authors'):
            form.cleaned_data['authors'].append(
                Author.objects.get(pk=request.user.pk))

        entry.last_update = timezone.now()
        entry.save()

    def queryset(self, request):
        """Make special filtering by user permissions"""
        queryset = super(EntryAdmin, self).queryset(request)
        if request.user.has_perm('zinnia.can_view_all'):
            return queryset
        return request.user.entries.all()

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        """Filters the disposable authors"""
        if db_field.name == 'authors':
            if request.user.has_perm('zinnia.can_change_author'):
                kwargs['queryset'] = Author.objects.filter(is_staff=True)
            else:
                kwargs['queryset'] = Author.objects.filter(pk=request.user.pk)

        return super(EntryAdmin, self).formfield_for_manytomany(
            db_field, request, **kwargs)

    def get_readonly_fields(self, request, obj=None):
        readonly_fields = super(EntryAdmin, self).get_readonly_fields(
            request, obj)
        if not request.user.has_perm('zinnia.can_change_status'):
            readonly_fields = list(readonly_fields)
            readonly_fields.append('status')
        return readonly_fields

    def get_actions(self, request):
        """Define user actions by permissions"""
        actions = super(EntryAdmin, self).get_actions(request)
        if not request.user.has_perm('zinnia.can_change_author') \
           or not request.user.has_perm('zinnia.can_view_all'):
            del actions['make_mine']
        if not request.user.has_perm('zinnia.can_change_status'):
            del actions['make_hidden']
            del actions['make_published']
        if not settings.PING_DIRECTORIES:
            del actions['ping_directories']
        if not settings.USE_TWITTER:
            del actions['make_tweet']

        return actions

    # Custom Actions
    def make_mine(self, request, queryset):
        """Set the entries to the user"""
        for entry in queryset:
            if request.user not in entry.authors.all():
                entry.authors.add(request.user)
        self.message_user(
            request, _('The selected entries now belong to you.'))
    make_mine.short_description = _('Set the entries to the user')

    def make_published(self, request, queryset):
        """Set entries selected as published"""
        queryset.update(status=PUBLISHED)
        self.ping_directories(request, queryset, messages=False)
        self.message_user(
            request, _('The selected entries are now marked as published.'))
    make_published.short_description = _('Set entries selected as published')

    def make_hidden(self, request, queryset):
        """Set entries selected as hidden"""
        queryset.update(status=HIDDEN)
        self.message_user(
            request, _('The selected entries are now marked as hidden.'))
    make_hidden.short_description = _('Set entries selected as hidden')

    def make_tweet(self, request, queryset):
        """Post an update on Twitter"""
        import tweepy
        auth = tweepy.OAuthHandler(settings.TWITTER_CONSUMER_KEY,
                                   settings.TWITTER_CONSUMER_SECRET)
        auth.set_access_token(settings.TWITTER_ACCESS_KEY,
                              settings.TWITTER_ACCESS_SECRET)
        api = tweepy.API(auth)
        for entry in queryset:
            short_url = entry.short_url
            message = '%s %s' % (entry.title[:139 - len(short_url)], short_url)
            api.update_status(message)
        self.message_user(
            request, _('The selected entries have been tweeted.'))
    make_tweet.short_description = _('Tweet entries selected')

    def close_comments(self, request, queryset):
        """Close the comments for selected entries"""
        queryset.update(comment_enabled=False)
        self.message_user(
            request, _('Comments are now closed for selected entries.'))
    close_comments.short_description = _('Close the comments for '\
                                         'selected entries')

    def close_pingbacks(self, request, queryset):
        """Close the pingbacks for selected entries"""
        queryset.update(pingback_enabled=False)
        self.message_user(
            request, _('Linkbacks are now closed for selected entries.'))
    close_pingbacks.short_description = _(
        'Close the linkbacks for selected entries')

    def put_on_top(self, request, queryset):
        """Put the selected entries on top at the current date"""
        queryset.update(creation_date=timezone.now())
        self.ping_directories(request, queryset, messages=False)
        self.message_user(request, _(
            'The selected entries are now set at the current date.'))
    put_on_top.short_description = _(
        'Put the selected entries on top at the current date')

    def ping_directories(self, request, queryset, messages=True):
        """Ping Directories for selected entries"""
        for directory in settings.PING_DIRECTORIES:
            pinger = DirectoryPinger(directory, queryset)
            pinger.join()
            if messages:
                success = 0
                for result in pinger.results:
                    if not result.get('flerror', True):
                        success += 1
                    else:
                        self.message_user(request,
                                          '%s : %s' % (directory,
                                                       result['message']))
                if success:
                    self.message_user(
                        request,
                        _('%(directory)s directory succesfully ' \
                          'pinged %(success)d entries.') %
                        {'directory': directory, 'success': success})
    ping_directories.short_description = _(
        'Ping Directories for selected entries')

    def get_urls(self):
        entry_admin_urls = super(EntryAdmin, self).get_urls()
        urls = patterns(
            '',
            url(r'^autocomplete_tags/$',
                self.admin_site.admin_view(self.autocomplete_tags),
                name='zinnia_entry_autocomplete_tags'),
            url(r'^wymeditor/$',
                self.admin_site.admin_view(self.wymeditor),
                name='zinnia_entry_wymeditor'),
            url(r'^markitup/$',
                self.admin_site.admin_view(self.markitup),
                name='zinnia_entry_markitup'),
            url(r'^markitup/preview/$',
                self.admin_site.admin_view(self.content_preview),
                name='zinnia_entry_markitup_preview'),)
        return urls + entry_admin_urls

    def autocomplete_tags(self, request):
        """View for tag autocompletion"""
        return TemplateResponse(
            request, 'admin/zinnia/entry/autocomplete_tags.js',
            mimetype='application/javascript')

    def wymeditor(self, request):
        """View for serving the config of WYMEditor"""
        return TemplateResponse(
            request, 'admin/zinnia/entry/wymeditor.js',
            {'lang': get_language().split('-')[0]},
            'application/javascript')

    def markitup(self, request):
        """View for serving the config of MarkItUp"""
        return TemplateResponse(
            request, 'admin/zinnia/entry/markitup.js',
            mimetype='application/javascript')

    @csrf_exempt
    def content_preview(self, request):
        """Admin view to preview Entry.content in HTML,
        useful when using markups to write entries"""
        data = request.POST.get('data', '')
        entry = self.model(content=data)
        return TemplateResponse(
            request, 'admin/zinnia/entry/preview.html',
            {'preview': entry.html_content})

    def _media(self):
        STATIC_URL = '%szinnia/' % project_settings.STATIC_URL
        media = super(EntryAdmin, self).media + Media(
            css={'all': ('%scss/jquery.autocomplete.css' % STATIC_URL,)},
            js=('%sjs/jquery.js' % STATIC_URL,
                '%sjs/jquery.bgiframe.js' % STATIC_URL,
                '%sjs/jquery.autocomplete.js' % STATIC_URL,
                reverse('admin:zinnia_entry_autocomplete_tags'),))

        if settings.WYSIWYG == 'wymeditor':
            media += Media(
                js=('%sjs/wymeditor/jquery.wymeditor.pack.js' % STATIC_URL,
                    '%sjs/wymeditor/plugins/hovertools/'
                    'jquery.wymeditor.hovertools.js' % STATIC_URL,
                    reverse('admin:zinnia_entry_wymeditor')))
        elif settings.WYSIWYG == 'tinymce':
            from tinymce.widgets import TinyMCE
            media += TinyMCE().media + Media(
                js=(reverse('tinymce-js', args=('admin/zinnia/entry',)),))
        elif settings.WYSIWYG == 'markitup':
            media += Media(
                js=('%sjs/markitup/jquery.markitup.js' % STATIC_URL,
                    '%sjs/markitup/sets/%s/set.js' % (
                        STATIC_URL, settings.MARKUP_LANGUAGE),
                    reverse('admin:zinnia_entry_markitup')),
                css={'all': (
                    '%sjs/markitup/skins/django/style.css' % STATIC_URL,
                    '%sjs/markitup/sets/%s/style.css' % (
                        STATIC_URL, settings.MARKUP_LANGUAGE))})
        return media
    media = property(_media)
<commit_msg>speed up the admin for entries<commit_after>"""EntryAdmin for Zinnia"""
from django.forms import Media
from django.contrib import admin
from django.conf.urls import url
from django.conf.urls import patterns
from django.utils import timezone
from django.utils.text import Truncator
from django.utils.html import strip_tags
from django.core.urlresolvers import reverse
from django.core.urlresolvers import NoReverseMatch
from django.conf import settings as project_settings
from django.views.decorators.csrf import csrf_exempt
from django.utils.translation import get_language
from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _

from zinnia import settings
from zinnia.managers import HIDDEN
from zinnia.managers import PUBLISHED
from zinnia.models.author import Author
from zinnia.ping import DirectoryPinger
from zinnia.admin.forms import EntryAdminForm
from zinnia.admin.filters import AuthorListFilter

class EntryAdmin(admin.ModelAdmin):
    """Admin for Entry model"""
    form = EntryAdminForm
    date_hierarchy = 'creation_date'
    fieldsets = ((_('Content'), {'fields': ('title', 'content',
                                            'image', 'status')}),
                 (_('Options'), {'fields': ('featured', 'excerpt', 'template',
                                            'related', 'authors',
                                            'creation_date',
                                            'start_publication',
                                            'end_publication'),
                                 'classes': ('collapse', 'collapse-closed')}),
                 (_('Privacy'), {'fields': ('password', 'login_required',),
                                 'classes': ('collapse', 'collapse-closed')}),
                 (_('Discussion'), {'fields': ('comment_enabled',
                                               'pingback_enabled')}),
                 (_('Publication'), {'fields': ('categories', 'tags',
                                                'sites', 'slug')}))
    list_filter = ('categories', AuthorListFilter, 'status', 'featured',
                   'login_required', 'comment_enabled', 'pingback_enabled',
                   'creation_date', 'start_publication',
                   'end_publication', 'sites')
    list_display = ('get_title', 'get_authors', 'get_categories',
                    'get_tags', 'get_sites',
                    'get_comments_are_open', 'pingback_enabled',
                    'get_is_actual', 'get_is_visible', 'get_link',
                    'get_short_url', 'creation_date')
    radio_fields = {'template': admin.VERTICAL}
    filter_horizontal = ('categories', 'authors', 'related')
    prepopulated_fields = {'slug': ('title', )}
    search_fields = ('title', 'excerpt', 'content', 'tags')
    actions = ['make_mine', 'make_published', 'make_hidden',
               'close_comments', 'close_pingbacks',
               'ping_directories', 'make_tweet', 'put_on_top']
    actions_on_top = True
    actions_on_bottom = True

    def __init__(self, model, admin_site):
        self.form.admin_site = admin_site
        super(EntryAdmin, self).__init__(model, admin_site)

    # Custom Display
    def get_title(self, entry):
        """Return the title with word count and number of comments"""
        title = _('%(title)s (%(word_count)i words)') % \
                {'title': entry.title, 'word_count': entry.word_count}
        return title
    get_title.short_description = _('title')

    def get_authors(self, entry):
        """Return the authors in HTML"""
        try:
            authors = ['%s' %
                       (reverse('zinnia_author_detail',
                                args=[author.username]),
                        author.username) for author in entry.authors.all()]
        except NoReverseMatch:
            authors = [author.username for author in entry.authors.all()]
        return ', '.join(authors)
    get_authors.allow_tags = True
    get_authors.short_description = _('author(s)')

    def get_categories(self, entry):
        """Return the categories linked in HTML"""
        try:
            categories = ['%s' %
                          (category.get_absolute_url(), category.title)
                          for category in entry.categories.all()]
        except NoReverseMatch:
            categories = [category.title for category in
                          entry.categories.all()]
        return ', '.join(categories)
    get_categories.allow_tags = True
    get_categories.short_description = _('category(s)')

    def get_tags(self, entry):
        """Return the tags linked in HTML"""
        try:
            return ', '.join(['%s' %
                              (reverse('zinnia_tag_detail', args=[tag]), tag)
                              for tag in entry.tag_list])
        except NoReverseMatch:
            return entry.tags
    get_tags.allow_tags = True
    get_tags.short_description = _('tag(s)')

    def get_sites(self, entry):
        """Return the sites linked in HTML"""
        return ', '.join(
            ['%(name)s' %
             site.__dict__ for site in entry.sites.all()])
    get_sites.allow_tags = True
    get_sites.short_description = _('site(s)')

    def get_comments_are_open(self, entry):
        """Admin wrapper for entry.comments_are_open"""
        return entry.comments_are_open
    get_comments_are_open.boolean = True
    get_comments_are_open.short_description = _('comment enabled')

    def get_is_actual(self, entry):
        """Admin wrapper for entry.is_actual"""
        return entry.is_actual
    get_is_actual.boolean = True
    get_is_actual.short_description = _('is actual')

    def get_is_visible(self, entry):
        """Admin wrapper for entry.is_visible"""
        return entry.is_visible
    get_is_visible.boolean = True
    get_is_visible.short_description = _('is visible')

    def get_link(self, entry):
        """Return a formated link to the entry"""
        return u'%s' % (
            entry.get_absolute_url(), _('View'))
    get_link.allow_tags = True
    get_link.short_description = _('View on site')

    def get_short_url(self, entry):
        """Return the short url in HTML"""
        short_url = entry.short_url
        if not short_url:
            return _('Unavailable')
        return '%(url)s' % \
               {'url': short_url}
    get_short_url.allow_tags = True
    get_short_url.short_description = _('short url')

    # Custom Methods
    def save_model(self, request, entry, form, change):
        """Save the authors, update time, make an excerpt"""
        if not form.cleaned_data.get('excerpt') and entry.status == PUBLISHED:
            entry.excerpt = Truncator(strip_tags(entry.content)).words(50)

        if entry.pk and not request.user.has_perm('zinnia.can_change_author'):
            form.cleaned_data['authors'] = entry.authors.all()

        if not form.cleaned_data.get('authors'):
            form.cleaned_data['authors'].append(
                Author.objects.get(pk=request.user.pk))

        entry.last_update = timezone.now()
        entry.save()

    def queryset(self, request):
        """Make special filtering by user permissions"""
        queryset = super(EntryAdmin, self).queryset(request)
        if request.user.has_perm('zinnia.can_view_all'):
            return queryset.prefetch_related('categories', 'authors', 'sites')
        return request.user.entries.all().prefetch_related('categories', 'authors', 'sites')

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        """Filters the disposable authors"""
        if db_field.name == 'authors':
            if request.user.has_perm('zinnia.can_change_author'):
                kwargs['queryset'] = Author.objects.filter(is_staff=True)
            else:
                kwargs['queryset'] = Author.objects.filter(pk=request.user.pk)

        return super(EntryAdmin, self).formfield_for_manytomany(
            db_field, request, **kwargs)

    def get_readonly_fields(self, request, obj=None):
        readonly_fields = super(EntryAdmin, self).get_readonly_fields(
            request, obj)
        if not request.user.has_perm('zinnia.can_change_status'):
            readonly_fields = list(readonly_fields)
            readonly_fields.append('status')
        return readonly_fields

    def get_actions(self, request):
        """Define user actions by permissions"""
        actions = super(EntryAdmin, self).get_actions(request)
        if not request.user.has_perm('zinnia.can_change_author') \
           or not request.user.has_perm('zinnia.can_view_all'):
            del actions['make_mine']
        if not request.user.has_perm('zinnia.can_change_status'):
            del actions['make_hidden']
            del actions['make_published']
        if not settings.PING_DIRECTORIES:
            del actions['ping_directories']
        if not settings.USE_TWITTER:
            del actions['make_tweet']

        return actions

    # Custom Actions
    def make_mine(self, request, queryset):
        """Set the entries to the user"""
        for entry in queryset:
            if request.user not in entry.authors.all():
                entry.authors.add(request.user)
        self.message_user(
            request, _('The selected entries now belong to you.'))
    make_mine.short_description = _('Set the entries to the user')

    def make_published(self, request, queryset):
        """Set entries selected as published"""
        queryset.update(status=PUBLISHED)
        self.ping_directories(request, queryset, messages=False)
        self.message_user(
            request, _('The selected entries are now marked as published.'))
    make_published.short_description = _('Set entries selected as published')

    def make_hidden(self, request, queryset):
        """Set entries selected as hidden"""
        queryset.update(status=HIDDEN)
        self.message_user(
            request, _('The selected entries are now marked as hidden.'))
    make_hidden.short_description = _('Set entries selected as hidden')

    def make_tweet(self, request, queryset):
        """Post an update on Twitter"""
        import tweepy
        auth = tweepy.OAuthHandler(settings.TWITTER_CONSUMER_KEY,
                                   settings.TWITTER_CONSUMER_SECRET)
        auth.set_access_token(settings.TWITTER_ACCESS_KEY,
                              settings.TWITTER_ACCESS_SECRET)
        api = tweepy.API(auth)
        for entry in queryset:
            short_url = entry.short_url
            message = '%s %s' % (entry.title[:139 - len(short_url)], short_url)
            api.update_status(message)
        self.message_user(
            request, _('The selected entries have been tweeted.'))
    make_tweet.short_description = _('Tweet entries selected')

    def close_comments(self, request, queryset):
        """Close the comments for selected entries"""
        queryset.update(comment_enabled=False)
        self.message_user(
            request, _('Comments are now closed for selected entries.'))
    close_comments.short_description = _('Close the comments for '\
                                         'selected entries')

    def close_pingbacks(self, request, queryset):
        """Close the pingbacks for selected entries"""
        queryset.update(pingback_enabled=False)
        self.message_user(
            request, _('Linkbacks are now closed for selected entries.'))
    close_pingbacks.short_description = _(
        'Close the linkbacks for selected entries')

    def put_on_top(self, request, queryset):
        """Put the selected entries on top at the current date"""
        queryset.update(creation_date=timezone.now())
        self.ping_directories(request, queryset, messages=False)
        self.message_user(request, _(
            'The selected entries are now set at the current date.'))
    put_on_top.short_description = _(
        'Put the selected entries on top at the current date')

    def ping_directories(self, request, queryset, messages=True):
        """Ping Directories for selected entries"""
        for directory in settings.PING_DIRECTORIES:
            pinger = DirectoryPinger(directory, queryset)
            pinger.join()
            if messages:
                success = 0
                for result in pinger.results:
                    if not result.get('flerror', True):
                        success += 1
                    else:
                        self.message_user(request,
                                          '%s : %s' % (directory,
                                                       result['message']))
                if success:
                    self.message_user(
                        request,
                        _('%(directory)s directory succesfully ' \
                          'pinged %(success)d entries.') %
                        {'directory': directory, 'success': success})
    ping_directories.short_description = _(
        'Ping Directories for selected entries')

    def get_urls(self):
        entry_admin_urls = super(EntryAdmin, self).get_urls()
        urls = patterns(
            '',
            url(r'^autocomplete_tags/$',
                self.admin_site.admin_view(self.autocomplete_tags),
                name='zinnia_entry_autocomplete_tags'),
            url(r'^wymeditor/$',
                self.admin_site.admin_view(self.wymeditor),
                name='zinnia_entry_wymeditor'),
            url(r'^markitup/$',
                self.admin_site.admin_view(self.markitup),
                name='zinnia_entry_markitup'),
            url(r'^markitup/preview/$',
                self.admin_site.admin_view(self.content_preview),
                name='zinnia_entry_markitup_preview'),)
        return urls + entry_admin_urls

    def autocomplete_tags(self, request):
        """View for tag autocompletion"""
        return TemplateResponse(
            request, 'admin/zinnia/entry/autocomplete_tags.js',
            mimetype='application/javascript')

    def wymeditor(self, request):
        """View for serving the config of WYMEditor"""
        return TemplateResponse(
            request, 'admin/zinnia/entry/wymeditor.js',
            {'lang': get_language().split('-')[0]},
            'application/javascript')

    def markitup(self, request):
        """View for serving the config of MarkItUp"""
        return TemplateResponse(
            request, 'admin/zinnia/entry/markitup.js',
            mimetype='application/javascript')

    @csrf_exempt
    def content_preview(self, request):
        """Admin view to preview Entry.content in HTML,
        useful when using markups to write entries"""
        data = request.POST.get('data', '')
        entry = self.model(content=data)
        return TemplateResponse(
            request, 'admin/zinnia/entry/preview.html',
            {'preview': entry.html_content})

    def _media(self):
        STATIC_URL = '%szinnia/' % project_settings.STATIC_URL
        media = super(EntryAdmin, self).media + Media(
            css={'all': ('%scss/jquery.autocomplete.css' % STATIC_URL,)},
            js=('%sjs/jquery.js' % STATIC_URL,
                '%sjs/jquery.bgiframe.js' % STATIC_URL,
                '%sjs/jquery.autocomplete.js' % STATIC_URL,
                reverse('admin:zinnia_entry_autocomplete_tags'),))

        if settings.WYSIWYG == 'wymeditor':
            media += Media(
                js=('%sjs/wymeditor/jquery.wymeditor.pack.js' % STATIC_URL,
                    '%sjs/wymeditor/plugins/hovertools/'
                    'jquery.wymeditor.hovertools.js' % STATIC_URL,
                    reverse('admin:zinnia_entry_wymeditor')))
        elif settings.WYSIWYG == 'tinymce':
            from tinymce.widgets import TinyMCE
            media += TinyMCE().media + Media(
                js=(reverse('tinymce-js', args=('admin/zinnia/entry',)),))
        elif settings.WYSIWYG == 'markitup':
            media += Media(
                js=('%sjs/markitup/jquery.markitup.js' % STATIC_URL,
                    '%sjs/markitup/sets/%s/set.js' % (
                        STATIC_URL, settings.MARKUP_LANGUAGE),
                    reverse('admin:zinnia_entry_markitup')),
                css={'all': (
                    '%sjs/markitup/skins/django/style.css' % STATIC_URL,
                    '%sjs/markitup/sets/%s/style.css' % (
                        STATIC_URL, settings.MARKUP_LANGUAGE))})
        return media
    media = property(_media)
<|endoftext|>


<commit_before># Copyright (c) 2017, Daniele Venzano
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Main points of entry for the Zoe web interface."""

from zoe_api.web.request_handler import ZoeWebRequestHandler
from zoe_api.exceptions import ZoeException
from zoe_lib.config import get_conf

class StatusEndpointWeb(ZoeWebRequestHandler):
    """Handler class"""

    def _calculate_load(self, sched):
        core_total = sched['platform_stats']['cores_total']
        memory_total = sched['platform_stats']['memory_total']
        core_reserved = 0
        memory_reserved = 0
        for node in sched['platform_stats']['nodes']:
            core_reserved += node['cores_reserved']
            memory_reserved += node['memory_reserved']
        core_usage = core_reserved / core_total
        memory_usage = memory_reserved / memory_total
        return core_usage, memory_usage

    def get(self):
        """Status and statistics page."""
        if self.current_user is None or not self.current_user.role.can_see_status:
            return

        stats = self.api_endpoint.statistics_scheduler()
        if stats is None:
            raise ZoeException('Cannot retrieve statistics from the Zoe master')

        executions_in_queue = {}
        for exec_id in stats['queue']:
            executions_in_queue[exec_id] = self.api_endpoint.execution_by_id(None, exec_id)
        for exec_id in stats['running_queue']:
            executions_in_queue[exec_id] = self.api_endpoint.execution_by_id(None, exec_id)
        for exec_id in stats['termination_queue']:
            executions_in_queue[exec_id] = self.api_endpoint.execution_by_id(None, exec_id)

        services_per_node = {}
        for node in stats['platform_stats']['nodes']:
            services_per_node[node['name']] = self.api_endpoint.sql.services.select(backend_host=node['name'], backend_status='started')
            for service in services_per_node[node['name']]:
                if service.id not in node['service_stats']:
                    node['service_stats'][service.id] = {
                        'mem_limit': 0,
                        'core_limit': 0
                    }

        max_service_count = max([len(services_per_node[name]) for name in services_per_node])

        template_vars = {
            "stats": stats,
            "executions_in_queue": executions_in_queue,
            "services_per_node": services_per_node,
            "max_service_count": max_service_count,
            'eurecom': get_conf().eurecom,
            'platform_load': self._calculate_load()
        }

        self.render('status.jinja2', **template_vars)
<commit_msg>Fix missing parameter<commit_after># Copyright (c) 2017, Daniele Venzano
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Main points of entry for the Zoe web interface."""

from zoe_api.web.request_handler import ZoeWebRequestHandler
from zoe_api.exceptions import ZoeException
from zoe_lib.config import get_conf

class StatusEndpointWeb(ZoeWebRequestHandler):
    """Handler class"""

    def _calculate_load(self, sched):
        core_total = sched['platform_stats']['cores_total']
        memory_total = sched['platform_stats']['memory_total']
        core_reserved = 0
        memory_reserved = 0
        for node in sched['platform_stats']['nodes']:
            core_reserved += node['cores_reserved']
            memory_reserved += node['memory_reserved']
        core_usage = core_reserved / core_total
        memory_usage = memory_reserved / memory_total
        return core_usage, memory_usage

    def get(self):
        """Status and statistics page."""
        if self.current_user is None or not self.current_user.role.can_see_status:
            return

        stats = self.api_endpoint.statistics_scheduler()
        if stats is None:
            raise ZoeException('Cannot retrieve statistics from the Zoe master')

        executions_in_queue = {}
        for exec_id in stats['queue']:
            executions_in_queue[exec_id] = self.api_endpoint.execution_by_id(None, exec_id)
        for exec_id in stats['running_queue']:
            executions_in_queue[exec_id] = self.api_endpoint.execution_by_id(None, exec_id)
        for exec_id in stats['termination_queue']:
            executions_in_queue[exec_id] = self.api_endpoint.execution_by_id(None, exec_id)

        services_per_node = {}
        for node in stats['platform_stats']['nodes']:
            services_per_node[node['name']] = self.api_endpoint.sql.services.select(backend_host=node['name'], backend_status='started')
            for service in services_per_node[node['name']]:
                if service.id not in node['service_stats']:
                    node['service_stats'][service.id] = {
                        'mem_limit': 0,
                        'core_limit': 0
                    }

        max_service_count = max([len(services_per_node[name]) for name in services_per_node])

        template_vars = {
            "stats": stats,
            "executions_in_queue": executions_in_queue,
            "services_per_node": services_per_node,
            "max_service_count": max_service_count,
            'eurecom': get_conf().eurecom,
            'platform_load': self._calculate_load(stats)
        }

        self.render('status.jinja2', **template_vars)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from . import settings
from . import utils

from collections import defaultdict

from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils import six

from . import settings
from .cache import CachedTranslation
from .models import Translation

SUPPORTED_FIELDS = (
    models.fields.CharField,
    models.fields.TextField,
)

def instance_only(instance):
    """
    Ensures instance is not None for ``__get__`` and ``__set__`` methods.
    """
    if instance is None:
        raise AttributeError('Can only be accessed via instance')

class TranslationDescriptor(object):
    """
    Translation Field Descriptor.
    """

    def __init__(self, field, translated_field, language):
        self.field = field
        self.translated_field = translated_field
        self.language = language
        self.attname = utils.build_localized_field_name(self.field.name, language)
        self.name = self.attname
        self.verbose_name = utils.build_localized_verbose_name(field.verbose_name, language)
        self.null = True
        self.blank = True
        self.column = None
        self.editable = True

    def __get__(self, instance, instance_type=None):
        instance_only(instance)

        obj = instance._linguist.get_cache(instance=instance,
                                           language=self.language,
                                           field_name=self.translated_field.name)
        return obj.field_value or None

    def __set__(self, instance, value):
        instance_only(instance)
        if value:
            instance._linguist.set_cache(instance=instance,
                                         language=self.language,
                                         field_name=self.translated_field.name,
                                         field_value=value)

    def db_type(self, connection):
        """
        Returning None will cause Django to exclude this field from the concrete
        field list (``_meta.concrete_fields``) resulting in the fact that syncdb
        will skip this field when creating tables in PostgreSQL.
        """
        return None

class CacheDescriptor(dict):
    """
    Linguist Cache Descriptor.
    """

    def __init__(self, translation_class):
        self._translation_class = translation_class
        self['identifier'] = self._translation_class.identifier
        self['fields'] = self._translation_class.fields
        self['default_language'] = self._translation_class.default_language or settings.DEFAULT_LANGUAGE
        self['language'] = self['default_language']
        self['translations'] = defaultdict(dict)

    @property
    def identifier(self):
        """
        Returns model identifier (from related translation class).
        Read-only.
        """
        return self['identifier']

    @property
    def language(self):
        """
        Returns current language.
        """
        return self['language']

    @language.setter
    def language(self, value):
        """
        Sets current language.
        """
        self['language'] = value

    @property
    def default_language(self):
        """
        Returns default language.
        """
        return self['default_language']

    @default_language.setter
    def default_language(self, value):
        """
        Sets default language.
        """
        self['default_language'] = value

    @property
    def supported_languages(self):
        return [code.replace('-', '_') for code, name in settings.SUPPORTED_LANGUAGES]

    @property
    def fields(self):
        """
        Returns translatable fields (from related translation class).
        Read-only.
        """
        return list(self['fields'])

    @property
    def suffixed_fields(self):
        suffixed = ['%s_%s' % (field, lang) for field in self.fields
                                            for lang in self.supported_languages]
        return self.fields + suffixed

    @property
    def cached_fields(self):
        return [k for k, v in six.iteritems(self.translations) if v]

    @property
    def cached_suffixed_fields(self):
        suffixed = ['%s_%s' % (field, lang) for field in self.cached_fields
                                            for lang in self.supported_languages]
        return self.cached_fields + suffixed

    @property
    def empty_fields(self):
        return list(set(self.fields) - set(self.cached_fields))

    @property
    def empty_suffixed_fields(self):
        return list(set(self.suffixed_fields) - set(self.cached_suffixed_fields))

    @property
    def translation_class(self):
        """
        Returns related translation class.
        Read-only.
        """
        return self._translation_class

    @property
    def translations(self):
        """
        Returns translations dictionary.
        """
        return self['translations']

    @property
    def translation_instances(self):
        """
        Returns translation instances.
        """
        return [instance
                for k, v in self.translations.items()
                for instance in v.values()]

    @property
    def translations_count(self):
        """
        Returns translations count.
        """
        return len(self.translation_instances)

    def get_cached_translation(self, instance, translation=None, language=None,
                               field_name=None, field_value=None):
        """
        Returns CachedTranslation instance.
        """
        if translation is None:
            if not (language and field_name):
                raise Exception("You must set language and field name")

        if translation is not None:
            language = translation.language
            field_name = translation.field_name
            field_value = translation.field_value

        return CachedTranslation(instance=instance,
                                 language=language,
                                 field_name=field_name,
                                 field_value=field_value)

    def get_cache(self, instance, translation=None, language=None, field_name=None,
                  field_value=None):
        """
        Returns translation from cache.
        """
        is_new = bool(instance.pk is None)

        cached_obj = self.get_cached_translation(instance=instance,
                                                 translation=translation,
                                                 language=language,
                                                 field_name=field_name,
                                                 field_value=field_value)

        try:
            cached_obj = self.translations[cached_obj.field_name][cached_obj.language]
        except KeyError:
            if not is_new:
                try:
                    obj = Translation.objects.get(**cached_obj.lookup)
                    cached_obj = cached_obj.from_object(obj)
                except Translation.DoesNotExist:
                    pass

            self.set_cache(cached_obj=cached_obj)

        return cached_obj

    def set_cache(self, instance=None, translation=None, language=None, field_name=None,
                  field_value=None, cached_obj=None):
        """
        Add a new translation into the cache.
        """
        if cached_obj is None:
            cached_obj = self.get_cached_translation(instance=instance,
                                                     translation=translation,
                                                     language=language,
                                                     field_name=field_name,
                                                     field_value=field_value)

        try:
            obj = self.translations[cached_obj.field_name][cached_obj.language]
            if cached_obj.field_value:
                obj.has_changed = (cached_obj.field_value != obj.field_value)
        except KeyError:
            if cached_obj.field_value is not None:
                self.translations[cached_obj.field_name][cached_obj.language] = cached_obj

        return cached_obj

class TranslationField(object):
    """
    Proxy of original field.
    """

    def __init__(self, translated_field, language, *args, **kwargs):
        self.__dict__.update(translated_field.__dict__)
        self.translated_field = translated_field
        self.language = language

        # All translations are optional
        if not isinstance(self, models.fields.BooleanField):
            self.null = True
        self.blank = True

        # Suffix field with '_fr', '_en', etc.
        self.attname = utils.build_localized_field_name(self.translated_field.name, language)
        self.name = self.attname
        self.verbose_name = utils.build_localized_verbose_name(translated_field.verbose_name, language)

        # No concrete field
        self.column = None

    def contribute_to_class(self, cls, name):
        setattr(cls, self.name, TranslationDescriptor(self, self.translated_field, self.language))
        cls._meta.add_field(self)
        cls._meta.virtual_fields.append(self)

def default_value_getter(field):
    """
    When accessing to the name of the field itself, the value
    in the current language will be returned. Unless it's set,
    the value in the default language will be returned.
    """
    def default_value_func_getter(self):
        language = self._linguist.language or self.default_language
        localized_field = utils.build_localized_field_name(field, language)
        return getattr(self, localized_field)

    return default_value_func_getter

def default_value_setter(field):
    """
    When setting to the name of the field itself, the value
    in the current language will be set.
    """
    def default_value_func_setter(self, value):
        language = self._linguist.language or self.default_language
        localized_field = utils.build_localized_field_name(field, language)
        setattr(self, localized_field, value)

    return default_value_func_setter

def field_factory(base_class):
    class TranslationFieldField(TranslationField, base_class):
        pass
    TranslationFieldField.__name__ = b'Translation%s' % base_class.__name__
    return TranslationFieldField

def create_translation_field(translated_field, language):
    """
    Takes the original field, a given language and return a Field class for model.
    """
    cls_name = translated_field.__class__.__name__

    if not isinstance(translated_field, SUPPORTED_FIELDS):
        raise ImproperlyConfigured('%s is not supported by Linguist.' % cls_name)

    translation_class = field_factory(translated_field.__class__)

    return translation_class(translated_field=translated_field, language=language)

def add_cache_property(translation_class):
    """
    Adds the linguist cache property to the translation class model.
    """
    translation_class.model.add_to_class('_linguist', CacheDescriptor(translation_class))

def add_translatable_fields(translation_class):
    """
    Adds translatable fields of translation class model.
    """
    model = translation_class.model
    fields = translation_class.fields
    for field_name in fields:
        setattr(model, field_name, property(default_value_getter(field_name),
                                            default_value_setter(field_name)))

def add_language_fields(translation_class):
    """
    Adds language fields for each translatable field of translation class model.
    """
    model = translation_class.model
    fields = translation_class.fields
    for field_name in fields:
        field = model._meta.get_field(field_name)
        for language_code, language_name in settings.SUPPORTED_LANGUAGES:
            localized_field_name = utils.build_localized_field_name(field.name, language_code)
            translation_field = create_translation_field(field, language_code)
            if hasattr(model, localized_field_name):
                raise ValueError(
                    "Error adding translation field. Model '%s' already contains a field named"
                    "'%s'." % (model._meta.object_name, localized_field_name))
            translation_field.contribute_to_class(model, localized_field_name)

def contribute_to_model(translation_class):
    """
    Add linguist fields and properties to translation class model.
    """
    add_cache_property(translation_class)
    add_language_fields(translation_class)
    add_translatable_fields(translation_class)
<commit_msg>Fix column bug.<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from . import settings
from . import utils

from collections import defaultdict

from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils import six

from . import settings
from .cache import CachedTranslation
from .models import Translation

SUPPORTED_FIELDS = (
    models.fields.CharField,
    models.fields.TextField,
)

def instance_only(instance):
    """
    Ensures instance is not None for ``__get__`` and ``__set__`` methods.
    """
    if instance is None:
        raise AttributeError('Can only be accessed via instance')

class TranslationDescriptor(object):
    """
    Translation Field Descriptor.
    """

    def __init__(self, field, translated_field, language):
        self.field = field
        self.translated_field = translated_field
        self.language = language
        self.attname = utils.build_localized_field_name(self.field.name, language)
        self.name = self.attname
        self.verbose_name = utils.build_localized_verbose_name(field.verbose_name, language)
        self.null = True
        self.blank = True
        self.column = None
        self.editable = True

    def __get__(self, instance, instance_type=None):
        instance_only(instance)

        obj = instance._linguist.get_cache(instance=instance,
                                           language=self.language,
                                           field_name=self.translated_field.name)
        return obj.field_value or None

    def __set__(self, instance, value):
        instance_only(instance)
        if value:
            instance._linguist.set_cache(instance=instance,
                                         language=self.language,
                                         field_name=self.translated_field.name,
                                         field_value=value)

    def db_type(self, connection):
        """
        Returning None will cause Django to exclude this field from the concrete
        field list (``_meta.concrete_fields``) resulting in the fact that syncdb
        will skip this field when creating tables in PostgreSQL.
        """
        return None

class CacheDescriptor(dict):
    """
    Linguist Cache Descriptor.
    """

    def __init__(self, translation_class):
        self._translation_class = translation_class
        self['identifier'] = self._translation_class.identifier
        self['fields'] = self._translation_class.fields
        self['default_language'] = self._translation_class.default_language or settings.DEFAULT_LANGUAGE
        self['language'] = self['default_language']
        self['translations'] = defaultdict(dict)

    @property
    def identifier(self):
        """
        Returns model identifier (from related translation class).
        Read-only.
        """
        return self['identifier']

    @property
    def language(self):
        """
        Returns current language.
        """
        return self['language']

    @language.setter
    def language(self, value):
        """
        Sets current language.
        """
        self['language'] = value

    @property
    def default_language(self):
        """
        Returns default language.
        """
        return self['default_language']

    @default_language.setter
    def default_language(self, value):
        """
        Sets default language.
        """
        self['default_language'] = value

    @property
    def supported_languages(self):
        return [code.replace('-', '_') for code, name in settings.SUPPORTED_LANGUAGES]

    @property
    def fields(self):
        """
        Returns translatable fields (from related translation class).
        Read-only.
        """
        return list(self['fields'])

    @property
    def suffixed_fields(self):
        suffixed = ['%s_%s' % (field, lang) for field in self.fields
                                            for lang in self.supported_languages]
        return self.fields + suffixed

    @property
    def cached_fields(self):
        return [k for k, v in six.iteritems(self.translations) if v]

    @property
    def cached_suffixed_fields(self):
        suffixed = ['%s_%s' % (field, lang) for field in self.cached_fields
                                            for lang in self.supported_languages]
        return self.cached_fields + suffixed

    @property
    def empty_fields(self):
        return list(set(self.fields) - set(self.cached_fields))

    @property
    def empty_suffixed_fields(self):
        return list(set(self.suffixed_fields) - set(self.cached_suffixed_fields))

    @property
    def translation_class(self):
        """
        Returns related translation class.
        Read-only.
        """
        return self._translation_class

    @property
    def translations(self):
        """
        Returns translations dictionary.
        """
        return self['translations']

    @property
    def translation_instances(self):
        """
        Returns translation instances.
        """
        return [instance
                for k, v in self.translations.items()
                for instance in v.values()]

    @property
    def translations_count(self):
        """
        Returns translations count.
        """
        return len(self.translation_instances)

    def get_cached_translation(self, instance, translation=None, language=None,
                               field_name=None, field_value=None):
        """
        Returns CachedTranslation instance.
        """
        if translation is None:
            if not (language and field_name):
                raise Exception("You must set language and field name")

        if translation is not None:
            language = translation.language
            field_name = translation.field_name
            field_value = translation.field_value

        return CachedTranslation(instance=instance,
                                 language=language,
                                 field_name=field_name,
                                 field_value=field_value)

    def get_cache(self, instance, translation=None, language=None, field_name=None,
                  field_value=None):
        """
        Returns translation from cache.
        """
        is_new = bool(instance.pk is None)

        cached_obj = self.get_cached_translation(instance=instance,
                                                 translation=translation,
                                                 language=language,
                                                 field_name=field_name,
                                                 field_value=field_value)

        try:
            cached_obj = self.translations[cached_obj.field_name][cached_obj.language]
        except KeyError:
            if not is_new:
                try:
                    obj = Translation.objects.get(**cached_obj.lookup)
                    cached_obj = cached_obj.from_object(obj)
                except Translation.DoesNotExist:
                    pass

            self.set_cache(cached_obj=cached_obj)

        return cached_obj

    def set_cache(self, instance=None, translation=None, language=None, field_name=None,
                  field_value=None, cached_obj=None):
        """
        Add a new translation into the cache.
        """
        if cached_obj is None:
            cached_obj = self.get_cached_translation(instance=instance,
                                                     translation=translation,
                                                     language=language,
                                                     field_name=field_name,
                                                     field_value=field_value)

        try:
            obj = self.translations[cached_obj.field_name][cached_obj.language]
            if cached_obj.field_value:
                obj.has_changed = (cached_obj.field_value != obj.field_value)
        except KeyError:
            if cached_obj.field_value is not None:
                self.translations[cached_obj.field_name][cached_obj.language] = cached_obj

        return cached_obj

class TranslationField(object):
    """
    Proxy of original field.
    """

    def __init__(self, translated_field, language, *args, **kwargs):
        self.__dict__.update(translated_field.__dict__)
        self.translated_field = translated_field
        self.language = language

        # All translations are optional
        if not isinstance(self, models.fields.BooleanField):
            self.null = True
        self.blank = True

        # Suffix field with '_fr', '_en', etc.
        self.attname = utils.build_localized_field_name(self.translated_field.name, language)
        self.name = self.attname
        self.verbose_name = utils.build_localized_verbose_name(translated_field.verbose_name, language)

        # No concrete field
        self.column = None

    def contribute_to_class(self, cls, name):
        setattr(cls, self.name, TranslationDescriptor(self, self.translated_field, self.language))
        cls._meta.add_field(self)
        cls._meta.virtual_fields.append(self)

    def db_type(self, connection):
        """
        Returning None will cause Django to exclude this field from the concrete
        field list (``_meta.concrete_fields``) resulting in the fact that syncdb
        will skip this field when creating tables in PostgreSQL.
        """
        return None

def default_value_getter(field):
    """
    When accessing to the name of the field itself, the value
    in the current language will be returned. Unless it's set,
    the value in the default language will be returned.
    """
    def default_value_func_getter(self):
        language = self._linguist.language or self.default_language
        localized_field = utils.build_localized_field_name(field, language)
        return getattr(self, localized_field)

    return default_value_func_getter

def default_value_setter(field):
    """
    When setting to the name of the field itself, the value
    in the current language will be set.
    """
    def default_value_func_setter(self, value):
        language = self._linguist.language or self.default_language
        localized_field = utils.build_localized_field_name(field, language)
        setattr(self, localized_field, value)

    return default_value_func_setter

def field_factory(base_class):
    class TranslationFieldField(TranslationField, base_class):
        pass
    TranslationFieldField.__name__ = b'Translation%s' % base_class.__name__
    return TranslationFieldField

def create_translation_field(translated_field, language):
    """
    Takes the original field, a given language and return a Field class for model.
    """
    cls_name = translated_field.__class__.__name__

    if not isinstance(translated_field, SUPPORTED_FIELDS):
        raise ImproperlyConfigured('%s is not supported by Linguist.' % cls_name)

    translation_class = field_factory(translated_field.__class__)

    return translation_class(translated_field=translated_field, language=language)

def add_cache_property(translation_class):
    """
    Adds the linguist cache property to the translation class model.
    """
    translation_class.model.add_to_class('_linguist', CacheDescriptor(translation_class))

def add_translatable_fields(translation_class):
    """
    Adds translatable fields of translation class model.
    """
    model = translation_class.model
    fields = translation_class.fields
    for field_name in fields:
        setattr(model, field_name, property(default_value_getter(field_name),
                                            default_value_setter(field_name)))

def add_language_fields(translation_class):
    """
    Adds language fields for each translatable field of translation class model.
    """
    model = translation_class.model
    fields = translation_class.fields
    for field_name in fields:
        field = model._meta.get_field(field_name)
        for language_code, language_name in settings.SUPPORTED_LANGUAGES:
            localized_field_name = utils.build_localized_field_name(field.name, language_code)
            translation_field = create_translation_field(field, language_code)
            if hasattr(model, localized_field_name):
                raise ValueError(
                    "Error adding translation field. Model '%s' already contains a field named"
                    "'%s'." % (model._meta.object_name, localized_field_name))
            translation_field.contribute_to_class(model, localized_field_name)

def contribute_to_model(translation_class):
    """
    Add linguist fields and properties to translation class model.
    """
    add_cache_property(translation_class)
    add_language_fields(translation_class)
    add_translatable_fields(translation_class)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
import collections
import copy
import itertools
import six

from collections import defaultdict
from contextlib import contextmanager

from django.db import transaction
from django.db.models import Q
from django.utils.functional import cached_property

from . import utils

class QuerySetMixin(object):
    """
    Linguist QuerySet Mixin.
    """

    def _filter_or_exclude(self, negate, *args, **kwargs):
        """
        Overrides default behavior to handle linguist fields.
        """
        from .models import Translation

        new_args = self.get_cleaned_args(args)
        new_kwargs = self.get_cleaned_kwargs(kwargs)

        translation_args = self.get_translation_args(args)
        translation_kwargs = self.get_translation_kwargs(kwargs)

        has_linguist_args = self.has_linguist_args(args)
        has_linguist_kwargs = self.has_linguist_kwargs(kwargs)

        if translation_args or translation_kwargs:
            ids = list(set(Translation.objects.filter(*translation_args, **translation_kwargs)
                                              .values_list('object_id', flat=True)))
            if ids:
                new_kwargs['id__in'] = ids

        has_kwargs = has_linguist_kwargs and not (new_kwargs or new_args)
        has_args = has_linguist_args and not (new_args or new_kwargs)

        # No translations but we looked for translations?
        # Returns empty queryset.
        if has_kwargs or has_args:
            return self._clone().none()

        return super(QuerySetMixin, self)._filter_or_exclude(negate, *new_args, **new_kwargs)

    @cached_property
    def concrete_field_names(self):
        """
        Returns model concrete field names.
        """
        return [f[0].name for f in self.model._meta.get_concrete_fields_with_model()]

    @cached_property
    def linguist_field_names(self):
        """
        Returns linguist field names (example: "title" and "title_fr").
        """
        return (list(self.model._linguist.fields) +
                list(utils.get_language_fields(self.model._linguist.fields)))

    def has_linguist_kwargs(self, kwargs):
        """
        Parses the given kwargs and returns True if they contain
        linguist lookups.
        """
        for k in kwargs:
            if self.is_linguist_lookup(k):
                return True
        return False

    def has_linguist_args(self, args):
        """
        Parses the given args and returns True if they contain
        linguist lookups.
        """
        linguist_args = []
        for arg in args:
            condition = self._get_linguist_condition(arg)
            if condition:
                linguist_args.append(condition)
        return bool(linguist_args)

    def get_translation_args(self, args):
        """
        Returns linguist args from model args.
        """
        translation_args = []
        for arg in args:
            condition = self._get_linguist_condition(arg, transform=True)
            if condition:
                translation_args.append(condition)
        return translation_args

    def get_translation_kwargs(self, kwargs):
        """
        Returns linguist lookup kwargs (related to Translation model).
        """
        lks = []
        for k, v in six.iteritems(kwargs):
            if self.is_linguist_lookup(k):
                lks.append(utils.get_translation_lookup(
                    self.model._linguist.identifier, k, v))

        translation_kwargs = {}
        for lk in lks:
            for k, v in six.iteritems(lk):
                if k not in translation_kwargs:
                    translation_kwargs[k] = v

        return translation_kwargs

    def is_linguist_lookup(self, lookup):
        """
        Returns true if the given lookup is a valid linguist lookup.
        """
        field = utils.get_field_name_from_lookup(lookup)

        # To keep default behavior with "FieldError: Cannot resolve keyword".
        if (field not in self.concrete_field_names and field in self.linguist_field_names):
            return True

        return False

    def _get_linguist_condition(self, condition, reverse=False, transform=False):
        """
        Parses Q tree and returns linguist lookups or model lookups
        if reverse is True.
        """
        # We deal with a node
        if isinstance(condition, Q):
            children = []
            for child in condition.children:
                parsed = self._get_linguist_condition(condition=child,
                                                      reverse=reverse,
                                                      transform=transform)
                if parsed is not None:
                    if (isinstance(parsed, Q) and parsed.children) or isinstance(parsed, tuple):
                        children.append(parsed)

            new_condition = copy.deepcopy(condition)
            new_condition.children = children

            return new_condition

        # We are dealing with a lookup ('field', 'value').
        lookup, value = condition
        is_linguist = self.is_linguist_lookup(lookup)

        if transform and is_linguist:
            return Q(**utils.get_translation_lookup(self.model._linguist.identifier,
                                                    lookup,
                                                    value))

        if (reverse and not is_linguist) or (not reverse and is_linguist):
            return condition

    def get_cleaned_args(self, args):
        """
        Returns positional arguments for related model query.
        """
        if not args:
            return args

        cleaned_args = []
        for arg in args:
            condition = self._get_linguist_condition(arg, True)
            if condition:
                cleaned_args.append(condition)

        return cleaned_args

    def get_cleaned_kwargs(self, kwargs):
        """
        Returns concrete field lookups.
        """
        cleaned_kwargs = kwargs.copy()

        if kwargs is not None:
            for k in kwargs:
                if self.is_linguist_lookup(k):
                    del cleaned_kwargs[k]

        return cleaned_kwargs

    def with_translations(self, **kwargs):
        """
        Prefetches translations.

        Takes three optional keyword arguments:

        * ``field_names``: ``field_name`` values for SELECT IN
        * ``languages``: ``language`` values for SELECT IN
        * ``chunks_length``: fetches IDs by chunk
        """
        from .models import Translation

        decider = self.model._meta.linguist.get('decider', Translation)
        identifier = self.model._meta.linguist.get('identifier', None)
        chunks_length = kwargs.get('chunks_length', None)

        if identifier is None:
            raise Exception('You must define Linguist "identifier" meta option')

        lookup = dict(identifier=identifier)

        for kwarg in ('field_names', 'languages'):
            value = kwargs.get(kwarg, None)
            if value is not None:
                if not isinstance(value, (list, tuple)):
                    value = [value]
                lookup['%s__in' % kwarg[:-1]] = value

        if chunks_length is not None:
            translations_qs = []

            for ids in utils.chunks(self.values_list('id', flat=True), chunks_length):
                ids_lookup = copy.copy(lookup)
                ids_lookup['object_id__in'] = ids
                translations_qs.append(decider.objects.filter(**ids_lookup))

            translations = itertools.chain.from_iterable(translations_qs)
        else:
            lookup['object_id__in'] = [obj.pk for obj in self]
            translations = decider.objects.filter(**lookup)

        grouped_translations = defaultdict(list)

        for obj in translations:
            grouped_translations[obj.object_id].append(obj)

        for instance in self:

            instance.clear_translations_cache()

            for translation in grouped_translations[instance.pk]:
                instance._linguist.set_cache(instance=instance, translation=translation)

        return self

    def activate_language(self, language):
        """
        Activates the given ``language`` for the QuerySet instances.
        """
        utils.activate_language(self, language)
        return self

class ManagerMixin(object):
    """
    Linguist Manager Mixin.
    """

    def get_queryset(self):
        from django.db import models
        QuerySet = type('LinguistQuerySet', (QuerySetMixin, models.query.QuerySet), {})
        return QuerySet(self.model)

    def with_translations(self, **kwargs):
        """
        Proxy for ``QuerySetMixin.with_translations()`` method.
        """
        return self.get_queryset().with_translations(**kwargs)

    def activate_language(self, language):
        """
        Proxy for ``QuerySetMixin.activate_language()`` method.
        """
        self.get_queryset().active_language(language)

class ModelMixin(object):

    @property
    def linguist_identifier(self):
        """
        Returns Linguist's identifier for this model.
        """
        return self._linguist.identifier

    @property
    def active_language(self):
        return self._linguist.language

    @property
    def translatable_fields(self):
        """
        Returns Linguist's translation class fields (translatable fields).
        """
        return self._linguist.fields

    @property
    def available_languages(self):
        """
        Returns available languages.
        """
        from .models import Translation

        return (Translation.objects
                .filter(identifier=self.linguist_identifier, object_id=self.pk)
                .values_list('language', flat=True)
                .distinct()
                .order_by('language'))

    @property
    def cached_translations_count(self):
        """
        Returns cached translations count.
        """
        return self._linguist.translations_count

    def clear_translations_cache(self):
        """
        Clears Linguist cache.
        """
        self._linguist.translations.clear()

    def get_translations(self, language=None):
        """
        Returns available (saved) translations for this instance.
        """
        from .models import Translation

        if not self.pk:
            return Translation.objects.none()

        return Translation.objects.get_translations(obj=self, language=language)

    def delete_translations(self, language=None):
        """
        Deletes related translations.
        """
        from .models import Translation

        return Translation.objects.delete_translations(obj=self, language=language)

    def activate_language(self, language):
        """
        Context manager to override the instance language.
        """
        self._linguist.language = language

    @contextmanager
    def override_language(self, language):
        """
        Context manager to override the instance language.
        """
        previous_language = self._linguist.language
        self._linguist.language = language
        yield
        self._linguist.language = previous_language

    def save(self, *args, **kwargs):
        """
        Overwrites model's ``save`` method to save translations after instance
        has been saved (required to retrieve the object ID for ``Translation``
        model).
        """
        super(ModelMixin, self).save(*args, **kwargs)

        self._linguist.decider.objects.save_translations([self, ])
<commit_msg>Add prefetch_translations() for single instance.<commit_after># -*- coding: utf-8 -*-
import collections
import copy
import itertools
import six

from collections import defaultdict
from contextlib import contextmanager

from django.db import transaction
from django.db.models import Q
from django.utils.functional import cached_property

from . import utils

class QuerySetMixin(object):
    """
    Linguist QuerySet Mixin.
    """

    def _filter_or_exclude(self, negate, *args, **kwargs):
        """
        Overrides default behavior to handle linguist fields.
        """
        from .models import Translation

        new_args = self.get_cleaned_args(args)
        new_kwargs = self.get_cleaned_kwargs(kwargs)

        translation_args = self.get_translation_args(args)
        translation_kwargs = self.get_translation_kwargs(kwargs)

        has_linguist_args = self.has_linguist_args(args)
        has_linguist_kwargs = self.has_linguist_kwargs(kwargs)

        if translation_args or translation_kwargs:
            ids = list(set(Translation.objects.filter(*translation_args, **translation_kwargs)
                                              .values_list('object_id', flat=True)))
            if ids:
                new_kwargs['id__in'] = ids

        has_kwargs = has_linguist_kwargs and not (new_kwargs or new_args)
        has_args = has_linguist_args and not (new_args or new_kwargs)

        # No translations but we looked for translations?
        # Returns empty queryset.
        if has_kwargs or has_args:
            return self._clone().none()

        return super(QuerySetMixin, self)._filter_or_exclude(negate, *new_args, **new_kwargs)

    @cached_property
    def concrete_field_names(self):
        """
        Returns model concrete field names.
        """
        return [f[0].name for f in self.model._meta.get_concrete_fields_with_model()]

    @cached_property
    def linguist_field_names(self):
        """
        Returns linguist field names (example: "title" and "title_fr").
        """
        return (list(self.model._linguist.fields) +
                list(utils.get_language_fields(self.model._linguist.fields)))

    def has_linguist_kwargs(self, kwargs):
        """
        Parses the given kwargs and returns True if they contain
        linguist lookups.
        """
        for k in kwargs:
            if self.is_linguist_lookup(k):
                return True
        return False

    def has_linguist_args(self, args):
        """
        Parses the given args and returns True if they contain
        linguist lookups.
        """
        linguist_args = []
        for arg in args:
            condition = self._get_linguist_condition(arg)
            if condition:
                linguist_args.append(condition)
        return bool(linguist_args)

    def get_translation_args(self, args):
        """
        Returns linguist args from model args.
        """
        translation_args = []
        for arg in args:
            condition = self._get_linguist_condition(arg, transform=True)
            if condition:
                translation_args.append(condition)
        return translation_args

    def get_translation_kwargs(self, kwargs):
        """
        Returns linguist lookup kwargs (related to Translation model).
        """
        lks = []
        for k, v in six.iteritems(kwargs):
            if self.is_linguist_lookup(k):
                lks.append(utils.get_translation_lookup(
                    self.model._linguist.identifier, k, v))

        translation_kwargs = {}
        for lk in lks:
            for k, v in six.iteritems(lk):
                if k not in translation_kwargs:
                    translation_kwargs[k] = v

        return translation_kwargs

    def is_linguist_lookup(self, lookup):
        """
        Returns true if the given lookup is a valid linguist lookup.
        """
        field = utils.get_field_name_from_lookup(lookup)

        # To keep default behavior with "FieldError: Cannot resolve keyword".
        if (field not in self.concrete_field_names and field in self.linguist_field_names):
            return True

        return False

    def _get_linguist_condition(self, condition, reverse=False, transform=False):
        """
        Parses Q tree and returns linguist lookups or model lookups
        if reverse is True.
        """
        # We deal with a node
        if isinstance(condition, Q):
            children = []
            for child in condition.children:
                parsed = self._get_linguist_condition(condition=child,
                                                      reverse=reverse,
                                                      transform=transform)
                if parsed is not None:
                    if (isinstance(parsed, Q) and parsed.children) or isinstance(parsed, tuple):
                        children.append(parsed)

            new_condition = copy.deepcopy(condition)
            new_condition.children = children

            return new_condition

        # We are dealing with a lookup ('field', 'value').
        lookup, value = condition
        is_linguist = self.is_linguist_lookup(lookup)

        if transform and is_linguist:
            return Q(**utils.get_translation_lookup(self.model._linguist.identifier,
                                                    lookup,
                                                    value))

        if (reverse and not is_linguist) or (not reverse and is_linguist):
            return condition

    def get_cleaned_args(self, args):
        """
        Returns positional arguments for related model query.
        """
        if not args:
            return args

        cleaned_args = []
        for arg in args:
            condition = self._get_linguist_condition(arg, True)
            if condition:
                cleaned_args.append(condition)

        return cleaned_args

    def get_cleaned_kwargs(self, kwargs):
        """
        Returns concrete field lookups.
        """
        cleaned_kwargs = kwargs.copy()

        if kwargs is not None:
            for k in kwargs:
                if self.is_linguist_lookup(k):
                    del cleaned_kwargs[k]

        return cleaned_kwargs

    def with_translations(self, **kwargs):
        """
        Prefetches translations.

        Takes three optional keyword arguments:

        * ``field_names``: ``field_name`` values for SELECT IN
        * ``languages``: ``language`` values for SELECT IN
        * ``chunks_length``: fetches IDs by chunk
        """
        from .models import Translation

        decider = self.model._meta.linguist.get('decider', Translation)
        identifier = self.model._meta.linguist.get('identifier', None)
        chunks_length = kwargs.get('chunks_length', None)

        if identifier is None:
            raise Exception('You must define Linguist "identifier" meta option')

        lookup = dict(identifier=identifier)

        for kwarg in ('field_names', 'languages'):
            value = kwargs.get(kwarg, None)
            if value is not None:
                if not isinstance(value, (list, tuple)):
                    value = [value]
                lookup['%s__in' % kwarg[:-1]] = value

        if chunks_length is not None:
            translations_qs = []

            for ids in utils.chunks(self.values_list('id', flat=True), chunks_length):
                ids_lookup = copy.copy(lookup)
                ids_lookup['object_id__in'] = ids
                translations_qs.append(decider.objects.filter(**ids_lookup))

            translations = itertools.chain.from_iterable(translations_qs)
        else:
            lookup['object_id__in'] = [obj.pk for obj in self]
            translations = decider.objects.filter(**lookup)

        grouped_translations = defaultdict(list)

        for obj in translations:
            grouped_translations[obj.object_id].append(obj)

        for instance in self:
            instance.clear_translations_cache()
            for translation in grouped_translations[instance.pk]:
                instance._linguist.set_cache(instance=instance, translation=translation)

        return self

    def activate_language(self, language):
        """
        Activates the given ``language`` for the QuerySet instances.
        """
        utils.activate_language(self, language)
        return self

class ManagerMixin(object):
    """
    Linguist Manager Mixin.
    """

    def get_queryset(self):
        from django.db import models
        QuerySet = type('LinguistQuerySet', (QuerySetMixin, models.query.QuerySet), {})
        return QuerySet(self.model)

    def with_translations(self, **kwargs):
        """
        Proxy for ``QuerySetMixin.with_translations()`` method.
        """
        return self.get_queryset().with_translations(**kwargs)

    def activate_language(self, language):
        """
        Proxy for ``QuerySetMixin.activate_language()`` method.
        """
        self.get_queryset().activate_language(language)

class ModelMixin(object):

    def prefetch_translations(self):
        if not self.pk:
            return

        from .models import Translation

        decider = self._meta.linguist.get('decider', Translation)
        identifier = self._meta.linguist.get('identifier', None)

        if identifier is None:
            raise Exception('You must define Linguist "identifier" meta option')

        translations = decider.objects.filter(identifier=identifier, object_id=self.pk)
        for translation in translations:
            self._linguist.set_cache(instance=self, translation=translation)

    @property
    def linguist_identifier(self):
        """
        Returns Linguist's identifier for this model.
        """
        return self._linguist.identifier

    @property
    def active_language(self):
        return self._linguist.language

    @property
    def translatable_fields(self):
        """
        Returns Linguist's translation class fields (translatable fields).
        """
        return self._linguist.fields

    @property
    def available_languages(self):
        """
        Returns available languages.
        """
        from .models import Translation

        return (Translation.objects
                .filter(identifier=self.linguist_identifier, object_id=self.pk)
                .values_list('language', flat=True)
                .distinct()
                .order_by('language'))

    @property
    def cached_translations_count(self):
        """
        Returns cached translations count.
        """
        return self._linguist.translations_count

    def clear_translations_cache(self):
        """
        Clears Linguist cache.
        """
        self._linguist.translations.clear()

    def get_translations(self, language=None):
        """
        Returns available (saved) translations for this instance.
        """
        from .models import Translation

        if not self.pk:
            return Translation.objects.none()

        return Translation.objects.get_translations(obj=self, language=language)

    def delete_translations(self, language=None):
        """
        Deletes related translations.
        """
        from .models import Translation

        return Translation.objects.delete_translations(obj=self, language=language)

    def activate_language(self, language):
        """
        Context manager to override the instance language.
        """
        self._linguist.language = language

    @contextmanager
    def override_language(self, language):
        """
        Context manager to override the instance language.
        """
        previous_language = self._linguist.language
        self._linguist.language = language
        yield
        self._linguist.language = previous_language

    def save(self, *args, **kwargs):
        """
        Overwrites model's ``save`` method to save translations after instance
        has been saved (required to retrieve the object ID for ``Translation``
        model).
        """
        super(ModelMixin, self).save(*args, **kwargs)

        self._linguist.decider.objects.save_translations([self, ])
<|endoftext|>